diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000000..96948b9dbe7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ +[*] +end_of_line = lf + +[caddytest/integration/caddyfile_adapt/*.caddyfiletest] +indent_style = tab \ No newline at end of file diff --git a/.gitattributes b/.gitattributes index f42d752c91f..a0717e4b3b9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,14 +1 @@ -# 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 +*.go text eol=lf \ No newline at end of file diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 42c2b589809..7142530e570 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,14 +1,14 @@ Contributing to Caddy ===================== -Welcome! Thank you for choosing to be a part of our community. Caddy wouldn't be great without your involvement! +Welcome! Thank you for choosing to be a part of our community. Caddy wouldn't be nearly as excellent without your involvement! For starters, we invite you to join [the Caddy forum](https://caddy.community) where you can hang out with other Caddy users and developers. ## Common Tasks - [Contributing code](#contributing-code) -- [Writing a plugin](#writing-a-plugin) +- [Writing a Caddy module](#writing-a-caddy-module) - [Asking or answering questions for help using Caddy](#getting-help-using-caddy) - [Reporting a bug](#reporting-bugs) - [Suggesting an enhancement or a new feature](#suggesting-features) @@ -17,61 +17,73 @@ For starters, we invite you to join [the Caddy forum](https://caddy.community) w Other menu items: - [Values](#values) -- [Responsible Disclosure](#responsible-disclosure) +- [Coordinated Disclosure](#coordinated-disclosure) - [Thank You](#thank-you) ### Contributing code -You can have a direct impact on the project by helping with its code. To contribute code to Caddy, open a [pull request](https://github.com/mholt/caddy/pulls) (PR). If you're new to our community, that's okay: **we gladly welcome pull requests from anyone, regardless of your native language or coding experience.** You can get familiar with Caddy's code base by using [code search at Sourcegraph](https://sourcegraph.com/github.com/mholt/caddy/-/search). +You can have a huge impact on the project by helping with its code. To contribute code to Caddy, first submit or comment in an issue to discuss your contribution, then open a [pull request](https://github.com/caddyserver/caddy/pulls) (PR). If you're new to our community, that's okay: **we gladly welcome pull requests from anyone, regardless of your native language or coding experience.** You can get familiar with Caddy's code base by using [code search at Sourcegraph](https://sourcegraph.com/github.com/caddyserver/caddy). -We hold contributions to a high standard for quality :bowtie:, so don't be surprised if we ask for revisions—even if it seems small or insignificant. Please don't take it personally. :wink: If your change is on the right track, we can guide you to make it mergable. +We hold contributions to a high standard for quality :bowtie:, so don't be surprised if we ask for revisions—even if it seems small or insignificant. Please don't take it personally. :blue_heart: If your change is on the right track, we can guide you to make it mergeable. Here are some of the expectations we have of contributors: -- If your change is more than just a minor alteration, **open an issue to propose your change first.** This way we can avoid confusion, coordinate what everyone is working on, and ensure that changes are in-line with the project's goals and the best interests of its users. If there's already an issue about it, comment on the existing issue to claim it. +- **Open an issue to propose your change first.** This way we can avoid confusion, coordinate what everyone is working on, and ensure that any changes are in-line with the project's goals and the best interests of its users. We can also discuss the best possible implementation. If there's already an issue about it, comment on the existing issue to claim it. A lot of valuable time can be saved by discussing a proposal first. -- **Keep pull requests small.** Smaller PRs are more likely to be merged because they are easier to review! We might ask you to break up large PRs into smaller ones. [An example of what we DON'T do.](https://twitter.com/iamdevloper/status/397664295875805184) +- **Keep pull requests small.** Smaller PRs are more likely to be merged because they are easier to review! We might ask you to break up large PRs into smaller ones. [An example of what we want to avoid.](https://twitter.com/iamdevloper/status/397664295875805184) - **Keep related commits together in a PR.** We do want pull requests to be small, but you should also keep multiple related commits in the same PR if they rely on each other. -- **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. +- **Write tests.** Good, automated tests are very valuable! Written properly, they ensure your change works, and that other changes in the future won't break your change. CI checks should pass. -- **Benchmarks should be included for optimizations.** Optimizations sometimes make code harder to read or have changes that are less than obvious. They should be proven with benchmarks or 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 and profiling. - **[Squash](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) insignificant commits.** Every commit should be significant. Commits which merely rewrite a comment or fix a typo can be combined into another commit that has more substance. Interactive rebase can do this, or a simpler way is `git reset --soft ` then `git commit -s`. -- **Own your contributions.** Caddy is a growing project, and it's much better when individual contributors help maintain their change after it is merged. +- **Be responsible for and maintain your contributions.** Caddy is a growing project, and it's much better when individual contributors help maintain their change after it is merged. - **Use comments properly.** We expect good godoc comments for package-level functions, types, and values. Comments are also useful whenever the purpose for a line of code is not obvious. -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! +- **Pull requests may still get closed.** The longer a PR stays open and idle, the more likely it is to be closed. If we haven't reviewed it in a while, it probably means the change is not a priority. Please don't take this personally, we're trying to balance a lot of tasks! If nobody else has commented or reacted to the PR, it likely means your change is useful only to you. The reality is this happens quite a lot. We don't tend to accept PRs that aren't generally helpful. For these reasons or others, the PR may get closed even after a review. We are not obligated to accept all proposed changes, even if the best justification we can give is something vague like, "It doesn't sit right." Sometimes PRs are just the wrong thing or the wrong time. Because it is open source, you can always build your own modified version of Caddy with a change you need, even if we reject it in the official repo. Plus, because Caddy is extensible, it's possible your feature could make a great plugin instead! + +- **You certify that you wrote and comprehend the code you submit.** The Caddy project welcomes original contributions that comply with [our CLA](https://cla-assistant.io/caddyserver/caddy), meaning that authors must be able to certify that they created or have rights to the code they are contributing. In addition, we require that code is not simply copy-pasted from Q/A sites or AI language models without full comprehension and rigorous testing. In other words: contributors are allowed to refer to communities for assistance and use AI tools such as language models for inspiration, but code which originates from or is assisted by these resources MUST be: + + - Licensed for you to freely share + - Fully comprehended by you (be able to explain every line of code) + - Verified by automated tests when feasible, or thorough manual tests otherwise + + We have found that current language models (LLMs, like ChatGPT) may understand code syntax and even problem spaces to an extent, but often fail in subtle ways to convey true knowledge and produce correct algorithms. Integrated tools such as GitHub Copilot and Sourcegraph Cody may be used for inspiration, but code generated by these tools still needs to meet our criteria for licensing, human comprehension, and testing. These tools may be used to help write code comments and tests as long as you can certify they are accurate and correct. Note that it is often more trouble than it's worth to certify that Copilot (for example) is not giving you code that is possibly plagiarised, unlicensed, or licensed with incompatible terms -- as the Caddy project cannot accept such contributions. If that's too difficult for you (or impossible), then we recommend using these resources only for inspiration and write your own code. Ultimately, you (the contributor) are responsible for the code you're submitting. + + As a courtesy to reviewers, we kindly ask that you disclose when contributing code that was generated by an AI tool or copied from another website so we can be aware of what to look for in code review. + +We often grant [collaborator status](#collaborator-instructions) to contributors who author one or more significant, high-quality PRs that are merged into the code base. #### HOW TO MAKE A PULL REQUEST TO CADDY -Contributing to Go projects on GitHub is fun and easy. We recommend the following workflow: +Contributing to Go projects on GitHub is fun and easy. After you have proposed your change in an issue, we recommend the following workflow: -1. [Fork this repo](https://github.com/mholt/caddy). This makes a copy of the code you can write to. +1. [Fork this repo](https://github.com/caddyserver/caddy). This makes a copy of the code you can write to. -2. If you don't already have this repo (mholt/caddy.git) repo on your computer, get it with `go get github.com/mholt/caddy/caddy`. +2. If you don't already have this repo (caddyserver/caddy.git) repo on your computer, clone it down: `git clone https://github.com/caddyserver/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` +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//caddy.git` -4. Make your changes in the mholt/caddy.git repo on your computer. +4. Make your changes in the caddyserver/caddy.git repo on your computer. 5. Push your changes to your fork: `git push myfork` -6. [Create a pull request](https://github.com/mholt/caddy/pull/new/master) to merge your changes into mholt/caddy @ master. (Click "compare across forks" and change the head fork.) +6. [Create a pull request](https://github.com/caddyserver/caddy/pull/new/master) to merge your changes into caddyserver/caddy @ master. (Click "compare across forks" and change the head fork.) This workflow is nice because you don't have to change import paths. You can get fancier by using different branches if you want. -### Writing a plugin +### Writing a Caddy module -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. +Caddy can do more with modules! Anyone can write one. Caddy modules are Go libraries that get compiled into Caddy, extending its feature set. They can add directives to the Caddyfile, add new configuration adapters, and even implement new server types (e.g. HTTP, DNS). -[Learn how to write and submit a plugin](https://github.com/mholt/caddy/wiki) on the wiki. You should also share and discuss your plugin idea [on the forums](https://caddy.community) to have people test it out. We don't use the Caddy issue tracker for plugins. +[Learn how to write a module here](https://caddyserver.com/docs/extending-caddy). You should also share and discuss your module idea [on the forums](https://caddy.community) to have people test it out. We don't use the Caddy issue tracker for third-party modules. ### Getting help using Caddy @@ -83,35 +95,61 @@ Many people on the forums could benefit from your experience and expertise, too. ### Reporting bugs -Like every software, Caddy has its flaws. If you find one, [search the issues](https://github.com/mholt/caddy/issues) to see if it has already been reported. If not, [open a new issue](https://github.com/mholt/caddy/issues/new) and describe the bug, and somebody will look into it! (This repository is only for Caddy, not plugins.) +Like every software, Caddy has its flaws. If you find one, [search the issues](https://github.com/caddyserver/caddy/issues) to see if it has already been reported. If not, [open a new issue](https://github.com/caddyserver/caddy/issues/new) and describe the bug, and somebody will look into it! (This repository is only for Caddy and its standard modules.) -**You can help stop bugs in their tracks!** Speed up the patch by identifying the bug in the code. This can sometimes be done by adding `fmt.Println()` statements (or similar) in relevant code paths to narrow down where the problem may be. It's a good way to [introduce yourself to the Go language](https://tour.golang.org), too. +**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. -Please follow the issue template so we have all the needed information. Unredacted—yes, actual values matter. We need to be able to repeat the bug using your instructions. Please simplify the issue as much as possible. 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 may reply with an issue template. Please follow the template so we have all the needed information. Unredacted—yes, actual values matter. We need to be able to repeat the bug using your instructions. Please simplify the issue as much as possible. If you don't, we might close your report. The burden is on you to make it easily reproducible and to convince us that it is actually a bug in Caddy. This is easiest to do when you write clear, concise instructions so we can reproduce the behavior (even if it seems obvious). The more detailed and specific you are, the faster we will be able to help you! We suggest reading [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html). Please be kind. :smile: Remember that Caddy comes at no cost to you, and you're getting free support when we fix your issues. If we helped you, please consider helping someone else! +#### Bug reporting expectations + +Maintainers---or more generally, developers---need three things to act on bugs: + +1. To agree or be convinced that it's a bug (reporter's responsibility). + - A bug is unintentional, undesired, or surprising behavior which violates documentation or relevant spec. It might be either a mistake in the documentation or a bug in the code. + - This project usually does not work around bugs in other software, systems, and dependencies; instead, we recommend that those bugs are fixed at their source. This sometimes means we close issues or reject PRs that attempt to fix, workaround, or hide bugs in other projects. + +2. To be able to understand what is happening (mostly reporter's responsibility). + - If the reporter can provide satisfactory instructions such that a developer can reproduce the bug, the developer will likely be able to understand the bug, write a test case, and implement a fix. This is the least amount of work for everyone and path to the fastest resolution. + - Otherwise, the burden is on the reporter to test possible solutions. This is less preferable because it loosens the feedback loop, slows down debugging efforts, obscures the true nature of the problem from the developers, and is unlikely to result in new test cases. + +3. A solution, or ideas toward a solution (mostly maintainer's responsibility). + - Sometimes the best solution is a documentation change. + - Usually the developers have the best domain knowledge for inventing a solution, but reporters may have ideas or preferences for how they would like the software to work. + - Security, correctness, and project goals/vision all take priority over a user's preferences. + - It's simply good business to yield a solution that satisfies the users, and it's even better business to leave them impressed. + +Thus, at the very least, the reporter is expected to: + +1. Convince the reader that it's a bug in Caddy (if it's not obvious). +2. Reduce the problem down to the minimum specific steps required to reproduce it. + +The maintainer is usually able to do the rest; but of course the reporter may invest additional effort to speed up the process. + + ### Suggesting features -First, [search to see if your feature has already been requested](https://github.com/mholt/caddy/issues). If it has, you can add a :+1: reaction to vote for it. If your feature idea is new, open an issue to request the feature. You don't have to follow the bug template for feature requests. Please describe your idea thoroughly so that we know how to implement it! Really vague requests may not be helpful or actionable and without clarification will have to be closed. +First, [search to see if your feature has already been requested](https://github.com/caddyserver/caddy/issues). If it has, you can add a :+1: reaction to vote for it. If your feature idea is new, open an issue to request the feature. Please describe your idea thoroughly so that we know how to implement it! Really vague requests may not be helpful or actionable and, without clarification, will have to be closed. -While we really do value your requests and implement many of them, not all features are a good fit for Caddy. Most of those [make good plugins](https://github.com/mholt/caddy/wiki), though, which can be made by anyone! But if a feature is not in the best interest of the Caddy project or its users in general, we may politely decline to implement it into Caddy core. +While we really do value your requests and implement many of them, not all features are a good fit for Caddy. Most of those [make good modules](#writing-a-caddy-module), which can be made by anyone! But if a feature is not in the best interest of the Caddy project or its users in general, we may politely decline to implement it into Caddy core. Additionally, some features are bad ideas altogether (for either obvious or non-obvious reasons) which may be rejected. We'll try to explain why we reject a feature, but sometimes the best we can do is, "It's not a good fit for the project." ### Improving documentation -Caddy's documentation is available at [https://caddyserver.com/docs](https://caddyserver.com/docs). 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. +Caddy's documentation is available at [https://caddyserver.com/docs](https://caddyserver.com/docs) and its source is in the [website repo](https://github.com/caddyserver/website). If you would like to make a fix to the docs, please submit an issue there describing the change to make. +Note that third-party module documentation is not hosted by the Caddy website, other than basic usage examples. They are managed by the individual module authors, and you will have to contact them to change their documentation. +Our documentation is scoped to the Caddy project only: it is not for describing how other software or systems work, even if they relate to Caddy or web servers. That kind of content [can be found in our community wiki](https://caddy.community/c/wiki/13), however. ## Collaborator Instructions -Collabators have push rights to the repository. We grant this permission after one or more successful, high-quality PRs are merged! We thank them for their help.The expectations we have of collaborators are: +Collaborators have push rights to the repository. We grant this permission after one or more successful, high-quality PRs are merged! We thank them for their help. The expectations we have of collaborators are: - **Help review pull requests.** Be meticulous, but also kind. We love our contributors, but we critique the contribution to make it better. Multiple, thorough reviews make for the best contributions! Here are some questions to consider: - Can the change be made more elegant? @@ -128,9 +166,9 @@ Collabators have push rights to the repository. We grant this permission after o - **Prefer squashed commits over a messy merge.** If there are many little commits, please [squash the commits](https://stackoverflow.com/a/11732910/1048862) so we don't clutter the commit history. -- **Don't accept new dependencies lightly.** Dependencies can make the world crash and burn, but they are sometimes necessary. Choose carefully. Extremely small dependencies (a few lines of code) can be inlined. The rest may not be needed. For those that are, Caddy vendors all dependencies with the help of [gvt](https://github.com/FiloSottile/gvt). All external dependencies must be vendored, and _Caddy must not export any types defined by those dependencies_. Check this diligently! +- **Don't accept new dependencies lightly.** Dependencies can make the world crash and burn, but they are sometimes necessary. Choose carefully. Extremely small dependencies (a few lines of code) can be inlined. The rest may not be needed. For those that are, Caddy uses [go modules](https://github.com/golang/go/wiki/Modules). All external dependencies must be installed as modules, and _Caddy must not export any types defined by those dependencies_. Check this diligently! -- **Be extra careful in some areas of the code.** There are some critical areas in the Caddy code base that we review extra meticulously: the `caddy` 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 `caddyhttp` and `caddytls` packages especially. - **Make sure tests test the actual thing.** Double-check that the tests fail without the change, and pass with it. It's important that they assert what they're purported to assert. @@ -142,19 +180,18 @@ Collabators have push rights to the repository. We grant this permission after o -## Values +## Values (WIP) - A person is always more important than code. People don't like being handled "efficiently". But we can still process issues and pull requests efficiently while being kind, patient, and considerate. - The ends justify the means, if the means are good. A good tree won't produce bad fruit. But if we cut corners or are hasty in our process, the end result will not be good. -## Responsible Disclosure +## Security Policy -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—and thousands of websites—be more secure! +If you think you've found a security vulnerability, please refer to our [Security Policy](https://github.com/caddyserver/caddy/security/policy) document. ## Thank you -Thanks for your help! Caddy would not be what it is today without your -contributions. +Thanks for your help! Caddy would not be what it is today without your contributions. diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000000..4703554bd31 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: [mholt] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index a7963b20c8a..00000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,32 +0,0 @@ - - -### 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. - - - - -### 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? - - diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 3d0eba238b0..00000000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,19 +0,0 @@ - - -### 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 diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 00000000000..557b4bac111 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,59 @@ +# 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.latest | ✔️ | +| 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—and thousands of websites—be more secure! diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..64284b90748 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +--- +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000000..7f8e6d501f5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,220 @@ +# 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.23' + - '1.24' + + include: + # Set the minimum Go patch version for the given Go minor + # Usable via ${{ matrix.GO_SEMVER }} + - go: '1.23' + GO_SEMVER: '~1.23.6' + + - go: '1.24' + GO_SEMVER: '~1.24.0' + + # Set some variables per OS, usable via ${{ matrix.VAR }} + # OS_LABEL: the VM label from GitHub Actions (see https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories) + # CADDY_BIN_PATH: the path to the compiled Caddy binary, for artifact publishing + # SUCCESS: the typical value for $? per OS (Windows/pwsh returns 'True') + - os: linux + OS_LABEL: ubuntu-latest + CADDY_BIN_PATH: ./cmd/caddy/caddy + SUCCESS: 0 + + - os: mac + OS_LABEL: macos-14 + CADDY_BIN_PATH: ./cmd/caddy/caddy + SUCCESS: 0 + + - os: windows + OS_LABEL: windows-latest + CADDY_BIN_PATH: ./cmd/caddy/caddy.exe + SUCCESS: 'True' + + runs-on: ${{ matrix.OS_LABEL }} + + 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 nobadger -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 == 'caddyserver/caddy' && 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: | + set +e + mkdir -p ~/.ssh && echo -e "${SSH_KEY//_/\\n}" > ~/.ssh/id_ecdsa && chmod og-rwx ~/.ssh/id_ecdsa + + # short sha is enough? + short_sha=$(git rev-parse --short HEAD) + + # To shorten the following lines + ssh_opts="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" + ssh_host="$CI_USER@ci-s390x.caddyserver.com" + + # The environment is fresh, so there's no point in keeping accepting and adding the key. + rsync -arz -e "ssh $ssh_opts" --progress --delete --exclude '.git' . "$ssh_host":/var/tmp/"$short_sha" + ssh $ssh_opts -t "$ssh_host" bash < 0)); do + CGO_ENABLED=0 go test -p 1 -tags nobadger -v ./... + exit_code=$? + if ((exit_code == 0)); then + break + fi + echo "\n\nTest failed: \$exit_code, retrying..." + ((retries--)) + done + echo "Remote exit code: \$exit_code" + exit \$exit_code + EOF + test_result=$? + + # There's no need leaving the files around + ssh $ssh_opts "$ssh_host" "rm -rf /var/tmp/'$short_sha'" + + echo "Test exit code: $test_result" + exit $test_result + env: + SSH_KEY: ${{ secrets.S390X_SSH_KEY }} + CI_USER: ${{ secrets.CI_USER }} + + goreleaser-check: + runs-on: ubuntu-latest + if: github.event.pull_request.head.repo.full_name == 'caddyserver/caddy' && github.actor != 'dependabot[bot]' + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - uses: goreleaser/goreleaser-action@v6 + with: + version: latest + args: check + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: "~1.24" + check-latest: true + - name: Install xcaddy + run: | + go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest + xcaddy version + - uses: goreleaser/goreleaser-action@v6 + with: + version: latest + args: build --single-target --snapshot + env: + TAG: ${{ github.head_ref || github.ref_name }} diff --git a/.github/workflows/cross-build.yml b/.github/workflows/cross-build.yml new file mode 100644 index 00000000000..231e4a6e2cd --- /dev/null +++ b/.github/workflows/cross-build.yml @@ -0,0 +1,73 @@ +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.23' + - '1.24' + + include: + # Set the minimum Go patch version for the given Go minor + # Usable via ${{ matrix.GO_SEMVER }} + - go: '1.23' + GO_SEMVER: '~1.23.6' + + - go: '1.24' + GO_SEMVER: '~1.24.0' + + 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,nomysql,nopgx -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000000..3cfe893df8f --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,67 @@ +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.24' + check-latest: true + + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: latest + + # Windows times out frequently after about 5m50s if we don't set a longer timeout. + args: --timeout 10m + + # Optional: show only new issues if it's a pull request. The default value is `false`. + # only-new-issues: true + + govulncheck: + runs-on: ubuntu-latest + steps: + - name: govulncheck + uses: golang/govulncheck-action@v1 + with: + go-version-input: '~1.24.0' + check-latest: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000000..df42679bb40 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,178 @@ +name: Release + +on: + push: + tags: + - 'v*.*.*' + +jobs: + release: + name: Release + strategy: + matrix: + os: + - ubuntu-latest + go: + - '1.24' + + include: + # Set the minimum Go patch version for the given Go minor + # Usable via ${{ matrix.GO_SEMVER }} + - go: '1.24' + GO_SEMVER: '~1.24.0' + + runs-on: ${{ matrix.os }} + # https://github.com/sigstore/cosign/issues/1258#issuecomment-1002251233 + # https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#adding-permissions-settings + permissions: + id-token: write + # https://docs.github.com/en/rest/overview/permissions-required-for-github-apps#permission-on-contents + # "Releases" is part of `contents`, so it needs the `write` + contents: write + + 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 + - name: Install xcaddy + run: | + go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest + xcaddy version + # GoReleaser will take care of publishing those artifacts into the release + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@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 diff --git a/.github/workflows/release_published.yml b/.github/workflows/release_published.yml new file mode 100644 index 00000000000..491dae75db4 --- /dev/null +++ b/.github/workflows/release_published.yml @@ -0,0 +1,35 @@ +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 }}"}' + diff --git a/.gitignore b/.gitignore index 4f3845ed411..381bf74030c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,31 @@ -.DS_Store -Thumbs.db _gitignore/ -Vagrantfile -.vagrant/ -/.idea +*.log +Caddyfile +Caddyfile.* +!caddyfile/ +!caddyfile.go -dist/builds/ -dist/release/ +# artifacts from pprof tooling +*.prof +*.test -error.log -access.log +# build artifacts and helpers +cmd/caddy/caddy +cmd/caddy/caddy.exe +cmd/caddy/tmp/*.exe +cmd/caddy/.env -/*.conf -Caddyfile +# mac specific +.DS_Store + +# go modules +vendor -og_static/ +# goreleaser artifacts +dist +caddy-build +caddy-dist -.vscode/ \ No newline at end of file +# IDE files +.idea/ +.vscode/ diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000000..aecff563eed --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,182 @@ +linters-settings: + errcheck: + exclude-functions: + - fmt.* + - (go.uber.org/zap/zapcore.ObjectEncoder).AddObject + - (go.uber.org/zap/zapcore.ObjectEncoder).AddArray + 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 + - 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. + timeout: 5m + issues-exit-code: 1 + tests: false + +# output configuration options +output: + formats: + - format: 'colored-line-number' + print-issued-lines: true + print-linter-name: true + +issues: + exclude-rules: + - text: 'G115' # TODO: Either we should fix the issues or nuke the linter if it's bad + linters: + - gosec + # 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 + - path: modules/caddyhttp/matchers.go + linters: + - dupl + - path: modules/caddyhttp/vars.go + linters: + - dupl + - path: _test\.go + linters: + - errcheck diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 00000000000..c7ed4b365e4 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,214 @@ +version: 2 + +before: + hooks: + # The build is done in this particular way to build Caddy in a designated directory named in .gitignore. + # This is so we can run goreleaser on tag without Git complaining of being dirty. The main.go in cmd/caddy directory + # cannot be built within that directory due to changes necessary for the build causing Git to be dirty, which + # subsequently causes gorleaser to refuse running. + - rm -rf caddy-build caddy-dist vendor + # vendor Caddy deps + - go mod vendor + - mkdir -p caddy-build + - cp cmd/caddy/main.go caddy-build/main.go + - /bin/sh -c 'cd ./caddy-build && go mod init caddy' + # prepare syso files for windows embedding + - /bin/sh -c 'for a in amd64 arm arm64; do XCADDY_SKIP_BUILD=1 GOOS=windows GOARCH=$a xcaddy build {{.Env.TAG}}; done' + - /bin/sh -c 'mv /tmp/buildenv_*/*.syso caddy-build' + # GoReleaser doesn't seem to offer {{.Tag}} at this stage, so we have to embed it into the env + # so we run: TAG=$(git describe --abbrev=0) goreleaser release --rm-dist --skip-publish --skip-validate + - go mod edit -require=github.com/caddyserver/caddy/v2@{{.Env.TAG}} ./caddy-build/go.mod + # as of Go 1.16, `go` commands no longer automatically change go.{mod,sum}. We now have to explicitly + # run `go mod tidy`. The `/bin/sh -c '...'` is because goreleaser can't find cd in PATH without shell invocation. + - /bin/sh -c 'cd ./caddy-build && go mod tidy' + # vendor the deps of the prepared to-build module + - /bin/sh -c 'cd ./caddy-build && go mod vendor' + - git clone --depth 1 https://github.com/caddyserver/dist caddy-dist + - mkdir -p caddy-dist/man + - go mod download + - go run cmd/caddy/main.go manpage --directory ./caddy-dist/man + - gzip -r ./caddy-dist/man/ + - /bin/sh -c 'go run cmd/caddy/main.go completion bash > ./caddy-dist/scripts/bash-completion' + +builds: +- env: + - CGO_ENABLED=0 + - GO111MODULE=on + dir: ./caddy-build + binary: caddy + goos: + - darwin + - linux + - windows + - freebsd + goarch: + - amd64 + - arm + - arm64 + - s390x + - ppc64le + - riscv64 + goarm: + - "5" + - "6" + - "7" + ignore: + - goos: darwin + goarch: arm + - goos: darwin + goarch: ppc64le + - goos: darwin + goarch: s390x + - goos: darwin + goarch: riscv64 + - goos: windows + goarch: ppc64le + - goos: windows + goarch: s390x + - goos: windows + goarch: riscv64 + - goos: freebsd + goarch: ppc64le + - goos: freebsd + goarch: s390x + - goos: freebsd + goarch: riscv64 + - goos: freebsd + goarch: arm + goarm: "5" + flags: + - -trimpath + - -mod=readonly + ldflags: + - -s -w + tags: + - nobadger + - nomysql + - nopgx + +signs: + - cmd: cosign + signature: "${artifact}.sig" + certificate: '{{ trimsuffix (trimsuffix .Env.artifact ".zip") ".tar.gz" }}.pem' + args: ["sign-blob", "--yes", "--output-signature=${signature}", "--output-certificate", "${certificate}", "${artifact}"] + artifacts: all + +sboms: + - artifacts: binary + documents: + - >- + {{ .ProjectName }}_ + {{- .Version }}_ + {{- if eq .Os "darwin" }}mac{{ else }}{{ .Os }}{{ end }}_ + {{- .Arch }} + {{- with .Arm }}v{{ . }}{{ end }} + {{- with .Mips }}_{{ . }}{{ end }} + {{- if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}.sbom + cmd: syft + args: ["$artifact", "--file", "${document}", "--output", "cyclonedx-json"] + +archives: + - id: default + format_overrides: + - goos: windows + formats: zip + name_template: >- + {{ .ProjectName }}_ + {{- .Version }}_ + {{- if eq .Os "darwin" }}mac{{ else }}{{ .Os }}{{ end }}_ + {{- .Arch }} + {{- with .Arm }}v{{ . }}{{ end }} + {{- with .Mips }}_{{ . }}{{ end }} + {{- if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }} + + # package the 'caddy-build' directory into a tarball, + # allowing users to build the exact same set of files as ours. + - id: source + meta: true + name_template: "{{ .ProjectName }}_{{ .Version }}_buildable-artifact" + files: + - src: LICENSE + dst: ./LICENSE + - src: README.md + dst: ./README.md + - src: AUTHORS + dst: ./AUTHORS + - src: ./caddy-build + dst: ./ + +source: + enabled: true + name_template: '{{ .ProjectName }}_{{ .Version }}_src' + format: 'tar.gz' + + # Additional files/template/globs you want to add to the source archive. + # + # Default: empty. + files: + - vendor + + +checksum: + algorithm: sha512 + +nfpms: + - id: default + package_name: caddy + + vendor: Dyanim + homepage: https://caddyserver.com + maintainer: Matthew Holt + description: | + Caddy - Powerful, enterprise-ready, open source web server with automatic HTTPS written in Go + license: Apache 2.0 + + formats: + - deb + # - rpm + + bindir: /usr/bin + contents: + - src: ./caddy-dist/init/caddy.service + dst: /lib/systemd/system/caddy.service + + - src: ./caddy-dist/init/caddy-api.service + dst: /lib/systemd/system/caddy-api.service + + - src: ./caddy-dist/welcome/index.html + dst: /usr/share/caddy/index.html + + - src: ./caddy-dist/scripts/bash-completion + dst: /etc/bash_completion.d/caddy + + - src: ./caddy-dist/config/Caddyfile + dst: /etc/caddy/Caddyfile + type: config + + - src: ./caddy-dist/man/* + dst: /usr/share/man/man8/ + + scripts: + postinstall: ./caddy-dist/scripts/postinstall.sh + preremove: ./caddy-dist/scripts/preremove.sh + postremove: ./caddy-dist/scripts/postremove.sh + + provides: + - httpd + +release: + github: + owner: caddyserver + name: caddy + draft: true + prerelease: auto + +changelog: + sort: asc + filters: + exclude: + - '^chore:' + - '^ci:' + - '^docs?:' + - '^readme:' + - '^tests?:' + - '^\w+\s+' # a hack to remove commit messages without colons thus don't correspond to a package diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1eba83c9092..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,39 +0,0 @@ -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) diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000000..3635dd880d5 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,10 @@ +# 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 +Light Code Labs +Ardan Labs diff --git a/vendor/google.golang.org/appengine/LICENSE b/LICENSE similarity index 100% rename from vendor/google.golang.org/appengine/LICENSE rename to LICENSE diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index 8dada3edaf5..00000000000 --- a/LICENSE.txt +++ /dev/null @@ -1,201 +0,0 @@ - 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. diff --git a/README.md b/README.md index 247e3bad0b2..3f071e6f0bd 100644 --- a/README.md +++ b/README.md @@ -1,158 +1,200 @@

- Caddy + + + + + Caddy + + +
+

a project

-

Every Site on HTTPS

-

Caddy is a general-purpose HTTP/2 web server that serves HTTPS by default.

+
+

Every site on HTTPS

+

Caddy is an extensible server platform that uses TLS by default.

- - - - + +
@caddyserver on Twitter Caddy Forum - Caddy on Sourcegraph +
+ Caddy on Sourcegraph + Cloudsmith

- Download · - Documentation · - Community + Releases · + Documentation · + Get Help

---- - -Caddy is fast, easy to use, and makes you more productive. -Available for Windows, Mac, Linux, BSD, Solaris, and [Android](https://github.com/mholt/caddy/wiki/Running-Caddy-on-Android). -## Menu +### Menu - [Features](#features) - [Install](#install) -- [Quick Start](#quick-start) -- [Running in Production](#running-in-production) -- [Contributing](#contributing) -- [Donors](#donors) -- [About the Project](#about-the-project) - -## Features - -- **Easy configuration** with the Caddyfile -- **Automatic HTTPS** on by default (via [Let's Encrypt](https://letsencrypt.org)) -- **HTTP/2** by default -- **Virtual hosting** so multiple sites just work -- Experimental **QUIC support** for 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) +- [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) + +

+ Powered by +
+ + + + + CertMagic + + +

-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). +## [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 +- **Runs anywhere** with **no external dependencies** (not even libc) +- Written in Go, a language with higher **memory safety guarantees** than other servers +- Actually **fun to use** +- So much more to [discover](https://caddyserver.com/features) ## Install -Caddy binaries have no dependencies and are available for every platform. Get Caddy any one of these ways: +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. -- **[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) +See [our online documentation](https://caddyserver.com/docs/install) for other install instructions. -Then make sure the `caddy` binary is in your PATH. +## Build from source +Requirements: -## Quick Start +- [Go 1.22.3 or newer](https://golang.org/dl/) -To serve static files from the current working directory, run: +### 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 ``` -caddy + +When you run Caddy, it may try to bind to low ports unless otherwise specified in your config. If your OS requires elevated privileges for this, you will need to give your new binary permission to do so. On Linux, this can be done easily with: `sudo setcap cap_net_bind_service=+ep ./caddy` + +If you prefer to use `go run` which only creates temporary binaries, you can still do this with the included `setcap.sh` like so: + +```bash +$ go run -exec ./setcap.sh main.go +``` + +If you don't want to type your password for `setcap`, use `sudo visudo` to edit your sudoers file and allow your user account to run that command without a password, for example: + +``` +username ALL=(ALL:ALL) NOPASSWD: /usr/sbin/setcap ``` -Caddy's default port is 2015, so open your browser to [http://localhost:2015](http://localhost:2015). +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. -### Go from 0 to HTTPS in 5 seconds +### With version information and/or plugins -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: +Using [our builder tool, `xcaddy`](https://github.com/caddyserver/xcaddy)... ``` -caddy -host example.com +$ xcaddy build ``` -This command serves static files from the current directory over HTTPS. Certificates are automatically obtained and renewed for you! +...the following steps are automated: -### Customizing your site +1. Create a new folder: `mkdir caddy` +2. Change into it: `cd caddy` +3. Copy [Caddy's main.go](https://github.com/caddyserver/caddy/blob/master/cmd/caddy/main.go) into the empty folder. Add imports for any custom plugins you want to add. +4. Initialize a Go module: `go mod init caddy` +5. (Optional) Pin Caddy version: `go get github.com/caddyserver/caddy/v2@version` replacing `version` with a git tag, commit, or branch name. +6. (Optional) Add plugins by adding their import: `_ "import/path/here"` +7. Compile: `go build -tags=nobadger,nomysql,nopgx` -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. +## Quick start + +The [Caddy website](https://caddyserver.com/docs/) has documentation that includes tutorials, quick-start guides, reference, and more. + +**We recommend that all users -- regardless of experience level -- do our [Getting Started](https://caddyserver.com/docs/getting-started) guide to become familiar with using Caddy.** + +If you've only got a minute, [the website has several quick-start tutorials](https://caddyserver.com/docs/quick-starts) to choose from! However, after finishing a quick-start tutorial, please read more documentation to understand how the software works. 🙂 + + -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. +## Overview -### Doing more with Caddy +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. -To host multiple sites and do more with the Caddyfile, please see the [Caddyfile tutorial](https://caddyserver.com/tutorial/caddyfile). +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. -Sites with qualifying hostnames are served over [HTTPS by default](https://caddyserver.com/docs/automatic-https). +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. -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. +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. -## Running in Production +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/). -Caddy is production-ready if you find it to be a good fit for your site and workflow. +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. -**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. +## Full documentation -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. +Our website has complete documentation: -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. +**https://caddyserver.com/docs/** +The docs are also open source. You can contribute to them here: https://github.com/caddyserver/website -## Contributing -**[Join our forum](https://caddy.community) where you can chat with other Caddy users and developers!** To get familiar with the code base, try [Caddy code search on Sourcegraph](https://sourcegraph.com/github.com/mholt/caddy/-/search)! -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). +## Getting help -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)! +- We advise companies using Caddy to secure a support contract through [Ardan Labs](https://www.ardanlabs.com) before help is needed. -If you want to contribute to the documentation, please submit pull requests to [caddyserver/website](https://github.com/caddyserver/website). +- 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! -Thanks for making Caddy -- and the Web -- better! +- 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). -## Donors -- [DigitalOcean](https://m.do.co/c/6d7bdafccf96) is hosting the Caddy project. -- [DNSimple](https://dnsimple.link/resolving-caddy) provides DNS services for Caddy's sites. -- [DNS Spy](https://dnsspy.io) keeps an eye on Caddy's DNS properties. -We thank them for their services. **If you want to help keep Caddy free, please [become a sponsor](https://caddyserver.com/pricing)!** +## About +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. -## About the Project +**The name "Caddy" is trademarked.** The name of the software is "Caddy", not "Caddy Server" or "CaddyServer". Please call it "Caddy" or, if you wish to clarify, "the Caddy web server". Caddy is a registered trademark of Stack Holdings GmbH. -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. +- _Project on Twitter: [@caddyserver](https://twitter.com/caddyserver)_ +- _Author on Twitter: [@mholt6](https://twitter.com/mholt6)_ -**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). +Caddy is a project of [ZeroSSL](https://zerossl.com), a Stack Holdings company. -*Author on Twitter: [@mholt6](https://twitter.com/mholt6)* +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. diff --git a/admin.go b/admin.go new file mode 100644 index 00000000000..6df5a23f7d7 --- /dev/null +++ b/admin.go @@ -0,0 +1,1407 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddy + +import ( + "bytes" + "context" + "crypto" + "crypto/tls" + "crypto/x509" + "encoding/base64" + "encoding/json" + "errors" + "expvar" + "fmt" + "hash" + "io" + "net" + "net/http" + "net/http/pprof" + "net/url" + "os" + "path" + "regexp" + "slices" + "strconv" + "strings" + "sync" + "time" + + "github.com/caddyserver/certmagic" + "github.com/cespare/xxhash/v2" + "github.com/prometheus/client_golang/prometheus" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +func init() { + // The hard-coded default `DefaultAdminListen` can be overridden + // by setting the `CADDY_ADMIN` environment variable. + // The environment variable may be used by packagers to change + // the default admin address to something more appropriate for + // that platform. See #5317 for discussion. + if env, exists := os.LookupEnv("CADDY_ADMIN"); exists { + DefaultAdminListen = env + } +} + +// AdminConfig configures Caddy's API endpoint, which is used +// to manage Caddy while it is running. +type AdminConfig struct { + // If true, the admin endpoint will be completely disabled. + // Note that this makes any runtime changes to the config + // impossible, since the interface to do so is through the + // admin endpoint. + Disabled bool `json:"disabled,omitempty"` + + // The address to which the admin endpoint's listener should + // bind itself. Can be any single network address that can be + // parsed by Caddy. Accepts placeholders. + // Default: the value of the `CADDY_ADMIN` environment variable, + // or `localhost:2019` otherwise. + // + // Remember: When changing this value through a config reload, + // be sure to use the `--address` CLI flag to specify the current + // admin address if the currently-running admin endpoint is not + // the default address. + Listen string `json:"listen,omitempty"` + + // If true, CORS headers will be emitted, and requests to the + // API will be rejected if their `Host` and `Origin` headers + // do not match the expected value(s). Use `origins` to + // customize which origins/hosts are allowed. If `origins` is + // not set, the listen address is the only value allowed by + // default. Enforced only on local (plaintext) endpoint. + EnforceOrigin bool `json:"enforce_origin,omitempty"` + + // The list of allowed origins/hosts for API requests. Only needed + // if accessing the admin endpoint from a host different from the + // socket's network interface or if `enforce_origin` is true. If not + // set, the listener address will be the default value. If set but + // empty, no origins will be allowed. Enforced only on local + // (plaintext) endpoint. + Origins []string `json:"origins,omitempty"` + + // Options pertaining to configuration management. + Config *ConfigSettings `json:"config,omitempty"` + + // Options that establish this server's identity. Identity refers to + // credentials which can be used to uniquely identify and authenticate + // this server instance. This is required if remote administration is + // enabled (but does not require remote administration to be enabled). + // Default: no identity management. + Identity *IdentityConfig `json:"identity,omitempty"` + + // Options pertaining to remote administration. By default, remote + // administration is disabled. If enabled, identity management must + // also be configured, as that is how the endpoint is secured. + // See the neighboring "identity" object. + // + // EXPERIMENTAL: This feature is subject to change. + Remote *RemoteAdmin `json:"remote,omitempty"` + + // Holds onto the routers so that we can later provision them + // if they require provisioning. + routers []AdminRouter +} + +// ConfigSettings configures the management of configuration. +type ConfigSettings struct { + // Whether to keep a copy of the active config on disk. Default is true. + // Note that "pulled" dynamic configs (using the neighboring "load" module) + // are not persisted; only configs that are pushed to Caddy get persisted. + Persist *bool `json:"persist,omitempty"` + + // Loads a new configuration. This is helpful if your configs are + // managed elsewhere and you want Caddy to pull its config dynamically + // when it starts. The pulled config completely replaces the current + // one, just like any other config load. It is an error if a pulled + // config is configured to pull another config without a load_delay, + // as this creates a tight loop. + // + // EXPERIMENTAL: Subject to change. + LoadRaw json.RawMessage `json:"load,omitempty" caddy:"namespace=caddy.config_loaders inline_key=module"` + + // The duration after which to load config. If set, config will be pulled + // from the config loader after this duration. A delay is required if a + // dynamically-loaded config is configured to load yet another config. To + // load configs on a regular interval, ensure this value is set the same + // on all loaded configs; it can also be variable if needed, and to stop + // the loop, simply remove dynamic config loading from the next-loaded + // config. + // + // EXPERIMENTAL: Subject to change. + LoadDelay Duration `json:"load_delay,omitempty"` +} + +// IdentityConfig configures management of this server's identity. An identity +// consists of credentials that uniquely verify this instance; for example, +// TLS certificates (public + private key pairs). +type IdentityConfig struct { + // List of names or IP addresses which refer to this server. + // Certificates will be obtained for these identifiers so + // secure TLS connections can be made using them. + Identifiers []string `json:"identifiers,omitempty"` + + // Issuers that can provide this admin endpoint its identity + // certificate(s). Default: ACME issuers configured for + // ZeroSSL and Let's Encrypt. Be sure to change this if you + // require credentials for private identifiers. + IssuersRaw []json.RawMessage `json:"issuers,omitempty" caddy:"namespace=tls.issuance inline_key=module"` + + issuers []certmagic.Issuer +} + +// RemoteAdmin enables and configures remote administration. If enabled, +// a secure listener enforcing mutual TLS authentication will be started +// on a different port from the standard plaintext admin server. +// +// This endpoint is secured using identity management, which must be +// configured separately (because identity management does not depend +// on remote administration). See the admin/identity config struct. +// +// EXPERIMENTAL: Subject to change. +type RemoteAdmin struct { + // The address on which to start the secure listener. Accepts placeholders. + // Default: :2021 + Listen string `json:"listen,omitempty"` + + // List of access controls for this secure admin endpoint. + // This configures TLS mutual authentication (i.e. authorized + // client certificates), but also application-layer permissions + // like which paths and methods each identity is authorized for. + AccessControl []*AdminAccess `json:"access_control,omitempty"` +} + +// AdminAccess specifies what permissions an identity or group +// of identities are granted. +type AdminAccess struct { + // Base64-encoded DER certificates containing public keys to accept. + // (The contents of PEM certificate blocks are base64-encoded DER.) + // Any of these public keys can appear in any part of a verified chain. + PublicKeys []string `json:"public_keys,omitempty"` + + // Limits what the associated identities are allowed to do. + // If unspecified, all permissions are granted. + Permissions []AdminPermissions `json:"permissions,omitempty"` + + publicKeys []crypto.PublicKey +} + +// AdminPermissions specifies what kinds of requests are allowed +// to be made to the admin endpoint. +type AdminPermissions struct { + // The API paths allowed. Paths are simple prefix matches. + // Any subpath of the specified paths will be allowed. + Paths []string `json:"paths,omitempty"` + + // The HTTP methods allowed for the given paths. + Methods []string `json:"methods,omitempty"` +} + +// newAdminHandler reads admin's config and returns an http.Handler suitable +// for use in an admin endpoint server, which will be listening on listenAddr. +func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool, _ Context) adminHandler { + muxWrap := adminHandler{mux: http.NewServeMux()} + + // secure the local or remote endpoint respectively + if remote { + muxWrap.remoteControl = admin.Remote + } else { + muxWrap.enforceHost = !addr.isWildcardInterface() + muxWrap.allowedOrigins = admin.allowedOrigins(addr) + muxWrap.enforceOrigin = admin.EnforceOrigin + } + + addRouteWithMetrics := func(pattern string, handlerLabel string, h http.Handler) { + labels := prometheus.Labels{"path": pattern, "handler": handlerLabel} + h = instrumentHandlerCounter( + adminMetrics.requestCount.MustCurryWith(labels), + h, + ) + muxWrap.mux.Handle(pattern, h) + } + // addRoute just calls muxWrap.mux.Handle after + // wrapping the handler with error handling + addRoute := func(pattern string, handlerLabel string, h AdminHandler) { + wrapper := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + err := h.ServeHTTP(w, r) + if err != nil { + labels := prometheus.Labels{ + "path": pattern, + "handler": handlerLabel, + "method": strings.ToUpper(r.Method), + } + adminMetrics.requestErrors.With(labels).Inc() + } + muxWrap.handleError(w, r, err) + }) + addRouteWithMetrics(pattern, handlerLabel, wrapper) + } + + const handlerLabel = "admin" + + // register standard config control endpoints + addRoute("/"+rawConfigKey+"/", handlerLabel, AdminHandlerFunc(handleConfig)) + addRoute("/id/", handlerLabel, AdminHandlerFunc(handleConfigID)) + addRoute("/stop", handlerLabel, AdminHandlerFunc(handleStop)) + + // register debugging endpoints + addRouteWithMetrics("/debug/pprof/", handlerLabel, http.HandlerFunc(pprof.Index)) + addRouteWithMetrics("/debug/pprof/cmdline", handlerLabel, http.HandlerFunc(pprof.Cmdline)) + addRouteWithMetrics("/debug/pprof/profile", handlerLabel, http.HandlerFunc(pprof.Profile)) + addRouteWithMetrics("/debug/pprof/symbol", handlerLabel, http.HandlerFunc(pprof.Symbol)) + addRouteWithMetrics("/debug/pprof/trace", handlerLabel, http.HandlerFunc(pprof.Trace)) + addRouteWithMetrics("/debug/vars", handlerLabel, expvar.Handler()) + + // register third-party module endpoints + for _, m := range GetModules("admin.api") { + router := m.New().(AdminRouter) + for _, route := range router.Routes() { + addRoute(route.Pattern, handlerLabel, route.Handler) + } + admin.routers = append(admin.routers, router) + } + + return muxWrap +} + +// provisionAdminRouters provisions all the router modules +// in the admin.api namespace that need provisioning. +func (admin *AdminConfig) provisionAdminRouters(ctx Context) error { + for _, router := range admin.routers { + provisioner, ok := router.(Provisioner) + if !ok { + continue + } + + err := provisioner.Provision(ctx) + if err != nil { + return err + } + } + + // We no longer need the routers once provisioned, allow for GC + admin.routers = nil + + return nil +} + +// allowedOrigins returns a list of origins that are allowed. +// If admin.Origins is nil (null), the provided listen address +// will be used as the default origin. If admin.Origins is +// empty, no origins will be allowed, effectively bricking the +// endpoint for non-unix-socket endpoints, but whatever. +func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL { + uniqueOrigins := make(map[string]struct{}) + for _, o := range admin.Origins { + uniqueOrigins[o] = struct{}{} + } + if admin.Origins == nil { + if addr.isLoopback() { + if addr.IsUnixNetwork() || addr.IsFdNetwork() { + // RFC 2616, Section 14.26: + // "A client MUST include a Host header field in all HTTP/1.1 request + // messages. If the requested URI does not include an Internet host + // name for the service being requested, then the Host header field MUST + // be given with an empty value." + // + // UPDATE July 2023: Go broke this by patching a minor security bug in 1.20.6. + // Understandable, but frustrating. See: + // https://github.com/golang/go/issues/60374 + // See also the discussion here: + // https://github.com/golang/go/issues/61431 + // + // We can no longer conform to RFC 2616 Section 14.26 from either Go or curl + // in purity. (Curl allowed no host between 7.40 and 7.50, but now requires a + // bogus host; see https://superuser.com/a/925610.) If we disable Host/Origin + // security checks, the infosec community assures me that it is secure to do + // so, because: + // 1) Browsers do not allow access to unix sockets + // 2) DNS is irrelevant to unix sockets + // + // I am not quite ready to trust either of those external factors, so instead + // of disabling Host/Origin checks, we now allow specific Host values when + // accessing the admin endpoint over unix sockets. I definitely don't trust + // DNS (e.g. I don't trust 'localhost' to always resolve to the local host), + // and IP shouldn't even be used, but if it is for some reason, I think we can + // at least be reasonably assured that 127.0.0.1 and ::1 route to the local + // machine, meaning that a hypothetical browser origin would have to be on the + // local machine as well. + uniqueOrigins[""] = struct{}{} + uniqueOrigins["127.0.0.1"] = struct{}{} + uniqueOrigins["::1"] = struct{}{} + } else { + uniqueOrigins[net.JoinHostPort("localhost", addr.port())] = struct{}{} + uniqueOrigins[net.JoinHostPort("::1", addr.port())] = struct{}{} + uniqueOrigins[net.JoinHostPort("127.0.0.1", addr.port())] = struct{}{} + } + } + if !addr.IsUnixNetwork() && !addr.IsFdNetwork() { + uniqueOrigins[addr.JoinHostPort(0)] = struct{}{} + } + } + allowed := make([]*url.URL, 0, len(uniqueOrigins)) + for originStr := range uniqueOrigins { + var origin *url.URL + if strings.Contains(originStr, "://") { + var err error + origin, err = url.Parse(originStr) + if err != nil { + continue + } + origin.Path = "" + origin.RawPath = "" + origin.Fragment = "" + origin.RawFragment = "" + origin.RawQuery = "" + } else { + origin = &url.URL{Host: originStr} + } + allowed = append(allowed, origin) + } + return allowed +} + +// replaceLocalAdminServer replaces the running local admin server +// according to the relevant configuration in cfg. If no configuration +// for the admin endpoint exists in cfg, a default one is used, so +// that there is always an admin server (unless it is explicitly +// configured to be disabled). +// Critically note that some elements and functionality of the context +// may not be ready, e.g. storage. Tread carefully. +func replaceLocalAdminServer(cfg *Config, ctx Context) error { + // always* be sure to close down the old admin endpoint + // as gracefully as possible, even if the new one is + // disabled -- careful to use reference to the current + // (old) admin endpoint since it will be different + // when the function returns + // (* except if the new one fails to start) + oldAdminServer := localAdminServer + var err error + defer func() { + // do the shutdown asynchronously so that any + // current API request gets a response; this + // goroutine may last a few seconds + if oldAdminServer != nil && err == nil { + go func(oldAdminServer *http.Server) { + err := stopAdminServer(oldAdminServer) + if err != nil { + Log().Named("admin").Error("stopping current admin endpoint", zap.Error(err)) + } + }(oldAdminServer) + } + }() + + // set a default if admin wasn't otherwise configured + if cfg.Admin == nil { + cfg.Admin = &AdminConfig{ + Listen: DefaultAdminListen, + } + } + + // if new admin endpoint is to be disabled, we're done + if cfg.Admin.Disabled { + Log().Named("admin").Warn("admin endpoint disabled") + return nil + } + + // extract a singular listener address + addr, err := parseAdminListenAddr(cfg.Admin.Listen, DefaultAdminListen) + if err != nil { + return err + } + + handler := cfg.Admin.newAdminHandler(addr, false, ctx) + + ln, err := addr.Listen(context.TODO(), 0, net.ListenConfig{}) + if err != nil { + return err + } + + serverMu.Lock() + localAdminServer = &http.Server{ + Addr: addr.String(), // for logging purposes only + Handler: handler, + ReadTimeout: 10 * time.Second, + ReadHeaderTimeout: 5 * time.Second, + IdleTimeout: 60 * time.Second, + MaxHeaderBytes: 1024 * 64, + } + serverMu.Unlock() + + adminLogger := Log().Named("admin") + go func() { + serverMu.Lock() + server := localAdminServer + serverMu.Unlock() + if err := server.Serve(ln.(net.Listener)); !errors.Is(err, http.ErrServerClosed) { + adminLogger.Error("admin server shutdown for unknown reason", zap.Error(err)) + } + }() + + adminLogger.Info("admin endpoint started", + zap.String("address", addr.String()), + zap.Bool("enforce_origin", cfg.Admin.EnforceOrigin), + zap.Array("origins", loggableURLArray(handler.allowedOrigins))) + + if !handler.enforceHost { + adminLogger.Warn("admin endpoint on open interface; host checking disabled", + zap.String("address", addr.String())) + } + + return nil +} + +// manageIdentity sets up automated identity management for this server. +func manageIdentity(ctx Context, cfg *Config) error { + if cfg == nil || cfg.Admin == nil || cfg.Admin.Identity == nil { + return nil + } + + // set default issuers; this is pretty hacky because we can't + // import the caddytls package -- but it works + if cfg.Admin.Identity.IssuersRaw == nil { + cfg.Admin.Identity.IssuersRaw = []json.RawMessage{ + json.RawMessage(`{"module": "acme"}`), + } + } + + // load and provision issuer modules + if cfg.Admin.Identity.IssuersRaw != nil { + val, err := ctx.LoadModule(cfg.Admin.Identity, "IssuersRaw") + if err != nil { + return fmt.Errorf("loading identity issuer modules: %s", err) + } + for _, issVal := range val.([]any) { + cfg.Admin.Identity.issuers = append(cfg.Admin.Identity.issuers, issVal.(certmagic.Issuer)) + } + } + + // we'll make a new cache when we make the CertMagic config, so stop any previous cache + if identityCertCache != nil { + identityCertCache.Stop() + } + + logger := Log().Named("admin.identity") + cmCfg := cfg.Admin.Identity.certmagicConfig(logger, true) + + // issuers have circular dependencies with the configs because, + // as explained in the caddytls package, they need access to the + // correct storage and cache to solve ACME challenges + for _, issuer := range cfg.Admin.Identity.issuers { + // avoid import cycle with caddytls package, so manually duplicate the interface here, yuck + if annoying, ok := issuer.(interface{ SetConfig(cfg *certmagic.Config) }); ok { + annoying.SetConfig(cmCfg) + } + } + + // obtain and renew server identity certificate(s) + return cmCfg.ManageAsync(ctx, cfg.Admin.Identity.Identifiers) +} + +// replaceRemoteAdminServer replaces the running remote admin server +// according to the relevant configuration in cfg. It stops any previous +// remote admin server and only starts a new one if configured. +func replaceRemoteAdminServer(ctx Context, cfg *Config) error { + if cfg == nil { + return nil + } + + remoteLogger := Log().Named("admin.remote") + + oldAdminServer := remoteAdminServer + defer func() { + if oldAdminServer != nil { + go func(oldAdminServer *http.Server) { + err := stopAdminServer(oldAdminServer) + if err != nil { + Log().Named("admin").Error("stopping current secure admin endpoint", zap.Error(err)) + } + }(oldAdminServer) + } + }() + + if cfg.Admin == nil || cfg.Admin.Remote == nil { + return nil + } + + addr, err := parseAdminListenAddr(cfg.Admin.Remote.Listen, DefaultRemoteAdminListen) + if err != nil { + return err + } + + // make the HTTP handler but disable Host/Origin enforcement + // because we are using TLS authentication instead + handler := cfg.Admin.newAdminHandler(addr, true, ctx) + + // create client certificate pool for TLS mutual auth, and extract public keys + // so that we can enforce access controls at the application layer + clientCertPool := x509.NewCertPool() + for i, accessControl := range cfg.Admin.Remote.AccessControl { + for j, certBase64 := range accessControl.PublicKeys { + cert, err := decodeBase64DERCert(certBase64) + if err != nil { + return fmt.Errorf("access control %d public key %d: parsing base64 certificate DER: %v", i, j, err) + } + accessControl.publicKeys = append(accessControl.publicKeys, cert.PublicKey) + clientCertPool.AddCert(cert) + } + } + + // create TLS config that will enforce mutual authentication + if identityCertCache == nil { + return fmt.Errorf("cannot enable remote admin without a certificate cache; configure identity management to initialize a certificate cache") + } + cmCfg := cfg.Admin.Identity.certmagicConfig(remoteLogger, false) + tlsConfig := cmCfg.TLSConfig() + tlsConfig.NextProtos = nil // this server does not solve ACME challenges + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + tlsConfig.ClientCAs = clientCertPool + + // convert logger to stdlib so it can be used by HTTP server + serverLogger, err := zap.NewStdLogAt(remoteLogger, zap.DebugLevel) + if err != nil { + return err + } + + serverMu.Lock() + // create secure HTTP server + remoteAdminServer = &http.Server{ + Addr: addr.String(), // for logging purposes only + Handler: handler, + TLSConfig: tlsConfig, + ReadTimeout: 10 * time.Second, + ReadHeaderTimeout: 5 * time.Second, + IdleTimeout: 60 * time.Second, + MaxHeaderBytes: 1024 * 64, + ErrorLog: serverLogger, + } + serverMu.Unlock() + + // start listener + lnAny, err := addr.Listen(ctx, 0, net.ListenConfig{}) + if err != nil { + return err + } + ln := lnAny.(net.Listener) + ln = tls.NewListener(ln, tlsConfig) + + go func() { + serverMu.Lock() + server := remoteAdminServer + serverMu.Unlock() + if err := server.Serve(ln); !errors.Is(err, http.ErrServerClosed) { + remoteLogger.Error("admin remote server shutdown for unknown reason", zap.Error(err)) + } + }() + + remoteLogger.Info("secure admin remote control endpoint started", + zap.String("address", addr.String())) + + return nil +} + +func (ident *IdentityConfig) certmagicConfig(logger *zap.Logger, makeCache bool) *certmagic.Config { + var cmCfg *certmagic.Config + if ident == nil { + // user might not have configured identity; that's OK, we can still make a + // certmagic config, although it'll be mostly useless for remote management + ident = new(IdentityConfig) + } + template := certmagic.Config{ + Storage: DefaultStorage, // do not act as part of a cluster (this is for the server's local identity) + Logger: logger, + Issuers: ident.issuers, + } + if makeCache { + identityCertCache = certmagic.NewCache(certmagic.CacheOptions{ + GetConfigForCert: func(certmagic.Certificate) (*certmagic.Config, error) { + return cmCfg, nil + }, + Logger: logger.Named("cache"), + }) + } + cmCfg = certmagic.New(identityCertCache, template) + return cmCfg +} + +// IdentityCredentials returns this instance's configured, managed identity credentials +// that can be used in TLS client authentication. +func (ctx Context) IdentityCredentials(logger *zap.Logger) ([]tls.Certificate, error) { + if ctx.cfg == nil || ctx.cfg.Admin == nil || ctx.cfg.Admin.Identity == nil { + return nil, fmt.Errorf("no server identity configured") + } + ident := ctx.cfg.Admin.Identity + if len(ident.Identifiers) == 0 { + return nil, fmt.Errorf("no identifiers configured") + } + if logger == nil { + logger = Log() + } + magic := ident.certmagicConfig(logger, false) + return magic.ClientCredentials(ctx, ident.Identifiers) +} + +// enforceAccessControls enforces application-layer access controls for r based on remote. +// It expects that the TLS server has already established at least one verified chain of +// trust, and then looks for a matching, authorized public key that is allowed to access +// the defined path(s) using the defined method(s). +func (remote RemoteAdmin) enforceAccessControls(r *http.Request) error { + for _, chain := range r.TLS.VerifiedChains { + for _, peerCert := range chain { + for _, adminAccess := range remote.AccessControl { + for _, allowedKey := range adminAccess.publicKeys { + // see if we found a matching public key; the TLS server already verified the chain + // so we know the client possesses the associated private key; this handy interface + // doesn't appear to be defined anywhere in the std lib, but was implemented here: + // https://github.com/golang/go/commit/b5f2c0f50297fa5cd14af668ddd7fd923626cf8c + comparer, ok := peerCert.PublicKey.(interface{ Equal(crypto.PublicKey) bool }) + if !ok || !comparer.Equal(allowedKey) { + continue + } + + // key recognized; make sure its HTTP request is permitted + for _, accessPerm := range adminAccess.Permissions { + // verify method + methodFound := accessPerm.Methods == nil || slices.Contains(accessPerm.Methods, r.Method) + if !methodFound { + return APIError{ + HTTPStatus: http.StatusForbidden, + Message: "not authorized to use this method", + } + } + + // verify path + pathFound := accessPerm.Paths == nil + for _, allowedPath := range accessPerm.Paths { + if strings.HasPrefix(r.URL.Path, allowedPath) { + pathFound = true + break + } + } + if !pathFound { + return APIError{ + HTTPStatus: http.StatusForbidden, + Message: "not authorized to access this path", + } + } + } + + // public key authorized, method and path allowed + return nil + } + } + } + } + + // in theory, this should never happen; with an unverified chain, the TLS server + // should not accept the connection in the first place, and the acceptable cert + // pool is configured using the same list of public keys we verify against + return APIError{ + HTTPStatus: http.StatusUnauthorized, + Message: "client identity not authorized", + } +} + +func stopAdminServer(srv *http.Server) error { + if srv == nil { + return fmt.Errorf("no admin server") + } + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + err := srv.Shutdown(ctx) + if err != nil { + return fmt.Errorf("shutting down admin server: %v", err) + } + Log().Named("admin").Info("stopped previous server", zap.String("address", srv.Addr)) + return nil +} + +// AdminRouter is a type which can return routes for the admin API. +type AdminRouter interface { + Routes() []AdminRoute +} + +// AdminRoute represents a route for the admin endpoint. +type AdminRoute struct { + Pattern string + Handler AdminHandler +} + +type adminHandler struct { + mux *http.ServeMux + + // security for local/plaintext endpoint + enforceOrigin bool + enforceHost bool + allowedOrigins []*url.URL + + // security for remote/encrypted endpoint + remoteControl *RemoteAdmin +} + +// ServeHTTP is the external entry point for API requests. +// It will only be called once per request. +func (h adminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ip, port, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + ip = r.RemoteAddr + port = "" + } + log := Log().Named("admin.api").With( + zap.String("method", r.Method), + zap.String("host", r.Host), + zap.String("uri", r.RequestURI), + zap.String("remote_ip", ip), + zap.String("remote_port", port), + zap.Reflect("headers", r.Header), + ) + if r.TLS != nil { + log = log.With( + zap.Bool("secure", true), + zap.Int("verified_chains", len(r.TLS.VerifiedChains)), + ) + } + if r.RequestURI == "/metrics" { + log.Debug("received request") + } else { + log.Info("received request") + } + h.serveHTTP(w, r) +} + +// serveHTTP is the internal entry point for API requests. It may +// be called more than once per request, for example if a request +// is rewritten (i.e. internal redirect). +func (h adminHandler) serveHTTP(w http.ResponseWriter, r *http.Request) { + if h.remoteControl != nil { + // enforce access controls on secure endpoint + if err := h.remoteControl.enforceAccessControls(r); err != nil { + h.handleError(w, r, err) + return + } + } + + if strings.Contains(r.Header.Get("Upgrade"), "websocket") { + // I've never been able demonstrate a vulnerability myself, but apparently + // WebSocket connections originating from browsers aren't subject to CORS + // restrictions, so we'll just be on the safe side + h.handleError(w, r, fmt.Errorf("websocket connections aren't allowed")) + return + } + + if h.enforceHost { + // DNS rebinding mitigation + err := h.checkHost(r) + if err != nil { + h.handleError(w, r, err) + return + } + } + + if h.enforceOrigin { + // cross-site mitigation + origin, err := h.checkOrigin(r) + if err != nil { + h.handleError(w, r, err) + return + } + + if r.Method == http.MethodOptions { + w.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET, POST, PUT, PATCH, DELETE") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Cache-Control") + w.Header().Set("Access-Control-Allow-Credentials", "true") + } + w.Header().Set("Access-Control-Allow-Origin", origin) + } + + h.mux.ServeHTTP(w, r) +} + +func (h adminHandler) handleError(w http.ResponseWriter, r *http.Request, err error) { + if err == nil { + return + } + if err == errInternalRedir { + h.serveHTTP(w, r) + return + } + + apiErr, ok := err.(APIError) + if !ok { + apiErr = APIError{ + HTTPStatus: http.StatusInternalServerError, + Err: err, + } + } + if apiErr.HTTPStatus == 0 { + apiErr.HTTPStatus = http.StatusInternalServerError + } + if apiErr.Message == "" && apiErr.Err != nil { + apiErr.Message = apiErr.Err.Error() + } + + Log().Named("admin.api").Error("request error", + zap.Error(err), + zap.Int("status_code", apiErr.HTTPStatus), + ) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(apiErr.HTTPStatus) + encErr := json.NewEncoder(w).Encode(apiErr) + if encErr != nil { + Log().Named("admin.api").Error("failed to encode error response", zap.Error(encErr)) + } +} + +// checkHost returns a handler that wraps next such that +// it will only be called if the request's Host header matches +// a trustworthy/expected value. This helps to mitigate DNS +// rebinding attacks. +func (h adminHandler) checkHost(r *http.Request) error { + allowed := slices.ContainsFunc(h.allowedOrigins, func(u *url.URL) bool { + return r.Host == u.Host + }) + if !allowed { + return APIError{ + HTTPStatus: http.StatusForbidden, + Err: fmt.Errorf("host not allowed: %s", r.Host), + } + } + return nil +} + +// checkOrigin ensures that the Origin header, if +// set, matches the intended target; prevents arbitrary +// sites from issuing requests to our listener. It +// returns the origin that was obtained from r. +func (h adminHandler) checkOrigin(r *http.Request) (string, error) { + originStr, origin := h.getOrigin(r) + if origin == nil { + return "", APIError{ + HTTPStatus: http.StatusForbidden, + Err: fmt.Errorf("required Origin header is missing or invalid"), + } + } + if !h.originAllowed(origin) { + return "", APIError{ + HTTPStatus: http.StatusForbidden, + Err: fmt.Errorf("client is not allowed to access from origin '%s'", originStr), + } + } + return origin.String(), nil +} + +func (h adminHandler) getOrigin(r *http.Request) (string, *url.URL) { + origin := r.Header.Get("Origin") + if origin == "" { + origin = r.Header.Get("Referer") + } + originURL, err := url.Parse(origin) + if err != nil { + return origin, nil + } + originURL.Path = "" + originURL.RawPath = "" + originURL.Fragment = "" + originURL.RawFragment = "" + originURL.RawQuery = "" + return origin, originURL +} + +func (h adminHandler) originAllowed(origin *url.URL) bool { + for _, allowedOrigin := range h.allowedOrigins { + if allowedOrigin.Scheme != "" && origin.Scheme != allowedOrigin.Scheme { + continue + } + if origin.Host == allowedOrigin.Host { + return true + } + } + return false +} + +// etagHasher returns a the hasher we used on the config to both +// produce and verify ETags. +func etagHasher() hash.Hash { return xxhash.New() } + +// makeEtag returns an Etag header value (including quotes) for +// the given config path and hash of contents at that path. +func makeEtag(path string, hash hash.Hash) string { + return fmt.Sprintf(`"%s %x"`, path, hash.Sum(nil)) +} + +// This buffer pool is used to keep buffers for +// reading the config file during eTag header generation +var bufferPool = sync.Pool{ + New: func() any { + return new(bytes.Buffer) + }, +} + +func handleConfig(w http.ResponseWriter, r *http.Request) error { + switch r.Method { + case http.MethodGet: + w.Header().Set("Content-Type", "application/json") + hash := etagHasher() + + // Read the config into a buffer instead of writing directly to + // the response writer, as we want to set the ETag as the header, + // not the trailer. + buf := bufferPool.Get().(*bytes.Buffer) + buf.Reset() + defer bufferPool.Put(buf) + + configWriter := io.MultiWriter(buf, hash) + err := readConfig(r.URL.Path, configWriter) + if err != nil { + return APIError{HTTPStatus: http.StatusBadRequest, Err: err} + } + + // we could consider setting up a sync.Pool for the summed + // hashes to reduce GC pressure. + w.Header().Set("Etag", makeEtag(r.URL.Path, hash)) + _, err = w.Write(buf.Bytes()) + if err != nil { + return APIError{HTTPStatus: http.StatusInternalServerError, Err: err} + } + + return nil + + case http.MethodPost, + http.MethodPut, + http.MethodPatch, + http.MethodDelete: + + // DELETE does not use a body, but the others do + var body []byte + if r.Method != http.MethodDelete { + if ct := r.Header.Get("Content-Type"); !strings.Contains(ct, "/json") { + return APIError{ + HTTPStatus: http.StatusBadRequest, + Err: fmt.Errorf("unacceptable content-type: %v; 'application/json' required", ct), + } + } + + buf := bufPool.Get().(*bytes.Buffer) + buf.Reset() + defer bufPool.Put(buf) + + _, err := io.Copy(buf, r.Body) + if err != nil { + return APIError{ + HTTPStatus: http.StatusBadRequest, + Err: fmt.Errorf("reading request body: %v", err), + } + } + body = buf.Bytes() + } + + forceReload := r.Header.Get("Cache-Control") == "must-revalidate" + + err := changeConfig(r.Method, r.URL.Path, body, r.Header.Get("If-Match"), forceReload) + if err != nil && !errors.Is(err, errSameConfig) { + return err + } + + default: + return APIError{ + HTTPStatus: http.StatusMethodNotAllowed, + Err: fmt.Errorf("method %s not allowed", r.Method), + } + } + + return nil +} + +func handleConfigID(w http.ResponseWriter, r *http.Request) error { + idPath := r.URL.Path + + parts := strings.Split(idPath, "/") + if len(parts) < 3 || parts[2] == "" { + return APIError{ + HTTPStatus: http.StatusBadRequest, + Err: fmt.Errorf("request path is missing object ID"), + } + } + if parts[0] != "" || parts[1] != "id" { + return APIError{ + HTTPStatus: http.StatusBadRequest, + Err: fmt.Errorf("malformed object path"), + } + } + id := parts[2] + + // map the ID to the expanded path + rawCfgMu.RLock() + expanded, ok := rawCfgIndex[id] + rawCfgMu.RUnlock() + if !ok { + return APIError{ + HTTPStatus: http.StatusNotFound, + Err: fmt.Errorf("unknown object ID '%s'", id), + } + } + + // piece the full URL path back together + parts = append([]string{expanded}, parts[3:]...) + r.URL.Path = path.Join(parts...) + + return errInternalRedir +} + +func handleStop(w http.ResponseWriter, r *http.Request) error { + if r.Method != http.MethodPost { + return APIError{ + HTTPStatus: http.StatusMethodNotAllowed, + Err: fmt.Errorf("method not allowed"), + } + } + + exitProcess(context.Background(), Log().Named("admin.api")) + return nil +} + +// unsyncedConfigAccess traverses into the current config and performs +// the operation at path according to method, using body and out as +// needed. This is a low-level, unsynchronized function; most callers +// will want to use changeConfig or readConfig instead. This requires a +// read or write lock on currentCtxMu, depending on method (GET needs +// only a read lock; all others need a write lock). +func unsyncedConfigAccess(method, path string, body []byte, out io.Writer) error { + var err error + var val any + + // if there is a request body, decode it into the + // variable that will be set in the config according + // to method and path + if len(body) > 0 { + err = json.Unmarshal(body, &val) + if err != nil { + return fmt.Errorf("decoding request body: %v", err) + } + } + + enc := json.NewEncoder(out) + + cleanPath := strings.Trim(path, "/") + if cleanPath == "" { + return fmt.Errorf("no traversable path") + } + + parts := strings.Split(cleanPath, "/") + if len(parts) == 0 { + return fmt.Errorf("path missing") + } + + // A path that ends with "..." implies: + // 1) the part before it is an array + // 2) the payload is an array + // and means that the user wants to expand the elements + // in the payload array and append each one into the + // destination array, like so: + // array = append(array, elems...) + // This special case is handled below. + ellipses := parts[len(parts)-1] == "..." + if ellipses { + parts = parts[:len(parts)-1] + } + + var ptr any = rawCfg + +traverseLoop: + for i, part := range parts { + switch v := ptr.(type) { + case map[string]any: + // if the next part enters a slice, and the slice is our destination, + // handle it specially (because appending to the slice copies the slice + // header, which does not replace the original one like we want) + if arr, ok := v[part].([]any); ok && i == len(parts)-2 { + var idx int + if method != http.MethodPost { + idxStr := parts[len(parts)-1] + idx, err = strconv.Atoi(idxStr) + if err != nil { + return fmt.Errorf("[%s] invalid array index '%s': %v", + path, idxStr, err) + } + if idx < 0 || (method != http.MethodPut && idx >= len(arr)) || idx > len(arr) { + return fmt.Errorf("[%s] array index out of bounds: %s", path, idxStr) + } + } + + switch method { + case http.MethodGet: + err = enc.Encode(arr[idx]) + if err != nil { + return fmt.Errorf("encoding config: %v", err) + } + case http.MethodPost: + if ellipses { + valArray, ok := val.([]any) + if !ok { + return fmt.Errorf("final element is not an array") + } + v[part] = append(arr, valArray...) + } else { + v[part] = append(arr, val) + } + case http.MethodPut: + // avoid creation of new slice and a second copy (see + // https://github.com/golang/go/wiki/SliceTricks#insert) + arr = append(arr, nil) + copy(arr[idx+1:], arr[idx:]) + arr[idx] = val + v[part] = arr + case http.MethodPatch: + arr[idx] = val + case http.MethodDelete: + v[part] = append(arr[:idx], arr[idx+1:]...) + default: + return fmt.Errorf("unrecognized method %s", method) + } + break traverseLoop + } + + if i == len(parts)-1 { + switch method { + case http.MethodGet: + err = enc.Encode(v[part]) + if err != nil { + return fmt.Errorf("encoding config: %v", err) + } + case http.MethodPost: + // if the part is an existing list, POST appends to + // it, otherwise it just sets or creates the value + if arr, ok := v[part].([]any); ok { + if ellipses { + valArray, ok := val.([]any) + if !ok { + return fmt.Errorf("final element is not an array") + } + v[part] = append(arr, valArray...) + } else { + v[part] = append(arr, val) + } + } else { + v[part] = val + } + case http.MethodPut: + if _, ok := v[part]; ok { + return APIError{ + HTTPStatus: http.StatusConflict, + Err: fmt.Errorf("[%s] key already exists: %s", path, part), + } + } + v[part] = val + case http.MethodPatch: + if _, ok := v[part]; !ok { + return APIError{ + HTTPStatus: http.StatusNotFound, + Err: fmt.Errorf("[%s] key does not exist: %s", path, part), + } + } + v[part] = val + case http.MethodDelete: + if _, ok := v[part]; !ok { + return APIError{ + HTTPStatus: http.StatusNotFound, + Err: fmt.Errorf("[%s] key does not exist: %s", path, part), + } + } + delete(v, part) + default: + return fmt.Errorf("unrecognized method %s", method) + } + } else { + // if we are "PUTting" a new resource, the key(s) in its path + // might not exist yet; that's OK but we need to make them as + // we go, while we still have a pointer from the level above + if v[part] == nil && method == http.MethodPut { + v[part] = make(map[string]any) + } + ptr = v[part] + } + + case []any: + partInt, err := strconv.Atoi(part) + if err != nil { + return fmt.Errorf("[/%s] invalid array index '%s': %v", + strings.Join(parts[:i+1], "/"), part, err) + } + if partInt < 0 || partInt >= len(v) { + return fmt.Errorf("[/%s] array index out of bounds: %s", + strings.Join(parts[:i+1], "/"), part) + } + ptr = v[partInt] + + default: + return fmt.Errorf("invalid traversal path at: %s", strings.Join(parts[:i+1], "/")) + } + } + + return nil +} + +// RemoveMetaFields removes meta fields like "@id" from a JSON message +// by using a simple regular expression. (An alternate way to do this +// would be to delete them from the raw, map[string]any +// representation as they are indexed, then iterate the index we made +// and add them back after encoding as JSON, but this is simpler.) +func RemoveMetaFields(rawJSON []byte) []byte { + return idRegexp.ReplaceAllFunc(rawJSON, func(in []byte) []byte { + // matches with a comma on both sides (when "@id" property is + // not the first or last in the object) need to keep exactly + // one comma for correct JSON syntax + comma := []byte{','} + if bytes.HasPrefix(in, comma) && bytes.HasSuffix(in, comma) { + return comma + } + return []byte{} + }) +} + +// AdminHandler is like http.Handler except ServeHTTP may return an error. +// +// If any handler encounters an error, it should be returned for proper +// handling. +type AdminHandler interface { + ServeHTTP(http.ResponseWriter, *http.Request) error +} + +// AdminHandlerFunc is a convenience type like http.HandlerFunc. +type AdminHandlerFunc func(http.ResponseWriter, *http.Request) error + +// ServeHTTP implements the Handler interface. +func (f AdminHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) error { + return f(w, r) +} + +// APIError is a structured error that every API +// handler should return for consistency in logging +// and client responses. If Message is unset, then +// Err.Error() will be serialized in its place. +type APIError struct { + HTTPStatus int `json:"-"` + Err error `json:"-"` + Message string `json:"error"` +} + +func (e APIError) Error() string { + if e.Err != nil { + return e.Err.Error() + } + return e.Message +} + +// parseAdminListenAddr extracts a singular listen address from either addr +// or defaultAddr, returning the network and the address of the listener. +func parseAdminListenAddr(addr string, defaultAddr string) (NetworkAddress, error) { + input, err := NewReplacer().ReplaceOrErr(addr, true, true) + if err != nil { + return NetworkAddress{}, fmt.Errorf("replacing listen address: %v", err) + } + if input == "" { + input = defaultAddr + } + listenAddr, err := ParseNetworkAddress(input) + if err != nil { + return NetworkAddress{}, fmt.Errorf("parsing listener address: %v", err) + } + if listenAddr.PortRangeSize() != 1 { + return NetworkAddress{}, fmt.Errorf("must be exactly one listener address; cannot listen on: %s", listenAddr) + } + return listenAddr, nil +} + +// decodeBase64DERCert base64-decodes, then DER-decodes, certStr. +func decodeBase64DERCert(certStr string) (*x509.Certificate, error) { + derBytes, err := base64.StdEncoding.DecodeString(certStr) + if err != nil { + return nil, err + } + return x509.ParseCertificate(derBytes) +} + +type loggableURLArray []*url.URL + +func (ua loggableURLArray) MarshalLogArray(enc zapcore.ArrayEncoder) error { + if ua == nil { + return nil + } + for _, u := range ua { + enc.AppendString(u.String()) + } + return nil +} + +var ( + // DefaultAdminListen is the address for the local admin + // listener, if none is specified at startup. + DefaultAdminListen = "localhost:2019" + + // DefaultRemoteAdminListen is the address for the remote + // (TLS-authenticated) admin listener, if enabled and not + // specified otherwise. + DefaultRemoteAdminListen = ":2021" +) + +// PIDFile writes a pidfile to the file at filename. It +// will get deleted before the process gracefully exits. +func PIDFile(filename string) error { + pid := []byte(strconv.Itoa(os.Getpid()) + "\n") + err := os.WriteFile(filename, pid, 0o600) + if err != nil { + return err + } + pidfile = filename + return nil +} + +// idRegexp is used to match ID fields and their associated values +// in the config. It also matches adjacent commas so that syntax +// can be preserved no matter where in the object the field appears. +// It supports string and most numeric values. +var idRegexp = regexp.MustCompile(`(?m),?\s*"` + idKey + `"\s*:\s*(-?[0-9]+(\.[0-9]+)?|(?U)".*")\s*,?`) + +// pidfile is the name of the pidfile, if any. +var pidfile string + +// errInternalRedir indicates an internal redirect +// and is useful when admin API handlers rewrite +// the request; in that case, authentication and +// authorization needs to happen again for the +// rewritten request. +var errInternalRedir = fmt.Errorf("internal redirect; re-authorization required") + +const ( + rawConfigKey = "config" + idKey = "@id" +) + +var bufPool = sync.Pool{ + New: func() any { + return new(bytes.Buffer) + }, +} + +// keep a reference to admin endpoint singletons while they're active +var ( + serverMu sync.Mutex + localAdminServer, remoteAdminServer *http.Server + identityCertCache *certmagic.Cache +) diff --git a/admin_test.go b/admin_test.go new file mode 100644 index 00000000000..b00cfaae251 --- /dev/null +++ b/admin_test.go @@ -0,0 +1,939 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddy + +import ( + "context" + "crypto/x509" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "reflect" + "sync" + "testing" + + "github.com/caddyserver/certmagic" + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" +) + +var testCfg = []byte(`{ + "apps": { + "http": { + "servers": { + "myserver": { + "listen": ["tcp/localhost:8080-8084"], + "read_timeout": "30s" + }, + "yourserver": { + "listen": ["127.0.0.1:5000"], + "read_header_timeout": "15s" + } + } + } + } + } + `) + +func TestUnsyncedConfigAccess(t *testing.T) { + // each test is performed in sequence, so + // each change builds on the previous ones; + // the config is not reset between tests + for i, tc := range []struct { + method string + path string // rawConfigKey will be prepended + payload string + expect string // JSON representation of what the whole config is expected to be after the request + shouldErr bool + }{ + { + method: "POST", + path: "", + payload: `{"foo": "bar", "list": ["a", "b", "c"]}`, // starting value + expect: `{"foo": "bar", "list": ["a", "b", "c"]}`, + }, + { + method: "POST", + path: "/foo", + payload: `"jet"`, + expect: `{"foo": "jet", "list": ["a", "b", "c"]}`, + }, + { + method: "POST", + path: "/bar", + payload: `{"aa": "bb", "qq": "zz"}`, + expect: `{"foo": "jet", "bar": {"aa": "bb", "qq": "zz"}, "list": ["a", "b", "c"]}`, + }, + { + method: "DELETE", + path: "/bar/qq", + expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c"]}`, + }, + { + method: "DELETE", + path: "/bar/qq", + expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c"]}`, + shouldErr: true, + }, + { + method: "POST", + path: "/list", + payload: `"e"`, + expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "e"]}`, + }, + { + method: "PUT", + path: "/list/3", + payload: `"d"`, + expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "d", "e"]}`, + }, + { + method: "DELETE", + path: "/list/3", + expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "e"]}`, + }, + { + method: "PATCH", + path: "/list/3", + payload: `"d"`, + expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "d"]}`, + }, + { + method: "POST", + path: "/list/...", + payload: `["e", "f", "g"]`, + expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "d", "e", "f", "g"]}`, + }, + } { + err := unsyncedConfigAccess(tc.method, rawConfigKey+tc.path, []byte(tc.payload), nil) + + if tc.shouldErr && err == nil { + t.Fatalf("Test %d: Expected error return value, but got: %v", i, err) + } + if !tc.shouldErr && err != nil { + t.Fatalf("Test %d: Should not have had error return value, but got: %v", i, err) + } + + // decode the expected config so we can do a convenient DeepEqual + var expectedDecoded any + err = json.Unmarshal([]byte(tc.expect), &expectedDecoded) + if err != nil { + t.Fatalf("Test %d: Unmarshaling expected config: %v", i, err) + } + + // make sure the resulting config is as we expect it + if !reflect.DeepEqual(rawCfg[rawConfigKey], expectedDecoded) { + t.Fatalf("Test %d:\nExpected:\n\t%#v\nActual:\n\t%#v", + i, expectedDecoded, rawCfg[rawConfigKey]) + } + } +} + +// TestLoadConcurrent exercises Load under concurrent conditions +// and is most useful under test with `-race` enabled. +func TestLoadConcurrent(t *testing.T) { + var wg sync.WaitGroup + + for i := 0; i < 100; i++ { + wg.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) + } +} + +func TestAdminHandlerErrorHandling(t *testing.T) { + initAdminMetrics() + + handler := adminHandler{ + mux: http.NewServeMux(), + } + + handler.mux.Handle("/error", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + err := fmt.Errorf("test error") + handler.handleError(w, r, err) + })) + + req := httptest.NewRequest(http.MethodGet, "/error", nil) + rr := httptest.NewRecorder() + + handler.ServeHTTP(rr, req) + + if rr.Code == http.StatusOK { + t.Error("expected error response, got success") + } + + var apiErr APIError + if err := json.NewDecoder(rr.Body).Decode(&apiErr); err != nil { + t.Fatalf("decoding response: %v", err) + } + if apiErr.Message != "test error" { + t.Errorf("expected error message 'test error', got '%s'", apiErr.Message) + } +} + +func initAdminMetrics() { + if adminMetrics.requestErrors != nil { + prometheus.Unregister(adminMetrics.requestErrors) + } + if adminMetrics.requestCount != nil { + prometheus.Unregister(adminMetrics.requestCount) + } + + adminMetrics.requestErrors = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "caddy", + Subsystem: "admin_http", + Name: "request_errors_total", + Help: "Number of errors that occurred handling admin endpoint requests", + }, []string{"handler", "path", "method"}) + + adminMetrics.requestCount = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "caddy", + Subsystem: "admin_http", + Name: "requests_total", + Help: "Count of requests to the admin endpoint", + }, []string{"handler", "path", "code", "method"}) // Added code and method labels + + prometheus.MustRegister(adminMetrics.requestErrors) + prometheus.MustRegister(adminMetrics.requestCount) +} + +func TestAdminHandlerBuiltinRouteErrors(t *testing.T) { + initAdminMetrics() + + cfg := &Config{ + Admin: &AdminConfig{ + Listen: "localhost:2019", + }, + } + + err := replaceLocalAdminServer(cfg, Context{}) + if err != nil { + t.Fatalf("setting up admin server: %v", err) + } + defer func() { + stopAdminServer(localAdminServer) + }() + + tests := []struct { + name string + path string + method string + expectedStatus int + }{ + { + name: "stop endpoint wrong method", + path: "/stop", + method: http.MethodGet, + expectedStatus: http.StatusMethodNotAllowed, + }, + { + name: "config endpoint wrong content-type", + path: "/config/", + method: http.MethodPost, + expectedStatus: http.StatusBadRequest, + }, + { + name: "config ID missing ID", + path: "/id/", + method: http.MethodGet, + expectedStatus: http.StatusBadRequest, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + req := httptest.NewRequest(test.method, fmt.Sprintf("http://localhost:2019%s", test.path), nil) + rr := httptest.NewRecorder() + + localAdminServer.Handler.ServeHTTP(rr, req) + + if rr.Code != test.expectedStatus { + t.Errorf("expected status %d but got %d", test.expectedStatus, rr.Code) + } + + metricValue := testGetMetricValue(map[string]string{ + "path": test.path, + "handler": "admin", + "method": test.method, + }) + if metricValue != 1 { + t.Errorf("expected error metric to be incremented once, got %v", metricValue) + } + }) + } +} + +func testGetMetricValue(labels map[string]string) float64 { + promLabels := prometheus.Labels{} + for k, v := range labels { + promLabels[k] = v + } + + metric, err := adminMetrics.requestErrors.GetMetricWith(promLabels) + if err != nil { + return 0 + } + + pb := &dto.Metric{} + metric.Write(pb) + return pb.GetCounter().GetValue() +} + +type mockRouter struct { + routes []AdminRoute +} + +func (m mockRouter) Routes() []AdminRoute { + return m.routes +} + +type mockModule struct { + mockRouter +} + +func (m *mockModule) CaddyModule() ModuleInfo { + return ModuleInfo{ + ID: "admin.api.mock", + New: func() Module { + mm := &mockModule{ + mockRouter: mockRouter{ + routes: m.routes, + }, + } + return mm + }, + } +} + +func TestNewAdminHandlerRouterRegistration(t *testing.T) { + originalModules := make(map[string]ModuleInfo) + for k, v := range modules { + originalModules[k] = v + } + defer func() { + modules = originalModules + }() + + mockRoute := AdminRoute{ + Pattern: "/mock", + Handler: AdminHandlerFunc(func(w http.ResponseWriter, r *http.Request) error { + w.WriteHeader(http.StatusOK) + return nil + }), + } + + mock := &mockModule{ + mockRouter: mockRouter{ + routes: []AdminRoute{mockRoute}, + }, + } + RegisterModule(mock) + + addr, err := ParseNetworkAddress("localhost:2019") + if err != nil { + t.Fatalf("Failed to parse address: %v", err) + } + + admin := &AdminConfig{ + EnforceOrigin: false, + } + handler := admin.newAdminHandler(addr, false, Context{}) + + req := httptest.NewRequest("GET", "/mock", nil) + req.Host = "localhost:2019" + rr := httptest.NewRecorder() + + handler.ServeHTTP(rr, req) + + if rr.Code != http.StatusOK { + t.Errorf("Expected status code %d but got %d", http.StatusOK, rr.Code) + t.Logf("Response body: %s", rr.Body.String()) + } + + if len(admin.routers) != 1 { + t.Errorf("Expected 1 router to be stored, got %d", len(admin.routers)) + } +} + +type mockProvisionableRouter struct { + mockRouter + provisionErr error + provisioned bool +} + +func (m *mockProvisionableRouter) Provision(Context) error { + m.provisioned = true + return m.provisionErr +} + +type mockProvisionableModule struct { + *mockProvisionableRouter +} + +func (m *mockProvisionableModule) CaddyModule() ModuleInfo { + return ModuleInfo{ + ID: "admin.api.mock_provision", + New: func() Module { + mm := &mockProvisionableModule{ + mockProvisionableRouter: &mockProvisionableRouter{ + mockRouter: m.mockRouter, + provisionErr: m.provisionErr, + }, + } + return mm + }, + } +} + +func TestAdminRouterProvisioning(t *testing.T) { + tests := []struct { + name string + provisionErr error + wantErr bool + routersAfter int // expected number of routers after provisioning + }{ + { + name: "successful provisioning", + provisionErr: nil, + wantErr: false, + routersAfter: 0, + }, + { + name: "provisioning error", + provisionErr: fmt.Errorf("provision failed"), + wantErr: true, + routersAfter: 1, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + originalModules := make(map[string]ModuleInfo) + for k, v := range modules { + originalModules[k] = v + } + defer func() { + modules = originalModules + }() + + mockRoute := AdminRoute{ + Pattern: "/mock", + Handler: AdminHandlerFunc(func(w http.ResponseWriter, r *http.Request) error { + return nil + }), + } + + // Create provisionable module + mock := &mockProvisionableModule{ + mockProvisionableRouter: &mockProvisionableRouter{ + mockRouter: mockRouter{ + routes: []AdminRoute{mockRoute}, + }, + provisionErr: test.provisionErr, + }, + } + RegisterModule(mock) + + admin := &AdminConfig{} + addr, err := ParseNetworkAddress("localhost:2019") + if err != nil { + t.Fatalf("Failed to parse address: %v", err) + } + + _ = admin.newAdminHandler(addr, false, Context{}) + err = admin.provisionAdminRouters(Context{}) + + if test.wantErr { + if err == nil { + t.Error("Expected error but got nil") + } + } else { + if err != nil { + t.Errorf("Expected no error but got: %v", err) + } + } + + if len(admin.routers) != test.routersAfter { + t.Errorf("Expected %d routers after provisioning, got %d", test.routersAfter, len(admin.routers)) + } + }) + } +} + +func TestAllowedOriginsUnixSocket(t *testing.T) { + tests := []struct { + name string + addr NetworkAddress + origins []string + expectOrigins []string + }{ + { + name: "unix socket with default origins", + addr: NetworkAddress{ + Network: "unix", + Host: "/tmp/caddy.sock", + }, + origins: nil, // default origins + expectOrigins: []string{ + "", // empty host as per RFC 2616 + "127.0.0.1", + "::1", + }, + }, + { + name: "unix socket with custom origins", + addr: NetworkAddress{ + Network: "unix", + Host: "/tmp/caddy.sock", + }, + origins: []string{"example.com"}, + expectOrigins: []string{ + "example.com", + }, + }, + { + name: "tcp socket on localhost gets all loopback addresses", + addr: NetworkAddress{ + Network: "tcp", + Host: "localhost", + StartPort: 2019, + EndPort: 2019, + }, + origins: nil, + expectOrigins: []string{ + "localhost:2019", + "[::1]:2019", + "127.0.0.1:2019", + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + admin := AdminConfig{ + Origins: test.origins, + } + + got := admin.allowedOrigins(test.addr) + + var gotOrigins []string + for _, u := range got { + gotOrigins = append(gotOrigins, u.Host) + } + + if len(gotOrigins) != len(test.expectOrigins) { + t.Errorf("Expected %d origins but got %d", len(test.expectOrigins), len(gotOrigins)) + return + } + + expectMap := make(map[string]struct{}) + for _, origin := range test.expectOrigins { + expectMap[origin] = struct{}{} + } + + gotMap := make(map[string]struct{}) + for _, origin := range gotOrigins { + gotMap[origin] = struct{}{} + } + + if !reflect.DeepEqual(expectMap, gotMap) { + t.Errorf("Origins mismatch.\nExpected: %v\nGot: %v", test.expectOrigins, gotOrigins) + } + }) + } +} + +func TestReplaceRemoteAdminServer(t *testing.T) { + const testCert = `MIIDCTCCAfGgAwIBAgIUXsqJ1mY8pKlHQtI3HJ23x2eZPqwwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIzMDEwMTAwMDAwMFoXDTI0MDEw +MTAwMDAwMFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA4O4S6BSoYcoxvRqI+h7yPOjF6KjntjzVVm9M+uHK4lzX +F1L3pSxJ2nDD4wZEV3FJ5yFOHVFqkG2vXG3BIczOlYG7UeNmKbQnKc5kZj3HGUrS +VGEktA4OJbeZhhWP15gcXN5eDM2eH3g9BFXVX6AURxLiUXzhNBUEZuj/OEyH9yEF +/qPCE+EjzVvWxvBXwgz/io4r4yok/Vq/bxJ6FlV6R7DX5oJSXyO0VEHZPi9DIyNU +kK3F/r4U1sWiJGWOs8i3YQWZ2ejh1C0aLFZpPcCGGgMNpoF31gyYP6ZuPDUyCXsE +g36UUw1JHNtIXYcLhnXuqj4A8TybTDpgXLqvwA9DBQIDAQABo1MwUTAdBgNVHQ4E +FgQUc13z30pFC63rr/HGKOE7E82vjXwwHwYDVR0jBBgwFoAUc13z30pFC63rr/HG +KOE7E82vjXwwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAHO3j +oeiUXXJ7xD4P8Wj5t9d+E8lE1Xv1Dk3Z+EdG5+dan+RcToE42JJp9zB7FIh5Qz8g +W77LAjqh5oyqz3A2VJcyVgfE3uJP1R1mJM7JfGHf84QH4TZF2Q1RZY4SZs0VQ6+q +5wSlIZ4NXDy4Q4XkIJBGS61wT8IzYFXYBpx4PCP1Qj0PIE4sevEGwjsBIgxK307o +BxF8AWe6N6e4YZmQLGjQ+SeH0iwZb6vpkHyAY8Kj2hvK+cq2P7vU3VGi0t3r1F8L +IvrXHCvO2BMNJ/1UK1M4YNX8LYJqQhg9hEsIROe1OE/m3VhxIYMJI+qZXk9yHfgJ +vq+SH04xKhtFudVBAQ==` + + tests := []struct { + name string + cfg *Config + wantErr bool + }{ + { + name: "nil config", + cfg: nil, + wantErr: false, + }, + { + name: "nil admin config", + cfg: &Config{ + Admin: nil, + }, + wantErr: false, + }, + { + name: "nil remote config", + cfg: &Config{ + Admin: &AdminConfig{}, + }, + wantErr: false, + }, + { + name: "invalid listen address", + cfg: &Config{ + Admin: &AdminConfig{ + Remote: &RemoteAdmin{ + Listen: "invalid:address", + }, + }, + }, + wantErr: true, + }, + { + name: "valid config", + cfg: &Config{ + Admin: &AdminConfig{ + Identity: &IdentityConfig{}, + Remote: &RemoteAdmin{ + Listen: "localhost:2021", + AccessControl: []*AdminAccess{ + { + PublicKeys: []string{testCert}, + Permissions: []AdminPermissions{{Methods: []string{"GET"}, Paths: []string{"/test"}}}, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "invalid certificate", + cfg: &Config{ + Admin: &AdminConfig{ + Identity: &IdentityConfig{}, + Remote: &RemoteAdmin{ + Listen: "localhost:2021", + AccessControl: []*AdminAccess{ + { + PublicKeys: []string{"invalid-cert-data"}, + Permissions: []AdminPermissions{{Methods: []string{"GET"}, Paths: []string{"/test"}}}, + }, + }, + }, + }, + }, + wantErr: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx := Context{ + Context: context.Background(), + cfg: test.cfg, + } + + if test.cfg != nil { + test.cfg.storage = &certmagic.FileStorage{Path: t.TempDir()} + } + + if test.cfg != nil && test.cfg.Admin != nil && test.cfg.Admin.Identity != nil { + identityCertCache = certmagic.NewCache(certmagic.CacheOptions{ + GetConfigForCert: func(certmagic.Certificate) (*certmagic.Config, error) { + return &certmagic.Config{}, nil + }, + }) + } + + err := replaceRemoteAdminServer(ctx, test.cfg) + + if test.wantErr { + if err == nil { + t.Error("Expected error but got nil") + } + } else { + if err != nil { + t.Errorf("Expected no error but got: %v", err) + } + } + + // Clean up + if remoteAdminServer != nil { + _ = stopAdminServer(remoteAdminServer) + } + }) + } +} + +type mockIssuer struct { + configSet *certmagic.Config +} + +func (m *mockIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest) (*certmagic.IssuedCertificate, error) { + return &certmagic.IssuedCertificate{ + Certificate: []byte(csr.Raw), + }, nil +} + +func (m *mockIssuer) SetConfig(cfg *certmagic.Config) { + m.configSet = cfg +} + +func (m *mockIssuer) IssuerKey() string { + return "mock" +} + +type mockIssuerModule struct { + *mockIssuer +} + +func (m *mockIssuerModule) CaddyModule() ModuleInfo { + return ModuleInfo{ + ID: "tls.issuance.acme", + New: func() Module { + return &mockIssuerModule{mockIssuer: new(mockIssuer)} + }, + } +} + +func TestManageIdentity(t *testing.T) { + originalModules := make(map[string]ModuleInfo) + for k, v := range modules { + originalModules[k] = v + } + defer func() { + modules = originalModules + }() + + RegisterModule(&mockIssuerModule{}) + + certPEM := []byte(`-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgIIE31FZVaPXTUwDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE +BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl +cm5ldCBBdXRob3JpdHkgRzIwHhcNMTQwMTI5MTMyNzQzWhcNMTQwNTI5MDAwMDAw +WjBpMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN +TW91bnRhaW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzEYMBYGA1UEAwwPbWFp +bC5nb29nbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3lcub2pUwkjC +5GJQA2ZZfJJi6d1QHhEmkX9VxKYGp6gagZuRqJWy9TXP6++1ZzQQxqZLD0TkuxZ9 +8i9Nz00000CCBjCCAQQwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMGgG +CCsGAQUFBwEBBFwwWjArBggrBgEFBQcwAoYfaHR0cDovL3BraS5nb29nbGUuY29t +L0dJQUcyLmNydDArBggrBgEFBQcwAYYfaHR0cDovL2NsaWVudHMxLmdvb2dsZS5j +b20vb2NzcDAdBgNVHQ4EFgQUiJxtimAuTfwb+aUtBn5UYKreKvMwDAYDVR0TAQH/ +BAIwADAfBgNVHSMEGDAWgBRK3QYWG7z2aLV29YG2u2IaulqBLzAXBgNVHREEEDAO +ggxtYWlsLmdvb2dsZTANBgkqhkiG9w0BAQUFAAOCAQEAMP6IWgNGZE8wP9TjFjSZ +3mmW3A1eIr0CuPwNZ2LJ5ZD1i70ojzcj4I9IdP5yPg9CAEV4hNASbM1LzfC7GmJE +tPzW5tRmpKVWZGRgTgZI8Hp/xZXMwLh9ZmXV4kESFAGj5G5FNvJyUV7R5Eh+7OZX +7G4jJ4ZGJh+5jzN9HdJJHQHGYNIYOzC7+HH9UMwCjX9vhQ4RjwFZJThS2Yb+y7pb +9yxTJZoXC6J0H5JpnZb7kZEJ+Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +-----END CERTIFICATE-----`) + + keyPEM := []byte(`-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRS0LmTwUT0iwP +... +-----END PRIVATE KEY-----`) + + testStorage := certmagic.FileStorage{Path: t.TempDir()} + err := testStorage.Store(context.Background(), "localhost/localhost.crt", certPEM) + if err != nil { + t.Fatal(err) + } + err = testStorage.Store(context.Background(), "localhost/localhost.key", keyPEM) + if err != nil { + t.Fatal(err) + } + + tests := []struct { + name string + cfg *Config + wantErr bool + checkState func(*testing.T, *Config) + }{ + { + name: "nil config", + cfg: nil, + }, + { + name: "nil admin config", + cfg: &Config{ + Admin: nil, + }, + }, + { + name: "nil identity config", + cfg: &Config{ + Admin: &AdminConfig{}, + }, + }, + { + name: "default issuer when none specified", + cfg: &Config{ + Admin: &AdminConfig{ + Identity: &IdentityConfig{ + Identifiers: []string{"localhost"}, + }, + }, + storage: &testStorage, + }, + checkState: func(t *testing.T, cfg *Config) { + if len(cfg.Admin.Identity.issuers) == 0 { + t.Error("Expected at least 1 issuer to be configured") + return + } + if _, ok := cfg.Admin.Identity.issuers[0].(*mockIssuerModule); !ok { + t.Error("Expected mock issuer to be configured") + } + }, + }, + { + name: "custom issuer", + cfg: &Config{ + Admin: &AdminConfig{ + Identity: &IdentityConfig{ + Identifiers: []string{"localhost"}, + IssuersRaw: []json.RawMessage{ + json.RawMessage(`{"module": "acme"}`), + }, + }, + }, + storage: &certmagic.FileStorage{Path: "testdata"}, + }, + checkState: func(t *testing.T, cfg *Config) { + if len(cfg.Admin.Identity.issuers) != 1 { + t.Fatalf("Expected 1 issuer, got %d", len(cfg.Admin.Identity.issuers)) + } + mockIss, ok := cfg.Admin.Identity.issuers[0].(*mockIssuerModule) + if !ok { + t.Fatal("Expected mock issuer") + } + if mockIss.configSet == nil { + t.Error("Issuer config was not set") + } + }, + }, + { + name: "invalid issuer module", + cfg: &Config{ + Admin: &AdminConfig{ + Identity: &IdentityConfig{ + Identifiers: []string{"localhost"}, + IssuersRaw: []json.RawMessage{ + json.RawMessage(`{"module": "doesnt_exist"}`), + }, + }, + }, + }, + wantErr: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if identityCertCache != nil { + // Reset the cert cache before each test + identityCertCache.Stop() + identityCertCache = nil + } + + ctx := Context{ + Context: context.Background(), + cfg: test.cfg, + moduleInstances: make(map[string][]Module), + } + + err := manageIdentity(ctx, test.cfg) + + if test.wantErr { + if err == nil { + t.Error("Expected error but got nil") + } + return + } + if err != nil { + t.Fatalf("Expected no error but got: %v", err) + } + + if test.checkState != nil { + test.checkState(t, test.cfg) + } + }) + } +} diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 36f1ff06338..00000000000 --- a/appveyor.yml +++ /dev/null @@ -1,39 +0,0 @@ -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 diff --git a/assets.go b/assets.go deleted file mode 100644 index e353af8d355..00000000000 --- a/assets.go +++ /dev/null @@ -1,34 +0,0 @@ -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") -} diff --git a/assets_test.go b/assets_test.go deleted file mode 100644 index 19336104827..00000000000 --- a/assets_test.go +++ /dev/null @@ -1,19 +0,0 @@ -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", "") -} diff --git a/caddy.go b/caddy.go index ffab1454b83..758b0b2f6ff 100644 --- a/caddy.go +++ b/caddy.go @@ -1,928 +1,1091 @@ -// Package caddy implements the Caddy server manager. +// Copyright 2015 Matthew Holt and The Caddy Authors // -// To use this package: +// 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 // -// 1. Set the AppName and AppVersion variables. -// 2. Call LoadCaddyfile() to get the Caddyfile. -// Pass in the name of the server type (like "http"). -// Make sure the server type's package is imported -// (import _ "github.com/mholt/caddy/caddyhttp"). -// 3. Call caddy.Start() to start Caddy. You get back -// an Instance, on which you can call Restart() to -// restart it or Stop() to stop it. +// http://www.apache.org/licenses/LICENSE-2.0 // -// You should call Wait() on your instance to wait for -// all servers to quit before your process exits. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package caddy import ( "bytes" - "encoding/gob" + "context" + "encoding/hex" + "encoding/json" + "errors" "fmt" "io" - "io/ioutil" + "io/fs" "log" - "net" + "net/http" "os" + "path" + "path/filepath" + "runtime/debug" "strconv" "strings" "sync" + "sync/atomic" "time" - "github.com/mholt/caddy/caddyfile" -) - -// Configurable application parameters -var ( - // AppName is the name of the application. - AppName string - - // AppVersion is the version of the application. - AppVersion string - - // Quiet mode will not show any informative output on initialization. - Quiet bool - - // PidFile is the path to the pidfile to create. - PidFile string - - // GracefulTimeout is the maximum duration of a graceful shutdown. - GracefulTimeout time.Duration - - // isUpgrade will be set to true if this process - // was started as part of an upgrade, where a parent - // Caddy process started this one. - isUpgrade = os.Getenv("CADDY__UPGRADE") == "1" - - // started will be set to true when the first - // instance is started; it never gets set to - // false after that. - started bool + "github.com/caddyserver/certmagic" + "github.com/google/uuid" + "go.uber.org/zap" - // mu protects the variables 'isUpgrade' and 'started'. - mu sync.Mutex + "github.com/caddyserver/caddy/v2/internal/filesystems" + "github.com/caddyserver/caddy/v2/notify" ) -// Instance contains the state of servers created as a result of -// calling Start and can be used to access or control those servers. -type Instance struct { - // serverType is the name of the instance's server type - serverType string - - // caddyfileInput is the input configuration text used for this process - caddyfileInput Input - - // wg is used to wait for all servers to shut down - wg *sync.WaitGroup - - // context is the context created for this instance. - context Context - - // servers is the list of servers with their listeners. - servers []ServerListener +// Config is the top (or beginning) of the Caddy configuration structure. +// Caddy config is expressed natively as a JSON document. If you prefer +// not to work with JSON directly, there are [many config adapters](/docs/config-adapters) +// available that can convert various inputs into Caddy JSON. +// +// Many parts of this config are extensible through the use of Caddy modules. +// Fields which have a json.RawMessage type and which appear as dots (•••) in +// the online docs can be fulfilled by modules in a certain module +// namespace. The docs show which modules can be used in a given place. +// +// Whenever a module is used, its name must be given either inline as part of +// the module, or as the key to the module's value. The docs will make it clear +// which to use. +// +// Generally, all config settings are optional, as it is Caddy convention to +// have good, documented default values. If a parameter is required, the docs +// should say so. +// +// Go programs which are directly building a Config struct value should take +// care to populate the JSON-encodable fields of the struct (i.e. the fields +// with `json` struct tags) if employing the module lifecycle (e.g. Provision +// method calls). +type Config struct { + Admin *AdminConfig `json:"admin,omitempty"` + Logging *Logging `json:"logging,omitempty"` + + // StorageRaw is a storage module that defines how/where Caddy + // stores assets (such as TLS certificates). The default storage + // module is `caddy.storage.file_system` (the local file system), + // and the default path + // [depends on the OS and environment](/docs/conventions#data-directory). + StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"` + + // AppsRaw are the apps that Caddy will load and run. The + // app module name is the key, and the app's config is the + // associated value. + AppsRaw ModuleMap `json:"apps,omitempty" caddy:"namespace="` + + apps map[string]App + storage certmagic.Storage + + cancelFunc context.CancelFunc + + // filesystems is a dict of filesystems that will later be loaded from and added to. + filesystems FileSystems +} - // these callbacks execute when certain events occur - onFirstStartup []func() error // starting, not as part of a restart - onStartup []func() error // starting, even as part of a restart - onRestart []func() error // before restart commences - onShutdown []func() error // stopping, even as part of a restart - onFinalShutdown []func() error // stopping, not as part of a restart +// App is a thing that Caddy runs. +type App interface { + Start() error + Stop() error } -// Servers returns the ServerListeners in i. -func (i *Instance) Servers() []ServerListener { return i.servers } - -// Stop stops all servers contained in i. It does NOT -// execute shutdown callbacks. -func (i *Instance) Stop() error { - // stop the servers - for _, s := range i.servers { - if gs, ok := s.server.(GracefulServer); ok { - if err := gs.Stop(); err != nil { - log.Printf("[ERROR] Stopping %s: %v", gs.Address(), err) - } - } +// Run runs the given config, replacing any existing config. +func Run(cfg *Config) error { + cfgJSON, err := json.Marshal(cfg) + if err != nil { + return err } + return Load(cfgJSON, true) +} - // splice i out of instance list, causing it to be garbage-collected - instancesMu.Lock() - for j, other := range instances { - if other == i { - instances = append(instances[:j], instances[j+1:]...) - break - } +// Load loads the given config JSON and runs it only +// if it is different from the current config or +// forceReload is true. +func Load(cfgJSON []byte, forceReload bool) error { + if err := notify.Reloading(); err != nil { + Log().Error("unable to notify service manager of reloading state", zap.Error(err)) } - instancesMu.Unlock() - - return nil -} -// ShutdownCallbacks executes all the shutdown callbacks of i, -// including ones that are scheduled only for the final shutdown -// of i. An error returned from one does not stop execution of -// the rest. All the non-nil errors will be returned. -func (i *Instance) ShutdownCallbacks() []error { - var errs []error - for _, shutdownFunc := range i.onShutdown { - err := shutdownFunc() + // after reload, notify system of success or, if + // failure, update with status (error message) + var err error + defer func() { if err != nil { - errs = append(errs, err) + if notifyErr := notify.Error(err, 0); notifyErr != nil { + Log().Error("unable to notify to service manager of reload error", + zap.Error(notifyErr), + zap.String("reload_err", err.Error())) + } + return } - } - for _, finalShutdownFunc := range i.onFinalShutdown { - err := finalShutdownFunc() - if err != nil { - errs = append(errs, err) + if err := notify.Ready(); err != nil { + Log().Error("unable to notify to service manager of ready state", zap.Error(err)) } + }() + + err = changeConfig(http.MethodPost, "/"+rawConfigKey, cfgJSON, "", forceReload) + if errors.Is(err, errSameConfig) { + err = nil // not really an error } - return errs + + return err } -// Restart replaces the servers in i with new servers created from -// executing the newCaddyfile. Upon success, it returns the new -// instance to replace i. Upon failure, i will not be replaced. -func (i *Instance) Restart(newCaddyfile Input) (*Instance, error) { - log.Println("[INFO] Reloading") +// changeConfig changes the current config (rawCfg) according to the +// method, traversed via the given path, and uses the given input as +// the new value (if applicable; i.e. "DELETE" doesn't have an input). +// If the resulting config is the same as the previous, no reload will +// occur unless forceReload is true. If the config is unchanged and not +// forcefully reloaded, then errConfigUnchanged This function is safe for +// concurrent use. +// The ifMatchHeader can optionally be given a string of the format: +// +// " " +// +// where is the absolute path in the config and is the expected hash of +// the config at that path. If the hash in the ifMatchHeader doesn't match +// the hash of the config, then an APIError with status 412 will be returned. +func changeConfig(method, path string, input []byte, ifMatchHeader string, forceReload bool) error { + switch method { + case http.MethodGet, + http.MethodHead, + http.MethodOptions, + http.MethodConnect, + http.MethodTrace: + return fmt.Errorf("method not allowed") + } - i.wg.Add(1) - defer i.wg.Done() + rawCfgMu.Lock() + defer rawCfgMu.Unlock() - // run restart callbacks - for _, fn := range i.onRestart { - err := fn() + if ifMatchHeader != "" { + // expect the first and last character to be quotes + if len(ifMatchHeader) < 2 || ifMatchHeader[0] != '"' || ifMatchHeader[len(ifMatchHeader)-1] != '"' { + return APIError{ + HTTPStatus: http.StatusBadRequest, + Err: fmt.Errorf("malformed If-Match header; expect quoted string"), + } + } + + // read out the parts + parts := strings.Fields(ifMatchHeader[1 : len(ifMatchHeader)-1]) + if len(parts) != 2 { + return APIError{ + HTTPStatus: http.StatusBadRequest, + Err: fmt.Errorf("malformed If-Match header; expect format \" \""), + } + } + + // get the current hash of the config + // at the given path + hash := etagHasher() + err := unsyncedConfigAccess(http.MethodGet, parts[0], nil, hash) if err != nil { - return i, err + return err + } + + if hex.EncodeToString(hash.Sum(nil)) != parts[1] { + return APIError{ + HTTPStatus: http.StatusPreconditionFailed, + Err: fmt.Errorf("If-Match header did not match current config hash"), + } } } - if newCaddyfile == nil { - newCaddyfile = i.caddyfileInput + err := unsyncedConfigAccess(method, path, input, nil) + if err != nil { + return err } - // Add file descriptors of all the sockets that are capable of it - restartFds := make(map[string]restartTriple) - for _, s := range i.servers { - gs, srvOk := s.server.(GracefulServer) - ln, lnOk := s.listener.(Listener) - pc, pcOk := s.packet.(PacketConn) - if srvOk { - if lnOk && pcOk { - restartFds[gs.Address()] = restartTriple{server: gs, listener: ln, packet: pc} - continue - } - if lnOk { - restartFds[gs.Address()] = restartTriple{server: gs, listener: ln} - continue - } - if pcOk { - restartFds[gs.Address()] = restartTriple{server: gs, packet: pc} - continue - } + // the mutation is complete, so encode the entire config as JSON + newCfg, err := json.Marshal(rawCfg[rawConfigKey]) + if err != nil { + return APIError{ + HTTPStatus: http.StatusBadRequest, + Err: fmt.Errorf("encoding new config: %v", err), } } - // create new instance; if the restart fails, it is simply discarded - newInst := &Instance{serverType: newCaddyfile.ServerType(), wg: i.wg} + // if nothing changed, no need to do a whole reload unless the client forces it + if !forceReload && bytes.Equal(rawCfgJSON, newCfg) { + Log().Info("config is unchanged") + return errSameConfig + } - // attempt to start new instance - err := startWithListenerFds(newCaddyfile, newInst, restartFds) + // find any IDs in this config and index them + idx := make(map[string]string) + err = indexConfigObjects(rawCfg[rawConfigKey], "/"+rawConfigKey, idx) if err != nil { - return i, err + return APIError{ + HTTPStatus: http.StatusInternalServerError, + Err: fmt.Errorf("indexing config: %v", err), + } } - // success! stop the old instance - for _, shutdownFunc := range i.onShutdown { - err := shutdownFunc() - if err != nil { - return i, err + // load this new config; if it fails, we need to revert to + // our old representation of caddy's actual config + err = unsyncedDecodeAndRun(newCfg, true) + if err != nil { + if len(rawCfgJSON) > 0 { + // restore old config state to keep it consistent + // with what caddy is still running; we need to + // unmarshal it again because it's likely that + // pointers deep in our rawCfg map were modified + var oldCfg any + err2 := json.Unmarshal(rawCfgJSON, &oldCfg) + if err2 != nil { + err = fmt.Errorf("%v; additionally, restoring old config: %v", err, err2) + } + rawCfg[rawConfigKey] = oldCfg } + + return fmt.Errorf("loading new config: %v", err) } - i.Stop() - log.Println("[INFO] Reloading complete") + // success, so update our stored copy of the encoded + // config to keep it consistent with what caddy is now + // running (storing an encoded copy is not strictly + // necessary, but avoids an extra json.Marshal for + // each config change) + rawCfgJSON = newCfg + rawCfgIndex = idx - return newInst, nil + return nil } -// SaveServer adds s and its associated listener ln to the -// internally-kept list of servers that is running. For -// saved servers, graceful restarts will be provided. -func (i *Instance) SaveServer(s Server, ln net.Listener) { - i.servers = append(i.servers, ServerListener{server: s, listener: ln}) +// readConfig traverses the current config to path +// and writes its JSON encoding to out. +func readConfig(path string, out io.Writer) error { + rawCfgMu.RLock() + defer rawCfgMu.RUnlock() + return unsyncedConfigAccess(http.MethodGet, path, nil, out) } -// HasListenerWithAddress returns whether this package is -// tracking a server using a listener with the address -// addr. -func HasListenerWithAddress(addr string) bool { - instancesMu.Lock() - defer instancesMu.Unlock() - for _, inst := range instances { - for _, sln := range inst.servers { - if listenerAddrEqual(sln.listener, addr) { - return true +// indexConfigObjects recursively searches ptr for object fields named +// "@id" and maps that ID value to the full configPath in the index. +// This function is NOT safe for concurrent access; obtain a write lock +// on currentCtxMu. +func indexConfigObjects(ptr any, configPath string, index map[string]string) error { + switch val := ptr.(type) { + case map[string]any: + for k, v := range val { + if k == idKey { + switch idVal := v.(type) { + case string: + index[idVal] = configPath + case float64: // all JSON numbers decode as float64 + index[fmt.Sprintf("%v", idVal)] = configPath + default: + return fmt.Errorf("%s: %s field must be a string or number", configPath, idKey) + } + continue + } + // traverse this object property recursively + err := indexConfigObjects(val[k], path.Join(configPath, k), index) + if err != nil { + return err + } + } + case []any: + // traverse each element of the array recursively + for i := range val { + err := indexConfigObjects(val[i], path.Join(configPath, strconv.Itoa(i)), index) + if err != nil { + return err } } } - return false + + return nil } -// listenerAddrEqual compares a listener's address with -// addr. Extra care is taken to match addresses with an -// empty hostname portion, as listeners tend to report -// [::]:80, for example, when the matching address that -// created the listener might be simply :80. -func listenerAddrEqual(ln net.Listener, addr string) bool { - lnAddr := ln.Addr().String() - hostname, port, err := net.SplitHostPort(addr) +// unsyncedDecodeAndRun removes any meta fields (like @id tags) +// from cfgJSON, decodes the result into a *Config, and runs +// it as the new config, replacing any other current config. +// It does NOT update the raw config state, as this is a +// lower-level function; most callers will want to use Load +// instead. A write lock on rawCfgMu is required! If +// allowPersist is false, it will not be persisted to disk, +// even if it is configured to. +func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error { + // remove any @id fields from the JSON, which would cause + // loading to break since the field wouldn't be recognized + strippedCfgJSON := RemoveMetaFields(cfgJSON) + + var newCfg *Config + err := StrictUnmarshalJSON(strippedCfgJSON, &newCfg) if err != nil { - return lnAddr == addr + return err } - if lnAddr == net.JoinHostPort("::", port) { - return true + + // prevent recursive config loads; that is a user error, and + // although frequent config loads should be safe, we cannot + // guarantee that in the presence of third party plugins, nor + // do we want this error to go unnoticed (we assume it was a + // pulled config if we're not allowed to persist it) + if !allowPersist && + newCfg != nil && + newCfg.Admin != nil && + newCfg.Admin.Config != nil && + newCfg.Admin.Config.LoadRaw != nil && + newCfg.Admin.Config.LoadDelay <= 0 { + return fmt.Errorf("recursive config loading detected: pulled configs cannot pull other configs without positive load_delay") } - if lnAddr == net.JoinHostPort("0.0.0.0", port) { - return true + + // run the new config and start all its apps + ctx, err := run(newCfg, true) + if err != nil { + return err } - return hostname != "" && lnAddr == addr -} -// TCPServer is a type that can listen and serve connections. -// A TCPServer must associate with exactly zero or one net.Listeners. -type TCPServer interface { - // Listen starts listening by creating a new listener - // and returning it. It does not start accepting - // connections. For UDP-only servers, this method - // can be a no-op that returns (nil, nil). - Listen() (net.Listener, error) - - // Serve starts serving using the provided listener. - // Serve must start the server loop nearly immediately, - // or at least not return any errors before the server - // loop begins. Serve blocks indefinitely, or in other - // words, until the server is stopped. For UDP-only - // servers, this method can be a no-op that returns nil. - Serve(net.Listener) error -} + // swap old context (including its config) with the new one + currentCtxMu.Lock() + oldCtx := currentCtx + currentCtx = ctx + currentCtxMu.Unlock() + + // Stop, Cleanup each old app + unsyncedStop(oldCtx) + + // autosave a non-nil config, if not disabled + if allowPersist && + newCfg != nil && + (newCfg.Admin == nil || + newCfg.Admin.Config == nil || + newCfg.Admin.Config.Persist == nil || + *newCfg.Admin.Config.Persist) { + dir := filepath.Dir(ConfigAutosavePath) + err := os.MkdirAll(dir, 0o700) + if err != nil { + Log().Error("unable to create folder for config autosave", + zap.String("dir", dir), + zap.Error(err)) + } else { + err := os.WriteFile(ConfigAutosavePath, cfgJSON, 0o600) + if err == nil { + Log().Info("autosaved config (load with --resume flag)", zap.String("file", ConfigAutosavePath)) + } else { + Log().Error("unable to autosave config", + zap.String("file", ConfigAutosavePath), + zap.Error(err)) + } + } + } -// UDPServer is a type that can listen and serve packets. -// A UDPServer must associate with exactly zero or one net.PacketConns. -type UDPServer interface { - // ListenPacket starts listening by creating a new packetconn - // and returning it. It does not start accepting connections. - // TCP-only servers may leave this method blank and return - // (nil, nil). - ListenPacket() (net.PacketConn, error) - - // ServePacket starts serving using the provided packetconn. - // ServePacket must start the server loop nearly immediately, - // or at least not return any errors before the server - // loop begins. ServePacket blocks indefinitely, or in other - // words, until the server is stopped. For TCP-only servers, - // this method can be a no-op that returns nil. - ServePacket(net.PacketConn) error + return nil } -// Server is a type that can listen and serve. It supports both -// TCP and UDP, although the UDPServer interface can be used -// for more than just UDP. +// run runs newCfg and starts all its apps if +// start is true. If any errors happen, cleanup +// is performed if any modules were provisioned; +// apps that were started already will be stopped, +// so this function should not leak resources if +// an error is returned. However, if no error is +// returned and start == false, you should cancel +// the config if you are not going to start it, +// so that each provisioned module will be +// cleaned up. // -// If the server uses TCP, it should implement TCPServer completely. -// If it uses UDP or some other protocol, it should implement -// UDPServer completely. If it uses both, both interfaces should be -// fully implemented. Any unimplemented methods should be made as -// no-ops that simply return nil values. -type Server interface { - TCPServer - UDPServer -} - -// Stopper is a type that can stop serving. The stop -// does not necessarily have to be graceful. -type Stopper interface { - // Stop stops the server. It blocks until the - // server is completely stopped. - Stop() error -} +// This is a low-level function; most callers +// will want to use Run instead, which also +// updates the config's raw state. +func run(newCfg *Config, start bool) (Context, error) { + ctx, err := provisionContext(newCfg, start) + if err != nil { + globalMetrics.configSuccess.Set(0) + return ctx, err + } -// GracefulServer is a Server and Stopper, the stopping -// of which is graceful (whatever that means for the kind -// of server being implemented). It must be able to return -// the address it is configured to listen on so that its -// listener can be paired with it upon graceful restarts. -// The net.Listener that a GracefulServer creates must -// implement the Listener interface for restarts to be -// graceful (assuming the listener is for TCP). -type GracefulServer interface { - Server - Stopper - - // Address returns the address the server should - // listen on; it is used to pair the server to - // its listener during a graceful/zero-downtime - // restart. Thus when implementing this method, - // you must not access a listener to get the - // address; you must store the address the - // server is to serve on some other way. - Address() string -} + if !start { + return ctx, nil + } -// Listener is a net.Listener with an underlying file descriptor. -// A server's listener should implement this interface if it is -// to support zero-downtime reloads. -type Listener interface { - net.Listener - File() (*os.File, error) -} + // Provision any admin routers which may need to access + // some of the other apps at runtime + err = ctx.cfg.Admin.provisionAdminRouters(ctx) + if err != nil { + globalMetrics.configSuccess.Set(0) + return ctx, err + } -// PacketConn is a net.PacketConn with an underlying file descriptor. -// A server's packetconn should implement this interface if it is -// to support zero-downtime reloads (in sofar this holds true for datagram -// connections). -type PacketConn interface { - net.PacketConn - File() (*os.File, error) + // Start + err = func() error { + started := make([]string, 0, len(ctx.cfg.apps)) + for name, a := range ctx.cfg.apps { + err := a.Start() + if err != nil { + // an app failed to start, so we need to stop + // all other apps that were already started + for _, otherAppName := range started { + err2 := ctx.cfg.apps[otherAppName].Stop() + if err2 != nil { + err = fmt.Errorf("%v; additionally, aborting app %s: %v", + err, otherAppName, err2) + } + } + return fmt.Errorf("%s app module: start: %v", name, err) + } + started = append(started, name) + } + return nil + }() + if err != nil { + globalMetrics.configSuccess.Set(0) + return ctx, err + } + globalMetrics.configSuccess.Set(1) + globalMetrics.configSuccessTime.SetToCurrentTime() + // now that the user's config is running, finish setting up anything else, + // such as remote admin endpoint, config loader, etc. + return ctx, finishSettingUp(ctx, ctx.cfg) } -// AfterStartup is an interface that can be implemented -// by a server type that wants to run some code after all -// servers for the same Instance have started. -type AfterStartup interface { - OnStartupComplete() -} +// provisionContext creates a new context from the given configuration and provisions +// storage and apps. +// If `newCfg` is nil a new empty configuration will be created. +// If `replaceAdminServer` is true any currently active admin server will be replaced +// with a new admin server based on the provided configuration. +func provisionContext(newCfg *Config, replaceAdminServer bool) (Context, error) { + // because we will need to roll back any state + // modifications if this function errors, we + // keep a single error value and scope all + // sub-operations to their own functions to + // ensure this error value does not get + // overridden or missed when it should have + // been set by a short assignment + var err error + + if newCfg == nil { + newCfg = new(Config) + } -// LoadCaddyfile loads a Caddyfile by calling the plugged in -// Caddyfile loader methods. An error is returned if more than -// one loader returns a non-nil Caddyfile input. If no loaders -// load a Caddyfile, the default loader is used. If no default -// loader is registered or it returns nil, the server type's -// default Caddyfile is loaded. If the server type does not -// specify any default Caddyfile value, then an empty Caddyfile -// is returned. Consequently, this function never returns a nil -// value as long as there are no errors. -func LoadCaddyfile(serverType string) (Input, error) { - // If we are finishing an upgrade, we must obtain the Caddyfile - // from our parent process, regardless of configured loaders. - if IsUpgrade() { - err := gob.NewDecoder(os.Stdin).Decode(&loadedGob) + // create a context within which to load + // modules - essentially our new config's + // execution environment; be sure that + // cleanup occurs when we return if there + // was an error; if no error, it will get + // cleaned up on next config cycle + ctx, cancel := NewContext(Context{Context: context.Background(), cfg: newCfg}) + defer func() { if err != nil { - return nil, err + globalMetrics.configSuccess.Set(0) + // if there were any errors during startup, + // we should cancel the new context we created + // since the associated config won't be used; + // this will cause all modules that were newly + // provisioned to clean themselves up + cancel() + + // also undo any other state changes we made + if currentCtx.cfg != nil { + certmagic.Default.Storage = currentCtx.cfg.storage + } } - return loadedGob.Caddyfile, nil - } + }() + newCfg.cancelFunc = cancel // clean up later - // Ask plugged-in loaders for a Caddyfile - cdyfile, err := loadCaddyfileInput(serverType) - if err != nil { - return nil, err + // set up logging before anything bad happens + if newCfg.Logging == nil { + newCfg.Logging = new(Logging) } - - // Otherwise revert to default - if cdyfile == nil { - cdyfile = DefaultInput(serverType) + err = newCfg.Logging.openLogs(ctx) + if err != nil { + return ctx, err } - // Still nil? Geez. - if cdyfile == nil { - cdyfile = CaddyfileInput{ServerTypeName: serverType} + // start the admin endpoint (and stop any prior one) + if replaceAdminServer { + err = replaceLocalAdminServer(newCfg, ctx) + if err != nil { + return ctx, fmt.Errorf("starting caddy administration endpoint: %v", err) + } } - return cdyfile, nil -} + // create the new filesystem map + newCfg.filesystems = &filesystems.FilesystemMap{} -// Wait blocks until all of i's servers have stopped. -func (i *Instance) Wait() { - i.wg.Wait() -} + // prepare the new config for use + newCfg.apps = make(map[string]App) -// CaddyfileFromPipe loads the Caddyfile input from f if f is -// not interactive input. f is assumed to be a pipe or stream, -// such as os.Stdin. If f is not a pipe, no error is returned -// but the Input value will be nil. An error is only returned -// if there was an error reading the pipe, even if the length -// of what was read is 0. -func CaddyfileFromPipe(f *os.File, serverType string) (Input, error) { - fi, err := f.Stat() - if err == nil && fi.Mode()&os.ModeCharDevice == 0 { - // Note that a non-nil error is not a problem. Windows - // will not create a stdin if there is no pipe, which - // produces an error when calling Stat(). But Unix will - // make one either way, which is why we also check that - // bitmask. - // NOTE: Reading from stdin after this fails (e.g. for the let's encrypt email address) (OS X) - confBody, err := ioutil.ReadAll(f) - if err != nil { - return nil, err + // set up global storage and make it CertMagic's default storage, too + err = func() error { + if newCfg.StorageRaw != nil { + val, err := ctx.LoadModule(newCfg, "StorageRaw") + if err != nil { + return fmt.Errorf("loading storage module: %v", err) + } + stor, err := val.(StorageConverter).CertMagicStorage() + if err != nil { + return fmt.Errorf("creating storage value: %v", err) + } + newCfg.storage = stor } - return CaddyfileInput{ - Contents: confBody, - Filepath: f.Name(), - ServerTypeName: serverType, - }, nil - } - // not having input from the pipe is not itself an error, - // just means no input to return. - return nil, nil -} - -// Caddyfile returns the Caddyfile used to create i. -func (i *Instance) Caddyfile() Input { - return i.caddyfileInput -} + if newCfg.storage == nil { + newCfg.storage = DefaultStorage + } + certmagic.Default.Storage = newCfg.storage -// Start starts Caddy with the given Caddyfile. -// -// This function blocks until all the servers are listening. -func Start(cdyfile Input) (*Instance, error) { - inst := &Instance{serverType: cdyfile.ServerType(), wg: new(sync.WaitGroup)} - err := startWithListenerFds(cdyfile, inst, nil) + return nil + }() if err != nil { - return inst, err + return ctx, err } - signalSuccessToParent() - if pidErr := writePidFile(); pidErr != nil { - log.Printf("[ERROR] Could not write pidfile: %v", pidErr) - } - return inst, nil + + // Load and Provision each app and their submodules + err = func() error { + for appName := range newCfg.AppsRaw { + if _, err := ctx.App(appName); err != nil { + return err + } + } + return nil + }() + return ctx, err } -func startWithListenerFds(cdyfile Input, inst *Instance, restartFds map[string]restartTriple) error { - if cdyfile == nil { - cdyfile = CaddyfileInput{} - } +// ProvisionContext creates a new context from the configuration and provisions storage +// and app modules. +// The function is intended for testing and advanced use cases only, typically `Run` should be +// use to ensure a fully functional caddy instance. +// EXPERIMENTAL: While this is public the interface and implementation details of this function may change. +func ProvisionContext(newCfg *Config) (Context, error) { + return provisionContext(newCfg, false) +} - err := ValidateAndExecuteDirectives(cdyfile, inst, false) +// finishSettingUp should be run after all apps have successfully started. +func finishSettingUp(ctx Context, cfg *Config) error { + // establish this server's identity (only after apps are loaded + // so that cert management of this endpoint doesn't prevent user's + // servers from starting which likely also use HTTP/HTTPS ports; + // but before remote management which may depend on these creds) + err := manageIdentity(ctx, cfg) if err != nil { - return err + return fmt.Errorf("provisioning remote admin endpoint: %v", err) } - slist, err := inst.context.MakeServers() + // replace any remote admin endpoint + err = replaceRemoteAdminServer(ctx, cfg) if err != nil { - return err + return fmt.Errorf("provisioning remote admin endpoint: %v", err) } - // run startup callbacks - if !IsUpgrade() && restartFds == nil { - // first startup means not a restart or upgrade - for _, firstStartupFunc := range inst.onFirstStartup { - err := firstStartupFunc() - if err != nil { - return err - } - } - } - for _, startupFunc := range inst.onStartup { - err := startupFunc() + // if dynamic config is requested, set that up and run it + if cfg != nil && cfg.Admin != nil && cfg.Admin.Config != nil && cfg.Admin.Config.LoadRaw != nil { + val, err := ctx.LoadModule(cfg.Admin.Config, "LoadRaw") if err != nil { - return err + return fmt.Errorf("loading config loader module: %s", err) } - } - err = startServers(slist, inst, restartFds) - if err != nil { - return err - } - - instancesMu.Lock() - instances = append(instances, inst) - instancesMu.Unlock() + logger := Log().Named("config_loader").With( + zap.String("module", val.(Module).CaddyModule().ID.Name()), + zap.Int("load_delay", int(cfg.Admin.Config.LoadDelay))) - // run any AfterStartup callbacks if this is not - // part of a restart; then show file descriptor notice - if restartFds == nil { - for _, srvln := range inst.servers { - if srv, ok := srvln.server.(AfterStartup); ok { - srv.OnStartupComplete() + runLoadedConfig := func(config []byte) error { + logger.Info("applying dynamically-loaded config") + err := changeConfig(http.MethodPost, "/"+rawConfigKey, config, "", false) + if errors.Is(err, errSameConfig) { + return err + } + if err != nil { + logger.Error("failed to run dynamically-loaded config", zap.Error(err)) + return err } + logger.Info("successfully applied dynamically-loaded config") + return nil } - if !Quiet { - for _, srvln := range inst.servers { - if !IsLoopback(srvln.listener.Addr().String()) { - checkFdlimit() + + if cfg.Admin.Config.LoadDelay > 0 { + go func() { + // the loop is here to iterate ONLY if there is an error, a no-op config load, + // or an unchanged config; in which case we simply wait the delay and try again + for { + timer := time.NewTimer(time.Duration(cfg.Admin.Config.LoadDelay)) + select { + case <-timer.C: + loadedConfig, err := val.(ConfigLoader).LoadConfig(ctx) + if err != nil { + logger.Error("failed loading dynamic config; will retry", zap.Error(err)) + continue + } + if loadedConfig == nil { + logger.Info("dynamically-loaded config was nil; will retry") + continue + } + err = runLoadedConfig(loadedConfig) + if errors.Is(err, errSameConfig) { + logger.Info("dynamically-loaded config was unchanged; will retry") + continue + } + case <-ctx.Done(): + if !timer.Stop() { + <-timer.C + } + logger.Info("stopping dynamic config loading") + } break } + }() + } else { + // if no LoadDelay is provided, will load config synchronously + loadedConfig, err := val.(ConfigLoader).LoadConfig(ctx) + if err != nil { + return fmt.Errorf("loading dynamic config from %T: %v", val, err) } + // do this in a goroutine so current config can finish being loaded; otherwise deadlock + go func() { _ = runLoadedConfig(loadedConfig) }() } } - mu.Lock() - started = true - mu.Unlock() - return nil } -// ValidateAndExecuteDirectives will load the server blocks from cdyfile -// by parsing it, then execute the directives configured by it and store -// the resulting server blocks into inst. If justValidate is true, parse -// callbacks will not be executed between directives, since the purpose -// is only to check the input for valid syntax. -func ValidateAndExecuteDirectives(cdyfile Input, inst *Instance, justValidate bool) error { - // If parsing only inst will be nil, create an instance for this function call only. - if justValidate { - inst = &Instance{serverType: cdyfile.ServerType(), wg: new(sync.WaitGroup)} - } +// ConfigLoader is a type that can load a Caddy config. If +// the return value is non-nil, it must be valid Caddy JSON; +// if nil or with non-nil error, it is considered to be a +// no-op load and may be retried later. +type ConfigLoader interface { + LoadConfig(Context) ([]byte, error) +} - stypeName := cdyfile.ServerType() +// Stop stops running the current configuration. +// It is the antithesis of Run(). This function +// will log any errors that occur during the +// stopping of individual apps and continue to +// stop the others. Stop should only be called +// if not replacing with a new config. +func Stop() error { + currentCtxMu.RLock() + ctx := currentCtx + currentCtxMu.RUnlock() - stype, err := getServerType(stypeName) - if err != nil { - return err - } + rawCfgMu.Lock() + unsyncedStop(ctx) - inst.caddyfileInput = cdyfile + currentCtxMu.Lock() + currentCtx = Context{} + currentCtxMu.Unlock() - sblocks, err := loadServerBlocks(stypeName, cdyfile.Path(), bytes.NewReader(cdyfile.Body())) - if err != nil { - return err - } + rawCfgJSON = nil + rawCfgIndex = nil + rawCfg[rawConfigKey] = nil + rawCfgMu.Unlock() - inst.context = stype.NewContext() - if inst.context == nil { - return fmt.Errorf("server type %s produced a nil Context", stypeName) - } + return nil +} - sblocks, err = inst.context.InspectServerBlocks(cdyfile.Path(), sblocks) - if err != nil { - return err +// unsyncedStop stops ctx from running, but has +// no locking around ctx. It is a no-op if ctx has a +// nil cfg. If any app returns an error when stopping, +// it is logged and the function continues stopping +// the next app. This function assumes all apps in +// ctx were successfully started first. +// +// A lock on rawCfgMu is required, even though this +// function does not access rawCfg, that lock +// synchronizes the stop/start of apps. +func unsyncedStop(ctx Context) { + if ctx.cfg == nil { + return } - err = executeDirectives(inst, cdyfile.Path(), stype.Directives(), sblocks, justValidate) - if err != nil { - return err + // stop each app + for name, a := range ctx.cfg.apps { + err := a.Stop() + if err != nil { + log.Printf("[ERROR] stop %s: %v", name, err) + } } - return nil + // clean up all modules + ctx.cfg.cancelFunc() } -func executeDirectives(inst *Instance, filename string, - directives []string, sblocks []caddyfile.ServerBlock, justValidate bool) error { - // map of server block ID to map of directive name to whatever. - storages := make(map[int]map[string]interface{}) - - // It is crucial that directives are executed in the proper order. - // We loop with the directives on the outer loop so we execute - // a directive for all server blocks before going to the next directive. - // This is important mainly due to the parsing callbacks (below). - for _, dir := range directives { - for i, sb := range sblocks { - var once sync.Once - if _, ok := storages[i]; !ok { - storages[i] = make(map[string]interface{}) - } +// Validate loads, provisions, and validates +// cfg, but does not start running it. +func Validate(cfg *Config) error { + _, err := run(cfg, false) + if err == nil { + cfg.cancelFunc() // call Cleanup on all modules + } + return err +} - for j, key := range sb.Keys { - // Execute directive if it is in the server block - if tokens, ok := sb.Tokens[dir]; ok { - controller := &Controller{ - instance: inst, - Key: key, - Dispenser: caddyfile.NewDispenserTokens(filename, tokens), - OncePerServerBlock: func(f func() error) error { - var err error - once.Do(func() { - err = f() - }) - return err - }, - ServerBlockIndex: i, - ServerBlockKeyIndex: j, - ServerBlockKeys: sb.Keys, - ServerBlockStorage: storages[i][dir], - } +// exitProcess exits the process as gracefully as possible, +// but it always exits, even if there are errors doing so. +// It stops all apps, cleans up external locks, removes any +// PID file, and shuts down admin endpoint(s) in a goroutine. +// Errors are logged along the way, and an appropriate exit +// code is emitted. +func exitProcess(ctx context.Context, logger *zap.Logger) { + // let the rest of the program know we're quitting; only do it once + if !atomic.CompareAndSwapInt32(exiting, 0, 1) { + return + } - setup, err := DirectiveAction(inst.serverType, dir) - if err != nil { - return err - } + // give the OS or service/process manager our 2 weeks' notice: we quit + if err := notify.Stopping(); err != nil { + Log().Error("unable to notify service manager of stopping state", zap.Error(err)) + } - err = setup(controller) - if err != nil { - return err - } + if logger == nil { + logger = Log() + } + logger.Warn("exiting; byeee!! 👋") - storages[i][dir] = controller.ServerBlockStorage // persist for this server block - } - } - } + exitCode := ExitCodeSuccess + lastContext := ActiveContext() - if !justValidate { - // See if there are any callbacks to execute after this directive - if allCallbacks, ok := parsingCallbacks[inst.serverType]; ok { - callbacks := allCallbacks[dir] - for _, callback := range callbacks { - if err := callback(inst.context); err != nil { - return err - } - } - } - } + // stop all apps + if err := Stop(); err != nil { + logger.Error("failed to stop apps", zap.Error(err)) + exitCode = ExitCodeFailedQuit } - return nil -} + // clean up certmagic locks + certmagic.CleanUpOwnLocks(ctx, logger) -func startServers(serverList []Server, inst *Instance, restartFds map[string]restartTriple) error { - errChan := make(chan error, len(serverList)) - - for _, s := range serverList { - var ( - ln net.Listener - pc net.PacketConn - err error - ) - - // if performing an upgrade, obtain listener file descriptors - // from parent process - if IsUpgrade() { - if gs, ok := s.(GracefulServer); ok { - addr := gs.Address() - if fdIndex, ok := loadedGob.ListenerFds["tcp"+addr]; ok { - file := os.NewFile(fdIndex, "") - ln, err = net.FileListener(file) - file.Close() - if err != nil { - return err - } - } - if fdIndex, ok := loadedGob.ListenerFds["udp"+addr]; ok { - file := os.NewFile(fdIndex, "") - pc, err = net.FilePacketConn(file) - file.Close() - if err != nil { - return err - } - } - } + // remove pidfile + if pidfile != "" { + err := os.Remove(pidfile) + if err != nil { + logger.Error("cleaning up PID file:", + zap.String("pidfile", pidfile), + zap.Error(err)) + exitCode = ExitCodeFailedQuit } + } - // If this is a reload and s is a GracefulServer, - // reuse the listener for a graceful restart. - if gs, ok := s.(GracefulServer); ok && restartFds != nil { - addr := gs.Address() - if old, ok := restartFds[addr]; ok { - // listener - if old.listener != nil { - file, err := old.listener.File() - if err != nil { - return err - } - ln, err = net.FileListener(file) - if err != nil { - return err - } - file.Close() - } - // packetconn - if old.packet != nil { - file, err := old.packet.File() - if err != nil { - return err - } - pc, err = net.FilePacketConn(file) - if err != nil { - return err - } - file.Close() - } + // execute any process-exit callbacks + for _, exitFunc := range lastContext.exitFuncs { + exitFunc(ctx) + } + exitFuncsMu.Lock() + for _, exitFunc := range exitFuncs { + exitFunc(ctx) + } + exitFuncsMu.Unlock() + + // shut down admin endpoint(s) in goroutines so that + // if this function was called from an admin handler, + // it has a chance to return gracefully + // use goroutine so that we can finish responding to API request + go func() { + defer func() { + logger = logger.With(zap.Int("exit_code", exitCode)) + if exitCode == ExitCodeSuccess { + logger.Info("shutdown complete") + } else { + logger.Error("unclean shutdown") } - } + os.Exit(exitCode) + }() - if ln == nil { - ln, err = s.Listen() + if remoteAdminServer != nil { + err := stopAdminServer(remoteAdminServer) if err != nil { - return err + exitCode = ExitCodeFailedQuit + logger.Error("failed to stop remote admin server gracefully", zap.Error(err)) } } - if pc == nil { - pc, err = s.ListenPacket() + if localAdminServer != nil { + err := stopAdminServer(localAdminServer) if err != nil { - return err + exitCode = ExitCodeFailedQuit + logger.Error("failed to stop local admin server gracefully", zap.Error(err)) } } + }() +} - inst.wg.Add(2) - go func(s Server, ln net.Listener, pc net.PacketConn, inst *Instance) { - defer inst.wg.Done() +var exiting = new(int32) // accessed atomically - go func() { - errChan <- s.Serve(ln) - defer inst.wg.Done() - }() - errChan <- s.ServePacket(pc) - }(s, ln, pc, inst) +// Exiting returns true if the process is exiting. +// EXPERIMENTAL API: subject to change or removal. +func Exiting() bool { return atomic.LoadInt32(exiting) == 1 } - inst.servers = append(inst.servers, ServerListener{server: s, listener: ln, packet: pc}) - } +// OnExit registers a callback to invoke during process exit. +// This registration is PROCESS-GLOBAL, meaning that each +// function should only be registered once forever, NOT once +// per config load (etc). +// +// EXPERIMENTAL API: subject to change or removal. +func OnExit(f func(context.Context)) { + exitFuncsMu.Lock() + exitFuncs = append(exitFuncs, f) + exitFuncsMu.Unlock() +} - // Log errors that may be returned from Serve() calls, - // these errors should only be occurring in the server loop. - go func() { - for err := range errChan { - if err == nil { - continue - } - if strings.Contains(err.Error(), "use of closed network connection") { - // this error is normal when closing the listener - continue - } - log.Println(err) - } - }() +var ( + exitFuncs []func(context.Context) + exitFuncsMu sync.Mutex +) - return nil -} +// Duration can be an integer or a string. An integer is +// interpreted as nanoseconds. If a string, it is a Go +// time.Duration value such as `300ms`, `1.5h`, or `2h45m`; +// valid units are `ns`, `us`/`µs`, `ms`, `s`, `m`, `h`, and `d`. +type Duration time.Duration -func getServerType(serverType string) (ServerType, error) { - stype, ok := serverTypes[serverType] - if ok { - return stype, nil +// UnmarshalJSON satisfies json.Unmarshaler. +func (d *Duration) UnmarshalJSON(b []byte) error { + if len(b) == 0 { + return io.EOF } - if len(serverTypes) == 0 { - return ServerType{}, fmt.Errorf("no server types plugged in") + var dur time.Duration + var err error + if b[0] == byte('"') && b[len(b)-1] == byte('"') { + dur, err = ParseDuration(strings.Trim(string(b), `"`)) + } else { + err = json.Unmarshal(b, &dur) } - if serverType == "" { - if len(serverTypes) == 1 { - for _, stype := range serverTypes { - return stype, nil - } - } - return ServerType{}, fmt.Errorf("multiple server types available; must choose one") - } - return ServerType{}, fmt.Errorf("unknown server type '%s'", serverType) + *d = Duration(dur) + return err } -func loadServerBlocks(serverType, filename string, input io.Reader) ([]caddyfile.ServerBlock, error) { - validDirectives := ValidDirectives(serverType) - serverBlocks, err := caddyfile.Parse(filename, input, validDirectives) - if err != nil { - return nil, err +// ParseDuration parses a duration string, adding +// support for the "d" unit meaning number of days, +// where a day is assumed to be 24h. The maximum +// input string length is 1024. +func ParseDuration(s string) (time.Duration, error) { + if len(s) > 1024 { + return 0, fmt.Errorf("parsing duration: input string too long") } - if len(serverBlocks) == 0 && serverTypes[serverType].DefaultInput != nil { - newInput := serverTypes[serverType].DefaultInput() - serverBlocks, err = caddyfile.Parse(newInput.Path(), - bytes.NewReader(newInput.Body()), validDirectives) - if err != nil { - return nil, err + var inNumber bool + var numStart int + for i := 0; i < len(s); i++ { + ch := s[i] + if ch == 'd' { + daysStr := s[numStart:i] + days, err := strconv.ParseFloat(daysStr, 64) + if err != nil { + return 0, err + } + hours := days * 24.0 + hoursStr := strconv.FormatFloat(hours, 'f', -1, 64) + s = s[:numStart] + hoursStr + "h" + s[i+1:] + i-- + continue + } + if !inNumber { + numStart = i } + inNumber = (ch >= '0' && ch <= '9') || ch == '.' || ch == '-' || ch == '+' } - return serverBlocks, nil + return time.ParseDuration(s) } -// Stop stops ALL servers. It blocks until they are all stopped. -// It does NOT execute shutdown callbacks, and it deletes all -// instances after stopping is completed. Do not re-use any -// references to old instances after calling Stop. -func Stop() error { - // This awkward for loop is to avoid a deadlock since - // inst.Stop() also acquires the instancesMu lock. - for { - instancesMu.Lock() - if len(instances) == 0 { - break +// InstanceID returns the UUID for this instance, and generates one if it +// does not already exist. The UUID is stored in the local data directory, +// regardless of storage configuration, since each instance is intended to +// have its own unique ID. +func InstanceID() (uuid.UUID, error) { + appDataDir := AppDataDir() + uuidFilePath := filepath.Join(appDataDir, "instance.uuid") + uuidFileBytes, err := os.ReadFile(uuidFilePath) + if errors.Is(err, fs.ErrNotExist) { + uuid, err := uuid.NewRandom() + if err != nil { + return uuid, err } - inst := instances[0] - instancesMu.Unlock() - if err := inst.Stop(); err != nil { - log.Printf("[ERROR] Stopping %s: %v", inst.serverType, err) + err = os.MkdirAll(appDataDir, 0o700) + if err != nil { + return uuid, err } + err = os.WriteFile(uuidFilePath, []byte(uuid.String()), 0o600) + return uuid, err + } else if err != nil { + return [16]byte{}, err } - return nil + return uuid.ParseBytes(uuidFileBytes) } -// IsLoopback returns true if the hostname of addr looks -// explicitly like a common local hostname. addr must only -// be a host or a host:port combination. -func IsLoopback(addr string) bool { - host, _, err := net.SplitHostPort(addr) - if err != nil { - host = addr // happens if the addr is just a hostname - } - return host == "localhost" || - strings.Trim(host, "[]") == "::1" || - strings.HasPrefix(host, "127.") -} - -// IsInternal returns true if the IP of addr -// belongs to a private network IP range. addr must only -// be an IP or an IP:port combination. -// Loopback addresses are considered false. -func IsInternal(addr string) bool { - privateNetworks := []string{ - "10.0.0.0/8", - "172.16.0.0/12", - "192.168.0.0/16", - "fc00::/7", - } - - host, _, err := net.SplitHostPort(addr) - if err != nil { - host = addr // happens if the addr is just a hostname, missing port - // if we encounter an error, the brackets need to be stripped - // because SplitHostPort didn't do it for us - host = strings.Trim(host, "[]") +// CustomVersion is an optional string that overrides Caddy's +// reported version. It can be helpful when downstream packagers +// need to manually set Caddy's version. If no other version +// information is available, the short form version (see +// Version()) will be set to CustomVersion, and the full version +// will include CustomVersion at the beginning. +// +// Set this variable during `go build` with `-ldflags`: +// +// -ldflags '-X github.com/caddyserver/caddy/v2.CustomVersion=v2.6.2' +// +// for example. +var CustomVersion string + +// Version returns the Caddy version in a simple/short form, and +// a full version string. The short form will not have spaces and +// is intended for User-Agent strings and similar, but may be +// omitting valuable information. Note that Caddy must be compiled +// in a special way to properly embed complete version information. +// First this function tries to get the version from the embedded +// build info provided by go.mod dependencies; then it tries to +// get info from embedded VCS information, which requires having +// built Caddy from a git repository. If no version is available, +// this function returns "(devel)" because Go uses that, but for +// the simple form we change it to "unknown". If still no version +// is available (e.g. no VCS repo), then it will use CustomVersion; +// CustomVersion is always prepended to the full version string. +// +// See relevant Go issues: https://github.com/golang/go/issues/29228 +// and https://github.com/golang/go/issues/50603. +// +// This function is experimental and subject to change or removal. +func Version() (simple, full string) { + // the currently-recommended way to build Caddy involves + // building it as a dependency so we can extract version + // information from go.mod tooling; once the upstream + // Go issues are fixed, we should just be able to use + // bi.Main... hopefully. + var module *debug.Module + bi, ok := debug.ReadBuildInfo() + if !ok { + if CustomVersion != "" { + full = CustomVersion + simple = CustomVersion + return + } + full = "unknown" + simple = "unknown" + return } - ip := net.ParseIP(host) - if ip == nil { - return false + // find the Caddy module in the dependency list + for _, dep := range bi.Deps { + if dep.Path == ImportPath { + module = dep + break + } } - for _, privateNetwork := range privateNetworks { - _, ipnet, _ := net.ParseCIDR(privateNetwork) - if ipnet.Contains(ip) { - return true + if module != nil { + simple, full = module.Version, module.Version + if module.Sum != "" { + full += " " + module.Sum + } + if module.Replace != nil { + full += " => " + module.Replace.Path + if module.Replace.Version != "" { + simple = module.Replace.Version + "_custom" + full += "@" + module.Replace.Version + } + if module.Replace.Sum != "" { + full += " " + module.Replace.Sum + } } } - return false -} - -// Started returns true if at least one instance has been -// started by this package. It never gets reset to false -// once it is set to true. -func Started() bool { - mu.Lock() - defer mu.Unlock() - return started -} -// CaddyfileInput represents a Caddyfile as input -// and is simply a convenient way to implement -// the Input interface. -type CaddyfileInput struct { - Filepath string - Contents []byte - ServerTypeName string -} + if full == "" { + var vcsRevision string + var vcsTime time.Time + var vcsModified bool + for _, setting := range bi.Settings { + switch setting.Key { + case "vcs.revision": + vcsRevision = setting.Value + case "vcs.time": + vcsTime, _ = time.Parse(time.RFC3339, setting.Value) + case "vcs.modified": + vcsModified, _ = strconv.ParseBool(setting.Value) + } + } -// Body returns c.Contents. -func (c CaddyfileInput) Body() []byte { return c.Contents } + if vcsRevision != "" { + var modified string + if vcsModified { + modified = "+modified" + } + full = fmt.Sprintf("%s%s (%s)", vcsRevision, modified, vcsTime.Format(time.RFC822)) + simple = vcsRevision -// Path returns c.Filepath. -func (c CaddyfileInput) Path() string { return c.Filepath } + // use short checksum for simple, if hex-only + if _, err := hex.DecodeString(simple); err == nil { + simple = simple[:8] + } -// ServerType returns c.ServerType. -func (c CaddyfileInput) ServerType() string { return c.ServerTypeName } + // append date to simple since it can be convenient + // to know the commit date as part of the version + if !vcsTime.IsZero() { + simple += "-" + vcsTime.Format("20060102") + } + } + } -// Input represents a Caddyfile; its contents and file path -// (which should include the file name at the end of the path). -// If path does not apply (e.g. piped input) you may use -// any understandable value. The path is mainly used for logging, -// error messages, and debugging. -type Input interface { - // Gets the Caddyfile contents - Body() []byte + if full == "" { + if CustomVersion != "" { + full = CustomVersion + } else { + full = "unknown" + } + } else if CustomVersion != "" { + full = CustomVersion + " " + full + } - // Gets the path to the origin file - Path() string + if simple == "" || simple == "(devel)" { + if CustomVersion != "" { + simple = CustomVersion + } else { + simple = "unknown" + } + } - // The type of server this input is intended for - ServerType() string + return } -// DefaultInput returns the default Caddyfile input -// to use when it is otherwise empty or missing. -// It uses the default host and port (depends on -// host, e.g. localhost is 2015, otherwise 443) and -// root. -func DefaultInput(serverType string) Input { - if _, ok := serverTypes[serverType]; !ok { - return nil - } - if serverTypes[serverType].DefaultInput == nil { - return nil - } - return serverTypes[serverType].DefaultInput() +// ActiveContext returns the currently-active context. +// This function is experimental and might be changed +// or removed in the future. +func ActiveContext() Context { + currentCtxMu.RLock() + defer currentCtxMu.RUnlock() + return currentCtx } -// writePidFile writes the process ID to the file at PidFile. -// It does nothing if PidFile is not set. -func writePidFile() error { - if PidFile == "" { - return nil +// CtxKey is a value type for use with context.WithValue. +type CtxKey string + +// This group of variables pertains to the current configuration. +var ( + // currentCtx is the root context for the currently-running + // configuration, which can be accessed through this value. + // If the Config contained in this value is not nil, then + // a config is currently active/running. + currentCtx Context + currentCtxMu sync.RWMutex + + // rawCfg is the current, generic-decoded configuration; + // we initialize it as a map with one field ("config") + // to maintain parity with the API endpoint and to avoid + // the special case of having to access/mutate the variable + // directly without traversing into it. + rawCfg = map[string]any{ + rawConfigKey: nil, } - pid := []byte(strconv.Itoa(os.Getpid()) + "\n") - return ioutil.WriteFile(PidFile, pid, 0644) -} -type restartTriple struct { - server GracefulServer - listener Listener - packet PacketConn -} + // rawCfgJSON is the JSON-encoded form of rawCfg. Keeping + // this around avoids an extra Marshal call during changes. + rawCfgJSON []byte -var ( - // instances is the list of running Instances. - instances []*Instance + // rawCfgIndex is the map of user-assigned ID to expanded + // path, for converting /id/ paths to /config/ paths. + rawCfgIndex map[string]string - // instancesMu protects instances. - instancesMu sync.Mutex + // rawCfgMu protects all the rawCfg fields and also + // essentially synchronizes config changes/reloads. + rawCfgMu sync.RWMutex ) -var ( - // DefaultConfigFile is the name of the configuration file that is loaded - // by default if no other file is specified. - DefaultConfigFile = "Caddyfile" -) +// errSameConfig is returned if the new config is the same +// as the old one. This isn't usually an actual, actionable +// error; it's mostly a sentinel value. +var errSameConfig = errors.New("config is unchanged") -// CtxKey is a value type for use with context.WithValue. -type CtxKey string +// ImportPath is the package import path for Caddy core. +// This identifier may be removed in the future. +const ImportPath = "github.com/caddyserver/caddy/v2" diff --git a/caddy/build.bash b/caddy/build.bash deleted file mode 100755 index f55a98826bc..00000000000 --- a/caddy/build.bash +++ /dev/null @@ -1,56 +0,0 @@ -#!/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}" diff --git a/caddy/caddymain/run.go b/caddy/caddymain/run.go deleted file mode 100644 index b88997192bc..00000000000 --- a/caddy/caddymain/run.go +++ /dev/null @@ -1,260 +0,0 @@ -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 -) diff --git a/caddy/caddymain/run_test.go b/caddy/caddymain/run_test.go deleted file mode 100644 index d14abffe15d..00000000000 --- a/caddy/caddymain/run_test.go +++ /dev/null @@ -1,44 +0,0 @@ -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) - } -} diff --git a/caddy/main.go b/caddy/main.go deleted file mode 100644 index 538a629f7ed..00000000000 --- a/caddy/main.go +++ /dev/null @@ -1,14 +0,0 @@ -// 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() -} diff --git a/caddy/main_test.go b/caddy/main_test.go deleted file mode 100644 index 52063a75369..00000000000 --- a/caddy/main_test.go +++ /dev/null @@ -1,17 +0,0 @@ -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") - } -} diff --git a/caddy_test.go b/caddy_test.go index a7eb19b5140..adf14350e5b 100644 --- a/caddy_test.go +++ b/caddy_test.go @@ -1,159 +1,74 @@ +// 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" ) -/* -// TODO -func TestCaddyStartStop(t *testing.T) { - caddyfile := "localhost:1984" - - for i := 0; i < 2; i++ { - _, err := Start(CaddyfileInput{Contents: []byte(caddyfile)}) - if err != nil { - t.Fatalf("Error starting, iteration %d: %v", i, err) - } - - client := http.Client{ - Timeout: time.Duration(2 * time.Second), - } - resp, err := client.Get("http://localhost:1984") - if err != nil { - t.Fatalf("Expected GET request to succeed (iteration %d), but it failed: %v", i, err) - } - resp.Body.Close() - - err = Stop() - if err != nil { - t.Fatalf("Error stopping, iteration %d: %v", i, err) - } - } -} -*/ - -func TestIsLoopback(t *testing.T) { - for i, test := range []struct { +func TestParseDuration(t *testing.T) { + const day = 24 * time.Hour + for i, tc := range []struct { input string - expect bool + expect time.Duration }{ - {"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}, + { + 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, + }, } { - 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) + actual, err := ParseDuration(tc.input) + if err != nil { + t.Errorf("Test %d ('%s'): Got error: %v", i, tc.input, err) + continue } - } -} - -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) + if actual != tc.expect { + t.Errorf("Test %d ('%s'): Expected=%s Actual=%s", i, tc.input, tc.expect, actual) } } } diff --git a/caddyconfig/caddyfile/adapter.go b/caddyconfig/caddyfile/adapter.go new file mode 100644 index 00000000000..da4f98337fb --- /dev/null +++ b/caddyconfig/caddyfile/adapter.go @@ -0,0 +1,145 @@ +// 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) diff --git a/caddyconfig/caddyfile/dispenser.go b/caddyconfig/caddyfile/dispenser.go new file mode 100644 index 00000000000..325bb54d3f3 --- /dev/null +++ b/caddyconfig/caddyfile/dispenser.go @@ -0,0 +1,521 @@ +// 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.WrapErr(errors.New(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" diff --git a/caddyfile/dispenser_test.go b/caddyconfig/caddyfile/dispenser_test.go similarity index 87% rename from caddyfile/dispenser_test.go rename to caddyconfig/caddyfile/dispenser_test.go index 300cd1a51b9..0f6ee5043f4 100644 --- a/caddyfile/dispenser_test.go +++ b/caddyconfig/caddyfile/dispenser_test.go @@ -1,6 +1,21 @@ +// 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" @@ -11,7 +26,7 @@ func TestDispenser_Val_Next(t *testing.T) { dir1 arg1 dir2 arg2 arg3 dir3` - d := NewDispenser("Testfile", strings.NewReader(input)) + d := NewTestDispenser(input) if val := d.Val(); val != "" { t.Fatalf("Val(): Should return empty string when no token loaded; got '%s'", val) @@ -49,7 +64,7 @@ func TestDispenser_NextArg(t *testing.T) { input := `dir1 arg1 dir2 arg2 arg3 dir3` - d := NewDispenser("Testfile", strings.NewReader(input)) + d := NewTestDispenser(input) assertNext := func(shouldLoad bool, expectedVal string, expectedCursor int) { if d.Next() != shouldLoad { @@ -96,7 +111,7 @@ func TestDispenser_NextLine(t *testing.T) { input := `host:port dir1 arg1 dir2 arg2 arg3` - d := NewDispenser("Testfile", strings.NewReader(input)) + d := NewTestDispenser(input) assertNextLine := func(shouldLoad bool, expectedVal string, expectedCursor int) { if d.NextLine() != shouldLoad { @@ -129,10 +144,10 @@ func TestDispenser_NextBlock(t *testing.T) { } foobar2 { }` - d := NewDispenser("Testfile", strings.NewReader(input)) + d := NewTestDispenser(input) assertNextBlock := func(shouldLoad bool, expectedCursor, expectedNesting int) { - if loaded := d.NextBlock(); loaded != shouldLoad { + if loaded := d.NextBlock(0); loaded != shouldLoad { t.Errorf("NextBlock(): Should return %v but got %v", shouldLoad, loaded) } if d.cursor != expectedCursor { @@ -159,7 +174,7 @@ func TestDispenser_Args(t *testing.T) { dir2 arg4 arg5 dir3 arg6 arg7 dir4` - d := NewDispenser("Testfile", strings.NewReader(input)) + d := NewTestDispenser(input) d.Next() // dir1 @@ -226,7 +241,7 @@ func TestDispenser_RemainingArgs(t *testing.T) { dir2 arg4 arg5 dir3 arg6 { arg7 dir4` - d := NewDispenser("Testfile", strings.NewReader(input)) + d := NewTestDispenser(input) d.Next() // dir1 @@ -263,7 +278,7 @@ func TestDispenser_ArgErr_Err(t *testing.T) { input := `dir1 { } dir2 arg1 arg2` - d := NewDispenser("Testfile", strings.NewReader(input)) + d := NewTestDispenser(input) d.cursor = 1 // { @@ -289,4 +304,10 @@ 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") + } } diff --git a/caddyconfig/caddyfile/formatter.go b/caddyconfig/caddyfile/formatter.go new file mode 100644 index 00000000000..d35f0ac6b68 --- /dev/null +++ b/caddyconfig/caddyfile/formatter.go @@ -0,0 +1,298 @@ +// 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') +} diff --git a/caddyconfig/caddyfile/formatter_fuzz.go b/caddyconfig/caddyfile/formatter_fuzz.go new file mode 100644 index 00000000000..7c1fc643928 --- /dev/null +++ b/caddyconfig/caddyfile/formatter_fuzz.go @@ -0,0 +1,27 @@ +// 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 +} diff --git a/caddyconfig/caddyfile/formatter_test.go b/caddyconfig/caddyfile/formatter_test.go new file mode 100644 index 00000000000..6eec822fe59 --- /dev/null +++ b/caddyconfig/caddyfile/formatter_test.go @@ -0,0 +1,451 @@ +// 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 < 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\[(.+)]`) +) diff --git a/caddyconfig/caddyfile/importgraph.go b/caddyconfig/caddyfile/importgraph.go new file mode 100644 index 00000000000..ca859299dce --- /dev/null +++ b/caddyconfig/caddyfile/importgraph.go @@ -0,0 +1,126 @@ +// 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" + "slices" +) + +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 + } + return slices.Contains(al, to) +} + +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 +} diff --git a/caddyconfig/caddyfile/lexer.go b/caddyconfig/caddyfile/lexer.go new file mode 100644 index 00000000000..9b523f397ad --- /dev/null +++ b/caddyconfig/caddyfile/lexer.go @@ -0,0 +1,399 @@ +// 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 <= 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 +} diff --git a/caddyconfig/caddyfile/lexer_fuzz.go b/caddyconfig/caddyfile/lexer_fuzz.go new file mode 100644 index 00000000000..6f75694b5a0 --- /dev/null +++ b/caddyconfig/caddyfile/lexer_fuzz.go @@ -0,0 +1,28 @@ +// 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 +} diff --git a/caddyconfig/caddyfile/lexer_test.go b/caddyconfig/caddyfile/lexer_test.go new file mode 100644 index 00000000000..7389af79b40 --- /dev/null +++ b/caddyconfig/caddyfile/lexer_test.go @@ -0,0 +1,541 @@ +// 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 <>`), + expected: []Token{ + {Line: 1, Text: `escaped-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 << >>`), + expected: []Token{ + {Line: 1, Text: `not-a-heredoc`}, + {Line: 1, Text: `<<`}, + {Line: 1, Text: `>>`}, + }, + }, + { + input: []byte(`not-a-heredoc < 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) + } + + // After the above, a comma surrounded by spaces would result + // in an empty token which we should ignore + if value != "" { + // Add the token as a site address + 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 := caddy.FastAbs(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 := caddy.FastAbs(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 = ":" +) diff --git a/caddyconfig/caddyfile/parse_test.go b/caddyconfig/caddyfile/parse_test.go new file mode 100644 index 00000000000..d3fada4e026 --- /dev/null +++ b/caddyconfig/caddyfile/parse_test.go @@ -0,0 +1,889 @@ +// 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"}, + }}, + + {`foo.example.com , example.com`, false, [][]string{ + {"foo.example.com", "example.com"}, + }}, + + {`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: %v", + i, len(test.keys[j]), j, len(block.Keys), 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)} +} diff --git a/caddyhttp/rewrite/testdata/testdir/empty b/caddyconfig/caddyfile/testdata/empty.txt similarity index 100% rename from caddyhttp/rewrite/testdata/testdir/empty rename to caddyconfig/caddyfile/testdata/empty.txt diff --git a/caddyconfig/caddyfile/testdata/glob/.dotfile.txt b/caddyconfig/caddyfile/testdata/glob/.dotfile.txt new file mode 100644 index 00000000000..faab100c604 --- /dev/null +++ b/caddyconfig/caddyfile/testdata/glob/.dotfile.txt @@ -0,0 +1,4 @@ +host1 { + dir1 + dir2 arg1 +} diff --git a/caddyfile/testdata/import_test1.txt b/caddyconfig/caddyfile/testdata/glob/import_test1.txt similarity index 100% rename from caddyfile/testdata/import_test1.txt rename to caddyconfig/caddyfile/testdata/glob/import_test1.txt diff --git a/caddyconfig/caddyfile/testdata/import_args0.txt b/caddyconfig/caddyfile/testdata/import_args0.txt new file mode 100644 index 00000000000..add211e378d --- /dev/null +++ b/caddyconfig/caddyfile/testdata/import_args0.txt @@ -0,0 +1 @@ +{args[0]} \ No newline at end of file diff --git a/caddyconfig/caddyfile/testdata/import_args1.txt b/caddyconfig/caddyfile/testdata/import_args1.txt new file mode 100644 index 00000000000..422692a2c23 --- /dev/null +++ b/caddyconfig/caddyfile/testdata/import_args1.txt @@ -0,0 +1 @@ +{args[0]} {args[1]} \ No newline at end of file diff --git a/caddyfile/testdata/import_glob0.txt b/caddyconfig/caddyfile/testdata/import_glob0.txt similarity index 100% rename from caddyfile/testdata/import_glob0.txt rename to caddyconfig/caddyfile/testdata/import_glob0.txt diff --git a/caddyfile/testdata/import_glob1.txt b/caddyconfig/caddyfile/testdata/import_glob1.txt similarity index 100% rename from caddyfile/testdata/import_glob1.txt rename to caddyconfig/caddyfile/testdata/import_glob1.txt diff --git a/caddyfile/testdata/import_glob2.txt b/caddyconfig/caddyfile/testdata/import_glob2.txt similarity index 100% rename from caddyfile/testdata/import_glob2.txt rename to caddyconfig/caddyfile/testdata/import_glob2.txt diff --git a/caddyconfig/caddyfile/testdata/import_recursive0.txt b/caddyconfig/caddyfile/testdata/import_recursive0.txt new file mode 100644 index 00000000000..4d827b33ecb --- /dev/null +++ b/caddyconfig/caddyfile/testdata/import_recursive0.txt @@ -0,0 +1 @@ +import import_recursive0.txt \ No newline at end of file diff --git a/caddyconfig/caddyfile/testdata/import_recursive1.txt b/caddyconfig/caddyfile/testdata/import_recursive1.txt new file mode 100644 index 00000000000..9b6102ed75c --- /dev/null +++ b/caddyconfig/caddyfile/testdata/import_recursive1.txt @@ -0,0 +1 @@ +import import_recursive2.txt \ No newline at end of file diff --git a/caddyconfig/caddyfile/testdata/import_recursive2.txt b/caddyconfig/caddyfile/testdata/import_recursive2.txt new file mode 100644 index 00000000000..5553dea3876 --- /dev/null +++ b/caddyconfig/caddyfile/testdata/import_recursive2.txt @@ -0,0 +1 @@ +import import_recursive3.txt \ No newline at end of file diff --git a/caddyconfig/caddyfile/testdata/import_recursive3.txt b/caddyconfig/caddyfile/testdata/import_recursive3.txt new file mode 100644 index 00000000000..fcf0237f6c0 --- /dev/null +++ b/caddyconfig/caddyfile/testdata/import_recursive3.txt @@ -0,0 +1 @@ +import import_recursive1.txt \ No newline at end of file diff --git a/caddyconfig/caddyfile/testdata/import_test1.txt b/caddyconfig/caddyfile/testdata/import_test1.txt new file mode 100644 index 00000000000..dac7b29be09 --- /dev/null +++ b/caddyconfig/caddyfile/testdata/import_test1.txt @@ -0,0 +1,2 @@ +dir2 arg1 arg2 +dir3 \ No newline at end of file diff --git a/caddyfile/testdata/import_test2.txt b/caddyconfig/caddyfile/testdata/import_test2.txt similarity index 100% rename from caddyfile/testdata/import_test2.txt rename to caddyconfig/caddyfile/testdata/import_test2.txt diff --git a/caddyconfig/caddyfile/testdata/only_white_space.txt b/caddyconfig/caddyfile/testdata/only_white_space.txt new file mode 100644 index 00000000000..705327cd431 --- /dev/null +++ b/caddyconfig/caddyfile/testdata/only_white_space.txt @@ -0,0 +1,7 @@ + + + + +  + + diff --git a/caddyconfig/configadapters.go b/caddyconfig/configadapters.go new file mode 100644 index 00000000000..0ca3c3af13f --- /dev/null +++ b/caddyconfig/configadapters.go @@ -0,0 +1,138 @@ +// 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) diff --git a/caddyconfig/httpcaddyfile/addresses.go b/caddyconfig/httpcaddyfile/addresses.go new file mode 100644 index 00000000000..1121776d98f --- /dev/null +++ b/caddyconfig/httpcaddyfile/addresses.go @@ -0,0 +1,501 @@ +// 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" +) + +// mapAddressToProtocolToServerBlocks 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) mapAddressToProtocolToServerBlocks(originalServerBlocks []serverBlock, + options map[string]any, +) (map[string]map[string][]serverBlock, error) { + addrToProtocolToServerBlocks := map[string]map[string][]serverBlock{} + + type keyWithParsedKey struct { + key caddyfile.Token + parsedKey Address + } + + 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 + addrToProtocolToKeyWithParsedKeys := map[string]map[string][]keyWithParsedKey{} + for j, key := range sblock.block.Keys { + parsedKey, err := ParseAddress(key.Text) + if err != nil { + return nil, fmt.Errorf("parsing key: %v", err) + } + parsedKey = parsedKey.Normalize() + + // 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) + listeners, err := st.listenersForServerBlockAddress(sblock, parsedKey, 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 its protocols and each listener address served with them + kwpk := keyWithParsedKey{key, parsedKey} + for addr, protocols := range listeners { + protocolToKeyWithParsedKeys, ok := addrToProtocolToKeyWithParsedKeys[addr] + if !ok { + protocolToKeyWithParsedKeys = map[string][]keyWithParsedKey{} + addrToProtocolToKeyWithParsedKeys[addr] = protocolToKeyWithParsedKeys + } + + // an empty protocol indicates the default, a nil or empty value in the ListenProtocols array + if len(protocols) == 0 { + protocols[""] = struct{}{} + } + for prot := range protocols { + protocolToKeyWithParsedKeys[prot] = append( + protocolToKeyWithParsedKeys[prot], + kwpk) + } + } + } + + // make a slice of the map keys so we can iterate in sorted order + addrs := make([]string, 0, len(addrToProtocolToKeyWithParsedKeys)) + for addr := range addrToProtocolToKeyWithParsedKeys { + addrs = append(addrs, addr) + } + 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 { + protocolToKeyWithParsedKeys := addrToProtocolToKeyWithParsedKeys[addr] + + prots := make([]string, 0, len(protocolToKeyWithParsedKeys)) + for prot := range protocolToKeyWithParsedKeys { + prots = append(prots, prot) + } + sort.Strings(prots) + + protocolToServerBlocks, ok := addrToProtocolToServerBlocks[addr] + if !ok { + protocolToServerBlocks = map[string][]serverBlock{} + addrToProtocolToServerBlocks[addr] = protocolToServerBlocks + } + + for _, prot := range prots { + keyWithParsedKeys := protocolToKeyWithParsedKeys[prot] + + keys := make([]caddyfile.Token, len(keyWithParsedKeys)) + parsedKeys := make([]Address, len(keyWithParsedKeys)) + + for k, keyWithParsedKey := range keyWithParsedKeys { + keys[k] = keyWithParsedKey.key + parsedKeys[k] = keyWithParsedKey.parsedKey + } + + protocolToServerBlocks[prot] = append(protocolToServerBlocks[prot], serverBlock{ + block: caddyfile.ServerBlock{ + Keys: keys, + Segments: sblock.block.Segments, + }, + pile: sblock.pile, + parsedKeys: parsedKeys, + }) + } + } + } + + return addrToProtocolToServerBlocks, nil +} + +// consolidateAddrMappings eliminates repetition of identical server blocks in a mapping of +// single listener addresses to protocols to lists of server blocks. Since multiple addresses +// may serve multiple protocols to 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(addrToProtocolToServerBlocks map[string]map[string][]serverBlock) []sbAddrAssociation { + sbaddrs := make([]sbAddrAssociation, 0, len(addrToProtocolToServerBlocks)) + + addrs := make([]string, 0, len(addrToProtocolToServerBlocks)) + for addr := range addrToProtocolToServerBlocks { + addrs = append(addrs, addr) + } + sort.Strings(addrs) + + for _, addr := range addrs { + protocolToServerBlocks := addrToProtocolToServerBlocks[addr] + + prots := make([]string, 0, len(protocolToServerBlocks)) + for prot := range protocolToServerBlocks { + prots = append(prots, prot) + } + sort.Strings(prots) + + for _, prot := range prots { + serverBlocks := protocolToServerBlocks[prot] + + // now find other addresses that map to identical + // server blocks and add them to our map of listener + // addresses and protocols, while removing them from + // the original map + listeners := map[string]map[string]struct{}{} + + for otherAddr, otherProtocolToServerBlocks := range addrToProtocolToServerBlocks { + for otherProt, otherServerBlocks := range otherProtocolToServerBlocks { + if addr == otherAddr && prot == otherProt || reflect.DeepEqual(serverBlocks, otherServerBlocks) { + listener, ok := listeners[otherAddr] + if !ok { + listener = map[string]struct{}{} + listeners[otherAddr] = listener + } + listener[otherProt] = struct{}{} + delete(otherProtocolToServerBlocks, otherProt) + } + } + } + + addresses := make([]string, 0, len(listeners)) + for lnAddr := range listeners { + addresses = append(addresses, lnAddr) + } + sort.Strings(addresses) + + addressesWithProtocols := make([]addressWithProtocols, 0, len(listeners)) + + for _, lnAddr := range addresses { + lnProts := listeners[lnAddr] + prots := make([]string, 0, len(lnProts)) + for prot := range lnProts { + prots = append(prots, prot) + } + sort.Strings(prots) + + addressesWithProtocols = append(addressesWithProtocols, addressWithProtocols{ + address: lnAddr, + protocols: prots, + }) + } + + sbaddrs = append(sbaddrs, sbAddrAssociation{ + addressesWithProtocols: addressesWithProtocols, + serverBlocks: serverBlocks, + }) + } + } + + return sbaddrs +} + +// listenersForServerBlockAddress essentially converts the Caddyfile site addresses to a map from +// Caddy listener addresses and the protocols to serve them with to the parsed address for each server block. +func (st *ServerType) listenersForServerBlockAddress(sblock serverBlock, addr Address, + options map[string]any, +) (map[string]map[string]struct{}, error) { + 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", addr.String()) + } + + // the bind directive specifies hosts (and potentially network), and the protocols to serve them with, but is optional + lnCfgVals := make([]addressesWithProtocols, 0, len(sblock.pile["bind"])) + for _, cfgVal := range sblock.pile["bind"] { + if val, ok := cfgVal.Value.(addressesWithProtocols); ok { + lnCfgVals = append(lnCfgVals, val) + } + } + if len(lnCfgVals) == 0 { + if defaultBindValues, ok := options["default_bind"].([]ConfigValue); ok { + for _, defaultBindValue := range defaultBindValues { + lnCfgVals = append(lnCfgVals, defaultBindValue.Value.(addressesWithProtocols)) + } + } else { + lnCfgVals = []addressesWithProtocols{{ + addresses: []string{""}, + protocols: nil, + }} + } + } + + // use a map to prevent duplication + listeners := map[string]map[string]struct{}{} + for _, lnCfgVal := range lnCfgVals { + for _, lnAddr := range lnCfgVal.addresses { + lnNetw, lnHost, _, err := caddy.SplitNetworkAddress(lnAddr) + if err != nil { + return nil, fmt.Errorf("splitting listener address: %v", err) + } + networkAddr, err := caddy.ParseNetworkAddress(caddy.JoinNetworkAddress(lnNetw, lnHost, lnPort)) + if err != nil { + return nil, fmt.Errorf("parsing network address: %v", err) + } + if _, ok := listeners[addr.String()]; !ok { + listeners[networkAddr.String()] = map[string]struct{}{} + } + for _, protocol := range lnCfgVal.protocols { + listeners[networkAddr.String()][protocol] = struct{}{} + } + } + } + + return listeners, nil +} + +// addressesWithProtocols associates a list of listen addresses +// with a list of protocols to serve them with +type addressesWithProtocols struct { + addresses []string + protocols []string +} + +// 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() +} diff --git a/caddyconfig/httpcaddyfile/addresses_fuzz.go b/caddyconfig/httpcaddyfile/addresses_fuzz.go new file mode 100644 index 00000000000..364ff971b11 --- /dev/null +++ b/caddyconfig/httpcaddyfile/addresses_fuzz.go @@ -0,0 +1,28 @@ +// 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 +} diff --git a/caddyconfig/httpcaddyfile/addresses_test.go b/caddyconfig/httpcaddyfile/addresses_test.go new file mode 100644 index 00000000000..232460d0ffb --- /dev/null +++ b/caddyconfig/httpcaddyfile/addresses_test.go @@ -0,0 +1,253 @@ +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) + } + } +} diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go new file mode 100644 index 00000000000..45570d01685 --- /dev/null +++ b/caddyconfig/httpcaddyfile/builtins.go @@ -0,0 +1,1171 @@ +// 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" + "html" + "net/http" + "reflect" + "strconv" + "strings" + "time" + + "github.com/caddyserver/certmagic" + "github.com/mholt/acmez/v3/acme" + "go.uber.org/zap/zapcore" + + "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" + "github.com/caddyserver/caddy/v2/modules/caddytls" +) + +func init() { + RegisterDirective("bind", parseBind) + RegisterDirective("tls", parseTLS) + RegisterHandlerDirective("fs", parseFilesystem) + RegisterDirective("root", parseRoot) + RegisterHandlerDirective("vars", parseVars) + RegisterHandlerDirective("redir", parseRedir) + RegisterHandlerDirective("respond", parseRespond) + RegisterHandlerDirective("abort", parseAbort) + RegisterHandlerDirective("error", parseError) + RegisterHandlerDirective("route", parseRoute) + RegisterHandlerDirective("handle", parseHandle) + RegisterDirective("handle_errors", parseHandleErrors) + RegisterHandlerDirective("invoke", parseInvoke) + RegisterDirective("log", parseLog) + RegisterHandlerDirective("skip_log", parseLogSkip) + RegisterHandlerDirective("log_skip", parseLogSkip) + RegisterHandlerDirective("log_name", parseLogName) +} + +// parseBind parses the bind directive. Syntax: +// +// bind [{ +// protocols [h1|h2|h2c|h3] [...] +// }] +func parseBind(h Helper) ([]ConfigValue, error) { + h.Next() // consume directive name + var addresses, protocols []string + addresses = h.RemainingArgs() + + for h.NextBlock(0) { + switch h.Val() { + case "protocols": + protocols = h.RemainingArgs() + if len(protocols) == 0 { + return nil, h.Errf("protocols requires one or more arguments") + } + default: + return nil, h.Errf("unknown subdirective: %s", h.Val()) + } + } + + return []ConfigValue{{Class: "bind", Value: addressesWithProtocols{ + addresses: addresses, + protocols: protocols, + }}}, nil +} + +// parseTLS parses the tls directive. Syntax: +// +// tls [|internal|force_automate]|[ ] { +// protocols [] +// ciphers +// curves +// client_auth { +// mode [request|require|verify_if_given|require_and_verify] +// trust_pool [...] +// trusted_leaf_cert +// trusted_leaf_cert_file +// } +// alpn +// load +// ca +// ca_root +// key_type [ed25519|p256|p384|rsa2048|rsa4096] +// dns [...] +// propagation_delay +// propagation_timeout +// resolvers +// dns_ttl +// dns_challenge_override_domain +// on_demand +// reuse_private_keys +// force_automate +// eab +// issuer [...] +// get_certificate [...] +// insecure_secrets_log +// } +func parseTLS(h Helper) ([]ConfigValue, error) { + h.Next() // consume directive name + + cp := new(caddytls.ConnectionPolicy) + var fileLoader caddytls.FileLoader + var folderLoader caddytls.FolderLoader + var certSelector caddytls.CustomCertSelectionPolicy + var acmeIssuer *caddytls.ACMEIssuer + var keyType string + var internalIssuer *caddytls.InternalIssuer + var issuers []certmagic.Issuer + var certManagers []certmagic.Manager + var onDemand bool + var reusePrivateKeys bool + var forceAutomate bool + + firstLine := h.RemainingArgs() + switch len(firstLine) { + case 0: + case 1: + if firstLine[0] == "internal" { + internalIssuer = new(caddytls.InternalIssuer) + } else if firstLine[0] == "force_automate" { + forceAutomate = true + } else if !strings.Contains(firstLine[0], "@") { + return nil, h.Err("single argument must either be 'internal', 'force_automate', or an email address") + } else { + acmeIssuer = &caddytls.ACMEIssuer{ + Email: firstLine[0], + } + } + + case 2: + // file certificate loader + certFilename := firstLine[0] + keyFilename := firstLine[1] + + // tag this certificate so if multiple certs match, specifically + // this one that the user has provided will be used, see #2588: + // https://github.com/caddyserver/caddy/issues/2588 ... but we + // must be careful about how we do this; being careless will + // lead to failed handshakes + // + // we need to remember which cert files we've seen, since we + // must load each cert only once; otherwise, they each get a + // different tag... since a cert loaded twice has the same + // bytes, it will overwrite the first one in the cache, and + // only the last cert (and its tag) will survive, so any conn + // policy that is looking for any tag other than the last one + // to be loaded won't find it, and TLS handshakes will fail + // (see end of issue #3004) + // + // tlsCertTags maps certificate filenames to their tag. + // This is used to remember which tag is used for each + // certificate files, since we need to avoid loading + // the same certificate files more than once, overwriting + // previous tags + tlsCertTags, ok := h.State["tlsCertTags"].(map[string]string) + if !ok { + tlsCertTags = make(map[string]string) + h.State["tlsCertTags"] = tlsCertTags + } + + tag, ok := tlsCertTags[certFilename] + if !ok { + // haven't seen this cert file yet, let's give it a tag + // and add a loader for it + tag = fmt.Sprintf("cert%d", len(tlsCertTags)) + fileLoader = append(fileLoader, caddytls.CertKeyFilePair{ + Certificate: certFilename, + Key: keyFilename, + Tags: []string{tag}, + }) + // remember this for next time we see this cert file + tlsCertTags[certFilename] = tag + } + certSelector.AnyTag = append(certSelector.AnyTag, tag) + + default: + return nil, h.ArgErr() + } + + var hasBlock bool + for h.NextBlock(0) { + hasBlock = true + + switch h.Val() { + case "protocols": + args := h.RemainingArgs() + if len(args) == 0 { + return nil, h.Errf("protocols requires one or two arguments") + } + if len(args) > 0 { + if _, ok := caddytls.SupportedProtocols[args[0]]; !ok { + return nil, h.Errf("wrong protocol name or protocol not supported: '%s'", args[0]) + } + cp.ProtocolMin = args[0] + } + if len(args) > 1 { + if _, ok := caddytls.SupportedProtocols[args[1]]; !ok { + return nil, h.Errf("wrong protocol name or protocol not supported: '%s'", args[1]) + } + cp.ProtocolMax = args[1] + } + + case "ciphers": + for h.NextArg() { + if !caddytls.CipherSuiteNameSupported(h.Val()) { + return nil, h.Errf("wrong cipher suite name or cipher suite not supported: '%s'", h.Val()) + } + cp.CipherSuites = append(cp.CipherSuites, h.Val()) + } + + case "curves": + for h.NextArg() { + if _, ok := caddytls.SupportedCurves[h.Val()]; !ok { + return nil, h.Errf("Wrong curve name or curve not supported: '%s'", h.Val()) + } + cp.Curves = append(cp.Curves, h.Val()) + } + + case "client_auth": + cp.ClientAuthentication = &caddytls.ClientAuthentication{} + if err := cp.ClientAuthentication.UnmarshalCaddyfile(h.NewFromNextSegment()); err != nil { + return nil, err + } + case "alpn": + args := h.RemainingArgs() + if len(args) == 0 { + return nil, h.ArgErr() + } + cp.ALPN = args + + case "load": + folderLoader = append(folderLoader, h.RemainingArgs()...) + + case "ca": + arg := h.RemainingArgs() + if len(arg) != 1 { + return nil, h.ArgErr() + } + if acmeIssuer == nil { + acmeIssuer = new(caddytls.ACMEIssuer) + } + acmeIssuer.CA = arg[0] + + case "key_type": + arg := h.RemainingArgs() + if len(arg) != 1 { + return nil, h.ArgErr() + } + keyType = arg[0] + + case "eab": + arg := h.RemainingArgs() + if len(arg) != 2 { + return nil, h.ArgErr() + } + if acmeIssuer == nil { + acmeIssuer = new(caddytls.ACMEIssuer) + } + acmeIssuer.ExternalAccount = &acme.EAB{ + KeyID: arg[0], + MACKey: arg[1], + } + + case "issuer": + if !h.NextArg() { + return nil, h.ArgErr() + } + modName := h.Val() + modID := "tls.issuance." + modName + unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID) + if err != nil { + return nil, err + } + issuer, ok := unm.(certmagic.Issuer) + if !ok { + return nil, h.Errf("module %s (%T) is not a certmagic.Issuer", modID, unm) + } + issuers = append(issuers, issuer) + + case "get_certificate": + if !h.NextArg() { + return nil, h.ArgErr() + } + modName := h.Val() + modID := "tls.get_certificate." + modName + unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID) + if err != nil { + return nil, err + } + certManager, ok := unm.(certmagic.Manager) + if !ok { + return nil, h.Errf("module %s (%T) is not a certmagic.CertificateManager", modID, unm) + } + certManagers = append(certManagers, certManager) + + case "dns": + if !h.NextArg() { + return nil, h.ArgErr() + } + provName := h.Val() + if acmeIssuer == nil { + acmeIssuer = new(caddytls.ACMEIssuer) + } + if acmeIssuer.Challenges == nil { + acmeIssuer.Challenges = new(caddytls.ChallengesConfig) + } + if acmeIssuer.Challenges.DNS == nil { + acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig) + } + modID := "dns.providers." + provName + unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID) + if err != nil { + return nil, err + } + acmeIssuer.Challenges.DNS.ProviderRaw = caddyconfig.JSONModuleObject(unm, "name", provName, h.warnings) + + case "resolvers": + args := h.RemainingArgs() + if len(args) == 0 { + return nil, h.ArgErr() + } + if acmeIssuer == nil { + acmeIssuer = new(caddytls.ACMEIssuer) + } + if acmeIssuer.Challenges == nil { + acmeIssuer.Challenges = new(caddytls.ChallengesConfig) + } + if acmeIssuer.Challenges.DNS == nil { + acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig) + } + acmeIssuer.Challenges.DNS.Resolvers = args + + case "propagation_delay": + arg := h.RemainingArgs() + if len(arg) != 1 { + return nil, h.ArgErr() + } + delayStr := arg[0] + delay, err := caddy.ParseDuration(delayStr) + if err != nil { + return nil, h.Errf("invalid propagation_delay duration %s: %v", delayStr, err) + } + if acmeIssuer == nil { + acmeIssuer = new(caddytls.ACMEIssuer) + } + if acmeIssuer.Challenges == nil { + acmeIssuer.Challenges = new(caddytls.ChallengesConfig) + } + if acmeIssuer.Challenges.DNS == nil { + acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig) + } + acmeIssuer.Challenges.DNS.PropagationDelay = caddy.Duration(delay) + + case "propagation_timeout": + arg := h.RemainingArgs() + if len(arg) != 1 { + return nil, h.ArgErr() + } + timeoutStr := arg[0] + var timeout time.Duration + if timeoutStr == "-1" { + timeout = time.Duration(-1) + } else { + var err error + timeout, err = caddy.ParseDuration(timeoutStr) + if err != nil { + return nil, h.Errf("invalid propagation_timeout duration %s: %v", timeoutStr, err) + } + } + if acmeIssuer == nil { + acmeIssuer = new(caddytls.ACMEIssuer) + } + if acmeIssuer.Challenges == nil { + acmeIssuer.Challenges = new(caddytls.ChallengesConfig) + } + if acmeIssuer.Challenges.DNS == nil { + acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig) + } + acmeIssuer.Challenges.DNS.PropagationTimeout = caddy.Duration(timeout) + + case "dns_ttl": + arg := h.RemainingArgs() + if len(arg) != 1 { + return nil, h.ArgErr() + } + ttlStr := arg[0] + ttl, err := caddy.ParseDuration(ttlStr) + if err != nil { + return nil, h.Errf("invalid dns_ttl duration %s: %v", ttlStr, err) + } + if acmeIssuer == nil { + acmeIssuer = new(caddytls.ACMEIssuer) + } + if acmeIssuer.Challenges == nil { + acmeIssuer.Challenges = new(caddytls.ChallengesConfig) + } + if acmeIssuer.Challenges.DNS == nil { + acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig) + } + acmeIssuer.Challenges.DNS.TTL = caddy.Duration(ttl) + + case "dns_challenge_override_domain": + arg := h.RemainingArgs() + if len(arg) != 1 { + return nil, h.ArgErr() + } + if acmeIssuer == nil { + acmeIssuer = new(caddytls.ACMEIssuer) + } + if acmeIssuer.Challenges == nil { + acmeIssuer.Challenges = new(caddytls.ChallengesConfig) + } + if acmeIssuer.Challenges.DNS == nil { + acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig) + } + acmeIssuer.Challenges.DNS.OverrideDomain = arg[0] + + case "ca_root": + arg := h.RemainingArgs() + if len(arg) != 1 { + return nil, h.ArgErr() + } + if acmeIssuer == nil { + acmeIssuer = new(caddytls.ACMEIssuer) + } + acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, arg[0]) + + case "on_demand": + if h.NextArg() { + return nil, h.ArgErr() + } + onDemand = true + + case "reuse_private_keys": + if h.NextArg() { + return nil, h.ArgErr() + } + reusePrivateKeys = true + + case "insecure_secrets_log": + if !h.NextArg() { + return nil, h.ArgErr() + } + cp.InsecureSecretsLog = h.Val() + + default: + return nil, h.Errf("unknown subdirective: %s", h.Val()) + } + } + + // a naked tls directive is not allowed + if len(firstLine) == 0 && !hasBlock { + return nil, h.ArgErr() + } + + // begin building the final config values + configVals := []ConfigValue{} + + // certificate loaders + if len(fileLoader) > 0 { + configVals = append(configVals, ConfigValue{ + Class: "tls.cert_loader", + Value: fileLoader, + }) + } + if len(folderLoader) > 0 { + configVals = append(configVals, ConfigValue{ + Class: "tls.cert_loader", + Value: folderLoader, + }) + } + + // some tls subdirectives are shortcuts that implicitly configure issuers, and the + // user can also configure issuers explicitly using the issuer subdirective; the + // logic to support both would likely be complex, or at least unintuitive + if len(issuers) > 0 && (acmeIssuer != nil || internalIssuer != nil) { + return nil, h.Err("cannot mix issuer subdirective (explicit issuers) with other issuer-specific subdirectives (implicit issuers)") + } + if acmeIssuer != nil && internalIssuer != nil { + return nil, h.Err("cannot create both ACME and internal certificate issuers") + } + + // now we should either have: explicitly-created issuers, or an implicitly-created + // ACME or internal issuer, or no issuers at all + switch { + case len(issuers) > 0: + for _, issuer := range issuers { + configVals = append(configVals, ConfigValue{ + Class: "tls.cert_issuer", + Value: issuer, + }) + } + + case acmeIssuer != nil: + // implicit ACME issuers (from various subdirectives) - use defaults; there might be more than one + defaultIssuers := caddytls.DefaultIssuers(acmeIssuer.Email) + + // if an ACME CA endpoint was set, the user expects to use that specific one, + // not any others that may be defaults, so replace all defaults with that ACME CA + if acmeIssuer.CA != "" { + defaultIssuers = []certmagic.Issuer{acmeIssuer} + } + + for _, issuer := range defaultIssuers { + // apply settings from the implicitly-configured ACMEIssuer to any + // default ACMEIssuers, but preserve each default issuer's CA endpoint, + // because, for example, if you configure the DNS challenge, it should + // apply to any of the default ACMEIssuers, but you don't want to trample + // out their unique CA endpoints + if iss, ok := issuer.(*caddytls.ACMEIssuer); ok && iss != nil { + acmeCopy := *acmeIssuer + acmeCopy.CA = iss.CA + issuer = &acmeCopy + } + configVals = append(configVals, ConfigValue{ + Class: "tls.cert_issuer", + Value: issuer, + }) + } + + case internalIssuer != nil: + configVals = append(configVals, ConfigValue{ + Class: "tls.cert_issuer", + Value: internalIssuer, + }) + } + + // certificate key type + if keyType != "" { + configVals = append(configVals, ConfigValue{ + Class: "tls.key_type", + Value: keyType, + }) + } + + // on-demand TLS + if onDemand { + configVals = append(configVals, ConfigValue{ + Class: "tls.on_demand", + Value: true, + }) + } + for _, certManager := range certManagers { + configVals = append(configVals, ConfigValue{ + Class: "tls.cert_manager", + Value: certManager, + }) + } + + // reuse private keys TLS + if reusePrivateKeys { + configVals = append(configVals, ConfigValue{ + Class: "tls.reuse_private_keys", + Value: true, + }) + } + + // if enabled, the names in the site addresses will be + // added to the automation policies + if forceAutomate { + configVals = append(configVals, ConfigValue{ + Class: "tls.force_automate", + Value: true, + }) + } + + // custom certificate selection + if len(certSelector.AnyTag) > 0 { + cp.CertSelection = &certSelector + } + + // connection policy -- always add one, to ensure that TLS + // is enabled, because this directive was used (this is + // needed, for instance, when a site block has a key of + // just ":5000" - i.e. no hostname, and only on-demand TLS + // is enabled) + configVals = append(configVals, ConfigValue{ + Class: "tls.connection_policy", + Value: cp, + }) + + return configVals, nil +} + +// parseRoot parses the root directive. Syntax: +// +// root [] +func parseRoot(h Helper) ([]ConfigValue, error) { + h.Next() // consume directive name + + // count the tokens to determine what to do + argsCount := h.CountRemainingArgs() + if argsCount == 0 { + return nil, h.Errf("too few arguments; must have at least a root path") + } + if argsCount > 2 { + return nil, h.Errf("too many arguments; should only be a matcher and a path") + } + + // with only one arg, assume it's a root path with no matcher token + if argsCount == 1 { + if !h.NextArg() { + return nil, h.ArgErr() + } + return h.NewRoute(nil, caddyhttp.VarsMiddleware{"root": h.Val()}), nil + } + + // parse the matcher token into a matcher set + userMatcherSet, err := h.ExtractMatcherSet() + if err != nil { + return nil, err + } + h.Next() // consume directive name again, matcher parsing does a reset + + // advance to the root path + if !h.NextArg() { + return nil, h.ArgErr() + } + // make the route with the matcher + return h.NewRoute(userMatcherSet, caddyhttp.VarsMiddleware{"root": h.Val()}), nil +} + +// parseFilesystem parses the fs directive. Syntax: +// +// fs +func parseFilesystem(h Helper) (caddyhttp.MiddlewareHandler, error) { + h.Next() // consume directive name + if !h.NextArg() { + return nil, h.ArgErr() + } + if h.NextArg() { + return nil, h.ArgErr() + } + return caddyhttp.VarsMiddleware{"fs": h.Val()}, nil +} + +// parseVars parses the vars directive. See its UnmarshalCaddyfile method for syntax. +func parseVars(h Helper) (caddyhttp.MiddlewareHandler, error) { + v := new(caddyhttp.VarsMiddleware) + err := v.UnmarshalCaddyfile(h.Dispenser) + return v, err +} + +// parseRedir parses the redir directive. Syntax: +// +// redir [] [] +// +// can be "permanent" for 301, "temporary" for 302 (default), +// a placeholder, or any number in the 3xx range or 401. The special +// code "html" can be used to redirect only browser clients (will +// respond with HTTP 200 and no Location header; redirect is performed +// with JS and a meta tag). +func parseRedir(h Helper) (caddyhttp.MiddlewareHandler, error) { + h.Next() // consume directive name + if !h.NextArg() { + return nil, h.ArgErr() + } + to := h.Val() + + var code string + if h.NextArg() { + code = h.Val() + } + + var body string + var hdr http.Header + switch code { + case "permanent": + code = "301" + + case "temporary", "": + code = "302" + + case "html": + // Script tag comes first since that will better imitate a redirect in the browser's + // history, but the meta tag is a fallback for most non-JS clients. + const metaRedir = ` + + + Redirecting... + + + + Redirecting to %s... + +` + safeTo := html.EscapeString(to) + body = fmt.Sprintf(metaRedir, safeTo, safeTo, safeTo, safeTo) + hdr = http.Header{"Content-Type": []string{"text/html; charset=utf-8"}} + code = "200" // don't redirect non-browser clients + + default: + // Allow placeholders for the code + if strings.HasPrefix(code, "{") { + break + } + // Try to validate as an integer otherwise + codeInt, err := strconv.Atoi(code) + if err != nil { + return nil, h.Errf("Not a supported redir code type or not valid integer: '%s'", code) + } + // Sometimes, a 401 with Location header is desirable because + // requests made with XHR will "eat" the 3xx redirect; so if + // the intent was to redirect to an auth page, a 3xx won't + // work. Responding with 401 allows JS code to read the + // Location header and do a window.location redirect manually. + // see https://stackoverflow.com/a/2573589/846934 + // see https://github.com/oauth2-proxy/oauth2-proxy/issues/1522 + if codeInt < 300 || (codeInt > 399 && codeInt != 401) { + return nil, h.Errf("Redir code not in the 3xx range or 401: '%v'", codeInt) + } + } + + // don't redirect non-browser clients + if code != "200" { + hdr = http.Header{"Location": []string{to}} + } + + return caddyhttp.StaticResponse{ + StatusCode: caddyhttp.WeakString(code), + Headers: hdr, + Body: body, + }, nil +} + +// parseRespond parses the respond directive. +func parseRespond(h Helper) (caddyhttp.MiddlewareHandler, error) { + sr := new(caddyhttp.StaticResponse) + err := sr.UnmarshalCaddyfile(h.Dispenser) + return sr, err +} + +// parseAbort parses the abort directive. +func parseAbort(h Helper) (caddyhttp.MiddlewareHandler, error) { + h.Next() // consume directive + for h.Next() || h.NextBlock(0) { + return nil, h.ArgErr() + } + return &caddyhttp.StaticResponse{Abort: true}, nil +} + +// parseError parses the error directive. +func parseError(h Helper) (caddyhttp.MiddlewareHandler, error) { + se := new(caddyhttp.StaticError) + err := se.UnmarshalCaddyfile(h.Dispenser) + return se, err +} + +// parseRoute parses the route directive. +func parseRoute(h Helper) (caddyhttp.MiddlewareHandler, error) { + allResults, err := parseSegmentAsConfig(h) + if err != nil { + return nil, err + } + + for _, result := range allResults { + switch result.Value.(type) { + case caddyhttp.Route, caddyhttp.Subroute: + default: + return nil, h.Errf("%s directive returned something other than an HTTP route or subroute: %#v (only handler directives can be used in routes)", result.directive, result.Value) + } + } + + return buildSubroute(allResults, h.groupCounter, false) +} + +func parseHandle(h Helper) (caddyhttp.MiddlewareHandler, error) { + return ParseSegmentAsSubroute(h) +} + +func parseHandleErrors(h Helper) ([]ConfigValue, error) { + h.Next() // consume directive name + + expression := "" + args := h.RemainingArgs() + if len(args) > 0 { + codes := []string{} + for _, val := range args { + if len(val) != 3 { + return nil, h.Errf("bad status value '%s'", val) + } + if strings.HasSuffix(val, "xx") { + val = val[:1] + _, err := strconv.Atoi(val) + if err != nil { + return nil, h.Errf("bad status value '%s': %v", val, err) + } + if expression != "" { + expression += " || " + } + expression += fmt.Sprintf("{http.error.status_code} >= %s00 && {http.error.status_code} <= %s99", val, val) + continue + } + _, err := strconv.Atoi(val) + if err != nil { + return nil, h.Errf("bad status value '%s': %v", val, err) + } + codes = append(codes, val) + } + if len(codes) > 0 { + if expression != "" { + expression += " || " + } + expression += "{http.error.status_code} in [" + strings.Join(codes, ", ") + "]" + } + // Reset cursor position to get ready for ParseSegmentAsSubroute + h.Reset() + h.Next() + h.RemainingArgs() + h.Prev() + } else { + // If no arguments present reset the cursor position to get ready for ParseSegmentAsSubroute + h.Prev() + } + + handler, err := ParseSegmentAsSubroute(h) + if err != nil { + return nil, err + } + subroute, ok := handler.(*caddyhttp.Subroute) + if !ok { + return nil, h.Errf("segment was not parsed as a subroute") + } + + if expression != "" { + statusMatcher := caddy.ModuleMap{ + "expression": h.JSON(caddyhttp.MatchExpression{Expr: expression}), + } + for i := range subroute.Routes { + subroute.Routes[i].MatcherSetsRaw = []caddy.ModuleMap{statusMatcher} + } + } + return []ConfigValue{ + { + Class: "error_route", + Value: subroute, + }, + }, nil +} + +// parseInvoke parses the invoke directive. +func parseInvoke(h Helper) (caddyhttp.MiddlewareHandler, error) { + h.Next() // consume directive + if !h.NextArg() { + return nil, h.ArgErr() + } + for h.Next() || h.NextBlock(0) { + return nil, h.ArgErr() + } + + // remember that we're invoking this name + // to populate the server with these named routes + if h.State[namedRouteKey] == nil { + h.State[namedRouteKey] = map[string]struct{}{} + } + h.State[namedRouteKey].(map[string]struct{})[h.Val()] = struct{}{} + + // return the handler + return &caddyhttp.Invoke{Name: h.Val()}, nil +} + +// parseLog parses the log directive. Syntax: +// +// log { +// hostnames +// output ... +// core ... +// format ... +// level +// } +func parseLog(h Helper) ([]ConfigValue, error) { + return parseLogHelper(h, nil) +} + +// parseLogHelper is used both for the parseLog directive within Server Blocks, +// as well as the global "log" option for configuring loggers at the global +// level. The parseAsGlobalOption parameter is used to distinguish any differing logic +// between the two. +func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue, error) { + h.Next() // consume option name + + // When the globalLogNames parameter is passed in, we make + // modifications to the parsing behavior. + parseAsGlobalOption := globalLogNames != nil + + var configValues []ConfigValue + + // Logic below expects that a name is always present when a + // global option is being parsed; or an optional override + // is supported for access logs. + var logName string + + if parseAsGlobalOption { + if h.NextArg() { + logName = h.Val() + + // Only a single argument is supported. + if h.NextArg() { + return nil, h.ArgErr() + } + } else { + // If there is no log name specified, we + // reference the default logger. See the + // setupNewDefault function in the logging + // package for where this is configured. + logName = caddy.DefaultLoggerName + } + + // Verify this name is unused. + _, used := globalLogNames[logName] + if used { + return nil, h.Err("duplicate global log option for: " + logName) + } + globalLogNames[logName] = struct{}{} + } else { + // An optional override of the logger name can be provided; + // otherwise a default will be used, like "log0", "log1", etc. + if h.NextArg() { + logName = h.Val() + + // Only a single argument is supported. + if h.NextArg() { + return nil, h.ArgErr() + } + } + } + + cl := new(caddy.CustomLog) + + // allow overriding the current site block's hostnames for this logger; + // this is useful for setting up loggers per subdomain in a site block + // with a wildcard domain + customHostnames := []string{} + noHostname := false + for h.NextBlock(0) { + switch h.Val() { + case "hostnames": + if parseAsGlobalOption { + return nil, h.Err("hostnames is not allowed in the log global options") + } + args := h.RemainingArgs() + if len(args) == 0 { + return nil, h.ArgErr() + } + customHostnames = append(customHostnames, args...) + + case "output": + if !h.NextArg() { + return nil, h.ArgErr() + } + moduleName := h.Val() + + // can't use the usual caddyfile.Unmarshaler flow with the + // standard writers because they are in the caddy package + // (because they are the default) and implementing that + // interface there would unfortunately create circular import + var wo caddy.WriterOpener + switch moduleName { + case "stdout": + wo = caddy.StdoutWriter{} + case "stderr": + wo = caddy.StderrWriter{} + case "discard": + wo = caddy.DiscardWriter{} + default: + modID := "caddy.logging.writers." + moduleName + unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID) + if err != nil { + return nil, err + } + var ok bool + wo, ok = unm.(caddy.WriterOpener) + if !ok { + return nil, h.Errf("module %s (%T) is not a WriterOpener", modID, unm) + } + } + cl.WriterRaw = caddyconfig.JSONModuleObject(wo, "output", moduleName, h.warnings) + + case "sampling": + d := h.Dispenser.NewFromNextSegment() + for d.NextArg() { + // consume any tokens on the same line, if any. + } + + sampling := &caddy.LogSampling{} + for nesting := d.Nesting(); d.NextBlock(nesting); { + subdir := d.Val() + switch subdir { + case "interval": + if !d.NextArg() { + return nil, d.ArgErr() + } + interval, err := time.ParseDuration(d.Val() + "ns") + if err != nil { + return nil, d.Errf("failed to parse interval: %v", err) + } + sampling.Interval = interval + case "first": + if !d.NextArg() { + return nil, d.ArgErr() + } + first, err := strconv.Atoi(d.Val()) + if err != nil { + return nil, d.Errf("failed to parse first: %v", err) + } + sampling.First = first + case "thereafter": + if !d.NextArg() { + return nil, d.ArgErr() + } + thereafter, err := strconv.Atoi(d.Val()) + if err != nil { + return nil, d.Errf("failed to parse thereafter: %v", err) + } + sampling.Thereafter = thereafter + default: + return nil, d.Errf("unrecognized subdirective: %s", subdir) + } + } + + cl.Sampling = sampling + + case "core": + if !h.NextArg() { + return nil, h.ArgErr() + } + moduleName := h.Val() + moduleID := "caddy.logging.cores." + moduleName + unm, err := caddyfile.UnmarshalModule(h.Dispenser, moduleID) + if err != nil { + return nil, err + } + core, ok := unm.(zapcore.Core) + if !ok { + return nil, h.Errf("module %s (%T) is not a zapcore.Core", moduleID, unm) + } + cl.CoreRaw = caddyconfig.JSONModuleObject(core, "module", moduleName, h.warnings) + + case "format": + if !h.NextArg() { + return nil, h.ArgErr() + } + moduleName := h.Val() + moduleID := "caddy.logging.encoders." + moduleName + unm, err := caddyfile.UnmarshalModule(h.Dispenser, moduleID) + if err != nil { + return nil, err + } + enc, ok := unm.(zapcore.Encoder) + if !ok { + return nil, h.Errf("module %s (%T) is not a zapcore.Encoder", moduleID, unm) + } + cl.EncoderRaw = caddyconfig.JSONModuleObject(enc, "format", moduleName, h.warnings) + + case "level": + if !h.NextArg() { + return nil, h.ArgErr() + } + cl.Level = h.Val() + if h.NextArg() { + return nil, h.ArgErr() + } + + case "include": + if !parseAsGlobalOption { + return nil, h.Err("include is not allowed in the log directive") + } + for h.NextArg() { + cl.Include = append(cl.Include, h.Val()) + } + + case "exclude": + if !parseAsGlobalOption { + return nil, h.Err("exclude is not allowed in the log directive") + } + for h.NextArg() { + cl.Exclude = append(cl.Exclude, h.Val()) + } + + case "no_hostname": + if h.NextArg() { + return nil, h.ArgErr() + } + noHostname = true + + default: + return nil, h.Errf("unrecognized subdirective: %s", h.Val()) + } + } + + var val namedCustomLog + val.hostnames = customHostnames + val.noHostname = noHostname + isEmptyConfig := reflect.DeepEqual(cl, new(caddy.CustomLog)) + + // Skip handling of empty logging configs + + if parseAsGlobalOption { + // Use indicated name for global log options + val.name = logName + } else { + if logName != "" { + val.name = logName + } else if !isEmptyConfig { + // Construct a log name for server log streams + logCounter, ok := h.State["logCounter"].(int) + if !ok { + logCounter = 0 + } + val.name = fmt.Sprintf("log%d", logCounter) + logCounter++ + h.State["logCounter"] = logCounter + } + if val.name != "" { + cl.Include = []string{"http.log.access." + val.name} + } + } + if !isEmptyConfig { + val.log = cl + } + configValues = append(configValues, ConfigValue{ + Class: "custom_log", + Value: val, + }) + return configValues, nil +} + +// parseLogSkip parses the log_skip directive. Syntax: +// +// log_skip [] +func parseLogSkip(h Helper) (caddyhttp.MiddlewareHandler, error) { + h.Next() // consume directive name + + // "skip_log" is deprecated, replaced by "log_skip" + if h.Val() == "skip_log" { + caddy.Log().Named("config.adapter.caddyfile").Warn("the 'skip_log' directive is deprecated, please use 'log_skip' instead!") + } + + if h.NextArg() { + return nil, h.ArgErr() + } + return caddyhttp.VarsMiddleware{"log_skip": true}, nil +} + +// parseLogName parses the log_name directive. Syntax: +// +// log_name +func parseLogName(h Helper) (caddyhttp.MiddlewareHandler, error) { + h.Next() // consume directive name + return caddyhttp.VarsMiddleware{ + caddyhttp.AccessLoggerNameVarKey: h.RemainingArgs(), + }, nil +} diff --git a/caddyconfig/httpcaddyfile/builtins_test.go b/caddyconfig/httpcaddyfile/builtins_test.go new file mode 100644 index 00000000000..c23531f22e6 --- /dev/null +++ b/caddyconfig/httpcaddyfile/builtins_test.go @@ -0,0 +1,369 @@ +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, + }, + { + input: `:8080 { + log { + sampling { + interval 2 + first 3 + thereafter 4 + } + } + } + `, + output: `{"logging":{"logs":{"default":{"exclude":["http.log.access.log0"]},"log0":{"sampling":{"interval":2,"first":3,"thereafter":4},"include":["http.log.access.log0"]}}},"apps":{"http":{"servers":{"srv0":{"listen":[":8080"],"logs":{"default_logger_name":"log0"}}}}}}`, + 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 + } + } +} diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go new file mode 100644 index 00000000000..f0687a7e937 --- /dev/null +++ b/caddyconfig/httpcaddyfile/directives.go @@ -0,0 +1,640 @@ +// 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" + "slices" + "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 + +// 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 slices.Contains(directiveOrder, 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 := slices.Contains(defaultDirectiveOrder, standardDir) + 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 + parsedKeys []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.parsedKeys { + 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.parsedKeys { + 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 { + return slices.ContainsFunc(sb.parsedKeys, func(addr Address) bool { + return addr.Host == "" + }) +} + +// isAllHTTP returns true if all sb keys explicitly specify +// the http:// scheme +func (sb serverBlock) isAllHTTP() bool { + return !slices.ContainsFunc(sb.parsedKeys, func(addr Address) bool { + return addr.Scheme != "http" + }) +} + +// 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) diff --git a/caddyconfig/httpcaddyfile/directives_test.go b/caddyconfig/httpcaddyfile/directives_test.go new file mode 100644 index 00000000000..2b4d3e6c370 --- /dev/null +++ b/caddyconfig/httpcaddyfile/directives_test.go @@ -0,0 +1,97 @@ +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{parsedKeys: 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) + } + } +} diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go new file mode 100644 index 00000000000..37a6f6b23cd --- /dev/null +++ b/caddyconfig/httpcaddyfile/httptype.go @@ -0,0 +1,1748 @@ +// 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 ( + "cmp" + "encoding/json" + "fmt" + "net" + "reflect" + "slices" + "sort" + "strconv" + "strings" + + "go.uber.org/zap" + + "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" + "github.com/caddyserver/caddy/v2/modules/caddypki" + "github.com/caddyserver/caddy/v2/modules/caddytls" +) + +func init() { + caddyconfig.RegisterAdapter("caddyfile", caddyfile.Adapter{ServerType: ServerType{}}) +} + +// App represents the configuration for a non-standard +// Caddy app module (e.g. third-party plugin) which was +// parsed from a global options block. +type App struct { + // The JSON key for the app being configured + Name string + + // The raw app config as JSON + Value json.RawMessage +} + +// ServerType can set up a config from an HTTP Caddyfile. +type ServerType struct{} + +// Setup makes a config from the tokens. +func (st ServerType) Setup( + inputServerBlocks []caddyfile.ServerBlock, + options map[string]any, +) (*caddy.Config, []caddyconfig.Warning, error) { + var warnings []caddyconfig.Warning + gc := counter{new(int)} + state := make(map[string]any) + + // load all the server blocks and associate them with a "pile" of config values + originalServerBlocks := make([]serverBlock, 0, len(inputServerBlocks)) + for _, sblock := range inputServerBlocks { + for j, k := range sblock.Keys { + if j == 0 && strings.HasPrefix(k.Text, "@") { + return nil, warnings, fmt.Errorf("%s:%d: cannot define a matcher outside of a site block: '%s'", k.File, k.Line, k.Text) + } + if _, ok := registeredDirectives[k.Text]; ok { + return nil, warnings, fmt.Errorf("%s:%d: parsed '%s' as a site address, but it is a known directive; directives must appear in a site block", k.File, k.Line, k.Text) + } + } + originalServerBlocks = append(originalServerBlocks, serverBlock{ + block: sblock, + pile: make(map[string][]ConfigValue), + }) + } + + // apply any global options + var err error + originalServerBlocks, err = st.evaluateGlobalOptionsBlock(originalServerBlocks, options) + if err != nil { + return nil, warnings, err + } + + // this will replace both static and user-defined placeholder shorthands + // with actual identifiers used by Caddy + replacer := NewShorthandReplacer() + + originalServerBlocks, err = st.extractNamedRoutes(originalServerBlocks, options, &warnings, replacer) + if err != nil { + return nil, warnings, err + } + + for _, sb := range originalServerBlocks { + for i := range sb.block.Segments { + replacer.ApplyToSegment(&sb.block.Segments[i]) + } + + if len(sb.block.Keys) == 0 { + return nil, warnings, fmt.Errorf("server block without any key is global configuration, and if used, it must be first") + } + + // extract matcher definitions + matcherDefs := make(map[string]caddy.ModuleMap) + for _, segment := range sb.block.Segments { + if dir := segment.Directive(); strings.HasPrefix(dir, matcherPrefix) { + d := sb.block.DispenseDirective(dir) + err := parseMatcherDefinitions(d, matcherDefs) + if err != nil { + return nil, warnings, err + } + } + } + + // evaluate each directive ("segment") in this block + for _, segment := range sb.block.Segments { + dir := segment.Directive() + + if strings.HasPrefix(dir, matcherPrefix) { + // matcher definitions were pre-processed + continue + } + + dirFunc, ok := registeredDirectives[dir] + if !ok { + tkn := segment[0] + message := "%s:%d: unrecognized directive: %s" + if !sb.block.HasBraces { + message += "\nDid you mean to define a second site? If so, you must use curly braces around each site to separate their configurations." + } + return nil, warnings, fmt.Errorf(message, tkn.File, tkn.Line, dir) + } + + h := Helper{ + Dispenser: caddyfile.NewDispenser(segment), + options: options, + warnings: &warnings, + matcherDefs: matcherDefs, + parentBlock: sb.block, + groupCounter: gc, + State: state, + } + + results, err := dirFunc(h) + if err != nil { + return nil, warnings, fmt.Errorf("parsing caddyfile tokens for '%s': %v", dir, err) + } + + dir = normalizeDirectiveName(dir) + + for _, result := range results { + result.directive = dir + sb.pile[result.Class] = append(sb.pile[result.Class], result) + } + + // specially handle named routes that were pulled out from + // the invoke directive, which could be nested anywhere within + // some subroutes in this directive; we add them to the pile + // for this server block + if state[namedRouteKey] != nil { + for name := range state[namedRouteKey].(map[string]struct{}) { + result := ConfigValue{Class: namedRouteKey, Value: name} + sb.pile[result.Class] = append(sb.pile[result.Class], result) + } + state[namedRouteKey] = nil + } + } + } + + // map + sbmap, err := st.mapAddressToProtocolToServerBlocks(originalServerBlocks, options) + if err != nil { + return nil, warnings, err + } + + // reduce + pairings := st.consolidateAddrMappings(sbmap) + + // each pairing of listener addresses to list of server + // blocks is basically a server definition + servers, err := st.serversFromPairings(pairings, options, &warnings, gc) + if err != nil { + return nil, warnings, err + } + + // hoist the metrics config from per-server to global + metrics, _ := options["metrics"].(*caddyhttp.Metrics) + for _, s := range servers { + if s.Metrics != nil { + metrics = cmp.Or[*caddyhttp.Metrics](metrics, &caddyhttp.Metrics{}) + metrics = &caddyhttp.Metrics{ + PerHost: metrics.PerHost || s.Metrics.PerHost, + } + s.Metrics = nil // we don't need it anymore + } + } + + // now that each server is configured, make the HTTP app + httpApp := caddyhttp.App{ + HTTPPort: tryInt(options["http_port"], &warnings), + HTTPSPort: tryInt(options["https_port"], &warnings), + GracePeriod: tryDuration(options["grace_period"], &warnings), + ShutdownDelay: tryDuration(options["shutdown_delay"], &warnings), + Metrics: metrics, + Servers: servers, + } + + // then make the TLS app + tlsApp, warnings, err := st.buildTLSApp(pairings, options, warnings) + if err != nil { + return nil, warnings, err + } + + // then make the PKI app + pkiApp, warnings, err := st.buildPKIApp(pairings, options, warnings) + if err != nil { + return nil, warnings, err + } + + // extract any custom logs, and enforce configured levels + var customLogs []namedCustomLog + var hasDefaultLog bool + addCustomLog := func(ncl namedCustomLog) { + if ncl.name == "" { + return + } + if ncl.name == caddy.DefaultLoggerName { + hasDefaultLog = true + } + if _, ok := options["debug"]; ok && ncl.log != nil && ncl.log.Level == "" { + ncl.log.Level = zap.DebugLevel.CapitalString() + } + customLogs = append(customLogs, ncl) + } + + // Apply global log options, when set + if options["log"] != nil { + for _, logValue := range options["log"].([]ConfigValue) { + addCustomLog(logValue.Value.(namedCustomLog)) + } + } + + if !hasDefaultLog { + // if the default log was not customized, ensure we + // configure it with any applicable options + if _, ok := options["debug"]; ok { + customLogs = append(customLogs, namedCustomLog{ + name: caddy.DefaultLoggerName, + log: &caddy.CustomLog{ + BaseLog: caddy.BaseLog{Level: zap.DebugLevel.CapitalString()}, + }, + }) + } + } + + // Apply server-specific log options + for _, p := range pairings { + for _, sb := range p.serverBlocks { + for _, clVal := range sb.pile["custom_log"] { + addCustomLog(clVal.Value.(namedCustomLog)) + } + } + } + + // annnd the top-level config, then we're done! + cfg := &caddy.Config{AppsRaw: make(caddy.ModuleMap)} + + // loop through the configured options, and if any of + // them are an httpcaddyfile App, then we insert them + // into the config as raw Caddy apps + for _, opt := range options { + if app, ok := opt.(App); ok { + cfg.AppsRaw[app.Name] = app.Value + } + } + + // insert the standard Caddy apps into the config + if len(httpApp.Servers) > 0 { + cfg.AppsRaw["http"] = caddyconfig.JSON(httpApp, &warnings) + } + if !reflect.DeepEqual(tlsApp, &caddytls.TLS{CertificatesRaw: make(caddy.ModuleMap)}) { + cfg.AppsRaw["tls"] = caddyconfig.JSON(tlsApp, &warnings) + } + if !reflect.DeepEqual(pkiApp, &caddypki.PKI{CAs: make(map[string]*caddypki.CA)}) { + cfg.AppsRaw["pki"] = caddyconfig.JSON(pkiApp, &warnings) + } + if filesystems, ok := options["filesystem"].(caddy.Module); ok { + cfg.AppsRaw["caddy.filesystems"] = caddyconfig.JSON( + filesystems, + &warnings) + } + + if storageCvtr, ok := options["storage"].(caddy.StorageConverter); ok { + cfg.StorageRaw = caddyconfig.JSONModuleObject(storageCvtr, + "module", + storageCvtr.(caddy.Module).CaddyModule().ID.Name(), + &warnings) + } + if adminConfig, ok := options["admin"].(*caddy.AdminConfig); ok && adminConfig != nil { + cfg.Admin = adminConfig + } + if pc, ok := options["persist_config"].(string); ok && pc == "off" { + if cfg.Admin == nil { + cfg.Admin = new(caddy.AdminConfig) + } + if cfg.Admin.Config == nil { + cfg.Admin.Config = new(caddy.ConfigSettings) + } + cfg.Admin.Config.Persist = new(bool) + } + + if len(customLogs) > 0 { + if cfg.Logging == nil { + cfg.Logging = &caddy.Logging{ + Logs: make(map[string]*caddy.CustomLog), + } + } + + // Add the default log first if defined, so that it doesn't + // accidentally get re-created below due to the Exclude logic + for _, ncl := range customLogs { + if ncl.name == caddy.DefaultLoggerName && ncl.log != nil { + cfg.Logging.Logs[caddy.DefaultLoggerName] = ncl.log + break + } + } + + // Add the rest of the custom logs + for _, ncl := range customLogs { + if ncl.log == nil || ncl.name == caddy.DefaultLoggerName { + continue + } + if ncl.name != "" { + cfg.Logging.Logs[ncl.name] = ncl.log + } + // most users seem to prefer not writing access logs + // to the default log when they are directed to a + // file or have any other special customization + if ncl.name != caddy.DefaultLoggerName && len(ncl.log.Include) > 0 { + defaultLog, ok := cfg.Logging.Logs[caddy.DefaultLoggerName] + if !ok { + defaultLog = new(caddy.CustomLog) + cfg.Logging.Logs[caddy.DefaultLoggerName] = defaultLog + } + defaultLog.Exclude = append(defaultLog.Exclude, ncl.log.Include...) + + // avoid duplicates by sorting + compacting + sort.Strings(defaultLog.Exclude) + defaultLog.Exclude = slices.Compact[[]string, string](defaultLog.Exclude) + } + } + // we may have not actually added anything, so remove if empty + if len(cfg.Logging.Logs) == 0 { + cfg.Logging = nil + } + } + + return cfg, warnings, nil +} + +// evaluateGlobalOptionsBlock evaluates the global options block, +// which is expected to be the first server block if it has zero +// keys. It returns the updated list of server blocks with the +// global options block removed, and updates options accordingly. +func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options map[string]any) ([]serverBlock, error) { + if len(serverBlocks) == 0 || len(serverBlocks[0].block.Keys) > 0 { + return serverBlocks, nil + } + + for _, segment := range serverBlocks[0].block.Segments { + opt := segment.Directive() + var val any + var err error + disp := caddyfile.NewDispenser(segment) + + optFunc, ok := registeredGlobalOptions[opt] + if !ok { + tkn := segment[0] + return nil, fmt.Errorf("%s:%d: unrecognized global option: %s", tkn.File, tkn.Line, opt) + } + + val, err = optFunc(disp, options[opt]) + if err != nil { + return nil, fmt.Errorf("parsing caddyfile tokens for '%s': %v", opt, err) + } + + // As a special case, fold multiple "servers" options together + // in an array instead of overwriting a possible existing value + if opt == "servers" { + existingOpts, ok := options[opt].([]serverOptions) + if !ok { + existingOpts = []serverOptions{} + } + serverOpts, ok := val.(serverOptions) + if !ok { + return nil, fmt.Errorf("unexpected type from 'servers' global options: %T", val) + } + options[opt] = append(existingOpts, serverOpts) + continue + } + // Additionally, fold multiple "log" options together into an + // array so that multiple loggers can be configured. + if opt == "log" { + existingOpts, ok := options[opt].([]ConfigValue) + if !ok { + existingOpts = []ConfigValue{} + } + logOpts, ok := val.([]ConfigValue) + if !ok { + return nil, fmt.Errorf("unexpected type from 'log' global options: %T", val) + } + options[opt] = append(existingOpts, logOpts...) + continue + } + // Also fold multiple "default_bind" options together into an + // array so that server blocks can have multiple binds by default. + if opt == "default_bind" { + existingOpts, ok := options[opt].([]ConfigValue) + if !ok { + existingOpts = []ConfigValue{} + } + defaultBindOpts, ok := val.([]ConfigValue) + if !ok { + return nil, fmt.Errorf("unexpected type from 'default_bind' global options: %T", val) + } + options[opt] = append(existingOpts, defaultBindOpts...) + continue + } + + options[opt] = val + } + + // If we got "servers" options, we'll sort them by their listener address + if serverOpts, ok := options["servers"].([]serverOptions); ok { + sort.Slice(serverOpts, func(i, j int) bool { + return len(serverOpts[i].ListenerAddress) > len(serverOpts[j].ListenerAddress) + }) + + // Reject the config if there are duplicate listener address + seen := make(map[string]bool) + for _, entry := range serverOpts { + if _, alreadySeen := seen[entry.ListenerAddress]; alreadySeen { + return nil, fmt.Errorf("cannot have 'servers' global options with duplicate listener addresses: %s", entry.ListenerAddress) + } + seen[entry.ListenerAddress] = true + } + } + + return serverBlocks[1:], nil +} + +// extractNamedRoutes pulls out any named route server blocks +// so they don't get parsed as sites, and stores them in options +// for later. +func (ServerType) extractNamedRoutes( + serverBlocks []serverBlock, + options map[string]any, + warnings *[]caddyconfig.Warning, + replacer ShorthandReplacer, +) ([]serverBlock, error) { + namedRoutes := map[string]*caddyhttp.Route{} + + gc := counter{new(int)} + state := make(map[string]any) + + // copy the server blocks so we can + // splice out the named route ones + filtered := append([]serverBlock{}, serverBlocks...) + index := -1 + + for _, sb := range serverBlocks { + index++ + if !sb.block.IsNamedRoute { + continue + } + + // splice out this block, because we know it's not a real server + filtered = append(filtered[:index], filtered[index+1:]...) + index-- + + if len(sb.block.Segments) == 0 { + continue + } + + wholeSegment := caddyfile.Segment{} + for i := range sb.block.Segments { + // replace user-defined placeholder shorthands in extracted named routes + replacer.ApplyToSegment(&sb.block.Segments[i]) + + // zip up all the segments since ParseSegmentAsSubroute + // was designed to take a directive+ + wholeSegment = append(wholeSegment, sb.block.Segments[i]...) + } + + h := Helper{ + Dispenser: caddyfile.NewDispenser(wholeSegment), + options: options, + warnings: warnings, + matcherDefs: nil, + parentBlock: sb.block, + groupCounter: gc, + State: state, + } + + handler, err := ParseSegmentAsSubroute(h) + if err != nil { + return nil, err + } + subroute := handler.(*caddyhttp.Subroute) + route := caddyhttp.Route{} + + if len(subroute.Routes) == 1 && len(subroute.Routes[0].MatcherSetsRaw) == 0 { + // if there's only one route with no matcher, then we can simplify + route.HandlersRaw = append(route.HandlersRaw, subroute.Routes[0].HandlersRaw[0]) + } else { + // otherwise we need the whole subroute + route.HandlersRaw = []json.RawMessage{caddyconfig.JSONModuleObject(handler, "handler", subroute.CaddyModule().ID.Name(), h.warnings)} + } + + namedRoutes[sb.block.GetKeysText()[0]] = &route + } + options["named_routes"] = namedRoutes + + return filtered, nil +} + +// serversFromPairings creates the servers for each pairing of addresses +// to server blocks. Each pairing is essentially a server definition. +func (st *ServerType) serversFromPairings( + pairings []sbAddrAssociation, + options map[string]any, + warnings *[]caddyconfig.Warning, + groupCounter counter, +) (map[string]*caddyhttp.Server, error) { + servers := make(map[string]*caddyhttp.Server) + defaultSNI := tryString(options["default_sni"], warnings) + fallbackSNI := tryString(options["fallback_sni"], warnings) + + httpPort := strconv.Itoa(caddyhttp.DefaultHTTPPort) + if hp, ok := options["http_port"].(int); ok { + httpPort = strconv.Itoa(hp) + } + httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPSPort) + if hsp, ok := options["https_port"].(int); ok { + httpsPort = strconv.Itoa(hsp) + } + autoHTTPS := []string{} + if ah, ok := options["auto_https"].([]string); ok { + autoHTTPS = ah + } + + for i, p := range pairings { + // detect ambiguous site definitions: server blocks which + // have the same host bound to the same interface (listener + // address), otherwise their routes will improperly be added + // to the same server (see issue #4635) + for j, sblock1 := range p.serverBlocks { + for _, key := range sblock1.block.GetKeysText() { + for k, sblock2 := range p.serverBlocks { + if k == j { + continue + } + if slices.Contains(sblock2.block.GetKeysText(), key) { + return nil, fmt.Errorf("ambiguous site definition: %s", key) + } + } + } + } + + var ( + addresses []string + protocols [][]string + ) + + for _, addressWithProtocols := range p.addressesWithProtocols { + addresses = append(addresses, addressWithProtocols.address) + protocols = append(protocols, addressWithProtocols.protocols) + } + + srv := &caddyhttp.Server{ + Listen: addresses, + ListenProtocols: protocols, + } + + // remove srv.ListenProtocols[j] if it only contains the default protocols + for j, lnProtocols := range srv.ListenProtocols { + srv.ListenProtocols[j] = nil + for _, lnProtocol := range lnProtocols { + if lnProtocol != "" { + srv.ListenProtocols[j] = lnProtocols + break + } + } + } + + // remove srv.ListenProtocols if it only contains the default protocols for all listen addresses + listenProtocols := srv.ListenProtocols + srv.ListenProtocols = nil + for _, lnProtocols := range listenProtocols { + if lnProtocols != nil { + srv.ListenProtocols = listenProtocols + break + } + } + + // handle the auto_https global option + for _, val := range autoHTTPS { + switch val { + case "off": + if srv.AutoHTTPS == nil { + srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig) + } + srv.AutoHTTPS.Disabled = true + + case "disable_redirects": + if srv.AutoHTTPS == nil { + srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig) + } + srv.AutoHTTPS.DisableRedir = true + + case "disable_certs": + if srv.AutoHTTPS == nil { + srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig) + } + srv.AutoHTTPS.DisableCerts = true + + case "ignore_loaded_certs": + if srv.AutoHTTPS == nil { + srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig) + } + srv.AutoHTTPS.IgnoreLoadedCerts = true + + case "prefer_wildcard": + if srv.AutoHTTPS == nil { + srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig) + } + srv.AutoHTTPS.PreferWildcard = true + } + } + + // Using paths in site addresses is deprecated + // See ParseAddress() where parsing should later reject paths + // See https://github.com/caddyserver/caddy/pull/4728 for a full explanation + for _, sblock := range p.serverBlocks { + for _, addr := range sblock.parsedKeys { + if addr.Path != "" { + caddy.Log().Named("caddyfile").Warn("Using a path in a site address is deprecated; please use the 'handle' directive instead", zap.String("address", addr.String())) + } + } + } + + // sort server blocks by their keys; this is important because + // only the first matching site should be evaluated, and we should + // attempt to match most specific site first (host and path), in + // case their matchers overlap; we do this somewhat naively by + // descending sort by length of host then path + sort.SliceStable(p.serverBlocks, func(i, j int) bool { + // TODO: we could pre-process the specificities for efficiency, + // but I don't expect many blocks will have THAT many keys... + var iLongestPath, jLongestPath string + var iLongestHost, jLongestHost string + var iWildcardHost, jWildcardHost bool + for _, addr := range p.serverBlocks[i].parsedKeys { + if strings.Contains(addr.Host, "*") || addr.Host == "" { + iWildcardHost = true + } + if specificity(addr.Host) > specificity(iLongestHost) { + iLongestHost = addr.Host + } + if specificity(addr.Path) > specificity(iLongestPath) { + iLongestPath = addr.Path + } + } + for _, addr := range p.serverBlocks[j].parsedKeys { + if strings.Contains(addr.Host, "*") || addr.Host == "" { + jWildcardHost = true + } + if specificity(addr.Host) > specificity(jLongestHost) { + jLongestHost = addr.Host + } + if specificity(addr.Path) > specificity(jLongestPath) { + jLongestPath = addr.Path + } + } + // catch-all blocks (blocks with no hostname) should always go + // last, even after blocks with wildcard hosts + if specificity(iLongestHost) == 0 { + return false + } + if specificity(jLongestHost) == 0 { + return true + } + if iWildcardHost != jWildcardHost { + // site blocks that have a key with a wildcard in the hostname + // must always be less specific than blocks without one; see + // https://github.com/caddyserver/caddy/issues/3410 + return jWildcardHost && !iWildcardHost + } + if specificity(iLongestHost) == specificity(jLongestHost) { + return len(iLongestPath) > len(jLongestPath) + } + return specificity(iLongestHost) > specificity(jLongestHost) + }) + + // collect all hosts that have a wildcard in them + wildcardHosts := []string{} + for _, sblock := range p.serverBlocks { + for _, addr := range sblock.parsedKeys { + if strings.HasPrefix(addr.Host, "*.") { + wildcardHosts = append(wildcardHosts, addr.Host[2:]) + } + } + } + + var hasCatchAllTLSConnPolicy, addressQualifiesForTLS bool + autoHTTPSWillAddConnPolicy := srv.AutoHTTPS == nil || !srv.AutoHTTPS.Disabled + + // if needed, the ServerLogConfig is initialized beforehand so + // that all server blocks can populate it with data, even when not + // coming with a log directive + for _, sblock := range p.serverBlocks { + if len(sblock.pile["custom_log"]) != 0 { + srv.Logs = new(caddyhttp.ServerLogConfig) + break + } + } + + // add named routes to the server if 'invoke' was used inside of it + configuredNamedRoutes := options["named_routes"].(map[string]*caddyhttp.Route) + for _, sblock := range p.serverBlocks { + if len(sblock.pile[namedRouteKey]) == 0 { + continue + } + for _, value := range sblock.pile[namedRouteKey] { + if srv.NamedRoutes == nil { + srv.NamedRoutes = map[string]*caddyhttp.Route{} + } + name := value.Value.(string) + if configuredNamedRoutes[name] == nil { + return nil, fmt.Errorf("cannot invoke named route '%s', which was not defined", name) + } + srv.NamedRoutes[name] = configuredNamedRoutes[name] + } + } + + // create a subroute for each site in the server block + for _, sblock := range p.serverBlocks { + matcherSetsEnc, err := st.compileEncodedMatcherSets(sblock) + if err != nil { + return nil, fmt.Errorf("server block %v: compiling matcher sets: %v", sblock.block.Keys, err) + } + + hosts := sblock.hostsFromKeys(false) + + // emit warnings if user put unspecified IP addresses; they probably want the bind directive + for _, h := range hosts { + if h == "0.0.0.0" || h == "::" { + caddy.Log().Named("caddyfile").Warn("Site block has an unspecified IP address which only matches requests having that Host header; you probably want the 'bind' directive to configure the socket", zap.String("address", h)) + } + } + + // collect hosts that are forced to be automated + forceAutomatedNames := make(map[string]struct{}) + if _, ok := sblock.pile["tls.force_automate"]; ok { + for _, host := range hosts { + forceAutomatedNames[host] = struct{}{} + } + } + + // tls: connection policies + if cpVals, ok := sblock.pile["tls.connection_policy"]; ok { + // tls connection policies + for _, cpVal := range cpVals { + cp := cpVal.Value.(*caddytls.ConnectionPolicy) + + // make sure the policy covers all hostnames from the block + for _, h := range hosts { + if h == defaultSNI { + hosts = append(hosts, "") + cp.DefaultSNI = defaultSNI + break + } + if h == fallbackSNI { + hosts = append(hosts, "") + cp.FallbackSNI = fallbackSNI + break + } + } + + if len(hosts) > 0 { + slices.Sort(hosts) // for deterministic JSON output + cp.MatchersRaw = caddy.ModuleMap{ + "sni": caddyconfig.JSON(hosts, warnings), // make sure to match all hosts, not just auto-HTTPS-qualified ones + } + } else { + cp.DefaultSNI = defaultSNI + cp.FallbackSNI = fallbackSNI + } + + // only append this policy if it actually changes something + if !cp.SettingsEmpty() || mapContains(forceAutomatedNames, hosts) { + srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp) + hasCatchAllTLSConnPolicy = len(hosts) == 0 + } + } + } + + for _, addr := range sblock.parsedKeys { + // if server only uses HTTP port, auto-HTTPS will not apply + if listenersUseAnyPortOtherThan(srv.Listen, httpPort) { + // exclude any hosts that were defined explicitly with "http://" + // in the key from automated cert management (issue #2998) + if addr.Scheme == "http" && addr.Host != "" { + if srv.AutoHTTPS == nil { + srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig) + } + if !slices.Contains(srv.AutoHTTPS.Skip, addr.Host) { + srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, addr.Host) + } + } + } + + // If TLS is specified as directive, it will also result in 1 or more connection policy being created + // Thus, catch-all address with non-standard port, e.g. :8443, can have TLS enabled without + // specifying prefix "https://" + // Second part of the condition is to allow creating TLS conn policy even though `auto_https` has been disabled + // ensuring compatibility with behavior described in below link + // https://caddy.community/t/making-sense-of-auto-https-and-why-disabling-it-still-serves-https-instead-of-http/9761 + createdTLSConnPolicies, ok := sblock.pile["tls.connection_policy"] + hasTLSEnabled := (ok && len(createdTLSConnPolicies) > 0) || + (addr.Host != "" && srv.AutoHTTPS != nil && !slices.Contains(srv.AutoHTTPS.Skip, addr.Host)) + + // we'll need to remember if the address qualifies for auto-HTTPS, so we + // can add a TLS conn policy if necessary + if addr.Scheme == "https" || + (addr.Scheme != "http" && addr.Port != httpPort && hasTLSEnabled) { + addressQualifiesForTLS = true + } + + // If prefer wildcard is enabled, then we add hosts that are + // already covered by the wildcard to the skip list + if addressQualifiesForTLS && srv.AutoHTTPS != nil && srv.AutoHTTPS.PreferWildcard { + baseDomain := addr.Host + if idx := strings.Index(baseDomain, "."); idx != -1 { + baseDomain = baseDomain[idx+1:] + } + if !strings.HasPrefix(addr.Host, "*.") && slices.Contains(wildcardHosts, baseDomain) { + srv.AutoHTTPS.SkipCerts = append(srv.AutoHTTPS.SkipCerts, addr.Host) + } + } + + // predict whether auto-HTTPS will add the conn policy for us; if so, we + // may not need to add one for this server + autoHTTPSWillAddConnPolicy = autoHTTPSWillAddConnPolicy && + (addr.Port == httpsPort || (addr.Port != httpPort && addr.Host != "")) + } + + // Look for any config values that provide listener wrappers on the server block + for _, listenerConfig := range sblock.pile["listener_wrapper"] { + listenerWrapper, ok := listenerConfig.Value.(caddy.ListenerWrapper) + if !ok { + return nil, fmt.Errorf("config for a listener wrapper did not provide a value that implements caddy.ListenerWrapper") + } + jsonListenerWrapper := caddyconfig.JSONModuleObject( + listenerWrapper, + "wrapper", + listenerWrapper.(caddy.Module).CaddyModule().ID.Name(), + warnings) + srv.ListenerWrappersRaw = append(srv.ListenerWrappersRaw, jsonListenerWrapper) + } + + // set up each handler directive, making sure to honor directive order + dirRoutes := sblock.pile["route"] + siteSubroute, err := buildSubroute(dirRoutes, groupCounter, true) + if err != nil { + return nil, err + } + + // add the site block's route(s) to the server + srv.Routes = appendSubrouteToRouteList(srv.Routes, siteSubroute, matcherSetsEnc, p, warnings) + + // if error routes are defined, add those too + if errorSubrouteVals, ok := sblock.pile["error_route"]; ok { + if srv.Errors == nil { + srv.Errors = new(caddyhttp.HTTPErrorConfig) + } + sort.SliceStable(errorSubrouteVals, func(i, j int) bool { + sri, srj := errorSubrouteVals[i].Value.(*caddyhttp.Subroute), errorSubrouteVals[j].Value.(*caddyhttp.Subroute) + if len(sri.Routes[0].MatcherSetsRaw) == 0 && len(srj.Routes[0].MatcherSetsRaw) != 0 { + return false + } + return true + }) + errorsSubroute := &caddyhttp.Subroute{} + for _, val := range errorSubrouteVals { + sr := val.Value.(*caddyhttp.Subroute) + errorsSubroute.Routes = append(errorsSubroute.Routes, sr.Routes...) + } + srv.Errors.Routes = appendSubrouteToRouteList(srv.Errors.Routes, errorsSubroute, matcherSetsEnc, p, warnings) + } + + // add log associations + // see https://github.com/caddyserver/caddy/issues/3310 + sblockLogHosts := sblock.hostsFromKeys(true) + for _, cval := range sblock.pile["custom_log"] { + ncl := cval.Value.(namedCustomLog) + + // if `no_hostname` is set, then this logger will not + // be associated with any of the site block's hostnames, + // and only be usable via the `log_name` directive + // or the `access_logger_names` variable + if ncl.noHostname { + continue + } + + if sblock.hasHostCatchAllKey() && len(ncl.hostnames) == 0 { + // all requests for hosts not able to be listed should use + // this log because it's a catch-all-hosts server block + srv.Logs.DefaultLoggerName = ncl.name + } else if len(ncl.hostnames) > 0 { + // if the logger overrides the hostnames, map that to the logger name + for _, h := range ncl.hostnames { + if srv.Logs.LoggerNames == nil { + srv.Logs.LoggerNames = make(map[string]caddyhttp.StringArray) + } + srv.Logs.LoggerNames[h] = append(srv.Logs.LoggerNames[h], ncl.name) + } + } else { + // otherwise, map each host to the logger name + for _, h := range sblockLogHosts { + // strip the port from the host, if any + host, _, err := net.SplitHostPort(h) + if err != nil { + host = h + } + if srv.Logs.LoggerNames == nil { + srv.Logs.LoggerNames = make(map[string]caddyhttp.StringArray) + } + srv.Logs.LoggerNames[host] = append(srv.Logs.LoggerNames[host], ncl.name) + } + } + } + if srv.Logs != nil && len(sblock.pile["custom_log"]) == 0 { + // server has access logs enabled, but this server block does not + // enable access logs; therefore, all hosts of this server block + // should not be access-logged + if len(hosts) == 0 { + // if the server block has a catch-all-hosts key, then we should + // not log reqs to any host unless it appears in the map + srv.Logs.SkipUnmappedHosts = true + } + srv.Logs.SkipHosts = append(srv.Logs.SkipHosts, sblockLogHosts...) + } + } + + // sort for deterministic JSON output + if srv.Logs != nil { + slices.Sort(srv.Logs.SkipHosts) + } + + // a server cannot (natively) serve both HTTP and HTTPS at the + // same time, so make sure the configuration isn't in conflict + err := detectConflictingSchemes(srv, p.serverBlocks, options) + if err != nil { + return nil, err + } + + // a catch-all TLS conn policy is necessary to ensure TLS can + // be offered to all hostnames of the server; even though only + // one policy is needed to enable TLS for the server, that + // policy might apply to only certain TLS handshakes; but when + // using the Caddyfile, user would expect all handshakes to at + // least have a matching connection policy, so here we append a + // catch-all/default policy if there isn't one already (it's + // important that it goes at the end) - see issue #3004: + // https://github.com/caddyserver/caddy/issues/3004 + // TODO: maybe a smarter way to handle this might be to just make the + // auto-HTTPS logic at provision-time detect if there is any connection + // policy missing for any HTTPS-enabled hosts, if so, add it... maybe? + if addressQualifiesForTLS && + !hasCatchAllTLSConnPolicy && + (len(srv.TLSConnPolicies) > 0 || !autoHTTPSWillAddConnPolicy || defaultSNI != "" || fallbackSNI != "") { + srv.TLSConnPolicies = append(srv.TLSConnPolicies, &caddytls.ConnectionPolicy{ + DefaultSNI: defaultSNI, + FallbackSNI: fallbackSNI, + }) + } + + // tidy things up a bit + srv.TLSConnPolicies, err = consolidateConnPolicies(srv.TLSConnPolicies) + if err != nil { + return nil, fmt.Errorf("consolidating TLS connection policies for server %d: %v", i, err) + } + srv.Routes = consolidateRoutes(srv.Routes) + + servers[fmt.Sprintf("srv%d", i)] = srv + } + + if err := applyServerOptions(servers, options, warnings); err != nil { + return nil, fmt.Errorf("applying global server options: %v", err) + } + + return servers, nil +} + +func detectConflictingSchemes(srv *caddyhttp.Server, serverBlocks []serverBlock, options map[string]any) error { + httpPort := strconv.Itoa(caddyhttp.DefaultHTTPPort) + if hp, ok := options["http_port"].(int); ok { + httpPort = strconv.Itoa(hp) + } + httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPSPort) + if hsp, ok := options["https_port"].(int); ok { + httpsPort = strconv.Itoa(hsp) + } + + var httpOrHTTPS string + checkAndSetHTTP := func(addr Address) error { + if httpOrHTTPS == "HTTPS" { + errMsg := fmt.Errorf("server listening on %v is configured for HTTPS and cannot natively multiplex HTTP and HTTPS: %s", + srv.Listen, addr.Original) + if addr.Scheme == "" && addr.Host == "" { + errMsg = fmt.Errorf("%s (try specifying https:// in the address)", errMsg) + } + return errMsg + } + if len(srv.TLSConnPolicies) > 0 { + // any connection policies created for an HTTP server + // is a logical conflict, as it would enable HTTPS + return fmt.Errorf("server listening on %v is HTTP, but attempts to configure TLS connection policies", srv.Listen) + } + httpOrHTTPS = "HTTP" + return nil + } + checkAndSetHTTPS := func(addr Address) error { + if httpOrHTTPS == "HTTP" { + return fmt.Errorf("server listening on %v is configured for HTTP and cannot natively multiplex HTTP and HTTPS: %s", + srv.Listen, addr.Original) + } + httpOrHTTPS = "HTTPS" + return nil + } + + for _, sblock := range serverBlocks { + for _, addr := range sblock.parsedKeys { + if addr.Scheme == "http" || addr.Port == httpPort { + if err := checkAndSetHTTP(addr); err != nil { + return err + } + } else if addr.Scheme == "https" || addr.Port == httpsPort || len(srv.TLSConnPolicies) > 0 { + if err := checkAndSetHTTPS(addr); err != nil { + return err + } + } else if addr.Host == "" { + if err := checkAndSetHTTP(addr); err != nil { + return err + } + } + } + } + + return nil +} + +// consolidateConnPolicies sorts any catch-all policy to the end, removes empty TLS connection +// policies, and combines equivalent ones for a cleaner overall output. +func consolidateConnPolicies(cps caddytls.ConnectionPolicies) (caddytls.ConnectionPolicies, error) { + // catch-all policies (those without any matcher) should be at the + // end, otherwise it nullifies any more specific policies + sort.SliceStable(cps, func(i, j int) bool { + return cps[j].MatchersRaw == nil && cps[i].MatchersRaw != nil + }) + + for i := 0; i < len(cps); i++ { + // compare it to the others + for j := 0; j < len(cps); j++ { + if j == i { + continue + } + + // if they're exactly equal in every way, just keep one of them + if reflect.DeepEqual(cps[i], cps[j]) { + cps = append(cps[:j], cps[j+1:]...) + i-- + break + } + + // if they have the same matcher, try to reconcile each field: either they must + // be identical, or we have to be able to combine them safely + if reflect.DeepEqual(cps[i].MatchersRaw, cps[j].MatchersRaw) { + if len(cps[i].ALPN) > 0 && + len(cps[j].ALPN) > 0 && + !reflect.DeepEqual(cps[i].ALPN, cps[j].ALPN) { + return nil, fmt.Errorf("two policies with same match criteria have conflicting ALPN: %v vs. %v", + cps[i].ALPN, cps[j].ALPN) + } + if len(cps[i].CipherSuites) > 0 && + len(cps[j].CipherSuites) > 0 && + !reflect.DeepEqual(cps[i].CipherSuites, cps[j].CipherSuites) { + return nil, fmt.Errorf("two policies with same match criteria have conflicting cipher suites: %v vs. %v", + cps[i].CipherSuites, cps[j].CipherSuites) + } + if cps[i].ClientAuthentication == nil && + cps[j].ClientAuthentication != nil && + !reflect.DeepEqual(cps[i].ClientAuthentication, cps[j].ClientAuthentication) { + return nil, fmt.Errorf("two policies with same match criteria have conflicting client auth configuration: %+v vs. %+v", + cps[i].ClientAuthentication, cps[j].ClientAuthentication) + } + if len(cps[i].Curves) > 0 && + len(cps[j].Curves) > 0 && + !reflect.DeepEqual(cps[i].Curves, cps[j].Curves) { + return nil, fmt.Errorf("two policies with same match criteria have conflicting curves: %v vs. %v", + cps[i].Curves, cps[j].Curves) + } + if cps[i].DefaultSNI != "" && + cps[j].DefaultSNI != "" && + cps[i].DefaultSNI != cps[j].DefaultSNI { + return nil, fmt.Errorf("two policies with same match criteria have conflicting default SNI: %s vs. %s", + cps[i].DefaultSNI, cps[j].DefaultSNI) + } + if cps[i].ProtocolMin != "" && + cps[j].ProtocolMin != "" && + cps[i].ProtocolMin != cps[j].ProtocolMin { + return nil, fmt.Errorf("two policies with same match criteria have conflicting min protocol: %s vs. %s", + cps[i].ProtocolMin, cps[j].ProtocolMin) + } + if cps[i].ProtocolMax != "" && + cps[j].ProtocolMax != "" && + cps[i].ProtocolMax != cps[j].ProtocolMax { + return nil, fmt.Errorf("two policies with same match criteria have conflicting max protocol: %s vs. %s", + cps[i].ProtocolMax, cps[j].ProtocolMax) + } + if cps[i].CertSelection != nil && cps[j].CertSelection != nil { + // merging fields other than AnyTag is not implemented + if !reflect.DeepEqual(cps[i].CertSelection.SerialNumber, cps[j].CertSelection.SerialNumber) || + !reflect.DeepEqual(cps[i].CertSelection.SubjectOrganization, cps[j].CertSelection.SubjectOrganization) || + cps[i].CertSelection.PublicKeyAlgorithm != cps[j].CertSelection.PublicKeyAlgorithm || + !reflect.DeepEqual(cps[i].CertSelection.AllTags, cps[j].CertSelection.AllTags) { + return nil, fmt.Errorf("two policies with same match criteria have conflicting cert selections: %+v vs. %+v", + cps[i].CertSelection, cps[j].CertSelection) + } + } + + // by now we've decided that we can merge the two -- we'll keep i and drop j + + if len(cps[i].ALPN) == 0 && len(cps[j].ALPN) > 0 { + cps[i].ALPN = cps[j].ALPN + } + if len(cps[i].CipherSuites) == 0 && len(cps[j].CipherSuites) > 0 { + cps[i].CipherSuites = cps[j].CipherSuites + } + if cps[i].ClientAuthentication == nil && cps[j].ClientAuthentication != nil { + cps[i].ClientAuthentication = cps[j].ClientAuthentication + } + if len(cps[i].Curves) == 0 && len(cps[j].Curves) > 0 { + cps[i].Curves = cps[j].Curves + } + if cps[i].DefaultSNI == "" && cps[j].DefaultSNI != "" { + cps[i].DefaultSNI = cps[j].DefaultSNI + } + if cps[i].ProtocolMin == "" && cps[j].ProtocolMin != "" { + cps[i].ProtocolMin = cps[j].ProtocolMin + } + if cps[i].ProtocolMax == "" && cps[j].ProtocolMax != "" { + cps[i].ProtocolMax = cps[j].ProtocolMax + } + + if cps[i].CertSelection == nil && cps[j].CertSelection != nil { + // if j is the only one with a policy, move it over to i + cps[i].CertSelection = cps[j].CertSelection + } else if cps[i].CertSelection != nil && cps[j].CertSelection != nil { + // if both have one, then combine AnyTag + for _, tag := range cps[j].CertSelection.AnyTag { + if !slices.Contains(cps[i].CertSelection.AnyTag, tag) { + cps[i].CertSelection.AnyTag = append(cps[i].CertSelection.AnyTag, tag) + } + } + } + + cps = append(cps[:j], cps[j+1:]...) + i-- + break + } + } + } + return cps, nil +} + +// appendSubrouteToRouteList appends the routes in subroute +// to the routeList, optionally qualified by matchers. +func appendSubrouteToRouteList(routeList caddyhttp.RouteList, + subroute *caddyhttp.Subroute, + matcherSetsEnc []caddy.ModuleMap, + p sbAddrAssociation, + warnings *[]caddyconfig.Warning, +) caddyhttp.RouteList { + // nothing to do if... there's nothing to do + if len(matcherSetsEnc) == 0 && len(subroute.Routes) == 0 && subroute.Errors == nil { + return routeList + } + + // No need to wrap the handlers in a subroute if this is the only server block + // and there is no matcher for it (doing so would produce unnecessarily nested + // JSON), *unless* there is a host matcher within this site block; if so, then + // we still need to wrap in a subroute because otherwise the host matcher from + // the inside of the site block would be a top-level host matcher, which is + // subject to auto-HTTPS (cert management), and using a host matcher within + // a site block is a valid, common pattern for excluding domains from cert + // management, leading to unexpected behavior; see issue #5124. + wrapInSubroute := true + if len(matcherSetsEnc) == 0 && len(p.serverBlocks) == 1 { + var hasHostMatcher bool + outer: + for _, route := range subroute.Routes { + for _, ms := range route.MatcherSetsRaw { + for matcherName := range ms { + if matcherName == "host" { + hasHostMatcher = true + break outer + } + } + } + } + wrapInSubroute = hasHostMatcher + } + + if wrapInSubroute { + route := caddyhttp.Route{ + // the semantics of a site block in the Caddyfile dictate + // that only the first matching one is evaluated, since + // site blocks do not cascade nor inherit + Terminal: true, + } + if len(matcherSetsEnc) > 0 { + route.MatcherSetsRaw = matcherSetsEnc + } + if len(subroute.Routes) > 0 || subroute.Errors != nil { + route.HandlersRaw = []json.RawMessage{ + caddyconfig.JSONModuleObject(subroute, "handler", "subroute", warnings), + } + } + if len(route.MatcherSetsRaw) > 0 || len(route.HandlersRaw) > 0 { + routeList = append(routeList, route) + } + } else { + routeList = append(routeList, subroute.Routes...) + } + + return routeList +} + +// buildSubroute turns the config values, which are expected to be routes +// into a clean and orderly subroute that has all the routes within it. +func buildSubroute(routes []ConfigValue, groupCounter counter, needsSorting bool) (*caddyhttp.Subroute, error) { + if needsSorting { + for _, val := range routes { + if !slices.Contains(directiveOrder, val.directive) { + return nil, fmt.Errorf("directive '%s' is not an ordered HTTP handler, so it cannot be used here - try placing within a route block or using the order global option", val.directive) + } + } + + sortRoutes(routes) + } + + subroute := new(caddyhttp.Subroute) + + // some directives are mutually exclusive (only first matching + // instance should be evaluated); this is done by putting their + // routes in the same group + mutuallyExclusiveDirs := map[string]*struct { + count int + groupName string + }{ + // as a special case, group rewrite directives so that they are mutually exclusive; + // this means that only the first matching rewrite will be evaluated, and that's + // probably a good thing, since there should never be a need to do more than one + // rewrite (I think?), and cascading rewrites smell bad... imagine these rewrites: + // rewrite /docs/json/* /docs/json/index.html + // rewrite /docs/* /docs/index.html + // (We use this on the Caddy website, or at least we did once.) The first rewrite's + // result is also matched by the second rewrite, making the first rewrite pointless. + // See issue #2959. + "rewrite": {}, + + // handle blocks are also mutually exclusive by definition + "handle": {}, + + // root just sets a variable, so if it was not mutually exclusive, intersecting + // root directives would overwrite previously-matched ones; they should not cascade + "root": {}, + } + + // we need to deterministically loop over each of these directives + // in order to keep the group numbers consistent + keys := make([]string, 0, len(mutuallyExclusiveDirs)) + for k := range mutuallyExclusiveDirs { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, meDir := range keys { + info := mutuallyExclusiveDirs[meDir] + + // see how many instances of the directive there are + for _, r := range routes { + if r.directive == meDir { + info.count++ + if info.count > 1 { + break + } + } + } + // if there is more than one, put them in a group + // (special case: "rewrite" directive must always be in + // its own group--even if there is only one--because we + // do not want a rewrite to be consolidated into other + // adjacent routes that happen to have the same matcher, + // see caddyserver/caddy#3108 - because the implied + // intent of rewrite is to do an internal redirect, + // we can't assume that the request will continue to + // match the same matcher; anyway, giving a route a + // unique group name should keep it from consolidating) + if info.count > 1 || meDir == "rewrite" { + info.groupName = groupCounter.nextGroup() + } + } + + // add all the routes piled in from directives + for _, r := range routes { + // put this route into a group if it is mutually exclusive + if info, ok := mutuallyExclusiveDirs[r.directive]; ok { + route := r.Value.(caddyhttp.Route) + route.Group = info.groupName + r.Value = route + } + + switch route := r.Value.(type) { + case caddyhttp.Subroute: + // if a route-class config value is actually a Subroute handler + // with nothing but a list of routes, then it is the intention + // of the directive to keep these handlers together and in this + // same order, but not necessarily in a subroute (if it wanted + // to keep them in a subroute, the directive would have returned + // a route with a Subroute as its handler); this is useful to + // keep multiple handlers/routes together and in the same order + // so that the sorting procedure we did above doesn't reorder them + if route.Errors != nil { + // if error handlers are also set, this is confusing; it's + // probably supposed to be wrapped in a Route and encoded + // as a regular handler route... programmer error. + panic("found subroute with more than just routes; perhaps it should have been wrapped in a route?") + } + subroute.Routes = append(subroute.Routes, route.Routes...) + case caddyhttp.Route: + subroute.Routes = append(subroute.Routes, route) + } + } + + subroute.Routes = consolidateRoutes(subroute.Routes) + + return subroute, nil +} + +// normalizeDirectiveName ensures directives that should be sorted +// at the same level are named the same before sorting happens. +func normalizeDirectiveName(directive string) string { + // As a special case, we want "handle_path" to be sorted + // at the same level as "handle", so we force them to use + // the same directive name after their parsing is complete. + // See https://github.com/caddyserver/caddy/issues/3675#issuecomment-678042377 + if directive == "handle_path" { + directive = "handle" + } + return directive +} + +// consolidateRoutes combines routes with the same properties +// (same matchers, same Terminal and Group settings) for a +// cleaner overall output. +func consolidateRoutes(routes caddyhttp.RouteList) caddyhttp.RouteList { + for i := 0; i < len(routes)-1; i++ { + if reflect.DeepEqual(routes[i].MatcherSetsRaw, routes[i+1].MatcherSetsRaw) && + routes[i].Terminal == routes[i+1].Terminal && + routes[i].Group == routes[i+1].Group { + // keep the handlers in the same order, then splice out repetitive route + routes[i].HandlersRaw = append(routes[i].HandlersRaw, routes[i+1].HandlersRaw...) + routes = append(routes[:i+1], routes[i+2:]...) + i-- + } + } + return routes +} + +func matcherSetFromMatcherToken( + tkn caddyfile.Token, + matcherDefs map[string]caddy.ModuleMap, + warnings *[]caddyconfig.Warning, +) (caddy.ModuleMap, bool, error) { + // matcher tokens can be wildcards, simple path matchers, + // or refer to a pre-defined matcher by some name + if tkn.Text == "*" { + // match all requests == no matchers, so nothing to do + return nil, true, nil + } + + // convenient way to specify a single path match + if strings.HasPrefix(tkn.Text, "/") { + return caddy.ModuleMap{ + "path": caddyconfig.JSON(caddyhttp.MatchPath{tkn.Text}, warnings), + }, true, nil + } + + // pre-defined matcher + if strings.HasPrefix(tkn.Text, matcherPrefix) { + m, ok := matcherDefs[tkn.Text] + if !ok { + return nil, false, fmt.Errorf("unrecognized matcher name: %+v", tkn.Text) + } + return m, true, nil + } + + return nil, false, nil +} + +func (st *ServerType) compileEncodedMatcherSets(sblock serverBlock) ([]caddy.ModuleMap, error) { + type hostPathPair struct { + hostm caddyhttp.MatchHost + pathm caddyhttp.MatchPath + } + + // keep routes with common host and path matchers together + var matcherPairs []*hostPathPair + + var catchAllHosts bool + for _, addr := range sblock.parsedKeys { + // choose a matcher pair that should be shared by this + // server block; if none exists yet, create one + var chosenMatcherPair *hostPathPair + for _, mp := range matcherPairs { + if (len(mp.pathm) == 0 && addr.Path == "") || + (len(mp.pathm) == 1 && mp.pathm[0] == addr.Path) { + chosenMatcherPair = mp + break + } + } + if chosenMatcherPair == nil { + chosenMatcherPair = new(hostPathPair) + if addr.Path != "" { + chosenMatcherPair.pathm = []string{addr.Path} + } + matcherPairs = append(matcherPairs, chosenMatcherPair) + } + + // if one of the keys has no host (i.e. is a catch-all for + // any hostname), then we need to null out the host matcher + // entirely so that it matches all hosts + if addr.Host == "" && !catchAllHosts { + chosenMatcherPair.hostm = nil + catchAllHosts = true + } + if catchAllHosts { + continue + } + + // add this server block's keys to the matcher + // pair if it doesn't already exist + if addr.Host != "" && !slices.Contains(chosenMatcherPair.hostm, addr.Host) { + chosenMatcherPair.hostm = append(chosenMatcherPair.hostm, addr.Host) + } + } + + // iterate each pairing of host and path matchers and + // put them into a map for JSON encoding + var matcherSets []map[string]caddyhttp.RequestMatcherWithError + for _, mp := range matcherPairs { + matcherSet := make(map[string]caddyhttp.RequestMatcherWithError) + if len(mp.hostm) > 0 { + matcherSet["host"] = mp.hostm + } + if len(mp.pathm) > 0 { + matcherSet["path"] = mp.pathm + } + if len(matcherSet) > 0 { + matcherSets = append(matcherSets, matcherSet) + } + } + + // finally, encode each of the matcher sets + matcherSetsEnc := make([]caddy.ModuleMap, 0, len(matcherSets)) + for _, ms := range matcherSets { + msEncoded, err := encodeMatcherSet(ms) + if err != nil { + return nil, fmt.Errorf("server block %v: %v", sblock.block.Keys, err) + } + matcherSetsEnc = append(matcherSetsEnc, msEncoded) + } + + return matcherSetsEnc, nil +} + +func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.ModuleMap) error { + d.Next() // advance to the first token + + // this is the "name" for "named matchers" + definitionName := d.Val() + + if _, ok := matchers[definitionName]; ok { + return fmt.Errorf("matcher is defined more than once: %s", definitionName) + } + matchers[definitionName] = make(caddy.ModuleMap) + + // given a matcher name and the tokens following it, parse + // the tokens as a matcher module and record it + makeMatcher := func(matcherName string, tokens []caddyfile.Token) error { + // create a new dispenser from the tokens + dispenser := caddyfile.NewDispenser(tokens) + + // set the matcher name (without @) in the dispenser context so + // that matcher modules can access it to use it as their name + // (e.g. regexp matchers which use the name for capture groups) + dispenser.SetContext(caddyfile.MatcherNameCtxKey, definitionName[1:]) + + mod, err := caddy.GetModule("http.matchers." + matcherName) + if err != nil { + return fmt.Errorf("getting matcher module '%s': %v", matcherName, err) + } + unm, ok := mod.New().(caddyfile.Unmarshaler) + if !ok { + return fmt.Errorf("matcher module '%s' is not a Caddyfile unmarshaler", matcherName) + } + err = unm.UnmarshalCaddyfile(dispenser) + if err != nil { + return err + } + + if rm, ok := unm.(caddyhttp.RequestMatcherWithError); ok { + matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil) + return nil + } + // nolint:staticcheck + if rm, ok := unm.(caddyhttp.RequestMatcher); ok { + matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil) + return nil + } + return fmt.Errorf("matcher module '%s' is not a request matcher", matcherName) + } + + // if the next token is quoted, we can assume it's not a matcher name + // and that it's probably an 'expression' matcher + if d.NextArg() { + if d.Token().Quoted() { + // since it was missing the matcher name, we insert a token + // in front of the expression token itself; we use Clone() to + // make the new token to keep the same the import location as + // the next token, if this is within a snippet or imported file. + // see https://github.com/caddyserver/caddy/issues/6287 + expressionToken := d.Token().Clone() + expressionToken.Text = "expression" + err := makeMatcher("expression", []caddyfile.Token{expressionToken, d.Token()}) + if err != nil { + return err + } + return nil + } + + // if it wasn't quoted, then we need to rewind after calling + // d.NextArg() so the below properly grabs the matcher name + d.Prev() + } + + // in case there are multiple instances of the same matcher, concatenate + // their tokens (we expect that UnmarshalCaddyfile should be able to + // handle more than one segment); otherwise, we'd overwrite other + // instances of the matcher in this set + tokensByMatcherName := make(map[string][]caddyfile.Token) + for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); { + matcherName := d.Val() + tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...) + } + for matcherName, tokens := range tokensByMatcherName { + err := makeMatcher(matcherName, tokens) + if err != nil { + return err + } + } + return nil +} + +func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcherWithError) (caddy.ModuleMap, error) { + msEncoded := make(caddy.ModuleMap) + for matcherName, val := range matchers { + jsonBytes, err := json.Marshal(val) + if err != nil { + return nil, fmt.Errorf("marshaling matcher set %#v: %v", matchers, err) + } + msEncoded[matcherName] = jsonBytes + } + return msEncoded, nil +} + +// WasReplacedPlaceholderShorthand checks if a token string was +// likely a replaced shorthand of the known Caddyfile placeholder +// replacement outputs. Useful to prevent some user-defined map +// output destinations from overlapping with one of the +// predefined shorthands. +func WasReplacedPlaceholderShorthand(token string) string { + prev := "" + for i, item := range placeholderShorthands() { + // only look at every 2nd item, which is the replacement + if i%2 == 0 { + prev = item + continue + } + if strings.Trim(token, "{}") == strings.Trim(item, "{}") { + // we return the original shorthand so it + // can be used for an error message + return prev + } + } + return "" +} + +// tryInt tries to convert val to an integer. If it fails, +// it downgrades the error to a warning and returns 0. +func tryInt(val any, warnings *[]caddyconfig.Warning) int { + intVal, ok := val.(int) + if val != nil && !ok && warnings != nil { + *warnings = append(*warnings, caddyconfig.Warning{Message: "not an integer type"}) + } + return intVal +} + +func tryString(val any, warnings *[]caddyconfig.Warning) string { + stringVal, ok := val.(string) + if val != nil && !ok && warnings != nil { + *warnings = append(*warnings, caddyconfig.Warning{Message: "not a string type"}) + } + return stringVal +} + +func tryDuration(val any, warnings *[]caddyconfig.Warning) caddy.Duration { + durationVal, ok := val.(caddy.Duration) + if val != nil && !ok && warnings != nil { + *warnings = append(*warnings, caddyconfig.Warning{Message: "not a duration type"}) + } + return durationVal +} + +// listenersUseAnyPortOtherThan returns true if there are any +// listeners in addresses that use a port which is not otherPort. +// Mostly borrowed from unexported method in caddyhttp package. +func listenersUseAnyPortOtherThan(addresses []string, otherPort string) bool { + otherPortInt, err := strconv.Atoi(otherPort) + if err != nil { + return false + } + for _, lnAddr := range addresses { + laddrs, err := caddy.ParseNetworkAddress(lnAddr) + if err != nil { + continue + } + if uint(otherPortInt) > laddrs.EndPort || uint(otherPortInt) < laddrs.StartPort { + return true + } + } + return false +} + +func mapContains[K comparable, V any](m map[K]V, keys []K) bool { + if len(m) == 0 || len(keys) == 0 { + return false + } + for _, key := range keys { + if _, ok := m[key]; ok { + return true + } + } + return false +} + +// specificity returns len(s) minus any wildcards (*) and +// placeholders ({...}). Basically, it's a length count +// that penalizes the use of wildcards and placeholders. +// This is useful for comparing hostnames and paths. +// However, wildcards in paths are not a sure answer to +// the question of specificity. For example, +// '*.example.com' is clearly less specific than +// 'a.example.com', but is '/a' more or less specific +// than '/a*'? +func specificity(s string) int { + l := len(s) - strings.Count(s, "*") + for len(s) > 0 { + start := strings.Index(s, "{") + if start < 0 { + return l + } + end := strings.Index(s[start:], "}") + start + 1 + if end <= start { + return l + } + l -= end - start + s = s[end:] + } + return l +} + +type counter struct { + n *int +} + +func (c counter) nextGroup() string { + name := fmt.Sprintf("group%d", *c.n) + *c.n++ + return name +} + +type namedCustomLog struct { + name string + hostnames []string + log *caddy.CustomLog + noHostname bool +} + +// addressWithProtocols associates a listen address with +// the protocols to serve it with +type addressWithProtocols struct { + address string + protocols []string +} + +// sbAddrAssociation is a mapping from a list of +// addresses with protocols, and a list of server +// blocks that are served on those addresses. +type sbAddrAssociation struct { + addressesWithProtocols []addressWithProtocols + serverBlocks []serverBlock +} + +const ( + matcherPrefix = "@" + namedRouteKey = "named_route" +) + +// Interface guard +var _ caddyfile.ServerType = (*ServerType)(nil) diff --git a/caddyconfig/httpcaddyfile/httptype_test.go b/caddyconfig/httpcaddyfile/httptype_test.go new file mode 100644 index 00000000000..69f55501cae --- /dev/null +++ b/caddyconfig/httpcaddyfile/httptype_test.go @@ -0,0 +1,211 @@ +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 + } + } +} diff --git a/caddyconfig/httpcaddyfile/options.go b/caddyconfig/httpcaddyfile/options.go new file mode 100644 index 00000000000..d4a42462435 --- /dev/null +++ b/caddyconfig/httpcaddyfile/options.go @@ -0,0 +1,572 @@ +// 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 ( + "slices" + "strconv" + + "github.com/caddyserver/certmagic" + "github.com/mholt/acmez/v3/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/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddytls" +) + +func init() { + RegisterGlobalOption("debug", parseOptTrue) + RegisterGlobalOption("http_port", parseOptHTTPPort) + RegisterGlobalOption("https_port", parseOptHTTPSPort) + RegisterGlobalOption("default_bind", parseOptDefaultBind) + RegisterGlobalOption("grace_period", parseOptDuration) + RegisterGlobalOption("shutdown_delay", parseOptDuration) + RegisterGlobalOption("default_sni", parseOptSingleString) + RegisterGlobalOption("fallback_sni", parseOptSingleString) + RegisterGlobalOption("order", parseOptOrder) + RegisterGlobalOption("storage", parseOptStorage) + RegisterGlobalOption("storage_check", parseStorageCheck) + RegisterGlobalOption("storage_clean_interval", parseStorageCleanInterval) + 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("metrics", parseMetricsOptions) + 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()) + + // if directive already had an order, drop it + newOrder := slices.DeleteFunc(directiveOrder, func(d string) bool { + return d == dirName + }) + + // act on the positional; if it's First or Last, we're done right away + 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 + + // if it's Before or After, continue + 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() + } + + // get the position of the target directive + targetIndex := slices.Index(newOrder, otherDir) + if targetIndex == -1 { + return nil, d.Errf("directive '%s' not found", otherDir) + } + // if we're inserting after, we need to increment the index to go after + if pos == After { + targetIndex++ + } + // insert the directive into the new order + newOrder = slices.Insert(newOrder, targetIndex, dirName) + + 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 parseStorageCheck(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("storage_check must be 'off'") + } + return val, nil +} + +func parseStorageCleanInterval(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 false, nil + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return nil, d.Errf("failed to parse storage_clean_interval, must be a duration or 'off' %w", err) + } + return caddy.Duration(dur), 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 parseOptDefaultBind(d *caddyfile.Dispenser, _ any) (any, error) { + d.Next() // consume option name + + var addresses, protocols []string + addresses = d.RemainingArgs() + + if len(addresses) == 0 { + addresses = append(addresses, "") + } + + for d.NextBlock(0) { + switch d.Val() { + case "protocols": + protocols = d.RemainingArgs() + if len(protocols) == 0 { + return nil, d.Errf("protocols requires one or more arguments") + } + default: + return nil, d.Errf("unknown subdirective: %s", d.Val()) + } + } + + return []ConfigValue{{Class: "bind", Value: addressesWithProtocols{ + addresses: addresses, + protocols: protocols, + }}}, 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": + return nil, d.Errf("the on_demand_tls 'interval' option is no longer supported, remove it from your config") + + case "burst": + return nil, d.Errf("the on_demand_tls 'burst' option is no longer supported, remove it from your config") + + 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 + val := d.RemainingArgs() + if len(val) == 0 { + return "", d.ArgErr() + } + for _, v := range val { + switch v { + case "off": + case "disable_redirects": + case "disable_certs": + case "ignore_loaded_certs": + case "prefer_wildcard": + break + + default: + return "", d.Errf("auto_https must be one of 'off', 'disable_redirects', 'disable_certs', 'ignore_loaded_certs', or 'prefer_wildcard'") + } + } + return val, nil +} + +func unmarshalCaddyfileMetricsOptions(d *caddyfile.Dispenser) (any, error) { + d.Next() // consume option name + metrics := new(caddyhttp.Metrics) + for d.NextBlock(0) { + switch d.Val() { + case "per_host": + metrics.PerHost = true + default: + return nil, d.Errf("unrecognized servers option '%s'", d.Val()) + } + } + return metrics, nil +} + +func parseMetricsOptions(d *caddyfile.Dispenser, _ any) (any, error) { + return unmarshalCaddyfileMetricsOptions(d) +} + +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 ... +// format ... +// level +// include +// exclude +// } +// +// 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) +} diff --git a/caddyconfig/httpcaddyfile/options_test.go b/caddyconfig/httpcaddyfile/options_test.go new file mode 100644 index 00000000000..bc9e8813404 --- /dev/null +++ b/caddyconfig/httpcaddyfile/options_test.go @@ -0,0 +1,64 @@ +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) + } + } +} diff --git a/caddyconfig/httpcaddyfile/pkiapp.go b/caddyconfig/httpcaddyfile/pkiapp.go new file mode 100644 index 00000000000..c57263baf92 --- /dev/null +++ b/caddyconfig/httpcaddyfile/pkiapp.go @@ -0,0 +1,229 @@ +// 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 [] { +// name +// root_cn +// intermediate_cn +// intermediate_lifetime +// root { +// cert +// key +// format +// } +// intermediate { +// cert +// key +// 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 +} diff --git a/caddyconfig/httpcaddyfile/serveroptions.go b/caddyconfig/httpcaddyfile/serveroptions.go new file mode 100644 index 00000000000..40a8af20962 --- /dev/null +++ b/caddyconfig/httpcaddyfile/serveroptions.go @@ -0,0 +1,344 @@ +// 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" + "slices" + + "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 slices.Contains(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 slices.Contains(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": + caddy.Log().Warn("The nested 'metrics' option inside `servers` is deprecated and will be removed in the next major version. Use the global 'metrics' option instead.") + serverOpts.Metrics = new(caddyhttp.Metrics) + for nesting := d.Nesting(); d.NextBlock(nesting); { + switch d.Val() { + case "per_host": + serverOpts.Metrics.PerHost = true + } + } + + 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 + optsIndex := slices.IndexFunc(serverOpts, func(s serverOptions) bool { + return s.ListenerAddress == "" || slices.Contains(server.Listen, s.ListenerAddress) + }) + + // if none apply, then move to the next server + if optsIndex == -1 { + continue + } + opts := serverOpts[optsIndex] + + // 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 +} diff --git a/caddyconfig/httpcaddyfile/shorthands.go b/caddyconfig/httpcaddyfile/shorthands.go new file mode 100644 index 00000000000..ca6e4f92c90 --- /dev/null +++ b/caddyconfig/httpcaddyfile/shorthands.go @@ -0,0 +1,102 @@ +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{ + "{host}", "{http.request.host}", + "{hostport}", "{http.request.hostport}", + "{port}", "{http.request.port}", + "{orig_method}", "{http.request.orig_method}", + "{orig_uri}", "{http.request.orig_uri}", + "{orig_path}", "{http.request.orig_uri.path}", + "{orig_dir}", "{http.request.orig_uri.path.dir}", + "{orig_file}", "{http.request.orig_uri.path.file}", + "{orig_query}", "{http.request.orig_uri.query}", + "{orig_?query}", "{http.request.orig_uri.prefixed_query}", + "{method}", "{http.request.method}", + "{uri}", "{http.request.uri}", + "{path}", "{http.request.uri.path}", + "{dir}", "{http.request.uri.path.dir}", + "{file}", "{http.request.uri.path.file}", + "{query}", "{http.request.uri.query}", + "{?query}", "{http.request.uri.prefixed_query}", + "{remote}", "{http.request.remote}", + "{remote_host}", "{http.request.remote.host}", + "{remote_port}", "{http.request.remote.port}", + "{scheme}", "{http.request.scheme}", + "{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) + } + } + } +} diff --git a/caddyconfig/httpcaddyfile/testdata/import_variadic.txt b/caddyconfig/httpcaddyfile/testdata/import_variadic.txt new file mode 100644 index 00000000000..f1e50e0109f --- /dev/null +++ b/caddyconfig/httpcaddyfile/testdata/import_variadic.txt @@ -0,0 +1,9 @@ +(t2) { + respond 200 { + body {args[:]} + } +} + +:8082 { + import t2 false +} \ No newline at end of file diff --git a/caddyconfig/httpcaddyfile/testdata/import_variadic_snippet.txt b/caddyconfig/httpcaddyfile/testdata/import_variadic_snippet.txt new file mode 100644 index 00000000000..a02fcf90a6f --- /dev/null +++ b/caddyconfig/httpcaddyfile/testdata/import_variadic_snippet.txt @@ -0,0 +1,9 @@ +(t1) { + respond 200 { + body {args[:]} + } +} + +:8081 { + import t1 false +} \ No newline at end of file diff --git a/caddyconfig/httpcaddyfile/testdata/import_variadic_with_import.txt b/caddyconfig/httpcaddyfile/testdata/import_variadic_with_import.txt new file mode 100644 index 00000000000..ab1b32d90b7 --- /dev/null +++ b/caddyconfig/httpcaddyfile/testdata/import_variadic_with_import.txt @@ -0,0 +1,15 @@ +(t1) { + respond 200 { + body {args[:]} + } +} + +:8081 { + import t1 false +} + +import import_variadic.txt + +:8083 { + import t2 true +} \ No newline at end of file diff --git a/caddyconfig/httpcaddyfile/tlsapp.go b/caddyconfig/httpcaddyfile/tlsapp.go new file mode 100644 index 00000000000..71b52492660 --- /dev/null +++ b/caddyconfig/httpcaddyfile/tlsapp.go @@ -0,0 +1,804 @@ +// 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" + "slices" + "sort" + "strconv" + "strings" + + "github.com/caddyserver/certmagic" + "github.com/mholt/acmez/v3/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 := []string{} + 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 !slices.Contains(autoHTTPS, "off") { + for _, pair := range pairings { + for _, sb := range pair.serverBlocks { + for _, addr := range sb.parsedKeys { + if addr.Host != "" { + continue + } + + // this server block has a hostless key, now + // go through and add all the hosts to the set + for _, otherAddr := range sb.parsedKeys { + 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) + } + + // collect all hosts that have a wildcard in them, and arent HTTP + wildcardHosts := []string{} + // hosts that have been explicitly marked to be automated, + // even if covered by another wildcard + forcedAutomatedNames := make(map[string]struct{}) + for _, p := range pairings { + var addresses []string + for _, addressWithProtocols := range p.addressesWithProtocols { + addresses = append(addresses, addressWithProtocols.address) + } + if !listenersUseAnyPortOtherThan(addresses, httpPort) { + continue + } + for _, sblock := range p.serverBlocks { + for _, addr := range sblock.parsedKeys { + if strings.HasPrefix(addr.Host, "*.") { + wildcardHosts = append(wildcardHosts, addr.Host[2:]) + } + } + } + } + + for _, p := range pairings { + // avoid setting up TLS automation policies for a server that is HTTP-only + var addresses []string + for _, addressWithProtocols := range p.addressesWithProtocols { + addresses = append(addresses, addressWithProtocols.address) + } + if !listenersUseAnyPortOtherThan(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 + } + + // make a plain copy so we can compare whether we made any changes + apCopy, 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 + } + + // collect hosts that are forced to be automated + if _, ok := sblock.pile["tls.force_automate"]; ok { + for _, host := range sblockHosts { + forcedAutomatedNames[host] = struct{}{} + } + } + + // 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 asserted, ok := cfgVal.Value.(addressesWithProtocols); ok && len(asserted.addresses) > 0 { + bindHost = asserted.addresses[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 + } + + hostsNotHTTP := sblock.hostsFromKeysNotHTTP(httpPort) + sort.Strings(hostsNotHTTP) // solely for deterministic test results + + // if the we prefer wildcards and the AP is unchanged, + // then we can skip this AP because it should be covered + // by an AP with a wildcard + if slices.Contains(autoHTTPS, "prefer_wildcard") { + if hostsCoveredByWildcard(hostsNotHTTP, wildcardHosts) && + reflect.DeepEqual(ap, apCopy) { + continue + } + } + + // associate our new automation policy with this server block's hosts + ap.SubjectsRaw = hostsNotHTTP + + // 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 + } + + // if the storage clean interval is a boolean, then it's "off" to disable cleaning + if sc, ok := options["storage_check"].(string); ok && sc == "off" { + tlsApp.DisableStorageCheck = true + } + + // if the storage clean interval is a boolean, then it's "off" to disable cleaning + if sci, ok := options["storage_clean_interval"].(bool); ok && !sci { + tlsApp.DisableStorageClean = true + } + + // 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 !slices.Contains(autoHTTPS, "off") && !slices.Contains(autoHTTPS, "disable_certs") { + for h := range httpsHostsSharedWithHostlessKey { + al = append(al, h) + if !certmagic.SubjectQualifiesForPublicCert(h) { + internalAP.SubjectsRaw = append(internalAP.SubjectsRaw, h) + } + } + } + for name := range forcedAutomatedNames { + if slices.Contains(al, name) { + continue + } + al = append(al, name) + } + slices.Sort(al) // to stabilize the adapt output + 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 && !slices.Contains(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 = slices.Delete(aps, i, 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 = slices.Delete(aps, j, 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 = slices.Delete(aps, i, i+1) + i-- + continue outer + } + } else { + // avoid repeated subjects + for _, subj := range aps[j].SubjectsRaw { + if !slices.Contains(aps[i].SubjectsRaw, subj) { + aps[i].SubjectsRaw = append(aps[i].SubjectsRaw, subj) + } + } + aps = slices.Delete(aps, j, 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 { + inSuperset := slices.ContainsFunc(b.SubjectsRaw, func(bSubj string) bool { + return certmagic.MatchWildcard(aSubj, bSubj) + }) + 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 { + return !slices.ContainsFunc(ap.SubjectsRaw, func(i string) bool { + return !subjectQualifiesForPublicCert(ap, i) || isTailscaleDomain(i) + }) +} + +func isTailscaleDomain(name string) bool { + return strings.HasSuffix(strings.ToLower(name), ".ts.net") +} + +func hostsCoveredByWildcard(hosts []string, wildcards []string) bool { + if len(hosts) == 0 || len(wildcards) == 0 { + return false + } + for _, host := range hosts { + for _, wildcard := range wildcards { + if strings.HasPrefix(host, "*.") { + continue + } + if certmagic.MatchWildcard(host, "*."+wildcard) { + return true + } + } + } + return false +} diff --git a/caddyconfig/httpcaddyfile/tlsapp_test.go b/caddyconfig/httpcaddyfile/tlsapp_test.go new file mode 100644 index 00000000000..d8edbdf9b19 --- /dev/null +++ b/caddyconfig/httpcaddyfile/tlsapp_test.go @@ -0,0 +1,56 @@ +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) + } + } +} diff --git a/caddyconfig/httploader.go b/caddyconfig/httploader.go new file mode 100644 index 00000000000..a25041a3435 --- /dev/null +++ b/caddyconfig/httploader.go @@ -0,0 +1,218 @@ +// 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. If 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) diff --git a/caddyconfig/load.go b/caddyconfig/load.go new file mode 100644 index 00000000000..9f5cda9050f --- /dev/null +++ b/caddyconfig/load.go @@ -0,0 +1,214 @@ +// 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) + }, +} diff --git a/caddyfile/dispenser.go b/caddyfile/dispenser.go deleted file mode 100644 index edb7bfafa39..00000000000 --- a/caddyfile/dispenser.go +++ /dev/null @@ -1,246 +0,0 @@ -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 -} diff --git a/caddyfile/json.go b/caddyfile/json.go deleted file mode 100644 index 16aab4e3d29..00000000000 --- a/caddyfile/json.go +++ /dev/null @@ -1,184 +0,0 @@ -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"` -} diff --git a/caddyfile/json_test.go b/caddyfile/json_test.go deleted file mode 100644 index 97d553c3368..00000000000 --- a/caddyfile/json_test.go +++ /dev/null @@ -1,164 +0,0 @@ -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) - } -} -*/ diff --git a/caddyfile/lexer.go b/caddyfile/lexer.go deleted file mode 100644 index ea3bad99931..00000000000 --- a/caddyfile/lexer.go +++ /dev/null @@ -1,136 +0,0 @@ -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) - } -} diff --git a/caddyfile/lexer_test.go b/caddyfile/lexer_test.go deleted file mode 100644 index 2a0b175c36e..00000000000 --- a/caddyfile/lexer_test.go +++ /dev/null @@ -1,171 +0,0 @@ -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 - } - } -} diff --git a/caddyfile/parse.go b/caddyfile/parse.go deleted file mode 100644 index 13b23db05be..00000000000 --- a/caddyfile/parse.go +++ /dev/null @@ -1,416 +0,0 @@ -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 -} diff --git a/caddyfile/parse_test.go b/caddyfile/parse_test.go deleted file mode 100644 index ad7cfa98377..00000000000 --- a/caddyfile/parse_test.go +++ /dev/null @@ -1,502 +0,0 @@ -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 -} diff --git a/caddyhttp/basicauth/basicauth.go b/caddyhttp/basicauth/basicauth.go deleted file mode 100644 index 5661a017d68..00000000000 --- a/caddyhttp/basicauth/basicauth.go +++ /dev/null @@ -1,171 +0,0 @@ -// 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: " - // 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 - } -} diff --git a/caddyhttp/basicauth/basicauth_test.go b/caddyhttp/basicauth/basicauth_test.go deleted file mode 100644 index 3f0113b2e15..00000000000 --- a/caddyhttp/basicauth/basicauth_test.go +++ /dev/null @@ -1,182 +0,0 @@ -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) - } - } -} diff --git a/caddyhttp/basicauth/setup.go b/caddyhttp/basicauth/setup.go deleted file mode 100644 index 9fa6ddd6003..00000000000 --- a/caddyhttp/basicauth/setup.go +++ /dev/null @@ -1,99 +0,0 @@ -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) -} diff --git a/caddyhttp/basicauth/setup_test.go b/caddyhttp/basicauth/setup_test.go deleted file mode 100644 index 1075b2bc0a3..00000000000 --- a/caddyhttp/basicauth/setup_test.go +++ /dev/null @@ -1,164 +0,0 @@ -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) - } - } - } -} diff --git a/caddyhttp/bind/bind.go b/caddyhttp/bind/bind.go deleted file mode 100644 index c69aa0b1dac..00000000000 --- a/caddyhttp/bind/bind.go +++ /dev/null @@ -1,24 +0,0 @@ -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 -} diff --git a/caddyhttp/bind/bind_test.go b/caddyhttp/bind/bind_test.go deleted file mode 100644 index 8d81f84af28..00000000000 --- a/caddyhttp/bind/bind_test.go +++ /dev/null @@ -1,24 +0,0 @@ -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) - } -} diff --git a/caddyhttp/browse/browse.go b/caddyhttp/browse/browse.go deleted file mode 100644 index e1368059c0b..00000000000 --- a/caddyhttp/browse/browse.go +++ /dev/null @@ -1,514 +0,0 @@ -// 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 - } - } - - isDir := f.IsDir() || isSymlinkTargetDir(f, urlPath, config) - - if 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: isDir, - 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 - } - - // a bit strange but we want Stat thru the jailed filesystem to be safe - target, err := config.Fs.Root.Open(filepath.Join(urlPath, f.Name())) - if err != nil { - return false - } - defer target.Close() - targetInto, err := target.Stat() - if err != nil { - return false - } - - return targetInto.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 -} diff --git a/caddyhttp/browse/browse_test.go b/caddyhttp/browse/browse_test.go deleted file mode 100644 index 1e666021c68..00000000000 --- a/caddyhttp/browse/browse_test.go +++ /dev/null @@ -1,602 +0,0 @@ -package browse - -import ( - "context" - "encoding/json" - "io/ioutil" - "net/http" - "net/http/httptest" - "net/url" - "os" - "path/filepath" - "sort" - "strings" - "testing" - "text/template" - "time" - - "github.com/mholt/caddy/caddyhttp/httpserver" - "github.com/mholt/caddy/caddyhttp/staticfiles" -) - -const testDirPrefix = "caddy_browse_test" - -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 := ` - - -Template - - -

Header

- -

/photos/

- -test1
- -test.html
- -test2.html
- -test3.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) - } - } -} - -func TestDirSymlink(t *testing.T) { - testCases := []struct { - source string - target string - pathScope string - url string - expectedName string - expectedURL string - }{ - // test case can expect a directory "dir" and a symlink to it called "symlink" - - {"dir", "$TMP/rel_symlink_to_dir", "/", "/", - "rel_symlink_to_dir", "./rel_symlink_to_dir/"}, - {"$TMP/dir", "$TMP/abs_symlink_to_dir", "/", "/", - "abs_symlink_to_dir", "./abs_symlink_to_dir/"}, - - {"../../dir", "$TMP/sub/dir/rel_symlink_to_dir", "/", "/sub/dir/", - "rel_symlink_to_dir", "./rel_symlink_to_dir/"}, - {"$TMP/dir", "$TMP/sub/dir/abs_symlink_to_dir", "/", "/sub/dir/", - "abs_symlink_to_dir", "./abs_symlink_to_dir/"}, - - {"../../dir", "$TMP/with/scope/rel_symlink_to_dir", "/with/scope", "/with/scope/", - "rel_symlink_to_dir", "./rel_symlink_to_dir/"}, - {"$TMP/dir", "$TMP/with/scope/abs_symlink_to_dir", "/with/scope", "/with/scope/", - "abs_symlink_to_dir", "./abs_symlink_to_dir/"}, - - {"../../../../dir", "$TMP/with/scope/sub/dir/rel_symlink_to_dir", "/with/scope", "/with/scope/sub/dir/", - "rel_symlink_to_dir", "./rel_symlink_to_dir/"}, - {"$TMP/dir", "$TMP/with/scope/sub/dir/abs_symlink_to_dir", "/with/scope", "/with/scope/sub/dir/", - "abs_symlink_to_dir", "./abs_symlink_to_dir/"}, - - {"symlink", "$TMP/rel_symlink_to_symlink", "/", "/", - "rel_symlink_to_symlink", "./rel_symlink_to_symlink/"}, - {"$TMP/symlink", "$TMP/abs_symlink_to_symlink", "/", "/", - "abs_symlink_to_symlink", "./abs_symlink_to_symlink/"}, - - {"../../symlink", "$TMP/sub/dir/rel_symlink_to_symlink", "/", "/sub/dir/", - "rel_symlink_to_symlink", "./rel_symlink_to_symlink/"}, - {"$TMP/symlink", "$TMP/sub/dir/abs_symlink_to_symlink", "/", "/sub/dir/", - "abs_symlink_to_symlink", "./abs_symlink_to_symlink/"}, - - {"../../symlink", "$TMP/with/scope/rel_symlink_to_symlink", "/with/scope", "/with/scope/", - "rel_symlink_to_symlink", "./rel_symlink_to_symlink/"}, - {"$TMP/symlink", "$TMP/with/scope/abs_symlink_to_symlink", "/with/scope", "/with/scope/", - "abs_symlink_to_symlink", "./abs_symlink_to_symlink/"}, - - {"../../../../symlink", "$TMP/with/scope/sub/dir/rel_symlink_to_symlink", "/with/scope", "/with/scope/sub/dir/", - "rel_symlink_to_symlink", "./rel_symlink_to_symlink/"}, - {"$TMP/symlink", "$TMP/with/scope/sub/dir/abs_symlink_to_symlink", "/with/scope", "/with/scope/sub/dir/", - "abs_symlink_to_symlink", "./abs_symlink_to_symlink/"}, - } - - for i, tc := range testCases { - func() { - tmpdir, err := ioutil.TempDir("", testDirPrefix) - if err != nil { - t.Fatalf("failed to create test directory: %v", err) - } - defer os.RemoveAll(tmpdir) - - if err := os.MkdirAll(filepath.Join(tmpdir, "dir"), 0755); err != nil { - t.Fatalf("failed to create test dir 'dir': %v", err) - } - if err := os.Symlink("dir", filepath.Join(tmpdir, "symlink")); err != nil { - t.Fatalf("failed to create test symlink 'symlink': %v", err) - } - - sourceResolved := strings.Replace(tc.source, "$TMP", tmpdir, -1) - targetResolved := strings.Replace(tc.target, "$TMP", tmpdir, -1) - - if err := os.MkdirAll(filepath.Dir(sourceResolved), 0755); err != nil { - t.Fatalf("failed to create source symlink dir: %v", err) - } - if err := os.MkdirAll(filepath.Dir(targetResolved), 0755); err != nil { - t.Fatalf("failed to create target symlink dir: %v", err) - } - if err := os.Symlink(sourceResolved, targetResolved); err != nil { - t.Fatalf("failed to create test symlink: %v", err) - } - - 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: tc.pathScope, - Fs: staticfiles.FileServer{ - Root: http.Dir(tmpdir), - }, - }, - }, - } - - req, err := http.NewRequest("GET", tc.url, nil) - req.Header.Add("Accept", "application/json") - if err != nil { - t.Fatalf("Test %d - could not create HTTP request: %v", i, err) - } - - rec := httptest.NewRecorder() - - returnCode, _ := b.ServeHTTP(rec, req) - if returnCode != http.StatusOK { - t.Fatalf("Test %d - wrong return code, expected %d, got %d", - i, http.StatusOK, returnCode) - } - - type jsonEntry struct { - Name string - IsDir bool - IsSymlink bool - URL string - } - var entries []jsonEntry - if err := json.Unmarshal(rec.Body.Bytes(), &entries); err != nil { - t.Fatalf("Test %d - failed to parse json: %v", i, err) - } - - found := false - for _, e := range entries { - if e.Name != tc.expectedName { - continue - } - found = true - if !e.IsDir { - t.Fatalf("Test %d - expected to be a dir, got %v", i, e.IsDir) - } - if !e.IsSymlink { - t.Fatalf("Test %d - expected to be a symlink, got %v", i, e.IsSymlink) - } - if e.URL != tc.expectedURL { - t.Fatalf("Test %d - wrong URL, expected %v, got %v", i, tc.expectedURL, e.URL) - } - } - if !found { - t.Fatalf("Test %d - failed, could not find name %v", i, tc.expectedName) - } - }() - } -} diff --git a/caddyhttp/browse/setup.go b/caddyhttp/browse/setup.go deleted file mode 100644 index 0abf728efdd..00000000000 --- a/caddyhttp/browse/setup.go +++ /dev/null @@ -1,488 +0,0 @@ -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 = ` - - - {{html .Name}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

- {{range $i, $crumb := .Breadcrumbs}}{{html $crumb.Text}}{{if ne $i 0}}/{{end}}{{end}} -

-
-
-
-
- {{.NumDirs}} director{{if eq 1 .NumDirs}}y{{else}}ies{{end}} - {{.NumFiles}} file{{if ne 1 .NumFiles}}s{{end}} - {{- if ne 0 .ItemsLimitedTo}} - (of which only {{.ItemsLimitedTo}} are displayed) - {{- end}} - -
-
-
- - - - - - - - - - {{- if .CanGoUp}} - - - - - - {{- end}} - {{- range .Items}} - - - {{- if .IsDir}} - - {{- else}} - - {{- end}} - - - {{- end}} - -
- {{- if and (eq .Sort "namedirfirst") (ne .Order "desc")}} - - {{- else if and (eq .Sort "namedirfirst") (ne .Order "asc")}} - - {{- else}} - - {{- end}} - - {{- if and (eq .Sort "name") (ne .Order "desc")}} - Name - {{- else if and (eq .Sort "name") (ne .Order "asc")}} - Name - {{- else}} - Name - {{- end}} - - {{- if and (eq .Sort "size") (ne .Order "desc")}} - Size - {{- else if and (eq .Sort "size") (ne .Order "asc")}} - Size - {{- else}} - Size - {{- end}} - - {{- if and (eq .Sort "time") (ne .Order "desc")}} - Modified - {{- else if and (eq .Sort "time") (ne .Order "asc")}} - Modified - {{- else}} - Modified - {{- end}} -
- - Go up - -
- - {{- if .IsDir}} - - {{- else}} - - {{- end}} - {{html .Name}} - - {{.HumanSize}}
-
-
- - - -` diff --git a/caddyhttp/browse/setup_test.go b/caddyhttp/browse/setup_test.go deleted file mode 100644 index 534eb16f688..00000000000 --- a/caddyhttp/browse/setup_test.go +++ /dev/null @@ -1,81 +0,0 @@ -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) - } -} diff --git a/caddyhttp/browse/testdata/header.html b/caddyhttp/browse/testdata/header.html deleted file mode 100644 index 78e5a6a4856..00000000000 --- a/caddyhttp/browse/testdata/header.html +++ /dev/null @@ -1 +0,0 @@ -

Header

diff --git a/caddyhttp/browse/testdata/photos.tpl b/caddyhttp/browse/testdata/photos.tpl deleted file mode 100644 index 5163ca0085b..00000000000 --- a/caddyhttp/browse/testdata/photos.tpl +++ /dev/null @@ -1,13 +0,0 @@ - - - -Template - - -{{.Include "header.html"}} -

{{.Path}}

-{{range .Items}} -{{.Name}}
-{{end}} - - diff --git a/caddyhttp/browse/testdata/photos/hidden.html b/caddyhttp/browse/testdata/photos/hidden.html deleted file mode 100644 index e0f5c6c20a5..00000000000 --- a/caddyhttp/browse/testdata/photos/hidden.html +++ /dev/null @@ -1 +0,0 @@ -Should be hidden diff --git a/caddyhttp/browse/testdata/photos/test.html b/caddyhttp/browse/testdata/photos/test.html deleted file mode 100644 index 40535a2234e..00000000000 --- a/caddyhttp/browse/testdata/photos/test.html +++ /dev/null @@ -1,8 +0,0 @@ - - - -Test - - - - diff --git a/caddyhttp/browse/testdata/photos/test1/test.html b/caddyhttp/browse/testdata/photos/test1/test.html deleted file mode 100644 index 40535a2234e..00000000000 --- a/caddyhttp/browse/testdata/photos/test1/test.html +++ /dev/null @@ -1,8 +0,0 @@ - - - -Test - - - - diff --git a/caddyhttp/browse/testdata/photos/test2.html b/caddyhttp/browse/testdata/photos/test2.html deleted file mode 100644 index 8e10c578040..00000000000 --- a/caddyhttp/browse/testdata/photos/test2.html +++ /dev/null @@ -1,8 +0,0 @@ - - - -Test 2 - - - - diff --git a/caddyhttp/browse/testdata/photos/test3.html b/caddyhttp/browse/testdata/photos/test3.html deleted file mode 100644 index 6c70af2fa67..00000000000 --- a/caddyhttp/browse/testdata/photos/test3.html +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/caddyhttp/caddyhttp.go b/caddyhttp/caddyhttp.go deleted file mode 100644 index 99215bdb74a..00000000000 --- a/caddyhttp/caddyhttp.go +++ /dev/null @@ -1,35 +0,0 @@ -package caddyhttp - -import ( - // plug in the server - _ "github.com/mholt/caddy/caddyhttp/httpserver" - - // plug in the standard directives - _ "github.com/mholt/caddy/caddyhttp/basicauth" - _ "github.com/mholt/caddy/caddyhttp/bind" - _ "github.com/mholt/caddy/caddyhttp/browse" - _ "github.com/mholt/caddy/caddyhttp/errors" - _ "github.com/mholt/caddy/caddyhttp/expvar" - _ "github.com/mholt/caddy/caddyhttp/extensions" - _ "github.com/mholt/caddy/caddyhttp/fastcgi" - _ "github.com/mholt/caddy/caddyhttp/gzip" - _ "github.com/mholt/caddy/caddyhttp/header" - _ "github.com/mholt/caddy/caddyhttp/index" - _ "github.com/mholt/caddy/caddyhttp/internalsrv" - _ "github.com/mholt/caddy/caddyhttp/limits" - _ "github.com/mholt/caddy/caddyhttp/log" - _ "github.com/mholt/caddy/caddyhttp/markdown" - _ "github.com/mholt/caddy/caddyhttp/mime" - _ "github.com/mholt/caddy/caddyhttp/pprof" - _ "github.com/mholt/caddy/caddyhttp/proxy" - _ "github.com/mholt/caddy/caddyhttp/push" - _ "github.com/mholt/caddy/caddyhttp/redirect" - _ "github.com/mholt/caddy/caddyhttp/requestid" - _ "github.com/mholt/caddy/caddyhttp/rewrite" - _ "github.com/mholt/caddy/caddyhttp/root" - _ "github.com/mholt/caddy/caddyhttp/status" - _ "github.com/mholt/caddy/caddyhttp/templates" - _ "github.com/mholt/caddy/caddyhttp/timeouts" - _ "github.com/mholt/caddy/caddyhttp/websocket" - _ "github.com/mholt/caddy/startupshutdown" -) diff --git a/caddyhttp/caddyhttp_test.go b/caddyhttp/caddyhttp_test.go deleted file mode 100644 index 99ffdbefa0f..00000000000 --- a/caddyhttp/caddyhttp_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package caddyhttp - -import ( - "strings" - "testing" - - "github.com/mholt/caddy" -) - -// TODO: this test could be improved; the purpose is to -// ensure that the standard plugins are in fact plugged in -// and registered properly; this is a quick/naive way to do it. -func TestStandardPlugins(t *testing.T) { - numStandardPlugins := 32 // importing caddyhttp plugs in this many plugins - s := caddy.DescribePlugins() - if got, want := strings.Count(s, "\n"), numStandardPlugins+5; got != want { - t.Errorf("Expected all standard plugins to be plugged in, got:\n%s", s) - } -} diff --git a/caddyhttp/errors/errors.go b/caddyhttp/errors/errors.go deleted file mode 100644 index a3412ce5020..00000000000 --- a/caddyhttp/errors/errors.go +++ /dev/null @@ -1,150 +0,0 @@ -// Package errors implements an HTTP error handling middleware. -package errors - -import ( - "fmt" - "io" - "net/http" - "os" - "runtime" - "strings" - "time" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func init() { - caddy.RegisterPlugin("errors", caddy.Plugin{ - ServerType: "http", - Action: setup, - }) -} - -// ErrorHandler handles HTTP errors (and errors from other middleware). -type ErrorHandler struct { - Next httpserver.Handler - GenericErrorPage string // default error page filename - ErrorPages map[int]string // map of status code to filename - Log *httpserver.Logger - Debug bool // if true, errors are written out to client rather than to a log -} - -func (h ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - defer h.recovery(w, r) - - status, err := h.Next.ServeHTTP(w, r) - - if err != nil { - errMsg := fmt.Sprintf("%s [ERROR %d %s] %v", time.Now().Format(timeFormat), status, r.URL.Path, err) - if h.Debug { - // Write error to response instead of to log - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(status) - fmt.Fprintln(w, errMsg) - return 0, err // returning 0 signals that a response has been written - } - h.Log.Println(errMsg) - } - - if status >= 400 { - h.errorPage(w, r, status) - return 0, err - } - - return status, err -} - -// errorPage serves a static error page to w according to the status -// code. If there is an error serving the error page, a plaintext error -// message is written instead, and the extra error is logged. -func (h ErrorHandler) errorPage(w http.ResponseWriter, r *http.Request, code int) { - // See if an error page for this status code was specified - if pagePath, ok := h.findErrorPage(code); ok { - // Try to open it - errorPage, err := os.Open(pagePath) - if err != nil { - // An additional error handling an error... - h.Log.Printf("%s [NOTICE %d %s] could not load error page: %v", - time.Now().Format(timeFormat), code, r.URL.String(), err) - httpserver.DefaultErrorFunc(w, r, code) - return - } - defer errorPage.Close() - - // Copy the page body into the response - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.WriteHeader(code) - _, err = io.Copy(w, errorPage) - - if err != nil { - // Epic fail... sigh. - h.Log.Printf("%s [NOTICE %d %s] could not respond with %s: %v", - time.Now().Format(timeFormat), code, r.URL.String(), pagePath, err) - httpserver.DefaultErrorFunc(w, r, code) - } - - return - } - - // Default error response - httpserver.DefaultErrorFunc(w, r, code) -} - -func (h ErrorHandler) findErrorPage(code int) (string, bool) { - if pagePath, ok := h.ErrorPages[code]; ok { - return pagePath, true - } - - if h.GenericErrorPage != "" { - return h.GenericErrorPage, true - } - - return "", false -} - -func (h ErrorHandler) recovery(w http.ResponseWriter, r *http.Request) { - rec := recover() - if rec == nil { - return - } - - // Obtain source of panic - // From: https://gist.github.com/swdunlop/9629168 - var name, file string // function name, file name - var line int - var pc [16]uintptr - n := runtime.Callers(3, pc[:]) - for _, pc := range pc[:n] { - fn := runtime.FuncForPC(pc) - if fn == nil { - continue - } - file, line = fn.FileLine(pc) - name = fn.Name() - if !strings.HasPrefix(name, "runtime.") { - break - } - } - - // Trim file path - delim := "/caddy/" - pkgPathPos := strings.Index(file, delim) - if pkgPathPos > -1 && len(file) > pkgPathPos+len(delim) { - file = file[pkgPathPos+len(delim):] - } - - panicMsg := fmt.Sprintf("%s [PANIC %s] %s:%d - %v", time.Now().Format(timeFormat), r.URL.String(), file, line, rec) - if h.Debug { - // Write error and stack trace to the response rather than to a log - var stackBuf [4096]byte - stack := stackBuf[:runtime.Stack(stackBuf[:], false)] - httpserver.WriteTextResponse(w, http.StatusInternalServerError, fmt.Sprintf("%s\n\n%s", panicMsg, stack)) - } else { - // Currently we don't use the function name, since file:line is more conventional - h.Log.Printf(panicMsg) - h.errorPage(w, r, http.StatusInternalServerError) - } -} - -const timeFormat = "02/Jan/2006:15:04:05 -0700" diff --git a/caddyhttp/errors/errors_test.go b/caddyhttp/errors/errors_test.go deleted file mode 100644 index 4833ecb9a42..00000000000 --- a/caddyhttp/errors/errors_test.go +++ /dev/null @@ -1,258 +0,0 @@ -package errors - -import ( - "bytes" - "errors" - "fmt" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "strconv" - "strings" - "testing" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestErrors(t *testing.T) { - // create a temporary page - const content = "This is a error page" - - path, err := createErrorPageFile("errors_test.html", content) - if err != nil { - t.Fatal(err) - } - defer os.Remove(path) - - buf := bytes.Buffer{} - em := ErrorHandler{ - ErrorPages: map[int]string{ - http.StatusNotFound: path, - http.StatusForbidden: "not_exist_file", - }, - Log: httpserver.NewTestLogger(&buf), - } - _, notExistErr := os.Open("not_exist_file") - - testErr := errors.New("test error") - tests := []struct { - next httpserver.Handler - expectedCode int - expectedBody string - expectedLog string - expectedErr error - }{ - { - next: genErrorHandler(http.StatusOK, nil, "normal"), - expectedCode: http.StatusOK, - expectedBody: "normal", - expectedLog: "", - expectedErr: nil, - }, - { - next: genErrorHandler(http.StatusMovedPermanently, testErr, ""), - expectedCode: http.StatusMovedPermanently, - expectedBody: "", - expectedLog: fmt.Sprintf("[ERROR %d %s] %v\n", http.StatusMovedPermanently, "/", testErr), - expectedErr: testErr, - }, - { - next: genErrorHandler(http.StatusBadRequest, nil, ""), - expectedCode: 0, - expectedBody: fmt.Sprintf("%d %s\n", http.StatusBadRequest, - http.StatusText(http.StatusBadRequest)), - expectedLog: "", - expectedErr: nil, - }, - { - next: genErrorHandler(http.StatusNotFound, nil, ""), - expectedCode: 0, - expectedBody: content, - expectedLog: "", - expectedErr: nil, - }, - { - next: genErrorHandler(http.StatusForbidden, nil, ""), - expectedCode: 0, - expectedBody: fmt.Sprintf("%d %s\n", http.StatusForbidden, - http.StatusText(http.StatusForbidden)), - expectedLog: fmt.Sprintf("[NOTICE %d /] could not load error page: %v\n", - http.StatusForbidden, notExistErr), - expectedErr: nil, - }, - } - - req, err := http.NewRequest("GET", "/", nil) - if err != nil { - t.Fatal(err) - } - for i, test := range tests { - em.Next = test.next - buf.Reset() - rec := httptest.NewRecorder() - code, err := em.ServeHTTP(rec, req) - - if err != test.expectedErr { - t.Errorf("Test %d: Expected error %v, but got %v", - i, test.expectedErr, err) - } - if code != test.expectedCode { - t.Errorf("Test %d: Expected status code %d, but got %d", - i, test.expectedCode, code) - } - if body := rec.Body.String(); body != test.expectedBody { - t.Errorf("Test %d: Expected body %q, but got %q", - i, test.expectedBody, body) - } - if log := buf.String(); !strings.Contains(log, test.expectedLog) { - t.Errorf("Test %d: Expected log %q, but got %q", - i, test.expectedLog, log) - } - } -} - -func TestVisibleErrorWithPanic(t *testing.T) { - const panicMsg = "I'm a panic" - eh := ErrorHandler{ - ErrorPages: make(map[int]string), - Debug: true, - Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - panic(panicMsg) - }), - } - - req, err := http.NewRequest("GET", "/", nil) - if err != nil { - t.Fatal(err) - } - rec := httptest.NewRecorder() - - code, err := eh.ServeHTTP(rec, req) - - if code != 0 { - t.Errorf("Expected error handler to return 0 (it should write to response), got status %d", code) - } - if err != nil { - t.Errorf("Expected error handler to return nil error (it should panic!), but got '%v'", err) - } - - body := rec.Body.String() - - if !strings.Contains(body, "[PANIC /] caddyhttp/errors/errors_test.go") { - t.Errorf("Expected response body to contain error log line, but it didn't:\n%s", body) - } - if !strings.Contains(body, panicMsg) { - t.Errorf("Expected response body to contain panic message, but it didn't:\n%s", body) - } - if len(body) < 500 { - t.Errorf("Expected response body to contain stack trace, but it was too short: len=%d", len(body)) - } -} - -func TestGenericErrorPage(t *testing.T) { - // create temporary generic error page - const genericErrorContent = "This is a generic error page" - - genericErrorPagePath, err := createErrorPageFile("generic_error_test.html", genericErrorContent) - if err != nil { - t.Fatal(err) - } - defer os.Remove(genericErrorPagePath) - - // create temporary error page - const notFoundErrorContent = "This is a error page" - - notFoundErrorPagePath, err := createErrorPageFile("not_found.html", notFoundErrorContent) - if err != nil { - t.Fatal(err) - } - defer os.Remove(notFoundErrorPagePath) - - buf := bytes.Buffer{} - em := ErrorHandler{ - GenericErrorPage: genericErrorPagePath, - ErrorPages: map[int]string{ - http.StatusNotFound: notFoundErrorPagePath, - }, - Log: httpserver.NewTestLogger(&buf), - } - - tests := []struct { - next httpserver.Handler - expectedCode int - expectedBody string - expectedLog string - expectedErr error - }{ - { - next: genErrorHandler(http.StatusNotFound, nil, ""), - expectedCode: 0, - expectedBody: notFoundErrorContent, - expectedLog: "", - expectedErr: nil, - }, - { - next: genErrorHandler(http.StatusInternalServerError, nil, ""), - expectedCode: 0, - expectedBody: genericErrorContent, - expectedLog: "", - expectedErr: nil, - }, - } - - req, err := http.NewRequest("GET", "/", nil) - if err != nil { - t.Fatal(err) - } - - for i, test := range tests { - em.Next = test.next - buf.Reset() - rec := httptest.NewRecorder() - code, err := em.ServeHTTP(rec, req) - - if err != test.expectedErr { - t.Errorf("Test %d: Expected error %v, but got %v", - i, test.expectedErr, err) - } - if code != test.expectedCode { - t.Errorf("Test %d: Expected status code %d, but got %d", - i, test.expectedCode, code) - } - if body := rec.Body.String(); body != test.expectedBody { - t.Errorf("Test %d: Expected body %q, but got %q", - i, test.expectedBody, body) - } - if log := buf.String(); !strings.Contains(log, test.expectedLog) { - t.Errorf("Test %d: Expected log %q, but got %q", - i, test.expectedLog, log) - } - } -} - -func genErrorHandler(status int, err error, body string) httpserver.Handler { - return httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - if len(body) > 0 { - w.Header().Set("Content-Length", strconv.Itoa(len(body))) - fmt.Fprint(w, body) - } - return status, err - }) -} - -func createErrorPageFile(name string, content string) (string, error) { - errorPageFilePath := filepath.Join(os.TempDir(), name) - f, err := os.Create(errorPageFilePath) - if err != nil { - return "", err - } - - _, err = f.WriteString(content) - if err != nil { - return "", err - } - f.Close() - - return errorPageFilePath, nil -} diff --git a/caddyhttp/errors/setup.go b/caddyhttp/errors/setup.go deleted file mode 100644 index 7844841579c..00000000000 --- a/caddyhttp/errors/setup.go +++ /dev/null @@ -1,120 +0,0 @@ -package errors - -import ( - "log" - "os" - "path/filepath" - "strconv" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -// setup configures a new errors middleware instance. -func setup(c *caddy.Controller) error { - handler, err := errorsParse(c) - - if err != nil { - return err - } - - handler.Log.Attach(c) - - httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler { - handler.Next = next - return handler - }) - - return nil -} - -func errorsParse(c *caddy.Controller) (*ErrorHandler, error) { - - // Very important that we make a pointer because the startup - // function that opens the log file must have access to the - // same instance of the handler, not a copy. - handler := &ErrorHandler{ - ErrorPages: make(map[int]string), - Log: &httpserver.Logger{}, - } - - cfg := httpserver.GetConfig(c) - - optionalBlock := func() error { - for c.NextBlock() { - - what := c.Val() - where := c.RemainingArgs() - - if httpserver.IsLogRollerSubdirective(what) { - var err error - err = httpserver.ParseRoller(handler.Log.Roller, what, where...) - if err != nil { - return err - } - } else { - if len(where) != 1 { - return c.ArgErr() - } - where := where[0] - - // Error page; ensure it exists - if !filepath.IsAbs(where) { - where = filepath.Join(cfg.Root, where) - } - - f, err := os.Open(where) - if err != nil { - log.Printf("[WARNING] Unable to open error page '%s': %v", where, err) - } - f.Close() - - if what == "*" { - if handler.GenericErrorPage != "" { - return c.Errf("Duplicate status code entry: %s", what) - } - handler.GenericErrorPage = where - } else { - whatInt, err := strconv.Atoi(what) - if err != nil { - return c.Err("Expecting a numeric status code or '*', got '" + what + "'") - } - - if _, exists := handler.ErrorPages[whatInt]; exists { - return c.Errf("Duplicate status code entry: %s", what) - } - - handler.ErrorPages[whatInt] = where - } - } - } - return nil - } - - for c.Next() { - // weird hack to avoid having the handler values overwritten. - if c.Val() == "}" { - continue - } - - args := c.RemainingArgs() - - if len(args) == 1 { - switch args[0] { - case "visible": - handler.Debug = true - default: - handler.Log.Output = args[0] - handler.Log.Roller = httpserver.DefaultLogRoller() - } - } - - // Configuration may be in a block - err := optionalBlock() - if err != nil { - return handler, err - } - } - - return handler, nil -} diff --git a/caddyhttp/errors/setup_test.go b/caddyhttp/errors/setup_test.go deleted file mode 100644 index db92f2b18ec..00000000000 --- a/caddyhttp/errors/setup_test.go +++ /dev/null @@ -1,185 +0,0 @@ -package errors - -import ( - "path/filepath" - "reflect" - "testing" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestSetup(t *testing.T) { - c := caddy.NewTestController("http", `errors`) - err := setup(c) - if err != nil { - t.Errorf("Expected no errors, got: %v", err) - } - mids := httpserver.GetConfig(c).Middleware() - if len(mids) == 0 { - t.Fatal("Expected middlewares, was nil instead") - } - - handler := mids[0](httpserver.EmptyNext) - myHandler, ok := handler.(*ErrorHandler) - if !ok { - t.Fatalf("Expected handler to be type ErrorHandler, got: %#v", handler) - } - - expectedLogger := &httpserver.Logger{} - - if !reflect.DeepEqual(expectedLogger, myHandler.Log) { - t.Errorf("Expected '%v' as the default Log, got: '%v'", expectedLogger, myHandler.Log) - } - - if !httpserver.SameNext(myHandler.Next, httpserver.EmptyNext) { - t.Error("'Next' field of handler was not set properly") - } - - // Test Startup function -- TODO - // if len(c.Startup) == 0 { - // t.Fatal("Expected 1 startup function, had 0") - // } - // c.Startup[0]() - // if myHandler.Log == nil { - // t.Error("Expected Log to be non-nil after startup because Debug is not enabled") - // } -} - -func TestErrorsParse(t *testing.T) { - testAbs, err := filepath.Abs("./404.html") - if err != nil { - t.Error(err) - } - tests := []struct { - inputErrorsRules string - shouldErr bool - expectedErrorHandler ErrorHandler - }{ - {`errors`, false, ErrorHandler{ - ErrorPages: map[int]string{}, - Log: &httpserver.Logger{}, - }}, - {`errors errors.txt`, false, ErrorHandler{ - ErrorPages: map[int]string{}, - Log: &httpserver.Logger{ - Output: "errors.txt", - Roller: httpserver.DefaultLogRoller(), - }, - }}, - {`errors visible`, false, ErrorHandler{ - ErrorPages: map[int]string{}, - Debug: true, - Log: &httpserver.Logger{}, - }}, - {`errors errors.txt { - 404 404.html - 500 500.html -}`, false, ErrorHandler{ - ErrorPages: map[int]string{ - 404: "404.html", - 500: "500.html", - }, - Log: &httpserver.Logger{ - Output: "errors.txt", - Roller: httpserver.DefaultLogRoller(), - }, - }}, - {`errors errors.txt { - rotate_size 2 - rotate_age 10 - rotate_keep 3 - rotate_compress - }`, false, ErrorHandler{ - ErrorPages: map[int]string{}, - Log: &httpserver.Logger{ - Output: "errors.txt", Roller: &httpserver.LogRoller{ - MaxSize: 2, - MaxAge: 10, - MaxBackups: 3, - Compress: true, - LocalTime: true, - }, - }, - }}, - {`errors errors.txt { - rotate_size 3 - rotate_age 11 - rotate_keep 5 - 404 404.html - 503 503.html -}`, false, ErrorHandler{ - ErrorPages: map[int]string{ - 404: "404.html", - 503: "503.html", - }, - Log: &httpserver.Logger{ - Output: "errors.txt", - Roller: &httpserver.LogRoller{ - MaxSize: 3, - MaxAge: 11, - MaxBackups: 5, - Compress: false, - LocalTime: true, - }, - }, - }}, - {`errors errors.txt { - * generic_error.html - 404 404.html - 503 503.html -}`, false, ErrorHandler{ - Log: &httpserver.Logger{ - Output: "errors.txt", - Roller: httpserver.DefaultLogRoller(), - }, - GenericErrorPage: "generic_error.html", - ErrorPages: map[int]string{ - 404: "404.html", - 503: "503.html", - }, - }}, - // test absolute file path - {`errors { - 404 ` + testAbs + ` - }`, - false, ErrorHandler{ - ErrorPages: map[int]string{ - 404: testAbs, - }, - Log: &httpserver.Logger{}, - }}, - {`errors errors.txt { rotate_size 2 rotate_age 10 rotate_keep 3 rotate_compress }`, - true, ErrorHandler{ErrorPages: map[int]string{}, Log: &httpserver.Logger{}}}, - {`errors errors.txt { - rotate_compress invalid - }`, - true, ErrorHandler{ErrorPages: map[int]string{}, Log: &httpserver.Logger{}}}, - // Next two test cases is the detection of duplicate status codes - {`errors { - 503 503.html - 503 503.html - }`, true, ErrorHandler{ErrorPages: map[int]string{}, Log: &httpserver.Logger{}}}, - - {`errors { - * generic_error.html - * generic_error.html - }`, true, ErrorHandler{ErrorPages: map[int]string{}, Log: &httpserver.Logger{}}}, - } - - for i, test := range tests { - actualErrorsRule, err := errorsParse(caddy.NewTestController("http", test.inputErrorsRules)) - - if err == nil && test.shouldErr { - t.Errorf("Test %d didn't error, but it should have", i) - } else if err != nil && !test.shouldErr { - t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err) - } else if err != nil && test.shouldErr { - continue - } - if !reflect.DeepEqual(actualErrorsRule, &test.expectedErrorHandler) { - t.Errorf("Test %d expect %v, but got %v", i, - test.expectedErrorHandler, actualErrorsRule) - } - } -} diff --git a/caddyhttp/expvar/expvar.go b/caddyhttp/expvar/expvar.go deleted file mode 100644 index d3107a0489e..00000000000 --- a/caddyhttp/expvar/expvar.go +++ /dev/null @@ -1,45 +0,0 @@ -package expvar - -import ( - "expvar" - "fmt" - "net/http" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -// ExpVar is a simple struct to hold expvar's configuration -type ExpVar struct { - Next httpserver.Handler - Resource Resource -} - -// ServeHTTP handles requests to expvar's configured entry point with -// expvar, or passes all other requests up the chain. -func (e ExpVar) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - if httpserver.Path(r.URL.Path).Matches(string(e.Resource)) { - expvarHandler(w, r) - return 0, nil - } - return e.Next.ServeHTTP(w, r) -} - -// expvarHandler returns a JSON object will all the published variables. -// -// This is lifted straight from the expvar package. -func expvarHandler(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=utf-8") - fmt.Fprintf(w, "{\n") - first := true - expvar.Do(func(kv expvar.KeyValue) { - if !first { - fmt.Fprintf(w, ",\n") - } - first = false - fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value) - }) - fmt.Fprintf(w, "\n}\n") -} - -// Resource contains the path to the expvar entry point -type Resource string diff --git a/caddyhttp/expvar/expvar_test.go b/caddyhttp/expvar/expvar_test.go deleted file mode 100644 index dfc7cb31184..00000000000 --- a/caddyhttp/expvar/expvar_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package expvar - -import ( - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestExpVar(t *testing.T) { - rw := ExpVar{ - Next: httpserver.HandlerFunc(contentHandler), - Resource: "/d/v", - } - - tests := []struct { - from string - result int - }{ - {"/d/v", 0}, - {"/x/y", http.StatusOK}, - } - - 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) - } - 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 -} diff --git a/caddyhttp/expvar/setup.go b/caddyhttp/expvar/setup.go deleted file mode 100644 index e411844e917..00000000000 --- a/caddyhttp/expvar/setup.go +++ /dev/null @@ -1,69 +0,0 @@ -package expvar - -import ( - "expvar" - "runtime" - "sync" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func init() { - caddy.RegisterPlugin("expvar", caddy.Plugin{ - ServerType: "http", - Action: setup, - }) -} - -// setup configures a new ExpVar middleware instance. -func setup(c *caddy.Controller) error { - resource, err := expVarParse(c) - if err != nil { - return err - } - - // publish any extra information/metrics we may want to capture - publishExtraVars() - - ev := ExpVar{Resource: resource} - - httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler { - ev.Next = next - return ev - }) - - return nil -} - -func expVarParse(c *caddy.Controller) (Resource, error) { - var resource Resource - var err error - - for c.Next() { - args := c.RemainingArgs() - switch len(args) { - case 0: - resource = Resource(defaultExpvarPath) - case 1: - resource = Resource(args[0]) - default: - return resource, c.ArgErr() - } - } - - return resource, err -} - -func publishExtraVars() { - // By using sync.Once instead of an init() function, we don't clutter - // the app's expvar export unnecessarily, or risk colliding with it. - publishOnce.Do(func() { - expvar.Publish("Goroutines", expvar.Func(func() interface{} { - return runtime.NumGoroutine() - })) - }) -} - -var publishOnce sync.Once // publishing variables should only be done once -var defaultExpvarPath = "/debug/vars" diff --git a/caddyhttp/expvar/setup_test.go b/caddyhttp/expvar/setup_test.go deleted file mode 100644 index b53719ebcd4..00000000000 --- a/caddyhttp/expvar/setup_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package expvar - -import ( - "testing" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestSetup(t *testing.T) { - c := caddy.NewTestController("http", `expvar`) - err := setup(c) - if err != nil { - t.Errorf("Expected no errors, got: %v", err) - } - mids := httpserver.GetConfig(c).Middleware() - if len(mids) == 0 { - t.Fatal("Expected middleware, got 0 instead") - } - - c = caddy.NewTestController("http", `expvar /d/v`) - err = setup(c) - if err != nil { - t.Errorf("Expected no errors, 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.(ExpVar) - if !ok { - t.Fatalf("Expected handler to be type ExpVar, got: %#v", handler) - } - if myHandler.Resource != "/d/v" { - t.Errorf("Expected /d/v as expvar resource") - } - if !httpserver.SameNext(myHandler.Next, httpserver.EmptyNext) { - t.Error("'Next' field of handler was not set properly") - } -} diff --git a/caddyhttp/extensions/ext.go b/caddyhttp/extensions/ext.go deleted file mode 100644 index 2c02fa73473..00000000000 --- a/caddyhttp/extensions/ext.go +++ /dev/null @@ -1,44 +0,0 @@ -// Package extensions contains middleware for clean URLs. -// -// The root path of the site is passed in as well as possible extensions -// to try internally for paths requested that don't match an existing -// resource. The first path+ext combination that matches a valid file -// will be used. -package extensions - -import ( - "net/http" - "os" - "path" - "strings" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -// Ext can assume an extension from clean URLs. -// It tries extensions in the order listed in Extensions. -type Ext struct { - // Next handler in the chain - Next httpserver.Handler - - // Path to site root - Root string - - // List of extensions to try - Extensions []string -} - -// ServeHTTP implements the httpserver.Handler interface. -func (e Ext) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - urlpath := strings.TrimSuffix(r.URL.Path, "/") - if len(r.URL.Path) > 0 && path.Ext(urlpath) == "" && r.URL.Path[len(r.URL.Path)-1] != '/' { - for _, ext := range e.Extensions { - _, err := os.Stat(httpserver.SafePath(e.Root, urlpath) + ext) - if err == nil { - r.URL.Path = urlpath + ext - break - } - } - } - return e.Next.ServeHTTP(w, r) -} diff --git a/caddyhttp/extensions/setup.go b/caddyhttp/extensions/setup.go deleted file mode 100644 index 5cec873a0c4..00000000000 --- a/caddyhttp/extensions/setup.go +++ /dev/null @@ -1,53 +0,0 @@ -package extensions - -import ( - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func init() { - caddy.RegisterPlugin("ext", caddy.Plugin{ - ServerType: "http", - Action: setup, - }) -} - -// setup configures a new instance of 'extensions' middleware for clean URLs. -func setup(c *caddy.Controller) error { - cfg := httpserver.GetConfig(c) - root := cfg.Root - - exts, err := extParse(c) - if err != nil { - return err - } - - httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler { - return Ext{ - Next: next, - Extensions: exts, - Root: root, - } - }) - - return nil -} - -// extParse sets up an instance of extension middleware -// from a middleware controller and returns a list of extensions. -func extParse(c *caddy.Controller) ([]string, error) { - var exts []string - - for c.Next() { - // At least one extension is required - if !c.NextArg() { - return exts, c.ArgErr() - } - exts = append(exts, c.Val()) - - // Tack on any other extensions that may have been listed - exts = append(exts, c.RemainingArgs()...) - } - - return exts, nil -} diff --git a/caddyhttp/extensions/setup_test.go b/caddyhttp/extensions/setup_test.go deleted file mode 100644 index 26c50e3c44a..00000000000 --- a/caddyhttp/extensions/setup_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package extensions - -import ( - "testing" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestSetup(t *testing.T) { - c := caddy.NewTestController("http", `ext .html .htm .php`) - err := setup(c) - if err != nil { - t.Fatalf("Expected no errors, got: %v", err) - } - - mids := httpserver.GetConfig(c).Middleware() - if len(mids) == 0 { - t.Fatal("Expected middleware, had 0 instead") - } - - handler := mids[0](httpserver.EmptyNext) - myHandler, ok := handler.(Ext) - - if !ok { - t.Fatalf("Expected handler to be type Ext, got: %#v", handler) - } - - if myHandler.Extensions[0] != ".html" { - t.Errorf("Expected .html in the list of Extensions") - } - if myHandler.Extensions[1] != ".htm" { - t.Errorf("Expected .htm in the list of Extensions") - } - if myHandler.Extensions[2] != ".php" { - t.Errorf("Expected .php in the list of Extensions") - } - if !httpserver.SameNext(myHandler.Next, httpserver.EmptyNext) { - t.Error("'Next' field of handler was not set properly") - } - -} - -func TestExtParse(t *testing.T) { - tests := []struct { - inputExts string - shouldErr bool - expectedExts []string - }{ - {`ext .html .htm .php`, false, []string{".html", ".htm", ".php"}}, - {`ext .php .html .xml`, false, []string{".php", ".html", ".xml"}}, - {`ext .txt .php .xml`, false, []string{".txt", ".php", ".xml"}}, - } - for i, test := range tests { - actualExts, err := extParse(caddy.NewTestController("http", test.inputExts)) - - 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(actualExts) != len(test.expectedExts) { - t.Fatalf("Test %d expected %d rules, but got %d", - i, len(test.expectedExts), len(actualExts)) - } - for j, actualExt := range actualExts { - if actualExt != test.expectedExts[j] { - t.Fatalf("Test %d expected %dth extension to be %s , but got %s", - i, j, test.expectedExts[j], actualExt) - } - } - } - -} diff --git a/caddyhttp/fastcgi/fastcgi.go b/caddyhttp/fastcgi/fastcgi.go deleted file mode 100644 index 550a795b61a..00000000000 --- a/caddyhttp/fastcgi/fastcgi.go +++ /dev/null @@ -1,417 +0,0 @@ -// Package fastcgi has middleware that acts as a FastCGI client. Requests -// that get forwarded to FastCGI stop the middleware execution chain. -// The most common use for this package is to serve PHP websites via php-fpm. -package fastcgi - -import ( - "context" - "errors" - "io" - "net" - "net/http" - "net/url" - "os" - "path" - "path/filepath" - "strconv" - "strings" - "sync/atomic" - "time" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -// Handler is a middleware type that can handle requests as a FastCGI client. -type Handler struct { - Next httpserver.Handler - Rules []Rule - Root string - FileSys http.FileSystem - - // These are sent to CGI scripts in env variables - SoftwareName string - SoftwareVersion string - ServerName string - ServerPort string -} - -// ServeHTTP satisfies the httpserver.Handler interface. -func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - for _, rule := range h.Rules { - // First requirement: Base path must match request path. If it doesn't, - // we check to make sure the leading slash is not missing, and if so, - // we check again with it prepended. This is in case people forget - // a leading slash when performing rewrites, and we don't want to expose - // the contents of the (likely PHP) script. See issue #1645. - hpath := httpserver.Path(r.URL.Path) - if !hpath.Matches(rule.Path) { - if strings.HasPrefix(string(hpath), "/") { - // this is a normal-looking path, and it doesn't match; try next rule - continue - } - hpath = httpserver.Path("/" + string(hpath)) // prepend leading slash - if !hpath.Matches(rule.Path) { - // even after fixing the request path, it still doesn't match; try next rule - continue - } - } - // The path must also be allowed (not ignored). - if !rule.AllowedPath(r.URL.Path) { - continue - } - - // In addition to matching the path, a request must meet some - // other criteria before being proxied as FastCGI. For example, - // we probably want to exclude static assets (CSS, JS, images...) - // but we also want to be flexible for the script we proxy to. - - fpath := r.URL.Path - - if idx, ok := httpserver.IndexFile(h.FileSys, fpath, rule.IndexFiles); ok { - fpath = idx - // Index file present. - // If request path cannot be split, return error. - if !rule.canSplit(fpath) { - return http.StatusInternalServerError, ErrIndexMissingSplit - } - } else { - // No index file present. - // If request path cannot be split, ignore request. - if !rule.canSplit(fpath) { - continue - } - } - - // These criteria work well in this order for PHP sites - if !h.exists(fpath) || fpath[len(fpath)-1] == '/' || strings.HasSuffix(fpath, rule.Ext) { - - // Create environment for CGI script - env, err := h.buildEnv(r, rule, fpath) - if err != nil { - return http.StatusInternalServerError, err - } - - // Connect to FastCGI gateway - network, address := parseAddress(rule.Address()) - - ctx := context.Background() - if rule.ConnectTimeout > 0 { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, rule.ConnectTimeout) - defer cancel() - } - - fcgiBackend, err := DialContext(ctx, network, address) - if err != nil { - return http.StatusBadGateway, err - } - defer fcgiBackend.Close() - - // read/write timeouts - if err := fcgiBackend.SetReadTimeout(rule.ReadTimeout); err != nil { - return http.StatusInternalServerError, err - } - if err := fcgiBackend.SetSendTimeout(rule.SendTimeout); err != nil { - return http.StatusInternalServerError, err - } - - var resp *http.Response - - var contentLength int64 - // if ContentLength is already set - if r.ContentLength > 0 { - contentLength = r.ContentLength - } else { - contentLength, _ = strconv.ParseInt(r.Header.Get("Content-Length"), 10, 64) - } - switch r.Method { - case "HEAD": - resp, err = fcgiBackend.Head(env) - case "GET": - resp, err = fcgiBackend.Get(env) - case "OPTIONS": - resp, err = fcgiBackend.Options(env) - default: - resp, err = fcgiBackend.Post(env, r.Method, r.Header.Get("Content-Type"), r.Body, contentLength) - } - - if resp != nil && resp.Body != nil { - defer resp.Body.Close() - } - - if err != nil { - if err, ok := err.(net.Error); ok && err.Timeout() { - return http.StatusGatewayTimeout, err - } else if err != io.EOF { - return http.StatusBadGateway, err - } - } - - // Write response header - writeHeader(w, resp) - - // Write the response body - _, err = io.Copy(w, resp.Body) - if err != nil { - return http.StatusBadGateway, err - } - - // Log any stderr output from upstream - if fcgiBackend.stderr.Len() != 0 { - // Remove trailing newline, error logger already does this. - err = LogError(strings.TrimSuffix(fcgiBackend.stderr.String(), "\n")) - } - - // Normally we would return the status code if it is an error status (>= 400), - // however, upstream FastCGI apps don't know about our contract and have - // probably already written an error page. So we just return 0, indicating - // that the response body is already written. However, we do return any - // error value so it can be logged. - // Note that the proxy middleware works the same way, returning status=0. - return 0, err - } - } - - return h.Next.ServeHTTP(w, r) -} - -// parseAddress returns the network and address of fcgiAddress. -// The first string is the network, "tcp" or "unix", implied from the scheme and address. -// The second string is fcgiAddress, with scheme prefixes removed. -// The two returned strings can be used as parameters to the Dial() function. -func parseAddress(fcgiAddress string) (string, string) { - // check if address has tcp scheme explicitly set - if strings.HasPrefix(fcgiAddress, "tcp://") { - return "tcp", fcgiAddress[len("tcp://"):] - } - // check if address has fastcgi scheme explicitly set - if strings.HasPrefix(fcgiAddress, "fastcgi://") { - return "tcp", fcgiAddress[len("fastcgi://"):] - } - // check if unix socket - if trim := strings.HasPrefix(fcgiAddress, "unix"); strings.HasPrefix(fcgiAddress, "/") || trim { - if trim { - return "unix", fcgiAddress[len("unix:"):] - } - return "unix", fcgiAddress - } - // default case, a plain tcp address with no scheme - return "tcp", fcgiAddress -} - -func writeHeader(w http.ResponseWriter, r *http.Response) { - for key, vals := range r.Header { - for _, val := range vals { - w.Header().Add(key, val) - } - } - w.WriteHeader(r.StatusCode) -} - -func (h Handler) exists(path string) bool { - if _, err := os.Stat(h.Root + path); err == nil { - return true - } - return false -} - -// buildEnv returns a set of CGI environment variables for the request. -func (h Handler) buildEnv(r *http.Request, rule Rule, fpath string) (map[string]string, error) { - var env map[string]string - - // Get absolute path of requested resource - absPath := filepath.Join(rule.Root, fpath) - - // Separate remote IP and port; more lenient than net.SplitHostPort - var ip, port string - if idx := strings.LastIndex(r.RemoteAddr, ":"); idx > -1 { - ip = r.RemoteAddr[:idx] - port = r.RemoteAddr[idx+1:] - } else { - ip = r.RemoteAddr - } - - // Remove [] from IPv6 addresses - ip = strings.Replace(ip, "[", "", 1) - ip = strings.Replace(ip, "]", "", 1) - - // Split path in preparation for env variables. - // Previous rule.canSplit checks ensure this can never be -1. - splitPos := rule.splitPos(fpath) - - // Request has the extension; path was split successfully - docURI := fpath[:splitPos+len(rule.SplitPath)] - pathInfo := fpath[splitPos+len(rule.SplitPath):] - scriptName := fpath - scriptFilename := absPath - - // Strip PATH_INFO from SCRIPT_NAME - scriptName = strings.TrimSuffix(scriptName, pathInfo) - - // Get the request URI from context. The context stores the original URI in case - // it was changed by a middleware such as rewrite. By default, we pass the - // original URI in as the value of REQUEST_URI (the user can overwrite this - // if desired). Most PHP apps seem to want the original URI. Besides, this is - // how nginx defaults: http://stackoverflow.com/a/12485156/1048862 - reqURL, _ := r.Context().Value(httpserver.OriginalURLCtxKey).(url.URL) - - // Retrieve name of remote user that was set by some downstream middleware such as basicauth. - remoteUser, _ := r.Context().Value(httpserver.RemoteUserCtxKey).(string) - - // Some variables are unused but cleared explicitly to prevent - // the parent environment from interfering. - env = map[string]string{ - // Variables defined in CGI 1.1 spec - "AUTH_TYPE": "", // Not used - "CONTENT_LENGTH": r.Header.Get("Content-Length"), - "CONTENT_TYPE": r.Header.Get("Content-Type"), - "GATEWAY_INTERFACE": "CGI/1.1", - "PATH_INFO": pathInfo, - "QUERY_STRING": r.URL.RawQuery, - "REMOTE_ADDR": ip, - "REMOTE_HOST": ip, // For speed, remote host lookups disabled - "REMOTE_PORT": port, - "REMOTE_IDENT": "", // Not used - "REMOTE_USER": remoteUser, - "REQUEST_METHOD": r.Method, - "SERVER_NAME": h.ServerName, - "SERVER_PORT": h.ServerPort, - "SERVER_PROTOCOL": r.Proto, - "SERVER_SOFTWARE": h.SoftwareName + "/" + h.SoftwareVersion, - - // Other variables - "DOCUMENT_ROOT": rule.Root, - "DOCUMENT_URI": docURI, - "HTTP_HOST": r.Host, // added here, since not always part of headers - "REQUEST_URI": reqURL.RequestURI(), - "SCRIPT_FILENAME": scriptFilename, - "SCRIPT_NAME": scriptName, - } - - // compliance with the CGI specification requires that - // PATH_TRANSLATED should only exist if PATH_INFO is defined. - // Info: https://www.ietf.org/rfc/rfc3875 Page 14 - if env["PATH_INFO"] != "" { - env["PATH_TRANSLATED"] = filepath.Join(rule.Root, pathInfo) // Info: http://www.oreilly.com/openbook/cgi/ch02_04.html - } - - // Some web apps rely on knowing HTTPS or not - if r.TLS != nil { - env["HTTPS"] = "on" - } - - // Add env variables from config (with support for placeholders in values) - replacer := httpserver.NewReplacer(r, nil, "") - for _, envVar := range rule.EnvVars { - env[envVar[0]] = replacer.Replace(envVar[1]) - } - - // Add all HTTP headers to env variables - for field, val := range r.Header { - header := strings.ToUpper(field) - header = headerNameReplacer.Replace(header) - env["HTTP_"+header] = strings.Join(val, ", ") - } - return env, nil -} - -// Rule represents a FastCGI handling rule. -// It is parsed from the fastcgi directive in the Caddyfile, see setup.go. -type Rule struct { - // The base path to match. Required. - Path string - - // upstream load balancer - balancer - - // Always process files with this extension with fastcgi. - Ext string - - // Use this directory as the fastcgi root directory. Defaults to the root - // directory of the parent virtual host. - Root string - - // The path in the URL will be split into two, with the first piece ending - // with the value of SplitPath. The first piece will be assumed as the - // actual resource (CGI script) name, and the second piece will be set to - // PATH_INFO for the CGI script to use. - SplitPath string - - // If the URL ends with '/' (which indicates a directory), these index - // files will be tried instead. - IndexFiles []string - - // Environment Variables - EnvVars [][2]string - - // Ignored paths - IgnoredSubPaths []string - - // The duration used to set a deadline when connecting to an upstream. - ConnectTimeout time.Duration - - // The duration used to set a deadline when reading from the FastCGI server. - ReadTimeout time.Duration - - // The duration used to set a deadline when sending to the FastCGI server. - SendTimeout time.Duration -} - -// balancer is a fastcgi upstream load balancer. -type balancer interface { - // Address picks an upstream address from the - // underlying load balancer. - Address() string -} - -// roundRobin is a round robin balancer for fastcgi upstreams. -type roundRobin struct { - // Known Go bug: https://golang.org/pkg/sync/atomic/#pkg-note-BUG - // must be first field for 64 bit alignment - // on x86 and arm. - index int64 - addresses []string -} - -func (r *roundRobin) Address() string { - index := atomic.AddInt64(&r.index, 1) % int64(len(r.addresses)) - return r.addresses[index] -} - -// canSplit checks if path can split into two based on rule.SplitPath. -func (r Rule) canSplit(path string) bool { - return r.splitPos(path) >= 0 -} - -// splitPos returns the index where path should be split -// based on rule.SplitPath. -func (r Rule) splitPos(path string) int { - if httpserver.CaseSensitivePath { - return strings.Index(path, r.SplitPath) - } - return strings.Index(strings.ToLower(path), strings.ToLower(r.SplitPath)) -} - -// AllowedPath checks if requestPath is not an ignored path. -func (r Rule) AllowedPath(requestPath string) bool { - for _, ignoredSubPath := range r.IgnoredSubPaths { - if httpserver.Path(path.Clean(requestPath)).Matches(path.Join(r.Path, ignoredSubPath)) { - return false - } - } - return true -} - -var ( - headerNameReplacer = strings.NewReplacer(" ", "_", "-", "_") - // ErrIndexMissingSplit describes an index configuration error. - ErrIndexMissingSplit = errors.New("configured index file(s) must include split value") -) - -// LogError is a non fatal error that allows requests to go through. -type LogError string - -// Error satisfies error interface. -func (l LogError) Error() string { - return string(l) -} diff --git a/caddyhttp/fastcgi/fastcgi_test.go b/caddyhttp/fastcgi/fastcgi_test.go deleted file mode 100644 index 6f7afe74297..00000000000 --- a/caddyhttp/fastcgi/fastcgi_test.go +++ /dev/null @@ -1,353 +0,0 @@ -package fastcgi - -import ( - "context" - "net" - "net/http" - "net/http/fcgi" - "net/http/httptest" - "net/url" - "strconv" - "sync" - "testing" - "time" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestServeHTTP(t *testing.T) { - body := "This is some test body content" - - bodyLenStr := strconv.Itoa(len(body)) - listener, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("Unable to create listener for test: %v", err) - } - defer listener.Close() - go fcgi.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Length", bodyLenStr) - w.Write([]byte(body)) - })) - - handler := Handler{ - Next: nil, - Rules: []Rule{{Path: "/", balancer: address(listener.Addr().String())}}, - } - r, err := http.NewRequest("GET", "/", nil) - if err != nil { - t.Fatalf("Unable to create request: %v", err) - } - w := httptest.NewRecorder() - - status, err := handler.ServeHTTP(w, r) - - if got, want := status, 0; got != want { - t.Errorf("Expected returned status code to be %d, got %d", want, got) - } - if err != nil { - t.Errorf("Expected nil error, got: %v", err) - } - if got, want := w.Header().Get("Content-Length"), bodyLenStr; got != want { - t.Errorf("Expected Content-Length to be '%s', got: '%s'", want, got) - } - if got, want := w.Body.String(), body; got != want { - t.Errorf("Expected response body to be '%s', got: '%s'", want, got) - } -} - -func TestRuleParseAddress(t *testing.T) { - getClientTestTable := []struct { - rule *Rule - expectednetwork string - expectedaddress string - }{ - {&Rule{balancer: address("tcp://172.17.0.1:9000")}, "tcp", "172.17.0.1:9000"}, - {&Rule{balancer: address("fastcgi://localhost:9000")}, "tcp", "localhost:9000"}, - {&Rule{balancer: address("172.17.0.15")}, "tcp", "172.17.0.15"}, - {&Rule{balancer: address("/my/unix/socket")}, "unix", "/my/unix/socket"}, - {&Rule{balancer: address("unix:/second/unix/socket")}, "unix", "/second/unix/socket"}, - } - - for _, entry := range getClientTestTable { - if actualnetwork, _ := parseAddress(entry.rule.Address()); actualnetwork != entry.expectednetwork { - t.Errorf("Unexpected network for address string %v. Got %v, expected %v", entry.rule.Address(), actualnetwork, entry.expectednetwork) - } - if _, actualaddress := parseAddress(entry.rule.Address()); actualaddress != entry.expectedaddress { - t.Errorf("Unexpected parsed address for address string %v. Got %v, expected %v", entry.rule.Address(), actualaddress, entry.expectedaddress) - } - } -} - -func TestRuleIgnoredPath(t *testing.T) { - rule := &Rule{ - Path: "/fastcgi", - IgnoredSubPaths: []string{"/download", "/static"}, - } - tests := []struct { - url string - expected bool - }{ - {"/fastcgi", true}, - {"/fastcgi/dl", true}, - {"/fastcgi/download", false}, - {"/fastcgi/download/static", false}, - {"/fastcgi/static", false}, - {"/fastcgi/static/download", false}, - {"/fastcgi/something/download", true}, - {"/fastcgi/something/static", true}, - {"/fastcgi//static", false}, - {"/fastcgi//static//download", false}, - {"/fastcgi//download", false}, - } - - for i, test := range tests { - allowed := rule.AllowedPath(test.url) - if test.expected != allowed { - t.Errorf("Test %d: expected %v found %v", i, test.expected, allowed) - } - } -} - -func TestBuildEnv(t *testing.T) { - testBuildEnv := func(r *http.Request, rule Rule, fpath string, envExpected map[string]string) { - var h Handler - env, err := h.buildEnv(r, rule, fpath) - if err != nil { - t.Error("Unexpected error:", err.Error()) - } - for k, v := range envExpected { - if env[k] != v { - t.Errorf("Unexpected %v. Got %v, expected %v", k, env[k], v) - } - } - } - - rule := Rule{} - url, err := url.Parse("http://localhost:2015/fgci_test.php?test=foobar") - if err != nil { - t.Error("Unexpected error:", err.Error()) - } - - var newReq = func() *http.Request { - r := http.Request{ - Method: "GET", - URL: url, - Proto: "HTTP/1.1", - ProtoMajor: 1, - ProtoMinor: 1, - Host: "localhost:2015", - RemoteAddr: "[2b02:1810:4f2d:9400:70ab:f822:be8a:9093]:51688", - RequestURI: "/fgci_test.php", - Header: map[string][]string{ - "Foo": {"Bar", "two"}, - }, - } - ctx := context.WithValue(r.Context(), httpserver.OriginalURLCtxKey, *r.URL) - return r.WithContext(ctx) - } - - fpath := "/fgci_test.php" - - var newEnv = func() map[string]string { - return map[string]string{ - "REMOTE_ADDR": "2b02:1810:4f2d:9400:70ab:f822:be8a:9093", - "REMOTE_PORT": "51688", - "SERVER_PROTOCOL": "HTTP/1.1", - "QUERY_STRING": "test=foobar", - "REQUEST_METHOD": "GET", - "HTTP_HOST": "localhost:2015", - } - } - - // request - var r *http.Request - - // expected environment variables - var envExpected map[string]string - - // 1. Test for full canonical IPv6 address - r = newReq() - testBuildEnv(r, rule, fpath, envExpected) - - // 2. Test for shorthand notation of IPv6 address - r = newReq() - r.RemoteAddr = "[::1]:51688" - envExpected = newEnv() - envExpected["REMOTE_ADDR"] = "::1" - testBuildEnv(r, rule, fpath, envExpected) - - // 3. Test for IPv4 address - r = newReq() - r.RemoteAddr = "192.168.0.10:51688" - envExpected = newEnv() - envExpected["REMOTE_ADDR"] = "192.168.0.10" - testBuildEnv(r, rule, fpath, envExpected) - - // 4. Test for environment variable - r = newReq() - rule.EnvVars = [][2]string{ - {"HTTP_HOST", "localhost:2016"}, - {"REQUEST_METHOD", "POST"}, - } - envExpected = newEnv() - envExpected["HTTP_HOST"] = "localhost:2016" - envExpected["REQUEST_METHOD"] = "POST" - testBuildEnv(r, rule, fpath, envExpected) - - // 5. Test for environment variable placeholders - r = newReq() - rule.EnvVars = [][2]string{ - {"HTTP_HOST", "{host}"}, - {"CUSTOM_URI", "custom_uri{uri}"}, - {"CUSTOM_QUERY", "custom=true&{query}"}, - } - envExpected = newEnv() - envExpected["HTTP_HOST"] = "localhost:2015" - envExpected["CUSTOM_URI"] = "custom_uri/fgci_test.php?test=foobar" - envExpected["CUSTOM_QUERY"] = "custom=true&test=foobar" - testBuildEnv(r, rule, fpath, envExpected) -} - -func TestReadTimeout(t *testing.T) { - tests := []struct { - sleep time.Duration - readTimeout time.Duration - shouldErr bool - }{ - {75 * time.Millisecond, 50 * time.Millisecond, true}, - {0, -1 * time.Second, true}, - {0, time.Minute, false}, - } - - var wg sync.WaitGroup - - for i, test := range tests { - listener, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("Test %d: Unable to create listener for test: %v", i, err) - } - defer listener.Close() - - handler := Handler{ - Next: nil, - Rules: []Rule{ - { - Path: "/", - balancer: address(listener.Addr().String()), - ReadTimeout: test.readTimeout, - }, - }, - } - r, err := http.NewRequest("GET", "/", nil) - if err != nil { - t.Fatalf("Test %d: Unable to create request: %v", i, err) - } - w := httptest.NewRecorder() - - wg.Add(1) - go fcgi.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - time.Sleep(test.sleep) - w.WriteHeader(http.StatusOK) - wg.Done() - })) - - got, err := handler.ServeHTTP(w, r) - if test.shouldErr { - if err == nil { - t.Errorf("Test %d: Expected i/o timeout error but had none", i) - } else if err, ok := err.(net.Error); !ok || !err.Timeout() { - t.Errorf("Test %d: Expected i/o timeout error, got: '%s'", i, err.Error()) - } - - want := http.StatusGatewayTimeout - if got != want { - t.Errorf("Test %d: Expected returned status code to be %d, got: %d", - i, want, got) - } - } else if err != nil { - t.Errorf("Test %d: Expected nil error, got: %v", i, err) - } - - wg.Wait() - } -} - -func TestSendTimeout(t *testing.T) { - tests := []struct { - sendTimeout time.Duration - shouldErr bool - }{ - {-1 * time.Second, true}, - {time.Minute, false}, - } - - for i, test := range tests { - listener, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("Test %d: Unable to create listener for test: %v", i, err) - } - defer listener.Close() - - handler := Handler{ - Next: nil, - Rules: []Rule{ - { - Path: "/", - balancer: address(listener.Addr().String()), - SendTimeout: test.sendTimeout, - }, - }, - } - r, err := http.NewRequest("GET", "/", nil) - if err != nil { - t.Fatalf("Test %d: Unable to create request: %v", i, err) - } - w := httptest.NewRecorder() - - go fcgi.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - })) - - got, err := handler.ServeHTTP(w, r) - if test.shouldErr { - if err == nil { - t.Errorf("Test %d: Expected i/o timeout error but had none", i) - } else if err, ok := err.(net.Error); !ok || !err.Timeout() { - t.Errorf("Test %d: Expected i/o timeout error, got: '%s'", i, err.Error()) - } - - want := http.StatusGatewayTimeout - if got != want { - t.Errorf("Test %d: Expected returned status code to be %d, got: %d", - i, want, got) - } - } else if err != nil { - t.Errorf("Test %d: Expected nil error, got: %v", i, err) - } - } -} - -func TestBalancer(t *testing.T) { - tests := [][]string{ - {"localhost", "host.local"}, - {"localhost"}, - {"localhost", "host.local", "example.com"}, - {"localhost", "host.local", "example.com", "127.0.0.1"}, - } - for i, test := range tests { - b := address(test...) - for _, host := range test { - a := b.Address() - if a != host { - t.Errorf("Test %d: expected %s, found %s", i, host, a) - } - } - } -} - -func address(addresses ...string) balancer { - return &roundRobin{ - addresses: addresses, - index: -1, - } -} diff --git a/caddyhttp/fastcgi/fcgi_test.php b/caddyhttp/fastcgi/fcgi_test.php deleted file mode 100644 index 3f5e5f2db20..00000000000 --- a/caddyhttp/fastcgi/fcgi_test.php +++ /dev/null @@ -1,79 +0,0 @@ - $val) { - $md5 = md5($val); - - if ($key != $md5) { - $stat = "FAILED"; - echo "server:err ".$md5." != ".$key."\n"; - } - - $length += strlen($key) + strlen($val); - - $ret .= $key."(".strlen($key).") "; - } - $ret .= "] ["; - foreach ($_FILES as $k0 => $val) { - - $error = $val["error"]; - if ($error == UPLOAD_ERR_OK) { - $tmp_name = $val["tmp_name"]; - $name = $val["name"]; - $datafile = "/tmp/test.go"; - move_uploaded_file($tmp_name, $datafile); - $md5 = md5_file($datafile); - - if ($k0 != $md5) { - $stat = "FAILED"; - echo "server:err ".$md5." != ".$key."\n"; - } - - $length += strlen($k0) + filesize($datafile); - - unlink($datafile); - $ret .= $k0."(".strlen($k0).") "; - } - else{ - $stat = "FAILED"; - echo "server:file err ".file_upload_error_message($error)."\n"; - } - } - $ret .= "]"; - echo "server:got data length " .$length."\n"; -} - - -echo "-{$stat}-POST(".count($_POST).") FILE(".count($_FILES).")\n"; - -function file_upload_error_message($error_code) { - switch ($error_code) { - case UPLOAD_ERR_INI_SIZE: - return 'The uploaded file exceeds the upload_max_filesize directive in php.ini'; - case UPLOAD_ERR_FORM_SIZE: - return 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'; - case UPLOAD_ERR_PARTIAL: - return 'The uploaded file was only partially uploaded'; - case UPLOAD_ERR_NO_FILE: - return 'No file was uploaded'; - case UPLOAD_ERR_NO_TMP_DIR: - return 'Missing a temporary folder'; - case UPLOAD_ERR_CANT_WRITE: - return 'Failed to write file to disk'; - case UPLOAD_ERR_EXTENSION: - return 'File upload stopped by extension'; - default: - return 'Unknown upload error'; - } -} \ No newline at end of file diff --git a/caddyhttp/fastcgi/fcgiclient.go b/caddyhttp/fastcgi/fcgiclient.go deleted file mode 100644 index 414e34f6a43..00000000000 --- a/caddyhttp/fastcgi/fcgiclient.go +++ /dev/null @@ -1,566 +0,0 @@ -// Forked Jan. 2015 from http://bitbucket.org/PinIdea/fcgi_client -// (which is forked from https://code.google.com/p/go-fastcgi-client/) - -// This fork contains several fixes and improvements by Matt Holt and -// other contributors to this project. - -// Copyright 2012 Junqing Tan and The Go Authors -// Use of this source code is governed by a BSD-style -// Part of source code is from Go fcgi package - -package fastcgi - -import ( - "bufio" - "bytes" - "context" - "encoding/binary" - "errors" - "io" - "io/ioutil" - "mime/multipart" - "net" - "net/http" - "net/http/httputil" - "net/textproto" - "net/url" - "os" - "path/filepath" - "strconv" - "strings" - "sync" - "time" -) - -// FCGIListenSockFileno describes listen socket file number. -const FCGIListenSockFileno uint8 = 0 - -// FCGIHeaderLen describes header length. -const FCGIHeaderLen uint8 = 8 - -// Version1 describes the version. -const Version1 uint8 = 1 - -// FCGINullRequestID describes the null request ID. -const FCGINullRequestID uint8 = 0 - -// FCGIKeepConn describes keep connection mode. -const FCGIKeepConn uint8 = 1 - -const ( - // BeginRequest is the begin request flag. - BeginRequest uint8 = iota + 1 - // AbortRequest is the abort request flag. - AbortRequest - // EndRequest is the end request flag. - EndRequest - // Params is the parameters flag. - Params - // Stdin is the standard input flag. - Stdin - // Stdout is the standard output flag. - Stdout - // Stderr is the standard error flag. - Stderr - // Data is the data flag. - Data - // GetValues is the get values flag. - GetValues - // GetValuesResult is the get values result flag. - GetValuesResult - // UnknownType is the unknown type flag. - UnknownType - // MaxType is the maximum type flag. - MaxType = UnknownType -) - -const ( - // Responder is the responder flag. - Responder uint8 = iota + 1 - // Authorizer is the authorizer flag. - Authorizer - // Filter is the filter flag. - Filter -) - -const ( - // RequestComplete is the completed request flag. - RequestComplete uint8 = iota - // CantMultiplexConns is the multiplexed connections flag. - CantMultiplexConns - // Overloaded is the overloaded flag. - Overloaded - // UnknownRole is the unknown role flag. - UnknownRole -) - -const ( - // MaxConns is the maximum connections flag. - MaxConns string = "MAX_CONNS" - // MaxRequests is the maximum requests flag. - MaxRequests string = "MAX_REQS" - // MultiplexConns is the multiplex connections flag. - MultiplexConns string = "MPXS_CONNS" -) - -const ( - maxWrite = 65500 // 65530 may work, but for compatibility - maxPad = 255 -) - -type header struct { - Version uint8 - Type uint8 - ID uint16 - ContentLength uint16 - PaddingLength uint8 - Reserved uint8 -} - -// for padding so we don't have to allocate all the time -// not synchronized because we don't care what the contents are -var pad [maxPad]byte - -func (h *header) init(recType uint8, reqID uint16, contentLength int) { - h.Version = 1 - h.Type = recType - h.ID = reqID - h.ContentLength = uint16(contentLength) - h.PaddingLength = uint8(-contentLength & 7) -} - -type record struct { - h header - rbuf []byte -} - -func (rec *record) read(r io.Reader) (buf []byte, err error) { - if err = binary.Read(r, binary.BigEndian, &rec.h); err != nil { - return - } - if rec.h.Version != 1 { - err = errors.New("fcgi: invalid header version") - return - } - if rec.h.Type == EndRequest { - err = io.EOF - return - } - n := int(rec.h.ContentLength) + int(rec.h.PaddingLength) - if len(rec.rbuf) < n { - rec.rbuf = make([]byte, n) - } - if _, err = io.ReadFull(r, rec.rbuf[:n]); err != nil { - return - } - buf = rec.rbuf[:int(rec.h.ContentLength)] - - return -} - -// FCGIClient implements a FastCGI client, which is a standard for -// interfacing external applications with Web servers. -type FCGIClient struct { - mutex sync.Mutex - rwc io.ReadWriteCloser - h header - buf bytes.Buffer - stderr bytes.Buffer - keepAlive bool - reqID uint16 - readTimeout time.Duration - sendTimeout time.Duration -} - -// DialWithDialerContext connects to the fcgi responder at the specified network address, using custom net.Dialer -// and a context. -// See func net.Dial for a description of the network and address parameters. -func DialWithDialerContext(ctx context.Context, network, address string, dialer net.Dialer) (fcgi *FCGIClient, err error) { - var conn net.Conn - conn, err = dialer.DialContext(ctx, network, address) - if err != nil { - return - } - - fcgi = &FCGIClient{ - rwc: conn, - keepAlive: false, - reqID: 1, - } - - return -} - -// DialContext is like Dial but passes ctx to dialer.Dial. -func DialContext(ctx context.Context, network, address string) (fcgi *FCGIClient, err error) { - return DialWithDialerContext(ctx, network, address, net.Dialer{}) -} - -// Dial connects to the fcgi responder at the specified network address, using default net.Dialer. -// See func net.Dial for a description of the network and address parameters. -func Dial(network, address string) (fcgi *FCGIClient, err error) { - return DialContext(context.Background(), network, address) -} - -// Close closes fcgi connnection -func (c *FCGIClient) Close() { - c.rwc.Close() -} - -func (c *FCGIClient) writeRecord(recType uint8, content []byte) (err error) { - c.mutex.Lock() - defer c.mutex.Unlock() - c.buf.Reset() - c.h.init(recType, c.reqID, len(content)) - if err := binary.Write(&c.buf, binary.BigEndian, c.h); err != nil { - return err - } - if _, err := c.buf.Write(content); err != nil { - return err - } - if _, err := c.buf.Write(pad[:c.h.PaddingLength]); err != nil { - return err - } - _, err = c.rwc.Write(c.buf.Bytes()) - return err -} - -func (c *FCGIClient) writeBeginRequest(role uint16, flags uint8) error { - b := [8]byte{byte(role >> 8), byte(role), flags} - return c.writeRecord(BeginRequest, b[:]) -} - -func (c *FCGIClient) writeEndRequest(appStatus int, protocolStatus uint8) error { - b := make([]byte, 8) - binary.BigEndian.PutUint32(b, uint32(appStatus)) - b[4] = protocolStatus - return c.writeRecord(EndRequest, b) -} - -func (c *FCGIClient) writePairs(recType uint8, pairs map[string]string) error { - w := newWriter(c, recType) - b := make([]byte, 8) - nn := 0 - for k, v := range pairs { - m := 8 + len(k) + len(v) - if m > maxWrite { - // param data size exceed 65535 bytes" - vl := maxWrite - 8 - len(k) - v = v[:vl] - } - n := encodeSize(b, uint32(len(k))) - n += encodeSize(b[n:], uint32(len(v))) - m = n + len(k) + len(v) - if (nn + m) > maxWrite { - w.Flush() - nn = 0 - } - nn += m - if _, err := w.Write(b[:n]); err != nil { - return err - } - if _, err := w.WriteString(k); err != nil { - return err - } - if _, err := w.WriteString(v); err != nil { - return err - } - } - w.Close() - return nil -} - -func encodeSize(b []byte, size uint32) int { - if size > 127 { - size |= 1 << 31 - binary.BigEndian.PutUint32(b, size) - return 4 - } - b[0] = byte(size) - return 1 -} - -// bufWriter encapsulates bufio.Writer but also closes the underlying stream when -// Closed. -type bufWriter struct { - closer io.Closer - *bufio.Writer -} - -func (w *bufWriter) Close() error { - if err := w.Writer.Flush(); err != nil { - w.closer.Close() - return err - } - return w.closer.Close() -} - -func newWriter(c *FCGIClient, recType uint8) *bufWriter { - s := &streamWriter{c: c, recType: recType} - w := bufio.NewWriterSize(s, maxWrite) - return &bufWriter{s, w} -} - -// streamWriter abstracts out the separation of a stream into discrete records. -// It only writes maxWrite bytes at a time. -type streamWriter struct { - c *FCGIClient - recType uint8 -} - -func (w *streamWriter) Write(p []byte) (int, error) { - nn := 0 - for len(p) > 0 { - n := len(p) - if n > maxWrite { - n = maxWrite - } - if err := w.c.writeRecord(w.recType, p[:n]); err != nil { - return nn, err - } - nn += n - p = p[n:] - } - return nn, nil -} - -func (w *streamWriter) Close() error { - // send empty record to close the stream - return w.c.writeRecord(w.recType, nil) -} - -type streamReader struct { - c *FCGIClient - buf []byte -} - -func (w *streamReader) Read(p []byte) (n int, err error) { - - if len(p) > 0 { - if len(w.buf) == 0 { - - // filter outputs for error log - for { - rec := &record{} - var buf []byte - buf, err = rec.read(w.c.rwc) - if err != nil { - return - } - // standard error output - if rec.h.Type == Stderr { - w.c.stderr.Write(buf) - continue - } - w.buf = buf - break - } - } - - n = len(p) - if n > len(w.buf) { - n = len(w.buf) - } - copy(p, w.buf[:n]) - w.buf = w.buf[n:] - } - - return -} - -// Do made the request and returns a io.Reader that translates the data read -// from fcgi responder out of fcgi packet before returning it. -func (c *FCGIClient) Do(p map[string]string, req io.Reader) (r io.Reader, err error) { - err = c.writeBeginRequest(uint16(Responder), 0) - if err != nil { - return - } - - err = c.writePairs(Params, p) - if err != nil { - return - } - - body := newWriter(c, Stdin) - if req != nil { - io.Copy(body, req) - } - body.Close() - - r = &streamReader{c: c} - return -} - -// clientCloser is a io.ReadCloser. It wraps a io.Reader with a Closer -// that closes FCGIClient connection. -type clientCloser struct { - *FCGIClient - io.Reader -} - -func (f clientCloser) Close() error { return f.rwc.Close() } - -// Request returns a HTTP Response with Header and Body -// from fcgi responder -func (c *FCGIClient) Request(p map[string]string, req io.Reader) (resp *http.Response, err error) { - r, err := c.Do(p, req) - if err != nil { - return - } - - rb := bufio.NewReader(r) - tp := textproto.NewReader(rb) - resp = new(http.Response) - - // Parse the response headers. - mimeHeader, err := tp.ReadMIMEHeader() - if err != nil && err != io.EOF { - return - } - resp.Header = http.Header(mimeHeader) - - if resp.Header.Get("Status") != "" { - statusParts := strings.SplitN(resp.Header.Get("Status"), " ", 2) - resp.StatusCode, err = strconv.Atoi(statusParts[0]) - if err != nil { - return - } - if len(statusParts) > 1 { - resp.Status = statusParts[1] - } - - } else { - resp.StatusCode = http.StatusOK - } - - // TODO: fixTransferEncoding ? - resp.TransferEncoding = resp.Header["Transfer-Encoding"] - resp.ContentLength, _ = strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64) - - if chunked(resp.TransferEncoding) { - resp.Body = clientCloser{c, httputil.NewChunkedReader(rb)} - } else { - resp.Body = clientCloser{c, ioutil.NopCloser(rb)} - } - return -} - -// Get issues a GET request to the fcgi responder. -func (c *FCGIClient) Get(p map[string]string) (resp *http.Response, err error) { - - p["REQUEST_METHOD"] = "GET" - p["CONTENT_LENGTH"] = "0" - - return c.Request(p, nil) -} - -// Head issues a HEAD request to the fcgi responder. -func (c *FCGIClient) Head(p map[string]string) (resp *http.Response, err error) { - - p["REQUEST_METHOD"] = "HEAD" - p["CONTENT_LENGTH"] = "0" - - return c.Request(p, nil) -} - -// Options issues an OPTIONS request to the fcgi responder. -func (c *FCGIClient) Options(p map[string]string) (resp *http.Response, err error) { - - p["REQUEST_METHOD"] = "OPTIONS" - p["CONTENT_LENGTH"] = "0" - - return c.Request(p, nil) -} - -// Post issues a POST request to the fcgi responder. with request body -// in the format that bodyType specified -func (c *FCGIClient) Post(p map[string]string, method string, bodyType string, body io.Reader, l int64) (resp *http.Response, err error) { - if p == nil { - p = make(map[string]string) - } - - p["REQUEST_METHOD"] = strings.ToUpper(method) - - if len(p["REQUEST_METHOD"]) == 0 || p["REQUEST_METHOD"] == "GET" { - p["REQUEST_METHOD"] = "POST" - } - - p["CONTENT_LENGTH"] = strconv.FormatInt(l, 10) - if len(bodyType) > 0 { - p["CONTENT_TYPE"] = bodyType - } else { - p["CONTENT_TYPE"] = "application/x-www-form-urlencoded" - } - - return c.Request(p, body) -} - -// PostForm issues a POST to the fcgi responder, with form -// as a string key to a list values (url.Values) -func (c *FCGIClient) PostForm(p map[string]string, data url.Values) (resp *http.Response, err error) { - body := bytes.NewReader([]byte(data.Encode())) - return c.Post(p, "POST", "application/x-www-form-urlencoded", body, int64(body.Len())) -} - -// PostFile issues a POST to the fcgi responder in multipart(RFC 2046) standard, -// with form as a string key to a list values (url.Values), -// and/or with file as a string key to a list file path. -func (c *FCGIClient) PostFile(p map[string]string, data url.Values, file map[string]string) (resp *http.Response, err error) { - buf := &bytes.Buffer{} - writer := multipart.NewWriter(buf) - bodyType := writer.FormDataContentType() - - for key, val := range data { - for _, v0 := range val { - err = writer.WriteField(key, v0) - if err != nil { - return - } - } - } - - for key, val := range file { - fd, e := os.Open(val) - if e != nil { - return nil, e - } - defer fd.Close() - - part, e := writer.CreateFormFile(key, filepath.Base(val)) - if e != nil { - return nil, e - } - _, err = io.Copy(part, fd) - if err != nil { - return - } - } - - err = writer.Close() - if err != nil { - return - } - - return c.Post(p, "POST", bodyType, buf, int64(buf.Len())) -} - -// SetReadTimeout sets the read timeout for future calls that read from the -// fcgi responder. A zero value for t means no timeout will be set. -func (c *FCGIClient) SetReadTimeout(t time.Duration) error { - if conn, ok := c.rwc.(net.Conn); ok && t != 0 { - return conn.SetReadDeadline(time.Now().Add(t)) - } - return nil -} - -// SetSendTimeout sets the read timeout for future calls that send data to -// the fcgi responder. A zero value for t means no timeout will be set. -func (c *FCGIClient) SetSendTimeout(t time.Duration) error { - if conn, ok := c.rwc.(net.Conn); ok && t != 0 { - return conn.SetWriteDeadline(time.Now().Add(t)) - } - return nil -} - -// Checks whether chunked is part of the encodings stack -func chunked(te []string) bool { return len(te) > 0 && te[0] == "chunked" } diff --git a/caddyhttp/fastcgi/fcgiclient_test.go b/caddyhttp/fastcgi/fcgiclient_test.go deleted file mode 100644 index ce897b10abe..00000000000 --- a/caddyhttp/fastcgi/fcgiclient_test.go +++ /dev/null @@ -1,275 +0,0 @@ -// NOTE: These tests were adapted from the original -// repository from which this package was forked. -// The tests are slow (~10s) and in dire need of rewriting. -// As such, the tests have been disabled to speed up -// automated builds until they can be properly written. - -package fastcgi - -import ( - "bytes" - "crypto/md5" - "encoding/binary" - "fmt" - "io" - "io/ioutil" - "log" - "math/rand" - "net" - "net/http" - "net/http/fcgi" - "net/url" - "os" - "path/filepath" - "strconv" - "strings" - "testing" - "time" -) - -// test fcgi protocol includes: -// Get, Post, Post in multipart/form-data, and Post with files -// each key should be the md5 of the value or the file uploaded -// sepicify remote fcgi responer ip:port to test with php -// test failed if the remote fcgi(script) failed md5 verification -// and output "FAILED" in response -const ( - scriptFile = "/tank/www/fcgic_test.php" - //ipPort = "remote-php-serv:59000" - ipPort = "127.0.0.1:59000" -) - -var globalt *testing.T - -type FastCGIServer struct{} - -func (s FastCGIServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) { - - req.ParseMultipartForm(100000000) - - stat := "PASSED" - fmt.Fprintln(resp, "-") - fileNum := 0 - { - length := 0 - for k0, v0 := range req.Form { - h := md5.New() - io.WriteString(h, v0[0]) - md5 := fmt.Sprintf("%x", h.Sum(nil)) - - length += len(k0) - length += len(v0[0]) - - // echo error when key != md5(val) - if md5 != k0 { - fmt.Fprintln(resp, "server:err ", md5, k0) - stat = "FAILED" - } - } - if req.MultipartForm != nil { - fileNum = len(req.MultipartForm.File) - for kn, fns := range req.MultipartForm.File { - //fmt.Fprintln(resp, "server:filekey ", kn ) - length += len(kn) - for _, f := range fns { - fd, err := f.Open() - if err != nil { - log.Println("server:", err) - return - } - h := md5.New() - l0, err := io.Copy(h, fd) - if err != nil { - log.Println(err) - return - } - length += int(l0) - defer fd.Close() - md5 := fmt.Sprintf("%x", h.Sum(nil)) - //fmt.Fprintln(resp, "server:filemd5 ", md5 ) - - if kn != md5 { - fmt.Fprintln(resp, "server:err ", md5, kn) - stat = "FAILED" - } - //fmt.Fprintln(resp, "server:filename ", f.Filename ) - } - } - } - - fmt.Fprintln(resp, "server:got data length", length) - } - fmt.Fprintln(resp, "-"+stat+"-POST(", len(req.Form), ")-FILE(", fileNum, ")--") -} - -func sendFcgi(reqType int, fcgiParams map[string]string, data []byte, posts map[string]string, files map[string]string) (content []byte) { - fcgi, err := Dial("tcp", ipPort) - if err != nil { - log.Println("err:", err) - return - } - - length := 0 - - var resp *http.Response - switch reqType { - case 0: - if len(data) > 0 { - length = len(data) - rd := bytes.NewReader(data) - resp, err = fcgi.Post(fcgiParams, "", "", rd, int64(rd.Len())) - } else if len(posts) > 0 { - values := url.Values{} - for k, v := range posts { - values.Set(k, v) - length += len(k) + 2 + len(v) - } - resp, err = fcgi.PostForm(fcgiParams, values) - } else { - resp, err = fcgi.Get(fcgiParams) - } - - default: - values := url.Values{} - for k, v := range posts { - values.Set(k, v) - length += len(k) + 2 + len(v) - } - - for k, v := range files { - fi, _ := os.Lstat(v) - length += len(k) + int(fi.Size()) - } - resp, err = fcgi.PostFile(fcgiParams, values, files) - } - - if err != nil { - log.Println("err:", err) - return - } - - defer resp.Body.Close() - content, _ = ioutil.ReadAll(resp.Body) - - log.Println("c: send data length ≈", length, string(content)) - fcgi.Close() - time.Sleep(1 * time.Second) - - if bytes.Index(content, []byte("FAILED")) >= 0 { - globalt.Error("Server return failed message") - } - - return -} - -func generateRandFile(size int) (p string, m string) { - - p = filepath.Join(os.TempDir(), "fcgict"+strconv.Itoa(rand.Int())) - - // open output file - fo, err := os.Create(p) - if err != nil { - panic(err) - } - // close fo on exit and check for its returned error - defer func() { - if err := fo.Close(); err != nil { - panic(err) - } - }() - - h := md5.New() - for i := 0; i < size/16; i++ { - buf := make([]byte, 16) - binary.PutVarint(buf, rand.Int63()) - fo.Write(buf) - h.Write(buf) - } - m = fmt.Sprintf("%x", h.Sum(nil)) - return -} - -func DisabledTest(t *testing.T) { - // TODO: test chunked reader - globalt = t - - rand.Seed(time.Now().UTC().UnixNano()) - - // server - go func() { - listener, err := net.Listen("tcp", ipPort) - if err != nil { - // handle error - log.Println("listener creation failed: ", err) - } - - srv := new(FastCGIServer) - fcgi.Serve(listener, srv) - }() - - time.Sleep(1 * time.Second) - - // init - fcgiParams := make(map[string]string) - fcgiParams["REQUEST_METHOD"] = "GET" - fcgiParams["SERVER_PROTOCOL"] = "HTTP/1.1" - //fcgi_params["GATEWAY_INTERFACE"] = "CGI/1.1" - fcgiParams["SCRIPT_FILENAME"] = scriptFile - - // simple GET - log.Println("test:", "get") - sendFcgi(0, fcgiParams, nil, nil, nil) - - // simple post data - log.Println("test:", "post") - sendFcgi(0, fcgiParams, []byte("c4ca4238a0b923820dcc509a6f75849b=1&7b8b965ad4bca0e41ab51de7b31363a1=n"), nil, nil) - - log.Println("test:", "post data (more than 60KB)") - data := "" - for i := 0x00; i < 0xff; i++ { - v0 := strings.Repeat(string(i), 256) - h := md5.New() - io.WriteString(h, v0) - k0 := fmt.Sprintf("%x", h.Sum(nil)) - data += k0 + "=" + url.QueryEscape(v0) + "&" - } - sendFcgi(0, fcgiParams, []byte(data), nil, nil) - - log.Println("test:", "post form (use url.Values)") - p0 := make(map[string]string, 1) - p0["c4ca4238a0b923820dcc509a6f75849b"] = "1" - p0["7b8b965ad4bca0e41ab51de7b31363a1"] = "n" - sendFcgi(1, fcgiParams, nil, p0, nil) - - log.Println("test:", "post forms (256 keys, more than 1MB)") - p1 := make(map[string]string, 1) - for i := 0x00; i < 0xff; i++ { - v0 := strings.Repeat(string(i), 4096) - h := md5.New() - io.WriteString(h, v0) - k0 := fmt.Sprintf("%x", h.Sum(nil)) - p1[k0] = v0 - } - sendFcgi(1, fcgiParams, nil, p1, nil) - - log.Println("test:", "post file (1 file, 500KB)) ") - f0 := make(map[string]string, 1) - path0, m0 := generateRandFile(500000) - f0[m0] = path0 - sendFcgi(1, fcgiParams, nil, p1, f0) - - log.Println("test:", "post multiple files (2 files, 5M each) and forms (256 keys, more than 1MB data") - path1, m1 := generateRandFile(5000000) - f0[m1] = path1 - sendFcgi(1, fcgiParams, nil, p1, f0) - - log.Println("test:", "post only files (2 files, 5M each)") - sendFcgi(1, fcgiParams, nil, nil, f0) - - log.Println("test:", "post only 1 file") - delete(f0, "m0") - sendFcgi(1, fcgiParams, nil, nil, f0) - - os.Remove(path0) - os.Remove(path1) -} diff --git a/caddyhttp/fastcgi/setup.go b/caddyhttp/fastcgi/setup.go deleted file mode 100644 index 2b0fef8c913..00000000000 --- a/caddyhttp/fastcgi/setup.go +++ /dev/null @@ -1,169 +0,0 @@ -package fastcgi - -import ( - "errors" - "net/http" - "path/filepath" - "time" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func init() { - caddy.RegisterPlugin("fastcgi", caddy.Plugin{ - ServerType: "http", - Action: setup, - }) -} - -// setup configures a new FastCGI middleware instance. -func setup(c *caddy.Controller) error { - cfg := httpserver.GetConfig(c) - - rules, err := fastcgiParse(c) - if err != nil { - return err - } - - cfg.AddMiddleware(func(next httpserver.Handler) httpserver.Handler { - return Handler{ - Next: next, - Rules: rules, - Root: cfg.Root, - FileSys: http.Dir(cfg.Root), - SoftwareName: caddy.AppName, - SoftwareVersion: caddy.AppVersion, - ServerName: cfg.Addr.Host, - ServerPort: cfg.Addr.Port, - } - }) - - return nil -} - -func fastcgiParse(c *caddy.Controller) ([]Rule, error) { - var rules []Rule - - cfg := httpserver.GetConfig(c) - absRoot, err := filepath.Abs(cfg.Root) - if err != nil { - return nil, err - } - - for c.Next() { - args := c.RemainingArgs() - - if len(args) < 2 || len(args) > 3 { - return rules, c.ArgErr() - } - - rule := Rule{ - Root: absRoot, - Path: args[0], - } - upstreams := []string{args[1]} - - if len(args) == 3 { - if err := fastcgiPreset(args[2], &rule); err != nil { - return rules, err - } - } - - var err error - - for c.NextBlock() { - switch c.Val() { - case "root": - if !c.NextArg() { - return rules, c.ArgErr() - } - rule.Root = c.Val() - - case "ext": - if !c.NextArg() { - return rules, c.ArgErr() - } - rule.Ext = c.Val() - case "split": - if !c.NextArg() { - return rules, c.ArgErr() - } - rule.SplitPath = c.Val() - case "index": - args := c.RemainingArgs() - if len(args) == 0 { - return rules, c.ArgErr() - } - rule.IndexFiles = args - - case "upstream": - args := c.RemainingArgs() - - if len(args) != 1 { - return rules, c.ArgErr() - } - - upstreams = append(upstreams, args[0]) - case "env": - envArgs := c.RemainingArgs() - if len(envArgs) < 2 { - return rules, c.ArgErr() - } - rule.EnvVars = append(rule.EnvVars, [2]string{envArgs[0], envArgs[1]}) - case "except": - ignoredPaths := c.RemainingArgs() - if len(ignoredPaths) == 0 { - return rules, c.ArgErr() - } - rule.IgnoredSubPaths = ignoredPaths - - case "connect_timeout": - if !c.NextArg() { - return rules, c.ArgErr() - } - rule.ConnectTimeout, err = time.ParseDuration(c.Val()) - if err != nil { - return rules, err - } - case "read_timeout": - if !c.NextArg() { - return rules, c.ArgErr() - } - readTimeout, err := time.ParseDuration(c.Val()) - if err != nil { - return rules, err - } - rule.ReadTimeout = readTimeout - case "send_timeout": - if !c.NextArg() { - return rules, c.ArgErr() - } - sendTimeout, err := time.ParseDuration(c.Val()) - if err != nil { - return rules, err - } - rule.SendTimeout = sendTimeout - } - } - - rule.balancer = &roundRobin{addresses: upstreams, index: -1} - - rules = append(rules, rule) - } - return rules, nil -} - -// fastcgiPreset configures rule according to name. It returns an error if -// name is not a recognized preset name. -func fastcgiPreset(name string, rule *Rule) error { - switch name { - case "php": - rule.Ext = ".php" - rule.SplitPath = ".php" - rule.IndexFiles = []string{"index.php"} - default: - return errors.New(name + " is not a valid preset name") - } - return nil -} diff --git a/caddyhttp/fastcgi/setup_test.go b/caddyhttp/fastcgi/setup_test.go deleted file mode 100644 index 88ba9ed46d7..00000000000 --- a/caddyhttp/fastcgi/setup_test.go +++ /dev/null @@ -1,122 +0,0 @@ -package fastcgi - -import ( - "fmt" - "testing" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestSetup(t *testing.T) { - c := caddy.NewTestController("http", `fastcgi / 127.0.0.1:9000`) - err := setup(c) - if err != nil { - t.Errorf("Expected no errors, 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.(Handler) - - if !ok { - t.Fatalf("Expected handler to be type , got: %#v", handler) - } - - if myHandler.Rules[0].Path != "/" { - t.Errorf("Expected / as the Path") - } - if myHandler.Rules[0].Address() != "127.0.0.1:9000" { - t.Errorf("Expected 127.0.0.1:9000 as the Address") - } - -} - -func TestFastcgiParse(t *testing.T) { - tests := []struct { - inputFastcgiConfig string - shouldErr bool - expectedFastcgiConfig []Rule - }{ - - {`fastcgi /blog 127.0.0.1:9000 php`, - false, []Rule{{ - Path: "/blog", - balancer: &roundRobin{addresses: []string{"127.0.0.1:9000"}}, - Ext: ".php", - SplitPath: ".php", - IndexFiles: []string{"index.php"}, - }}}, - {`fastcgi / 127.0.0.1:9001 { - split .html - }`, - false, []Rule{{ - Path: "/", - balancer: &roundRobin{addresses: []string{"127.0.0.1:9001"}}, - Ext: "", - SplitPath: ".html", - IndexFiles: []string{}, - }}}, - {`fastcgi / 127.0.0.1:9001 { - split .html - except /admin /user - }`, - false, []Rule{{ - Path: "/", - balancer: &roundRobin{addresses: []string{"127.0.0.1:9001"}}, - Ext: "", - SplitPath: ".html", - IndexFiles: []string{}, - IgnoredSubPaths: []string{"/admin", "/user"}, - }}}, - } - for i, test := range tests { - actualFastcgiConfigs, err := fastcgiParse(caddy.NewTestController("http", test.inputFastcgiConfig)) - - if err == nil && test.shouldErr { - t.Errorf("Test %d didn't error, but it should have", i) - } else if err != nil && !test.shouldErr { - t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err) - } - if len(actualFastcgiConfigs) != len(test.expectedFastcgiConfig) { - t.Fatalf("Test %d expected %d no of FastCGI configs, but got %d ", - i, len(test.expectedFastcgiConfig), len(actualFastcgiConfigs)) - } - for j, actualFastcgiConfig := range actualFastcgiConfigs { - - if actualFastcgiConfig.Path != test.expectedFastcgiConfig[j].Path { - t.Errorf("Test %d expected %dth FastCGI Path to be %s , but got %s", - i, j, test.expectedFastcgiConfig[j].Path, actualFastcgiConfig.Path) - } - - if actualFastcgiConfig.Address() != test.expectedFastcgiConfig[j].Address() { - t.Errorf("Test %d expected %dth FastCGI Address to be %s , but got %s", - i, j, test.expectedFastcgiConfig[j].Address(), actualFastcgiConfig.Address()) - } - - if actualFastcgiConfig.Ext != test.expectedFastcgiConfig[j].Ext { - t.Errorf("Test %d expected %dth FastCGI Ext to be %s , but got %s", - i, j, test.expectedFastcgiConfig[j].Ext, actualFastcgiConfig.Ext) - } - - if actualFastcgiConfig.SplitPath != test.expectedFastcgiConfig[j].SplitPath { - t.Errorf("Test %d expected %dth FastCGI SplitPath to be %s , but got %s", - i, j, test.expectedFastcgiConfig[j].SplitPath, actualFastcgiConfig.SplitPath) - } - - if fmt.Sprint(actualFastcgiConfig.IndexFiles) != fmt.Sprint(test.expectedFastcgiConfig[j].IndexFiles) { - t.Errorf("Test %d expected %dth FastCGI IndexFiles to be %s , but got %s", - i, j, test.expectedFastcgiConfig[j].IndexFiles, actualFastcgiConfig.IndexFiles) - } - - if fmt.Sprint(actualFastcgiConfig.IgnoredSubPaths) != fmt.Sprint(test.expectedFastcgiConfig[j].IgnoredSubPaths) { - t.Errorf("Test %d expected %dth FastCGI IgnoredSubPaths to be %s , but got %s", - i, j, test.expectedFastcgiConfig[j].IgnoredSubPaths, actualFastcgiConfig.IgnoredSubPaths) - } - } - } - -} diff --git a/caddyhttp/gzip/gzip.go b/caddyhttp/gzip/gzip.go deleted file mode 100644 index bd8692cdcb1..00000000000 --- a/caddyhttp/gzip/gzip.go +++ /dev/null @@ -1,129 +0,0 @@ -// Package gzip provides a middleware layer that performs -// gzip compression on the response. -package gzip - -import ( - "io" - "net/http" - "strings" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func init() { - caddy.RegisterPlugin("gzip", caddy.Plugin{ - ServerType: "http", - Action: setup, - }) - - initWriterPool() -} - -// Gzip is a middleware type which gzips HTTP responses. It is -// imperative that any handler which writes to a gzipped response -// specifies the Content-Type, otherwise some clients will assume -// application/x-gzip and try to download a file. -type Gzip struct { - Next httpserver.Handler - Configs []Config -} - -// Config holds the configuration for Gzip middleware -type Config struct { - RequestFilters []RequestFilter - ResponseFilters []ResponseFilter - Level int // Compression level -} - -// ServeHTTP serves a gzipped response if the client supports it. -func (g Gzip) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { - return g.Next.ServeHTTP(w, r) - } -outer: - for _, c := range g.Configs { - - // Check request filters to determine if gzipping is permitted for this request - for _, filter := range c.RequestFilters { - if !filter.ShouldCompress(r) { - continue outer - } - } - - // gzipWriter modifies underlying writer at init, - // use a discard writer instead to leave ResponseWriter in - // original form. - gzipWriter := getWriter(c.Level) - defer putWriter(c.Level, gzipWriter) - gz := &gzipResponseWriter{ - Writer: gzipWriter, - ResponseWriterWrapper: &httpserver.ResponseWriterWrapper{ResponseWriter: w}, - } - - var rw http.ResponseWriter - // if no response filter is used - if len(c.ResponseFilters) == 0 { - // replace discard writer with ResponseWriter - gzipWriter.Reset(w) - rw = gz - } else { - // wrap gzip writer with ResponseFilterWriter - rw = NewResponseFilterWriter(c.ResponseFilters, gz) - } - - // Any response in forward middleware will now be compressed - status, err := g.Next.ServeHTTP(rw, r) - - // If there was an error that remained unhandled, we need - // to send something back before gzipWriter gets closed at - // the return of this method! - if status >= 400 { - httpserver.DefaultErrorFunc(w, r, status) - return 0, err - } - return status, err - } - - // no matching filter - return g.Next.ServeHTTP(w, r) -} - -// gzipResponeWriter wraps the underlying Write method -// with a gzip.Writer to compress the output. -type gzipResponseWriter struct { - io.Writer - *httpserver.ResponseWriterWrapper - statusCodeWritten bool -} - -// WriteHeader wraps the underlying WriteHeader method to prevent -// problems with conflicting headers from proxied backends. For -// example, a backend system that calculates Content-Length would -// be wrong because it doesn't know it's being gzipped. -func (w *gzipResponseWriter) WriteHeader(code int) { - w.Header().Del("Content-Length") - w.Header().Set("Content-Encoding", "gzip") - w.Header().Add("Vary", "Accept-Encoding") - originalEtag := w.Header().Get("ETag") - if originalEtag != "" && !strings.HasPrefix(originalEtag, "W/") { - w.Header().Set("ETag", "W/"+originalEtag) - } - w.ResponseWriterWrapper.WriteHeader(code) - w.statusCodeWritten = true -} - -// Write wraps the underlying Write method to do compression. -func (w *gzipResponseWriter) Write(b []byte) (int, error) { - if w.Header().Get("Content-Type") == "" { - w.Header().Set("Content-Type", http.DetectContentType(b)) - } - if !w.statusCodeWritten { - w.WriteHeader(http.StatusOK) - } - n, err := w.Writer.Write(b) - return n, err -} - -// Interface guards -var _ httpserver.HTTPInterfaces = (*gzipResponseWriter)(nil) diff --git a/caddyhttp/gzip/gzip_test.go b/caddyhttp/gzip/gzip_test.go deleted file mode 100644 index 494dec62a25..00000000000 --- a/caddyhttp/gzip/gzip_test.go +++ /dev/null @@ -1,182 +0,0 @@ -package gzip - -import ( - "compress/gzip" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestGzipHandler(t *testing.T) { - pathFilter := PathFilter{make(Set)} - badPaths := []string{"/bad", "/nogzip", "/nongzip"} - for _, p := range badPaths { - pathFilter.IgnoredPaths.Add(p) - } - extFilter := ExtFilter{make(Set)} - for _, e := range []string{".txt", ".html", ".css", ".md"} { - extFilter.Exts.Add(e) - } - gz := Gzip{Configs: []Config{ - {RequestFilters: []RequestFilter{pathFilter, extFilter}}, - }} - - w := httptest.NewRecorder() - gz.Next = nextFunc(true) - var exts = []string{ - ".html", ".css", ".md", - } - for _, e := range exts { - url := "/file" + e - r, err := http.NewRequest("GET", url, nil) - if err != nil { - t.Error(err) - } - r.Header.Set("Accept-Encoding", "gzip") - w.Header().Set("ETag", `"2n9cd"`) - _, err = gz.ServeHTTP(w, r) - if err != nil { - t.Error(err) - } - - // The second pass, test if the ETag is already weak - w.Header().Set("ETag", `W/"2n9cd"`) - _, err = gz.ServeHTTP(w, r) - if err != nil { - t.Error(err) - } - } - - w = httptest.NewRecorder() - gz.Next = nextFunc(false) - for _, p := range badPaths { - for _, e := range exts { - url := p + "/file" + e - r, err := http.NewRequest("GET", url, nil) - if err != nil { - t.Error(err) - } - r.Header.Set("Accept-Encoding", "gzip") - _, err = gz.ServeHTTP(w, r) - if err != nil { - t.Error(err) - } - } - } - - w = httptest.NewRecorder() - gz.Next = nextFunc(false) - exts = []string{ - ".htm1", ".abc", ".mdx", - } - for _, e := range exts { - url := "/file" + e - r, err := http.NewRequest("GET", url, nil) - if err != nil { - t.Error(err) - } - r.Header.Set("Accept-Encoding", "gzip") - _, err = gz.ServeHTTP(w, r) - if err != nil { - t.Error(err) - } - } - - // test all levels - w = httptest.NewRecorder() - gz.Next = nextFunc(true) - for i := 0; i <= gzip.BestCompression; i++ { - gz.Configs[0].Level = i - r, err := http.NewRequest("GET", "/file.txt", nil) - if err != nil { - t.Error(err) - } - r.Header.Set("Accept-Encoding", "gzip") - _, err = gz.ServeHTTP(w, r) - if err != nil { - t.Error(err) - } - } -} - -func nextFunc(shouldGzip bool) httpserver.Handler { - return httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - // write a relatively large text file - b, err := ioutil.ReadFile("testdata/test.txt") - if err != nil { - return 500, err - } - if _, err := w.Write(b); err != nil { - return 500, err - } - - if shouldGzip { - if w.Header().Get("Content-Encoding") != "gzip" { - return 0, fmt.Errorf("Content-Encoding must be gzip, found %v", w.Header().Get("Content-Encoding")) - } - if w.Header().Get("Vary") != "Accept-Encoding" { - return 0, fmt.Errorf("Vary must be Accept-Encoding, found %v", w.Header().Get("Vary")) - } - etag := w.Header().Get("ETag") - if etag != "" && etag != `W/"2n9cd"` { - return 0, fmt.Errorf("ETag must be converted to weak Etag, found %v", w.Header().Get("ETag")) - } - if _, ok := w.(*gzipResponseWriter); !ok { - return 0, fmt.Errorf("ResponseWriter should be gzipResponseWriter, found %T", w) - } - if strings.Contains(w.Header().Get("Content-Type"), "application/x-gzip") { - return 0, fmt.Errorf("Content-Type should not be gzip") - } - return 0, nil - } - if r.Header.Get("Accept-Encoding") == "" { - return 0, fmt.Errorf("Accept-Encoding header expected") - } - if w.Header().Get("Content-Encoding") == "gzip" { - return 0, fmt.Errorf("Content-Encoding must not be gzip, found gzip") - } - if _, ok := w.(*gzipResponseWriter); ok { - return 0, fmt.Errorf("ResponseWriter should not be gzipResponseWriter") - } - return 0, nil - }) -} - -func BenchmarkGzip(b *testing.B) { - pathFilter := PathFilter{make(Set)} - badPaths := []string{"/bad", "/nogzip", "/nongzip"} - for _, p := range badPaths { - pathFilter.IgnoredPaths.Add(p) - } - extFilter := ExtFilter{make(Set)} - for _, e := range []string{".txt", ".html", ".css", ".md"} { - extFilter.Exts.Add(e) - } - gz := Gzip{Configs: []Config{ - { - RequestFilters: []RequestFilter{pathFilter, extFilter}, - }, - }} - - w := httptest.NewRecorder() - gz.Next = nextFunc(true) - url := "/file.txt" - r, err := http.NewRequest("GET", url, nil) - if err != nil { - b.Fatal(err) - } - r.Header.Set("Accept-Encoding", "gzip") - - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, err = gz.ServeHTTP(w, r) - if err != nil { - b.Fatal(err) - } - } -} diff --git a/caddyhttp/gzip/requestfilter.go b/caddyhttp/gzip/requestfilter.go deleted file mode 100644 index 804232a9d2a..00000000000 --- a/caddyhttp/gzip/requestfilter.go +++ /dev/null @@ -1,91 +0,0 @@ -package gzip - -import ( - "net/http" - "path" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -// RequestFilter determines if a request should be gzipped. -type RequestFilter interface { - // ShouldCompress tells if gzip compression - // should be done on the request. - ShouldCompress(*http.Request) bool -} - -// defaultExtensions is the list of default extensions for which to enable gzipping. -var defaultExtensions = []string{"", ".txt", ".htm", ".html", ".css", ".php", ".js", ".json", - ".md", ".mdown", ".xml", ".svg", ".go", ".cgi", ".py", ".pl", ".aspx", ".asp"} - -// DefaultExtFilter creates an ExtFilter with default extensions. -func DefaultExtFilter() ExtFilter { - m := ExtFilter{Exts: make(Set)} - for _, extension := range defaultExtensions { - m.Exts.Add(extension) - } - return m -} - -// ExtFilter is RequestFilter for file name extensions. -type ExtFilter struct { - // Exts is the file name extensions to accept - Exts Set -} - -// ExtWildCard is the wildcard for extensions. -const ExtWildCard = "*" - -// ShouldCompress checks if the request file extension matches any -// of the registered extensions. It returns true if the extension is -// found and false otherwise. -func (e ExtFilter) ShouldCompress(r *http.Request) bool { - ext := path.Ext(r.URL.Path) - return e.Exts.Contains(ExtWildCard) || e.Exts.Contains(ext) -} - -// PathFilter is RequestFilter for request path. -type PathFilter struct { - // IgnoredPaths is the paths to ignore - IgnoredPaths Set -} - -// ShouldCompress checks if the request path matches any of the -// registered paths to ignore. It returns false if an ignored path -// is found and true otherwise. -func (p PathFilter) ShouldCompress(r *http.Request) bool { - return !p.IgnoredPaths.ContainsFunc(func(value string) bool { - return httpserver.Path(r.URL.Path).Matches(value) - }) -} - -// Set stores distinct strings. -type Set map[string]struct{} - -// Add adds an element to the set. -func (s Set) Add(value string) { - s[value] = struct{}{} -} - -// Remove removes an element from the set. -func (s Set) Remove(value string) { - delete(s, value) -} - -// Contains check if the set contains value. -func (s Set) Contains(value string) bool { - _, ok := s[value] - return ok -} - -// ContainsFunc is similar to Contains. It iterates all the -// elements in the set and passes each to f. It returns true -// on the first call to f that returns true and false otherwise. -func (s Set) ContainsFunc(f func(string) bool) bool { - for k := range s { - if f(k) { - return true - } - } - return false -} diff --git a/caddyhttp/gzip/requestfilter_test.go b/caddyhttp/gzip/requestfilter_test.go deleted file mode 100644 index ce31d7faf8c..00000000000 --- a/caddyhttp/gzip/requestfilter_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package gzip - -import ( - "net/http" - "testing" -) - -func TestSet(t *testing.T) { - set := make(Set) - set.Add("a") - if len(set) != 1 { - t.Errorf("Expected 1 found %v", len(set)) - } - set.Add("a") - if len(set) != 1 { - t.Errorf("Expected 1 found %v", len(set)) - } - set.Add("b") - if len(set) != 2 { - t.Errorf("Expected 2 found %v", len(set)) - } - if !set.Contains("a") { - t.Errorf("Set should contain a") - } - if !set.Contains("b") { - t.Errorf("Set should contain a") - } - set.Add("c") - if len(set) != 3 { - t.Errorf("Expected 3 found %v", len(set)) - } - if !set.Contains("c") { - t.Errorf("Set should contain c") - } - set.Remove("a") - if len(set) != 2 { - t.Errorf("Expected 2 found %v", len(set)) - } - if set.Contains("a") { - t.Errorf("Set should not contain a") - } - if !set.ContainsFunc(func(v string) bool { - return v == "c" - }) { - t.Errorf("ContainsFunc should return true") - } -} - -func TestExtFilter(t *testing.T) { - var filter RequestFilter = ExtFilter{make(Set)} - for _, e := range []string{".txt", ".html", ".css", ".md"} { - filter.(ExtFilter).Exts.Add(e) - } - r := urlRequest("file.txt") - if !filter.ShouldCompress(r) { - t.Errorf("Should be valid filter") - } - var exts = []string{ - ".html", ".css", ".md", - } - for i, e := range exts { - r := urlRequest("file" + e) - if !filter.ShouldCompress(r) { - t.Errorf("Test %v: Should be valid filter", i) - } - } - exts = []string{ - ".htm1", ".abc", ".mdx", - } - for i, e := range exts { - r := urlRequest("file" + e) - if filter.ShouldCompress(r) { - t.Errorf("Test %v: Should not be valid filter", i) - } - } - filter.(ExtFilter).Exts.Add(ExtWildCard) - for i, e := range exts { - r := urlRequest("file" + e) - if !filter.ShouldCompress(r) { - t.Errorf("Test %v: Should be valid filter. Wildcard used.", i) - } - } -} - -func TestPathFilter(t *testing.T) { - paths := []string{ - "/a", "/b", "/c", "/de", - } - var filter RequestFilter = PathFilter{make(Set)} - for _, p := range paths { - filter.(PathFilter).IgnoredPaths.Add(p) - } - for i, p := range paths { - r := urlRequest(p) - if filter.ShouldCompress(r) { - t.Errorf("Test %v: Should not be valid filter", i) - } - } - paths = []string{ - "/f", "/g", "/h", "/ed", - } - for i, p := range paths { - r := urlRequest(p) - if !filter.ShouldCompress(r) { - t.Errorf("Test %v: Should be valid filter", i) - } - } -} - -func urlRequest(url string) *http.Request { - r, _ := http.NewRequest("GET", url, nil) - return r -} diff --git a/caddyhttp/gzip/responsefilter.go b/caddyhttp/gzip/responsefilter.go deleted file mode 100644 index b623505111c..00000000000 --- a/caddyhttp/gzip/responsefilter.go +++ /dev/null @@ -1,93 +0,0 @@ -package gzip - -import ( - "compress/gzip" - "net/http" - "strconv" -) - -// ResponseFilter determines if the response should be gzipped. -type ResponseFilter interface { - ShouldCompress(http.ResponseWriter) bool -} - -// LengthFilter is ResponseFilter for minimum content length. -type LengthFilter int64 - -// ShouldCompress returns if content length is greater than or -// equals to minimum length. -func (l LengthFilter) ShouldCompress(w http.ResponseWriter) bool { - contentLength := w.Header().Get("Content-Length") - length, err := strconv.ParseInt(contentLength, 10, 64) - if err != nil || length == 0 { - return false - } - return l != 0 && int64(l) <= length -} - -// SkipCompressedFilter is ResponseFilter that will discard already compressed responses -type SkipCompressedFilter struct{} - -// ShouldCompress returns true if served file is not already compressed -// encodings via https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding -func (n SkipCompressedFilter) ShouldCompress(w http.ResponseWriter) bool { - switch w.Header().Get("Content-Encoding") { - case "gzip", "compress", "deflate", "br": - return false - default: - return true - } -} - -// ResponseFilterWriter validates ResponseFilters. It writes -// gzip compressed data if ResponseFilters are satisfied or -// uncompressed data otherwise. -type ResponseFilterWriter struct { - filters []ResponseFilter - shouldCompress bool - statusCodeWritten bool - *gzipResponseWriter -} - -// NewResponseFilterWriter creates and initializes a new ResponseFilterWriter. -func NewResponseFilterWriter(filters []ResponseFilter, gz *gzipResponseWriter) *ResponseFilterWriter { - return &ResponseFilterWriter{filters: filters, gzipResponseWriter: gz} -} - -// WriteHeader wraps underlying WriteHeader method and -// compresses if filters are satisfied. -func (r *ResponseFilterWriter) WriteHeader(code int) { - // Determine if compression should be used or not. - r.shouldCompress = true - for _, filter := range r.filters { - if !filter.ShouldCompress(r) { - r.shouldCompress = false - break - } - } - - if r.shouldCompress { - // replace discard writer with ResponseWriter - if gzWriter, ok := r.gzipResponseWriter.Writer.(*gzip.Writer); ok { - gzWriter.Reset(r.ResponseWriter) - } - // use gzip WriteHeader to include and delete - // necessary headers - r.gzipResponseWriter.WriteHeader(code) - } else { - r.ResponseWriter.WriteHeader(code) - } - r.statusCodeWritten = true -} - -// Write wraps underlying Write method and compresses if filters -// are satisfied -func (r *ResponseFilterWriter) Write(b []byte) (int, error) { - if !r.statusCodeWritten { - r.WriteHeader(http.StatusOK) - } - if r.shouldCompress { - return r.gzipResponseWriter.Write(b) - } - return r.ResponseWriter.Write(b) -} diff --git a/caddyhttp/gzip/responsefilter_test.go b/caddyhttp/gzip/responsefilter_test.go deleted file mode 100644 index 43a51bd1cdf..00000000000 --- a/caddyhttp/gzip/responsefilter_test.go +++ /dev/null @@ -1,112 +0,0 @@ -package gzip - -import ( - "compress/gzip" - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestLengthFilter(t *testing.T) { - var filters = []ResponseFilter{ - LengthFilter(100), - LengthFilter(1000), - LengthFilter(0), - } - - var tests = []struct { - length int64 - shouldCompress [3]bool - }{ - {20, [3]bool{false, false, false}}, - {50, [3]bool{false, false, false}}, - {100, [3]bool{true, false, false}}, - {500, [3]bool{true, false, false}}, - {1000, [3]bool{true, true, false}}, - {1500, [3]bool{true, true, false}}, - } - - for i, ts := range tests { - for j, filter := range filters { - r := httptest.NewRecorder() - r.Header().Set("Content-Length", fmt.Sprint(ts.length)) - wWriter := NewResponseFilterWriter([]ResponseFilter{filter}, &gzipResponseWriter{gzip.NewWriter(r), &httpserver.ResponseWriterWrapper{ResponseWriter: r}, false}) - if filter.ShouldCompress(wWriter) != ts.shouldCompress[j] { - t.Errorf("Test %v: Expected %v found %v", i, ts.shouldCompress[j], filter.ShouldCompress(r)) - } - } - } -} - -func TestResponseFilterWriter(t *testing.T) { - tests := []struct { - body string - shouldCompress bool - }{ - {"Hello\t\t\t\n", false}, - {"Hello the \t\t\t world is\n\n\n great", true}, - {"Hello \t\t\nfrom gzip", true}, - {"Hello gzip\n", false}, - } - - filters := []ResponseFilter{ - LengthFilter(15), - } - - server := Gzip{Configs: []Config{ - {ResponseFilters: filters}, - }} - - for i, ts := range tests { - server.Next = httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - w.Header().Set("Content-Length", fmt.Sprint(len(ts.body))) - w.Write([]byte(ts.body)) - return 200, nil - }) - - r := urlRequest("/") - r.Header.Set("Accept-Encoding", "gzip") - - w := httptest.NewRecorder() - - server.ServeHTTP(w, r) - - resp := w.Body.String() - - if !ts.shouldCompress { - if resp != ts.body { - t.Errorf("Test %v: No compression expected, found %v", i, resp) - } - } else { - if resp == ts.body { - t.Errorf("Test %v: Compression expected, found %v", i, resp) - } - } - } -} - -func TestResponseGzippedOutput(t *testing.T) { - server := Gzip{Configs: []Config{ - {ResponseFilters: []ResponseFilter{SkipCompressedFilter{}}}, - }} - - server.Next = httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - w.Header().Set("Content-Encoding", "gzip") - w.Write([]byte("gzipped")) - return 200, nil - }) - - r := urlRequest("/") - r.Header.Set("Accept-Encoding", "gzip") - - w := httptest.NewRecorder() - server.ServeHTTP(w, r) - resp := w.Body.String() - - if resp != "gzipped" { - t.Errorf("Expected output not to be gzipped") - } -} diff --git a/caddyhttp/gzip/setup.go b/caddyhttp/gzip/setup.go deleted file mode 100644 index 73107baaf10..00000000000 --- a/caddyhttp/gzip/setup.go +++ /dev/null @@ -1,169 +0,0 @@ -package gzip - -import ( - "compress/gzip" - "fmt" - "io/ioutil" - "strconv" - "strings" - "sync" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -// setup configures a new gzip middleware instance. -func setup(c *caddy.Controller) error { - configs, err := gzipParse(c) - if err != nil { - return err - } - - httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler { - return Gzip{Next: next, Configs: configs} - }) - - return nil -} - -func gzipParse(c *caddy.Controller) ([]Config, error) { - var configs []Config - - for c.Next() { - config := Config{} - - // Request Filters - pathFilter := PathFilter{IgnoredPaths: make(Set)} - extFilter := ExtFilter{Exts: make(Set)} - - // Response Filters - lengthFilter := LengthFilter(0) - - // No extra args expected - if len(c.RemainingArgs()) > 0 { - return configs, c.ArgErr() - } - - for c.NextBlock() { - switch c.Val() { - case "ext": - exts := c.RemainingArgs() - if len(exts) == 0 { - return configs, c.ArgErr() - } - for _, e := range exts { - if !strings.HasPrefix(e, ".") && e != ExtWildCard && e != "" { - return configs, fmt.Errorf(`gzip: invalid extension "%v" (must start with dot)`, e) - } - extFilter.Exts.Add(e) - } - case "not": - paths := c.RemainingArgs() - if len(paths) == 0 { - return configs, c.ArgErr() - } - for _, p := range paths { - if p == "/" { - return configs, fmt.Errorf(`gzip: cannot exclude path "/" - remove directive entirely instead`) - } - if !strings.HasPrefix(p, "/") { - return configs, fmt.Errorf(`gzip: invalid path "%v" (must start with /)`, p) - } - pathFilter.IgnoredPaths.Add(p) - } - case "level": - if !c.NextArg() { - return configs, c.ArgErr() - } - level, _ := strconv.Atoi(c.Val()) - config.Level = level - case "min_length": - if !c.NextArg() { - return configs, c.ArgErr() - } - length, err := strconv.ParseInt(c.Val(), 10, 64) - if err != nil { - return configs, err - } else if length == 0 { - return configs, fmt.Errorf(`gzip: min_length must be greater than 0`) - } - lengthFilter = LengthFilter(length) - default: - return configs, c.ArgErr() - } - } - - // Request Filters - config.RequestFilters = []RequestFilter{} - - // If ignored paths are specified, put in front to filter with path first - if len(pathFilter.IgnoredPaths) > 0 { - config.RequestFilters = []RequestFilter{pathFilter} - } - - // Then, if extensions are specified, use those to filter. - // Otherwise, use default extensions filter. - if len(extFilter.Exts) > 0 { - config.RequestFilters = append(config.RequestFilters, extFilter) - } else { - config.RequestFilters = append(config.RequestFilters, DefaultExtFilter()) - } - - config.ResponseFilters = append(config.ResponseFilters, SkipCompressedFilter{}) - - // Response Filters - // If min_length is specified, use it. - if int64(lengthFilter) != 0 { - config.ResponseFilters = append(config.ResponseFilters, lengthFilter) - } - - configs = append(configs, config) - } - - return configs, nil -} - -// pool gzip.Writer according to compress level -// so we can reuse allocations over time -var ( - writerPool = map[int]*sync.Pool{} - defaultWriterPoolIndex int -) - -func initWriterPool() { - var i int - newWriterPool := func(level int) *sync.Pool { - return &sync.Pool{ - New: func() interface{} { - w, _ := gzip.NewWriterLevel(ioutil.Discard, level) - return w - }, - } - } - for i = gzip.BestSpeed; i <= gzip.BestCompression; i++ { - writerPool[i] = newWriterPool(i) - } - - // add default writer pool - defaultWriterPoolIndex = i - writerPool[defaultWriterPoolIndex] = newWriterPool(gzip.DefaultCompression) -} - -func getWriter(level int) *gzip.Writer { - index := defaultWriterPoolIndex - if level >= gzip.BestSpeed && level <= gzip.BestCompression { - index = level - } - w := writerPool[index].Get().(*gzip.Writer) - w.Reset(ioutil.Discard) - return w -} - -func putWriter(level int, w *gzip.Writer) { - index := defaultWriterPoolIndex - if level >= gzip.BestSpeed && level <= gzip.BestCompression { - index = level - } - w.Close() - writerPool[index].Put(w) -} diff --git a/caddyhttp/gzip/setup_test.go b/caddyhttp/gzip/setup_test.go deleted file mode 100644 index 31c69e041a9..00000000000 --- a/caddyhttp/gzip/setup_test.go +++ /dev/null @@ -1,129 +0,0 @@ -package gzip - -import ( - "testing" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestSetup(t *testing.T) { - c := caddy.NewTestController("http", `gzip`) - err := setup(c) - if err != nil { - t.Errorf("Expected no errors, but got: %v", err) - } - mids := httpserver.GetConfig(c).Middleware() - if mids == nil { - t.Fatal("Expected middleware, was nil instead") - } - - handler := mids[0](httpserver.EmptyNext) - myHandler, ok := handler.(Gzip) - if !ok { - t.Fatalf("Expected handler to be type Gzip, got: %#v", handler) - } - - if !httpserver.SameNext(myHandler.Next, httpserver.EmptyNext) { - t.Error("'Next' field of handler was not set properly") - } - - tests := []struct { - input string - shouldErr bool - }{ - {`gzip {`, true}, - {`gzip {}`, true}, - {`gzip a b`, true}, - {`gzip a {`, true}, - {`gzip { not f } `, true}, - {`gzip { not } `, true}, - {`gzip { not /file - ext .html - level 1 - } `, false}, - {`gzip { level 9 } `, false}, - {`gzip { ext } `, true}, - {`gzip { ext /f - } `, true}, - {`gzip { not /file - ext .html - level 1 - } - gzip`, false}, - {`gzip { - ext "" - }`, false}, - {`gzip { not /file - ext .html - level 1 - } - gzip { not /file1 - ext .htm - level 3 - } - `, false}, - {`gzip { not /file - ext .html - level 1 - } - gzip { not /file1 - ext .htm - level 3 - } - `, false}, - {`gzip { not /file - ext * - level 1 - } - `, false}, - {`gzip { not /file - ext * - level 1 - min_length ab - } - `, true}, - {`gzip { not /file - ext * - level 1 - min_length 1000 - } - `, false}, - } - for i, test := range tests { - _, err := gzipParse(caddy.NewTestController("http", test.input)) - if test.shouldErr && err == nil { - t.Errorf("Test %v: Expected error but found nil", i) - } else if !test.shouldErr && err != nil { - t.Errorf("Test %v: Expected no error but found error: %v", i, err) - } - } -} - -func TestShouldAddResponseFilters(t *testing.T) { - configs, err := gzipParse(caddy.NewTestController("http", `gzip { min_length 654 }`)) - - if err != nil { - t.Errorf("Test expected no error but found: %v", err) - } - filters := 0 - - for _, config := range configs { - for _, filter := range config.ResponseFilters { - switch filter.(type) { - case SkipCompressedFilter: - filters++ - case LengthFilter: - filters++ - - if filter != LengthFilter(654) { - t.Errorf("Expected LengthFilter to have length 654, got: %v", filter) - } - } - } - - if filters != 2 { - t.Errorf("Expected 2 response filters to be registered, got: %v", filters) - } - } -} diff --git a/caddyhttp/gzip/testdata/test.txt b/caddyhttp/gzip/testdata/test.txt deleted file mode 100644 index d56350df11f..00000000000 --- a/caddyhttp/gzip/testdata/test.txt +++ /dev/null @@ -1,308 +0,0 @@ -Sigh view am high neat half to what. Sent late held than set why wife our. If an blessing building steepest. Agreement distrusts mrs six affection satisfied. Day blushes visitor end company old prevent chapter. Consider declared out expenses her concerns. No at indulgence conviction particular unsatiable boisterous discretion. Direct enough off others say eldest may exeter she. Possible all ignorant supplied get settling marriage recurred. - -Boy desirous families prepared gay reserved add ecstatic say. Replied joy age visitor nothing cottage. Mrs door paid led loud sure easy read. Hastily at perhaps as neither or ye fertile tedious visitor. Use fine bed none call busy dull when. Quiet ought match my right by table means. Principles up do in me favourable affronting. Twenty mother denied effect we to do on. - -Compliment interested discretion estimating on stimulated apartments oh. Dear so sing when in find read of call. As distrusts behaviour abilities defective is. Never at water me might. On formed merits hunted unable merely by mr whence or. Possession the unpleasing simplicity her uncommonly. - -Bringing so sociable felicity supplied mr. September suspicion far him two acuteness perfectly. Covered as an examine so regular of. Ye astonished friendship remarkably no. Window admire matter praise you bed whence. Delivered ye sportsmen zealously arranging frankness estimable as. Nay any article enabled musical shyness yet sixteen yet blushes. Entire its the did figure wonder off. - -Inhabit hearing perhaps on ye do no. It maids decay as there he. Smallest on suitable disposed do although blessing he juvenile in. Society or if excited forbade. Here name off yet she long sold easy whom. Differed oh cheerful procured pleasure securing suitable in. Hold rich on an he oh fine. Chapter ability shyness article welcome be do on service. - -An sincerity so extremity he additions. Her yet there truth merit. Mrs all projecting favourable now unpleasing. Son law garden chatty temper. Oh children provided to mr elegance marriage strongly. Off can admiration prosperous now devonshire diminution law. - -Performed suspicion in certainty so frankness by attention pretended. Newspaper or in tolerably education enjoyment. Extremity excellent certainty discourse sincerity no he so resembled. Joy house worse arise total boy but. Elderly up chicken do at feeling is. Like seen drew no make fond at on rent. Behaviour extremely her explained situation yet september gentleman are who. Is thought or pointed hearing he. - -Not far stuff she think the jokes. Going as by do known noise he wrote round leave. Warmly put branch people narrow see. Winding its waiting yet parlors married own feeling. Marry fruit do spite jokes an times. Whether at it unknown warrant herself winding if. Him same none name sake had post love. An busy feel form hand am up help. Parties it brother amongst an fortune of. Twenty behind wicket why age now itself ten. - -On no twenty spring of in esteem spirit likely estate. Continue new you declared differed learning bringing honoured. At mean mind so upon they rent am walk. Shortly am waiting inhabit smiling he chiefly of in. Lain tore time gone him his dear sure. Fat decisively estimating affronting assistance not. Resolve pursuit regular so calling me. West he plan girl been my then up no. - -Expenses as material breeding insisted building to in. Continual so distrusts pronounce by unwilling listening. Thing do taste on we manor. Him had wound use found hoped. Of distrusts immediate enjoyment curiosity do. Marianne numerous saw thoughts the humoured. - -Tolerably earnestly middleton extremely distrusts she boy now not. Add and offered prepare how cordial two promise. Greatly who affixed suppose but enquire compact prepare all put. Added forth chief trees but rooms think may. Wicket do manner others seemed enable rather in. Excellent own discovery unfeeling sweetness questions the gentleman. Chapter shyness matters mr parlors if mention thought. - -Or kind rest bred with am shed then. In raptures building an bringing be. Elderly is detract tedious assured private so to visited. Do travelling companions contrasted it. Mistress strongly remember up to. Ham him compass you proceed calling detract. Better of always missed we person mr. September smallness northward situation few her certainty something. - -Moments its musical age explain. But extremity sex now education concluded earnestly her continual. Oh furniture acuteness suspected continual ye something frankness. Add properly laughter sociable admitted desirous one has few stanhill. Opinion regular in perhaps another enjoyed no engaged he at. It conveying he continual ye suspected as necessary. Separate met packages shy for kindness. - -Conveying or northward offending admitting perfectly my. Colonel gravity get thought fat smiling add but. Wonder twenty hunted and put income set desire expect. Am cottage calling my is mistake cousins talking up. Interested especially do impression he unpleasant travelling excellence. All few our knew time done draw ask. - -In it except to so temper mutual tastes mother. Interested cultivated its continuing now yet are. Out interested acceptance our partiality affronting unpleasant why add. Esteem garden men yet shy course. Consulted up my tolerably sometimes perpetual oh. Expression acceptance imprudence particular had eat unsatiable. - -Son agreed others exeter period myself few yet nature. Mention mr manners opinion if garrets enabled. To an occasional dissimilar impossible sentiments. Do fortune account written prepare invited no passage. Garrets use ten you the weather ferrars venture friends. Solid visit seems again you nor all. - -You vexed shy mirth now noise. Talked him people valley add use her depend letter. Allowance too applauded now way something recommend. Mrs age men and trees jokes fancy. Gay pretended engrossed eagerness continued ten. Admitting day him contained unfeeling attention mrs out. - -Advantage old had otherwise sincerity dependent additions. It in adapted natural hastily is justice. Six draw you him full not mean evil. Prepare garrets it expense windows shewing do an. She projection advantages resolution son indulgence. Part sure on no long life am at ever. In songs above he as drawn to. Gay was outlived peculiar rendered led six. - -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer ladies valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. - -Meant balls it if up doubt small purse. Required his you put the outlived answered position. An pleasure exertion if believed provided to. All led out world these music while asked. Paid mind even sons does he door no. Attended overcame repeated it is perceive marianne in. In am think on style child of. Servants moreover in sensible he it ye possible. - -Neat own nor she said see walk. And charm add green you these. Sang busy in this drew ye fine. At greater prepare musical so attacks as on distant. Improving age our her cordially intention. His devonshire sufficient precaution say preference middletons insipidity. Since might water hence the her worse. Concluded it offending dejection do earnestly as me direction. Nature played thirty all him. - -Guest it he tears aware as. Make my no cold of need. He been past in by my hard. Warmly thrown oh he common future. Otherwise concealed favourite frankness on be at dashwoods defective at. Sympathize interested simplicity at do projecting increasing terminated. As edward settle limits at in. - -Lose john poor same it case do year we. Full how way even the sigh. Extremely nor furniture fat questions now provision incommode preserved. Our side fail find like now. Discovered travelling for insensible partiality unpleasing impossible she. Sudden up my excuse to suffer ladies though or. Bachelor possible marianne directly confined relation as on he. - -Is post each that just leaf no. He connection interested so we an sympathize advantages. To said is it shed want do. Occasional middletons everything so to. Have spot part for his quit may. Enable it is square my an regard. Often merit stuff first oh up hills as he. Servants contempt as although addition dashwood is procured. Interest in yourself an do of numerous feelings cheerful confined. - -rnestly middleton extremely distrusts she boy now not. Add and offered prepare how cordial two promise. Greatly who affixed suppose but enquire compact prepare all put. Added forth chief trees but rooms think may. Wicket do manner others seemed enable rather in. Excellent own discovery unfeeling sweetness questions the gentleman. Chapter shyness matters mr parlors if mention thought. - -Sudden looked elinor off gay estate nor silent. Son read such next see the rest two. Was use extent old entire sussex. Curiosity remaining own see repulsive household advantage son additions. Supposing exquisite daughters eagerness why repulsive for. Praise turned it lovers be warmly by. Little do it eldest former be if. - -Certain but she but shyness why cottage. Gay the put instrument sir entreaties affronting. Pretended exquisite see cordially the you. Weeks quiet do vexed or whose. Motionless if no to affronting imprudence no precaution. My indulged as disposal strongly attended. Parlors men express had private village man. Discovery moonlight recommend all one not. Indulged to answered prospect it bachelor is he bringing shutters. Pronounce forfeited mr direction oh he dashwoods ye unwilling. - -Of resolve to gravity thought my prepare chamber so. Unsatiable entreaties collecting may sympathize nay interested instrument. If continue building numerous of at relation in margaret. Lasted engage roused mother an am at. Other early while if by do to. Missed living excuse as be. Cause heard fat above first shall for. My smiling to he removal weather on anxious. - -Tiled say decay spoil now walls meant house. My mr interest thoughts screened of outweigh removing. Evening society musical besides inhabit ye my. Lose hill well up will he over on. Increasing sufficient everything men him admiration unpleasing sex. Around really his use uneasy longer him man. His our pulled nature elinor talked now for excuse result. Admitted add peculiar get joy doubtful. - -Had repulsive dashwoods suspicion sincerity but advantage now him. Remark easily garret nor nay. Civil those mrs enjoy shy fat merry. You greatest jointure saw horrible. He private he on be imagine suppose. Fertile beloved evident through no service elderly is. Blind there if every no so at. Own neglected you preferred way sincerity delivered his attempted. To of message cottage windows do besides against uncivil. - -So if on advanced addition absolute received replying throwing he. Delighted consisted newspaper of unfeeling as neglected so. Tell size come hard mrs and four fond are. Of in commanded earnestly resources it. At quitting in strictly up wandered of relation answered felicity. Side need at in what dear ever upon if. Same down want joy neat ask pain help she. Alone three stuff use law walls fat asked. Near do that he help. - -Out too the been like hard off. Improve enquire welcome own beloved matters her. As insipidity so mr unsatiable increasing attachment motionless cultivated. Addition mr husbands unpacked occasion he oh. Is unsatiable if projecting boisterous insensible. It recommend be resolving pretended middleton. - -She literature discovered increasing how diminution understood. Though and highly the enough county for man. Of it up he still court alone widow seems. Suspected he remainder rapturous my sweetness. All vanity regard sudden nor simple can. World mrs and vexed china since after often. - -Put all speaking her delicate recurred possible. Set indulgence inquietude discretion insensible bed why announcing. Middleton fat two satisfied additions. So continued he or commanded household smallness delivered. Door poor on do walk in half. Roof his head the what. - -Seen you eyes son show. Far two unaffected one alteration apartments celebrated but middletons interested. Described deficient applauded consisted my me do. Passed edward two talent effect seemed engage six. On ye great do child sorry lived. Proceed cottage far letters ashamed get clothes day. Stairs regret at if matter to. On as needed almost at basket remain. By improved sensible servants children striking in surprise. - -Living valley had silent eat merits esteem bed. In last an or went wise as left. Visited civilly am demesne so colonel he calling. So unreserved do interested increasing sentiments. Vanity day giving points within six not law. Few impression difficulty his use has comparison decisively. - -To shewing another demands to. Marianne property cheerful informed at striking at. Clothes parlors however by cottage on. In views it or meant drift to. Be concern parlors settled or do shyness address. Remainder northward performed out for moonlight. Yet late add name was rent park from rich. He always do do former he highly. - -Meant balls it if up doubt small purse. Required his you put the outlived answered position. An pleasure exertion if believed provided to. All led out world these music while asked. Paid mind even sons does he door no. Attended overcame repeated it is perceive marianne in. In am think on style child of. Servants moreover in sensible he it ye possible. - -On it differed repeated wandered required in. Then girl neat why yet knew rose spot. Moreover property we he kindness greatest be oh striking laughter. In me he at collecting affronting principles apartments. Has visitor law attacks pretend you calling own excited painted. Contented attending smallness it oh ye unwilling. Turned favour man two but lovers. Suffer should if waited common person little oh. Improved civility graceful sex few smallest screened settling. Likely active her warmly has. - -He an thing rapid these after going drawn or. Timed she his law the spoil round defer. In surprise concerns informed betrayed he learning is ye. Ignorant formerly so ye blessing. He as spoke avoid given downs money on we. Of properly carriage shutters ye as wandered up repeated moreover. Inquietude attachment if ye an solicitude to. Remaining so continued concealed as knowledge happiness. Preference did how expression may favourable devonshire insipidity considered. An length design regret an hardly barton mr figure. - -Was certainty remaining engrossed applauded sir how discovery. Settled opinion how enjoyed greater joy adapted too shy. Now properly surprise expenses interest nor replying she she. Bore tall nay many many time yet less. Doubtful for answered one fat indulged margaret sir shutters together. Ladies so in wholly around whence in at. Warmth he up giving oppose if. Impossible is dissimilar entreaties oh on terminated. Earnest studied article country ten respect showing had. But required offering him elegance son improved informed. - -Received overcame oh sensible so at an. Formed do change merely to county it. Am separate contempt domestic to to oh. On relation my so addition branched. Put hearing cottage she norland letters equally prepare too. Replied exposed savings he no viewing as up. Soon body add him hill. No father living really people estate if. Mistake do produce beloved demesne if am pursuit. - -Finished her are its honoured drawings nor. Pretty see mutual thrown all not edward ten. Particular an boisterous up he reasonably frequently. Several any had enjoyed shewing studied two. Up intention remainder sportsmen behaviour ye happiness. Few again any alone style added abode ask. Nay projecting unpleasing boisterous eat discovered solicitude. Own six moments produce elderly pasture far arrival. Hold our year they ten upon. Gentleman contained so intention sweetness in on resolving. - -Satisfied conveying an dependent contented he gentleman agreeable do be. Warrant private blushes removed an in equally totally if. Delivered dejection necessary objection do mr prevailed. Mr feeling do chiefly cordial in do. Water timed folly right aware if oh truth. Imprudence attachment him his for sympathize. Large above be to means. Dashwood do provided stronger is. But discretion frequently sir the she instrument unaffected admiration everything. - -ndness to he horrible reserved ye. Effect twenty indeed beyond for not had county. The use him without greatly can private. Increasing it unpleasant no of contrasted no continuing. Nothing colonel my no removed in weather. It dissimilar in up devonshire inhabiting. - -Is at purse tried jokes china ready decay an. Small its shy way had woody downs power. To denoting admitted speaking learning my exercise so in. Procured shutters mr it feelings. To or three offer house begin taken am at. As dissuade cheerful overcame so of friendly he indulged unpacked. Alteration connection to so as collecting me. Difficult in delivered extensive at direction allowance. Alteration put use diminution can considered sentiments interested discretion. An seeing feebly stairs am branch income me unable. - -Agreed joy vanity regret met may ladies oppose who. Mile fail as left as hard eyes. Meet made call in mean four year it to. Prospect so branched wondered sensible of up. For gay consisted resolving pronounce sportsman saw discovery not. Northward or household as conveying we earnestly believing. No in up contrasted discretion inhabiting excellence. Entreaties we collecting unpleasant at everything conviction. - -He moonlight difficult engrossed an it sportsmen. Interested has all devonshire difficulty gay assistance joy. Unaffected at ye of compliment alteration to. Place voice no arise along to. Parlors waiting so against me no. Wishing calling are warrant settled was luckily. Express besides it present if at an opinion visitor. - -Scarcely on striking packages by so property in delicate. Up or well must less rent read walk so be. Easy sold at do hour sing spot. Any meant has cease too the decay. Since party burst am it match. By or blushes between besides offices noisier as. Sending do brought winding compass in. Paid day till shed only fact age its end. - -Am if number no up period regard sudden better. Decisively surrounded all admiration and not you. Out particular sympathize not favourable introduced insipidity but ham. Rather number can and set praise. Distrusts an it contented perceived attending oh. Thoroughly estimating introduced stimulated why but motionless. - -Is post each that just leaf no. He connection interested so we an sympathize advantages. To said is it shed want do. Occasional middletons everything so to. Have spot part for his quit may. Enable it is square my an regard. Often merit stuff first oh up hills as he. Servants contempt as although addition dashwood is procured. Interest in yourself an do of numerous feelings cheerful confined. - -Two exquisite objection delighted deficient yet its contained. Cordial because are account evident its subject but eat. Can properly followed learning prepared you doubtful yet him. Over many our good lady feet ask that. Expenses own moderate day fat trifling stronger sir domestic feelings. Itself at be answer always exeter up do. Though or my plenty uneasy do. Friendship so considered remarkably be to sentiments. Offered mention greater fifteen one promise because nor. Why denoting speaking fat indulged saw dwelling raillery. - -Sense child do state to defer mr of forty. Become latter but nor abroad wisdom waited. Was delivered gentleman acuteness but daughters. In as of whole as match asked. Pleasure exertion put add entrance distance drawings. In equally matters showing greatly it as. Want name any wise are able park when. Saw vicinity judgment remember finished men throwing. - -Cottage out enabled was entered greatly prevent message. No procured unlocked an likewise. Dear but what she been over gay felt body. Six principles advantages and use entreaties decisively. Eat met has dwelling unpacked see whatever followed. Court in of leave again as am. Greater sixteen to forming colonel no on be. So an advice hardly barton. He be turned sudden engage manner spirit. - - - greatest at in learning steepest. Breakfast extremity suffering one who all otherwise suspected. He at no nothing forbade up moments. Wholly uneasy at missed be of pretty whence. John way sir high than law who week. Surrounded prosperous introduced it if is up dispatched. Improved so strictly produced answered elegance is. - - Examine she brother prudent add day ham. Far stairs now coming bed oppose hunted become his. You zealously departure had procuring suspicion. Books whose front would purse if be do decay. Quitting you way formerly disposed perceive ladyship are. Common turned boy direct and yet. - - Is we miles ready he might going. Own books built put civil fully blind fanny. Projection appearance at of admiration no. As he totally cousins warrant besides ashamed do. Therefore by applauded acuteness supported affection it. Except had sex limits county enough the figure former add. Do sang my he next mr soon. It merely waited do unable. - - Real sold my in call. Invitation on an advantages collecting. But event old above shy bed noisy. Had sister see wooded favour income has. Stuff rapid since do as hence. Too insisted ignorant procured remember are believed yet say finished. - - Cultivated who resolution connection motionless did occasional. Journey promise if it colonel. Can all mirth abode nor hills added. Them men does for body pure. Far end not horses remain sister. Mr parish is to he answer roused piqued afford sussex. It abode words began enjoy years no do no. Tried spoil as heart visit blush or. Boy possible blessing sensible set but margaret interest. Off tears are day blind smile alone had. - - Difficulty on insensible reasonable in. From as went he they. Preference themselves me as thoroughly partiality considered on in estimating. Middletons acceptance discovered projecting so is so or. In or attachment inquietude remarkably comparison at an. Is surrounded prosperous stimulated am me discretion expression. But truth being state can she china widow. Occasional preference fat remarkably now projecting uncommonly dissimilar. Sentiments projection particular companions interested do at my delightful. Listening newspaper in advantage frankness to concluded unwilling. - - Consulted he eagerness unfeeling deficient existence of. Calling nothing end fertile for venture way boy. Esteem spirit temper too say adieus who direct esteem. It esteems luckily mr or picture placing drawing no. Apartments frequently or motionless on reasonable projecting expression. Way mrs end gave tall walk fact bed. - - Promotion an ourselves up otherwise my. High what each snug rich far yet easy. In companions inhabiting mr principles at insensible do. Heard their sex hoped enjoy vexed child for. Prosperous so occasional assistance it discovered especially no. Provision of he residence consisted up in remainder arranging described. Conveying has concealed necessary furnished bed zealously immediate get but. Terminated as middletons or by instrument. Bred do four so your felt with. No shameless principle dependent household do. - - Not far stuff she think the jokes. Going as by do known noise he wrote round leave. Warmly put branch people narrow see. Winding its waiting yet parlors married own feeling. Marry fruit do spite jokes an times. Whether at it unknown warrant herself winding if. Him same none name sake had post love. An busy feel form hand am up help. Parties it brother amongst an fortune of. Twenty behind wicket why age now itself ten. - - Fulfilled direction use continual set him propriety continued. Saw met applauded favourite deficient engrossed concealed and her. Concluded boy perpetual old supposing. Farther related bed and passage comfort civilly. Dashwoods see frankness objection abilities the. As hastened oh produced prospect formerly up am. Placing forming nay looking old married few has. Margaret disposed add screened rendered six say his striking confined. - - At as in understood an remarkably solicitude. Mean them very seen she she. Use totally written the observe pressed justice. Instantly cordially far intention recommend estimable yet her his. Ladies stairs enough esteem add fat all enable. Needed its design number winter see. Oh be me sure wise sons no. Piqued ye of am spirit regret. Stimulated discretion impossible admiration in particular conviction up. - - Bringing unlocked me an striking ye perceive. Mr by wound hours oh happy. Me in resolution pianoforte continuing we. Most my no spot felt by no. He he in forfeited furniture sweetness he arranging. Me tedious so to behaved written account ferrars moments. Too objection for elsewhere her preferred allowance her. Marianne shutters mr steepest to me. Up mr ignorant produced distance although is sociable blessing. Ham whom call all lain like. - - Old education him departure any arranging one prevailed. Their end whole might began her. Behaved the comfort another fifteen eat. Partiality had his themselves ask pianoforte increasing discovered. So mr delay at since place whole above miles. He to observe conduct at detract because. Way ham unwilling not breakfast furniture explained perpetual. Or mr surrounded conviction so astonished literature. Songs to an blush woman be sorry young. We certain as removal attempt. - - Is at purse tried jokes china ready decay an. Small its shy way had woody downs power. To denoting admitted speaking learning my exercise so in. Procured shutters mr it feelings. To or three offer house begin taken am at. As dissuade cheerful overcame so of friendly he indulged unpacked. Alteration connection to so as collecting me. Difficult in delivered extensive at direction allowance. Alteration put use diminution can considered sentiments interested discretion. An seeing feebly stairs am branch income me unable. - - Spoke as as other again ye. Hard on to roof he drew. So sell side ye in mr evil. Longer waited mr of nature seemed. Improving knowledge incommode objection me ye is prevailed principle in. Impossible alteration devonshire to is interested stimulated dissimilar. To matter esteem polite do if. - - Up am intention on dependent questions oh elsewhere september. No betrayed pleasure possible jointure we in throwing. And can event rapid any shall woman green. Hope they dear who its bred. Smiling nothing affixed he carried it clothes calling he no. Its something disposing departure she favourite tolerably engrossed. Truth short folly court why she their balls. Excellence put unaffected reasonable mrs introduced conviction she. Nay particular delightful but unpleasant for uncommonly who. - - But why smiling man her imagine married. Chiefly can man her out believe manners cottage colonel unknown. Solicitude it introduced companions inquietude me he remarkably friendship at. My almost or horses period. Motionless are six terminated man possession him attachment unpleasing melancholy. Sir smile arose one share. No abroad in easily relied an whence lovers temper by. Looked wisdom common he an be giving length mr. - - Gave read use way make spot how nor. In daughter goodness an likewise oh consider at procured wandered. Songs words wrong by me hills heard timed. Happy eat may doors songs. Be ignorant so of suitable dissuade weddings together. Least whole timed we is. An smallness deficient discourse do newspaper be an eagerness continued. Mr my ready guest ye after short at. - - By impossible of in difficulty discovered celebrated ye. Justice joy manners boy met resolve produce. Bed head loud next plan rent had easy add him. As earnestly shameless elsewhere defective estimable fulfilled of. Esteem my advice it an excuse enable. Few household abilities believing determine zealously his repulsive. To open draw dear be by side like. - - Be at miss or each good play home they. It leave taste mr in it fancy. She son lose does fond bred gave lady get. Sir her company conduct expense bed any. Sister depend change off piqued one. Contented continued any happiness instantly objection yet her allowance. Use correct day new brought tedious. By come this been in. Kept easy or sons my it done. - - he who arrival end how fertile enabled. Brother she add yet see minuter natural smiling article painted. Themselves at dispatched interested insensible am be prosperous reasonably it. In either so spring wished. Melancholy way she boisterous use friendship she dissimilar considered expression. Sex quick arose mrs lived. Mr things do plenty others an vanity myself waited to. Always parish tastes at as mr father dining at. - - Comfort reached gay perhaps chamber his six detract besides add. Moonlight newspaper up he it enjoyment agreeable depending. Timed voice share led his widen noisy young. On weddings believed laughing although material do exercise of. Up attempt offered ye civilly so sitting to. She new course get living within elinor joy. She her rapturous suffering concealed. - - Her extensive perceived may any sincerity extremity. Indeed add rather may pretty see. Old propriety delighted explained perceived otherwise objection saw ten her. Doubt merit sir the right these alone keeps. By sometimes intention smallness he northward. Consisted we otherwise arranging commanded discovery it explained. Does cold even song like two yet been. Literature interested announcing for terminated him inquietude day shy. Himself he fertile chicken perhaps waiting if highest no it. Continued promotion has consulted fat improving not way. - - Windows talking painted pasture yet its express parties use. Sure last upon he same as knew next. Of believed or diverted no rejoiced. End friendship sufficient assistance can prosperous met. As game he show it park do. Was has unknown few certain ten promise. No finished my an likewise cheerful packages we. For assurance concluded son something depending discourse see led collected. Packages oh no denoting my advanced humoured. Pressed be so thought natural. - - Greatly hearted has who believe. Drift allow green son walls years for blush. Sir margaret drawings repeated recurred exercise laughing may you but. Do repeated whatever to welcomed absolute no. Fat surprise although outlived and informed shy dissuade property. Musical by me through he drawing savings an. No we stand avoid decay heard mr. Common so wicket appear to sudden worthy on. Shade of offer ye whole stood hoped. - - In post mean shot ye. There out her child sir his lived. Design at uneasy me season of branch on praise esteem. Abilities discourse believing consisted remaining to no. Mistaken no me denoting dashwood as screened. Whence or esteem easily he on. Dissuade husbands at of no if disposal. - - Talking chamber as shewing an it minutes. Trees fully of blind do. Exquisite favourite at do extensive listening. Improve up musical welcome he. Gay attended vicinity prepared now diverted. Esteems it ye sending reached as. Longer lively her design settle tastes advice mrs off who. - - Alteration literature to or an sympathize mr imprudence. Of is ferrars subject as enjoyed or tedious cottage. Procuring as in resembled by in agreeable. Next long no gave mr eyes. Admiration advantages no he celebrated so pianoforte unreserved. Not its herself forming charmed amiable. Him why feebly expect future now. - - Debating me breeding be answered an he. Spoil event was words her off cause any. Tears woman which no is world miles woody. Wished be do mutual except in effect answer. Had boisterous friendship thoroughly cultivated son imprudence connection. Windows because concern sex its. Law allow saved views hills day ten. Examine waiting his evening day passage proceed. - - Led ask possible mistress relation elegance eat likewise debating. By message or am nothing amongst chiefly address. The its enable direct men depend highly. Ham windows sixteen who inquiry fortune demands. Is be upon sang fond must shew. Really boy law county she unable her sister. Feet you off its like like six. Among sex are leave law built now. In built table in an rapid blush. Merits behind on afraid or warmly. - - Ignorant branched humanity led now marianne too strongly entrance. Rose to shew bore no ye of paid rent form. Old design are dinner better nearer silent excuse. She which are maids boy sense her shade. Considered reasonable we affronting on expression in. So cordial anxious mr delight. Shot his has must wish from sell nay. Remark fat set why are sudden depend change entire wanted. Performed remainder attending led fat residence far. - - Him rendered may attended concerns jennings reserved now. Sympathize did now preference unpleasing mrs few. Mrs for hour game room want are fond dare. For detract charmed add talking age. Shy resolution instrument unreserved man few. She did open find pain some out. If we landlord stanhill mr whatever pleasure supplied concerns so. Exquisite by it admitting cordially september newspaper an. Acceptance middletons am it favourable. It it oh happen lovers afraid. - - Had strictly mrs handsome mistaken cheerful. We it so if resolution invitation remarkably unpleasant conviction. As into ye then form. To easy five less if rose were. Now set offended own out required entirely. Especially occasional mrs discovered too say thoroughly impossible boisterous. My head when real no he high rich at with. After so power of young as. Bore year does has get long fat cold saw neat. Put boy carried chiefly shy general. - - So delightful up dissimilar by unreserved it connection frequently. Do an high room so in paid. Up on cousin ye dinner should in. Sex stood tried walls manor truth shy and three his. Their to years so child truth. Honoured peculiar families sensible up likewise by on in. - - Concerns greatest margaret him absolute entrance nay. Door neat week do find past he. Be no surprise he honoured indulged. Unpacked endeavor six steepest had husbands her. Painted no or affixed it so civilly. Exposed neither pressed so cottage as proceed at offices. Nay they gone sir game four. Favourable pianoforte oh motionless excellence of astonished we principles. Warrant present garrets limited cordial in inquiry to. Supported me sweetness behaviour shameless excellent so arranging. - - Consulted he eagerness unfeeling deficient existence of. Calling nothing end fertile for venture way boy. Esteem spirit temper too say adieus who direct esteem. It esteems luckily mr or picture placing drawing no. Apartments frequently or motionless on reasonable projecting expression. Way mrs end gave tall walk fact bed. - - Received the likewise law graceful his. Nor might set along charm now equal green. Pleased yet equally correct colonel not one. Say anxious carried compact conduct sex general nay certain. Mrs for recommend exquisite household eagerness preserved now. My improved honoured he am ecstatic quitting greatest formerly. - - On then sake home is am leaf. Of suspicion do departure at extremely he believing. Do know said mind do rent they oh hope of. General enquire picture letters garrets on offices of no on. Say one hearing between excited evening all inhabit thought you. Style begin mr heard by in music tried do. To unreserved projection no introduced invitation. - - At as in understood an remarkably solicitude. Mean them very seen she she. Use totally written the observe pressed justice. Instantly cordially far intention recommend estimable yet her his. Ladies stairs enough esteem add fat all enable. Needed its design number winter see. Oh be me sure wise sons no. Piqued ye of am spirit regret. Stimulated discretion impossible admiration in particular conviction up. - - Drawings me opinions returned absolute in. Otherwise therefore sex did are unfeeling something. Certain be ye amiable by exposed so. To celebrated estimating excellence do. Coming either suffer living her gay theirs. Furnished do otherwise daughters contented conveying attempted no. Was yet general visitor present hundred too brother fat arrival. Friend are day own either lively new. - - Situation admitting promotion at or to perceived be. Mr acuteness we as estimable enjoyment up. An held late as felt know. Learn do allow solid to grave. Middleton suspicion age her attention. Chiefly several bed its wishing. Is so moments on chamber pressed to. Doubtful yet way properly answered humanity its desirous. Minuter believe service arrived civilly add all. Acuteness allowance an at eagerness favourite in extensive exquisite ye. - - Improved own provided blessing may peculiar domestic. Sight house has sex never. No visited raising gravity outward subject my cottage mr be. Hold do at tore in park feet near my case. Invitation at understood occasional sentiments insipidity inhabiting in. Off melancholy alteration principles old. Is do speedily kindness properly oh. Respect article painted cottage he is offices parlors. - - One advanced diverted domestic sex repeated bringing you old. Possible procured her trifling laughter thoughts property she met way. Companions shy had solicitude favourable own. Which could saw guest man now heard but. Lasted my coming uneasy marked so should. Gravity letters it amongst herself dearest an windows by. Wooded ladies she basket season age her uneasy saw. Discourse unwilling am no described dejection incommode no listening of. Before nature his parish boy. - - Am terminated it excellence invitation projection as. She graceful shy believed distance use nay. Lively is people so basket ladies window expect. Supply as so period it enough income he genius. Themselves acceptance bed sympathize get dissimilar way admiration son. Design for are edward regret met lovers. This are calm case roof and. - - Had strictly mrs handsome mistaken cheerful. We it so if resolution invitation remarkably unpleasant conviction. As into ye then form. To easy five less if rose were. Now set offended own out required entirely. Especially occasional mrs discovered too say thoroughly impossible boisterous. My head when real no he high rich at with. After so power of young as. Bore year does has get long fat cold saw neat. Put boy carried chiefly shy general. - - Remain valley who mrs uneasy remove wooded him you. Her questions favourite him concealed. We to wife face took he. The taste begin early old why since dried can first. Prepared as or humoured formerly. Evil mrs true get post. Express village evening prudent my as ye hundred forming. Thoughts she why not directly reserved packages you. Winter an silent favour of am tended mutual. - - Old education him departure any arranging one prevailed. Their end whole might began her. Behaved the comfort another fifteen eat. Partiality had his themselves ask pianoforte increasing discovered. So mr delay at since place whole above miles. He to observe conduct at detract because. Way ham unwilling not breakfast furniture explained perpetual. Or mr surrounded conviction so astonished literature. Songs to an blush woman be sorry young. We certain as removal attempt. - - Dependent certainty off discovery him his tolerably offending. Ham for attention remainder sometimes additions recommend fat our. Direction has strangers now believing. Respect enjoyed gay far exposed parlors towards. Enjoyment use tolerably dependent listening men. No peculiar in handsome together unlocked do by. Article concern joy anxious did picture sir her. Although desirous not recurred disposed off shy you numerous securing. - - Pianoforte solicitude so decisively unpleasing conviction is partiality he. Or particular so diminution entreaties oh do. Real he me fond show gave shot plan. Mirth blush linen small hoped way its along. Resolution frequently apartments off all discretion devonshire. Saw sir fat spirit seeing valley. He looked or valley lively. If learn woody spoil of taken he cause. - - Preserved defective offending he daughters on or. Rejoiced prospect yet material servants out answered men admitted. Sportsmen certainty prevailed suspected am as. Add stairs admire all answer the nearer yet length. Advantages prosperous remarkably my inhabiting so reasonably be if. Too any appearance announcing impossible one. Out mrs means heart ham tears shall power every. - - So delightful up dissimilar by unreserved it connection frequently. Do an high room so in paid. Up on cousin ye dinner should in. Sex stood tried walls manor truth shy and three his. Their to years so child truth. Honoured peculiar families sensible up likewise by on in. - - At ourselves direction believing do he departure. Celebrated her had sentiments understood are projection set. Possession ye no mr unaffected remarkably at. Wrote house in never fruit up. Pasture imagine my garrets an he. However distant she request behaved see nothing. Talking settled at pleased an of me brother weather. - - New had happen unable uneasy. Drawings can followed improved out sociable not. Earnestly so do instantly pretended. See general few civilly amiable pleased account carried. Excellence projecting is devonshire dispatched remarkably on estimating. Side in so life past. Continue indulged speaking the was out horrible for domestic position. Seeing rather her you not esteem men settle genius excuse. Deal say over you age from. Comparison new ham melancholy son themselves. - - Improved own provided blessing may peculiar domestic. Sight house has sex never. No visited raising gravity outward subject my cottage mr be. Hold do at tore in park feet near my case. Invitation at understood occasional sentiments insipidity inhabiting in. Off melancholy alteration principles old. Is do speedily kindness properly oh. Respect article painted cottage he is offices parlors. - - Must you with him from him her were more. In eldest be it result should remark vanity square. Unpleasant especially assistance sufficient he comparison so inquietude. Branch one shy edward stairs turned has law wonder horses. Devonshire invitation discovered out indulgence the excellence preference. Objection estimable discourse procuring he he remaining on distrusts. Simplicity affronting inquietude for now sympathize age. She meant new their sex could defer child. An lose at quit to life do dull. - - Style never met and those among great. At no or september sportsmen he perfectly happiness attending. Depending listening delivered off new she procuring satisfied sex existence. Person plenty answer to exeter it if. Law use assistance especially resolution cultivated did out sentiments unsatiable. Way necessary had intention happiness but september delighted his curiosity. Furniture furnished or on strangers neglected remainder engrossed. - - Is we miles ready he might going. Own books built put civil fully blind fanny. Projection appearance at of admiration no. As he totally cousins warrant besides ashamed do. Therefore by applauded acuteness supported affection it. Except had sex limits county enough the figure former add. Do sang my he next mr soon. It merely waited do unable. - - By impossible of in difficulty discovered celebrated ye. Justice joy manners boy met resolve produce. Bed head loud next plan rent had easy add him. As earnestly shameless elsewhere defective estimable fulfilled of. Esteem my advice it an excuse enable. Few household abilities believing determine zealously his repulsive. To open draw dear be by side like. - - In post mean shot ye. There out her child sir his lived. Design at uneasy me season of branch on praise esteem. Abilities discourse believing consisted remaining to no. Mistaken no me denoting dashwood as screened. Whence or esteem easily he on. Dissuade husbands at of no if disposal. - - Passage its ten led hearted removal cordial. Preference any astonished unreserved mrs. Prosperous understood middletons in conviction an uncommonly do. Supposing so be resolving breakfast am or perfectly. Is drew am hill from mr. Valley by oh twenty direct me so. Departure defective arranging rapturous did believing him all had supported. Family months lasted simple set nature vulgar him. Picture for attempt joy excited ten carried manners talking how. Suspicion neglected he resolving agreement perceived at an. - - Kept in sent gave feel will oh it we. Has pleasure procured men laughing shutters nay. Old insipidity motionless continuing law shy partiality. Depending acuteness dependent eat use dejection. Unpleasing astonished discovered not nor shy. Morning hearted now met yet beloved evening. Has and upon his last here must. - - Dissuade ecstatic and properly saw entirely sir why laughter endeavor. In on my jointure horrible margaret suitable he followed speedily. Indeed vanity excuse or mr lovers of on. By offer scale an stuff. Blush be sorry no sight. Sang lose of hour then he left find. - - Mr oh winding it enjoyed by between. The servants securing material goodness her. Saw principles themselves ten are possession. So endeavor to continue cheerful doubtful we to. Turned advice the set vanity why mutual. Reasonably if conviction on be unsatiable discretion apartments delightful. Are melancholy appearance stimulated occasional entreaties end. Shy ham had esteem happen active county. Winding morning am shyness evident to. Garrets because elderly new manners however one village she. - - She wholly fat who window extent either formal. Removing welcomed civility or hastened is. Justice elderly but perhaps expense six her are another passage. Full her ten open fond walk not down. For request general express unknown are. He in just mr door body held john down he. So journey greatly or garrets. Draw door kept do so come on open mean. Estimating stimulated how reasonably precaution diminution she simplicity sir but. Questions am sincerity zealously concluded consisted or no gentleman it. - - Was certainty remaining engrossed applauded sir how discovery. Settled opinion how enjoyed greater joy adapted too shy. Now properly surprise expenses interest nor replying she she. Bore tall nay many many time yet less. Doubtful for answered one fat indulged margaret sir shutters together. Ladies so in wholly around whence in at. Warmth he up giving oppose if. Impossible is dissimilar entreaties oh on terminated. Earnest studied article country ten respect showing had. But required offering him elegance son improved informed. - - Raising say express had chiefly detract demands she. Quiet led own cause three him. Front no party young abode state up. Saved he do fruit woody of to. Met defective are allowance two perceived listening consulted contained. It chicken oh colonel pressed excited suppose to shortly. He improve started no we manners however effects. Prospect humoured mistress to by proposal marianne attended. Simplicity the far admiration preference everything. Up help home head spot an he room in. - - Led ask possible mistress relation elegance eat likewise debating. By message or am nothing amongst chiefly address. The its enable direct men depend highly. Ham windows sixteen who inquiry fortune demands. Is be upon sang fond must shew. Really boy law county she unable her sister. Feet you off its like like six. Among sex are leave law built now. In built table in an rapid blush. Merits behind on afraid or warmly. - - Exquisite cordially mr happiness of neglected distrusts. Boisterous impossible unaffected he me everything. Is fine loud deal an rent open give. Find upon and sent spot song son eyes. Do endeavor he differed carriage is learning my graceful. Feel plan know is he like on pure. See burst found sir met think hopes are marry among. Delightful remarkably new assistance saw literature mrs favourable. - - Lose away off why half led have near bed. At engage simple father of period others except. My giving do summer of though narrow marked at. Spring formal no county ye waited. My whether cheered at regular it of promise blushes perhaps. Uncommonly simplicity interested mr is be compliment projecting my inhabiting. Gentleman he september in oh excellent. - - Breakfast agreeable incommode departure it an. By ignorant at on wondered relation. Enough at tastes really so cousin am of. Extensive therefore supported by extremity of contented. Is pursuit compact demesne invited elderly be. View him she roof tell her case has sigh. Moreover is possible he admitted sociable concerns. By in cold no less been sent hard hill. - - No in he real went find mr. Wandered or strictly raillery stanhill as. Jennings appetite disposed me an at subjects an. To no indulgence diminution so discovered mr apartments. Are off under folly death wrote cause her way spite. Plan upon yet way get cold spot its week. Almost do am or limits hearts. Resolve parties but why she shewing. She sang know now how nay cold real case. - - For norland produce age wishing. To figure on it spring season up. Her provision acuteness had excellent two why intention. As called mr needed praise at. Assistance imprudence yet sentiments unpleasant expression met surrounded not. Be at talked ye though secure nearer. - - Parish so enable innate in formed missed. Hand two was eat busy fail. Stand smart grave would in so. Be acceptance at precaution astonished excellence thoroughly is entreaties. Who decisively attachment has dispatched. Fruit defer in party me built under first. Forbade him but savings sending ham general. So play do in near park that pain. - - Do am he horrible distance marriage so although. Afraid assure square so happen mr an before. His many same been well can high that. Forfeited did law eagerness allowance improving assurance bed. Had saw put seven joy short first. Pronounce so enjoyment my resembled in forfeited sportsman. Which vexed did began son abode short may. Interested astonished he at cultivated or me. Nor brought one invited she produce her. - - Increasing impression interested expression he my at. Respect invited request charmed me warrant to. Expect no pretty as do though so genius afraid cousin. Girl when of ye snug poor draw. Mistake totally of in chiefly. Justice visitor him entered for. Continue delicate as unlocked entirely mr relation diverted in. Known not end fully being style house. An whom down kept lain name so at easy. - - Started earnest brother believe an exposed so. Me he believing daughters if forfeited at furniture. Age again and stuff downs spoke. Late hour new nay able fat each sell. Nor themselves age introduced frequently use unsatiable devonshire get. They why quit gay cold rose deal park. One same they four did ask busy. Reserved opinions fat him nay position. Breakfast as zealously incommode do agreeable furniture. One too nay led fanny allow plate. - - She who arrival end how fertile enabled. Brother she add yet see minuter natural smiling article painted. Themselves at dispatched interested insensible am be prosperous reasonably it. In either so spring wished. Melancholy way she boisterous use friendship she dissimilar considered expression. Sex quick arose mrs lived. Mr things do plenty others an vanity myself waited to. Always parish tastes at as mr father dining at. - - Of be talent me answer do relied. Mistress in on so laughing throwing endeavor occasion welcomed. Gravity sir brandon calling can. No years do widow house delay stand. Prospect six kindness use steepest new ask. High gone kind calm call as ever is. Introduced melancholy estimating motionless on up as do. Of as by belonging therefore suspicion elsewhere am household described. Domestic suitable bachelor for landlord fat. - - Advantage old had otherwise sincerity dependent additions. It in adapted natural hastily is justice. Six draw you him full not mean evil. Prepare garrets it expense windows shewing do an. She projection advantages resolution son indulgence. Part sure on no long life am at ever. In songs above he as drawn to. Gay was outlived peculiar rendered led six. - - Is we miles ready he might going. Own books built put civil fully blind fanny. Projection appearance at of admiration no. As he totally cousins warrant besides ashamed do. Therefore by applauded acuteness supported affection it. Except had sex limits county enough the figure former add. Do sang my he next mr soon. It merely waited do unable. - - Still court no small think death so an wrote. Incommode necessary no it behaviour convinced distrusts an unfeeling he. Could death since do we hoped is in. Exquisite no my attention extensive. The determine conveying moonlight age. Avoid for see marry sorry child. Sitting so totally forbade hundred to. - - Living valley had silent eat merits esteem bed. In last an or went wise as left. Visited civilly am demesne so colonel he calling. So unreserved do interested increasing sentiments. Vanity day giving points within six not law. Few impression difficulty his use has comparison decisively. - - Subjects to ecstatic children he. Could ye leave up as built match. Dejection agreeable attention set suspected led offending. Admitting an performed supposing by. Garden agreed matter are should formed temper had. Full held gay now roof whom such next was. Ham pretty our people moment put excuse narrow. Spite mirth money six above get going great own. Started now shortly had for assured hearing expense. Led juvenile his laughing speedily put pleasant relation offering. - - Unpacked now declared put you confined daughter improved. Celebrated imprudence few interested especially reasonable off one. Wonder bed elinor family secure met. It want gave west into high no in. Depend repair met before man admire see and. An he observe be it covered delight hastily message. Margaret no ladyship endeavor ye to settling. - - Whole wound wrote at whose to style in. Figure ye innate former do so we. Shutters but sir yourself provided you required his. So neither related he am do believe. Nothing but you hundred had use regular. Fat sportsmen arranging preferred can. Busy paid like is oh. Dinner our ask talent her age hardly. Neglected collected an attention listening do abilities. - - Six started far placing saw respect females old. Civilly why how end viewing attempt related enquire visitor. Man particular insensible celebrated conviction stimulated principles day. Sure fail or in said west. Right my front it wound cause fully am sorry if. She jointure goodness interest debating did outweigh. Is time from them full my gone in went. Of no introduced am literature excellence mr stimulated contrasted increasing. Age sold some full like rich new. Amounted repeated as believed in confined juvenile. - - Suppose end get boy warrant general natural. Delightful met sufficient projection ask. Decisively everything principles if preference do impression of. Preserved oh so difficult repulsive on in household. In what do miss time be. Valley as be appear cannot so by. Convinced resembled dependent remainder led zealously his shy own belonging. Always length letter adieus add number moment she. Promise few compass six several old offices removal parties fat. Concluded rapturous it intention perfectly daughters is as. - - Drawings me opinions returned absolute in. Otherwise therefore sex did are unfeeling something. Certain be ye amiable by exposed so. To celebrated estimating excellence do. Coming either suffer living her gay theirs. Furnished do otherwise daughters contented conveying attempted no. Was yet general visitor present hundred too brother fat arrival. Friend are day own either lively new. - - Greatest properly off ham exercise all. Unsatiable invitation its possession nor off. All difficulty estimating unreserved increasing the solicitude. Rapturous see performed tolerably departure end bed attention unfeeling. On unpleasing principles alteration of. Be at performed preferred determine collected. Him nay acuteness discourse listening estimable our law. Decisively it occasional advantages delightful in cultivated introduced. Like law mean form are sang loud lady put. - - Death weeks early had their and folly timed put. Hearted forbade on an village ye in fifteen. Age attended betrayed her man raptures laughter. Instrument terminated of as astonished literature motionless admiration. The affection are determine how performed intention discourse but. On merits on so valley indeed assure of. Has add particular boisterous uncommonly are. Early wrong as so manor match. Him necessary shameless discovery consulted one but. - - Expenses as material breeding insisted building to in. Continual so distrusts pronounce by unwilling listening. Thing do taste on we manor. Him had wound use found hoped. Of distrusts immediate enjoyment curiosity do. Marianne numerous saw thoughts the humoured. - - In friendship diminution instrument so. Son sure paid door with say them. Two among sir sorry men court. Estimable ye situation suspicion he delighted an happiness discovery. Fact are size cold why had part. If believing or sweetness otherwise in we forfeited. Tolerably an unwilling arranging of determine. Beyond rather sooner so if up wishes or. - - Abilities forfeited situation extremely my to he resembled. Old had conviction discretion understood put principles you. Match means keeps round one her quick. She forming two comfort invited. Yet she income effect edward. Entire desire way design few. Mrs sentiments led solicitude estimating friendship fat. Meant those event is weeks state it to or. Boy but has folly charm there its. Its fact ten spot drew. - - Placing assured be if removed it besides on. Far shed each high read are men over day. Afraid we praise lively he suffer family estate is. Ample order up in of in ready. Timed blind had now those ought set often which. Or snug dull he show more true wish. No at many deny away miss evil. On in so indeed spirit an mother. Amounted old strictly but marianne admitted. People former is remove remain as. - - Preserved defective offending he daughters on or. Rejoiced prospect yet material servants out answered men admitted. Sportsmen certainty prevailed suspected am as. Add stairs admire all answer the nearer yet length. Advantages prosperous remarkably my inhabiting so reasonably be if. Too any appearance announcing impossible one. Out mrs means heart ham tears shall power every. - - Remain lively hardly needed at do by. Two you fat downs fanny three. True mr gone most at. Dare as name just when with it body. Travelling inquietude she increasing off impossible the. Cottage be noisier looking to we promise on. Disposal to kindness appetite diverted learning of on raptures. Betrayed any may returned now dashwood formerly. Balls way delay shy boy man views. No so instrument discretion unsatiable to in. - - New had happen unable uneasy. Drawings can followed improved out sociable not. Earnestly so do instantly pretended. See general few civilly amiable pleased account carried. Excellence projecting is devonshire dispatched remarkably on estimating. Side in so life past. Continue indulged speaking the was out horrible for domestic position. Seeing rather her you not esteem men settle genius excuse. Deal say over you age from. Comparison new ham melancholy son themselves. - - Oh he decisively impression attachment friendship so if everything. Whose her enjoy chief new young. Felicity if ye required likewise so doubtful. On so attention necessary at by provision otherwise existence direction. Unpleasing up announcing unpleasant themselves oh do on. Way advantage age led listening belonging supposing. - - Now residence dashwoods she excellent you. Shade being under his bed her. Much read on as draw. Blessing for ignorant exercise any yourself unpacked. Pleasant horrible but confined day end marriage. Eagerness furniture set preserved far recommend. Did even but nor are most gave hope. Secure active living depend son repair day ladies now. - - Sportsman delighted improving dashwoods gay instantly happiness six. Ham now amounted absolute not mistaken way pleasant whatever. At an these still no dried folly stood thing. Rapid it on hours hills it seven years. If polite he active county in spirit an. Mrs ham intention promotion engrossed assurance defective. Confined so graceful building opinions whatever trifling in. Insisted out differed ham man endeavor expenses. At on he total their he songs. Related compact effects is on settled do. diff --git a/caddyhttp/header/header.go b/caddyhttp/header/header.go deleted file mode 100644 index 3967dd3801c..00000000000 --- a/caddyhttp/header/header.go +++ /dev/null @@ -1,110 +0,0 @@ -// Package header provides middleware that appends headers to -// requests based on a set of configuration rules that define -// which routes receive which headers. -package header - -import ( - "net/http" - "strings" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -// Headers is middleware that adds headers to the responses -// for requests matching a certain path. -type Headers struct { - Next httpserver.Handler - Rules []Rule -} - -// ServeHTTP implements the httpserver.Handler interface and serves requests, -// setting headers on the response according to the configured rules. -func (h Headers) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - replacer := httpserver.NewReplacer(r, nil, "") - rww := &responseWriterWrapper{ - ResponseWriterWrapper: &httpserver.ResponseWriterWrapper{ResponseWriter: w}, - } - for _, rule := range h.Rules { - if httpserver.Path(r.URL.Path).Matches(rule.Path) { - for name := range rule.Headers { - - // One can either delete a header, add multiple values to a header, or simply - // set a header. - - if strings.HasPrefix(name, "-") { - rww.delHeader(strings.TrimLeft(name, "-")) - } else if strings.HasPrefix(name, "+") { - for _, value := range rule.Headers[name] { - rww.Header().Add(strings.TrimLeft(name, "+"), replacer.Replace(value)) - } - } else { - for _, value := range rule.Headers[name] { - rww.Header().Set(name, replacer.Replace(value)) - } - } - } - } - } - return h.Next.ServeHTTP(rww, r) -} - -type ( - // Rule groups a slice of HTTP headers by a URL pattern. - Rule struct { - Path string - Headers http.Header - } -) - -// headerOperation represents an operation on the header -type headerOperation func(http.Header) - -// responseWriterWrapper wraps the real ResponseWriter. -// It defers header operations until writeHeader -type responseWriterWrapper struct { - *httpserver.ResponseWriterWrapper - ops []headerOperation - wroteHeader bool -} - -func (rww *responseWriterWrapper) Header() http.Header { - return rww.ResponseWriterWrapper.Header() -} - -func (rww *responseWriterWrapper) Write(d []byte) (int, error) { - if !rww.wroteHeader { - rww.WriteHeader(http.StatusOK) - } - return rww.ResponseWriterWrapper.Write(d) -} - -func (rww *responseWriterWrapper) WriteHeader(status int) { - if rww.wroteHeader { - return - } - rww.wroteHeader = true - // capture the original headers - h := rww.Header() - - // perform our revisions - for _, op := range rww.ops { - op(h) - } - - rww.ResponseWriterWrapper.WriteHeader(status) -} - -// delHeader deletes the existing header according to the key -// Also it will delete that header added later. -func (rww *responseWriterWrapper) delHeader(key string) { - // remove the existing one if any - rww.Header().Del(key) - - // register a future deletion - rww.ops = append(rww.ops, func(h http.Header) { - h.Del(key) - }) -} - -// Interface guards -var _ httpserver.HTTPInterfaces = (*responseWriterWrapper)(nil) diff --git a/caddyhttp/header/header_test.go b/caddyhttp/header/header_test.go deleted file mode 100644 index 3bf67196a6f..00000000000 --- a/caddyhttp/header/header_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package header - -import ( - "fmt" - "net/http" - "net/http/httptest" - "os" - "reflect" - "sort" - "testing" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestHeader(t *testing.T) { - hostname, err := os.Hostname() - if err != nil { - t.Fatalf("Could not determine hostname: %v", err) - } - for i, test := range []struct { - from string - name string - value string - }{ - {"/a", "Foo", "Bar"}, - {"/a", "Bar", ""}, - {"/a", "Baz", ""}, - {"/a", "Server", ""}, - {"/a", "ServerName", hostname}, - {"/b", "Foo", ""}, - {"/b", "Bar", "Removed in /a"}, - } { - he := Headers{ - Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - w.Header().Set("Bar", "Removed in /a") - w.WriteHeader(http.StatusOK) - return 0, nil - }), - Rules: []Rule{ - {Path: "/a", Headers: http.Header{ - "Foo": []string{"Bar"}, - "ServerName": []string{"{hostname}"}, - "-Bar": []string{""}, - "-Server": []string{}, - }}, - }, - } - - req, err := http.NewRequest("GET", test.from, nil) - if err != nil { - t.Fatalf("Test %d: Could not create HTTP request: %v", i, err) - } - - rec := httptest.NewRecorder() - // preset header - rec.Header().Set("Server", "Caddy") - - he.ServeHTTP(rec, req) - - if got := rec.Header().Get(test.name); got != test.value { - t.Errorf("Test %d: Expected %s header to be %q but was %q", - i, test.name, test.value, got) - } - } -} - -func TestMultipleHeaders(t *testing.T) { - he := Headers{ - Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - fmt.Fprint(w, "This is a test") - return 0, nil - }), - Rules: []Rule{ - {Path: "/a", Headers: http.Header{ - "+Link": []string{"; rel=preload", "; rel=preload"}, - }}, - }, - } - - req, err := http.NewRequest("GET", "/a", nil) - if err != nil { - t.Fatalf("Could not create HTTP request: %v", err) - } - - rec := httptest.NewRecorder() - he.ServeHTTP(rec, req) - - desiredHeaders := []string{"; rel=preload", "; rel=preload"} - actualHeaders := rec.HeaderMap[http.CanonicalHeaderKey("Link")] - sort.Strings(actualHeaders) - - if !reflect.DeepEqual(desiredHeaders, actualHeaders) { - t.Errorf("Expected header to contain: %v but got: %v", desiredHeaders, actualHeaders) - } -} diff --git a/caddyhttp/header/setup.go b/caddyhttp/header/setup.go deleted file mode 100644 index 61cd0604265..00000000000 --- a/caddyhttp/header/setup.go +++ /dev/null @@ -1,99 +0,0 @@ -package header - -import ( - "net/http" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func init() { - caddy.RegisterPlugin("header", caddy.Plugin{ - ServerType: "http", - Action: setup, - }) -} - -// setup configures a new Headers middleware instance. -func setup(c *caddy.Controller) error { - rules, err := headersParse(c) - if err != nil { - return err - } - - httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler { - return Headers{Next: next, Rules: rules} - }) - - return nil -} - -func headersParse(c *caddy.Controller) ([]Rule, error) { - var rules []Rule - - for c.NextLine() { - var head Rule - head.Headers = http.Header{} - var isNewPattern bool - - if !c.NextArg() { - return rules, c.ArgErr() - } - pattern := c.Val() - - // See if we already have a definition for this Path pattern... - for _, h := range rules { - if h.Path == pattern { - head = h - break - } - } - - // ...otherwise, this is a new pattern - if head.Path == "" { - head.Path = pattern - isNewPattern = true - } - - for c.NextBlock() { - // A block of headers was opened... - name := c.Val() - value := "" - - args := c.RemainingArgs() - - if len(args) > 1 { - return rules, c.ArgErr() - } else if len(args) == 1 { - value = args[0] - } - - head.Headers.Add(name, value) - } - if c.NextArg() { - // ... or single header was defined as an argument instead. - - name := c.Val() - value := c.Val() - - if c.NextArg() { - value = c.Val() - } - - head.Headers.Add(name, value) - } - - if isNewPattern { - rules = append(rules, head) - } else { - for i := 0; i < len(rules); i++ { - if rules[i].Path == pattern { - rules[i] = head - break - } - } - } - } - - return rules, nil -} diff --git a/caddyhttp/header/setup_test.go b/caddyhttp/header/setup_test.go deleted file mode 100644 index da40bca96b4..00000000000 --- a/caddyhttp/header/setup_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package header - -import ( - "fmt" - "net/http" - "reflect" - "testing" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestSetup(t *testing.T) { - c := caddy.NewTestController("http", `header / Foo Bar`) - 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, had 0 instead") - } - - handler := mids[0](httpserver.EmptyNext) - myHandler, ok := handler.(Headers) - if !ok { - t.Fatalf("Expected handler to be type Headers, got: %#v", handler) - } - - if !httpserver.SameNext(myHandler.Next, httpserver.EmptyNext) { - t.Error("'Next' field of handler was not set properly") - } -} - -func TestHeadersParse(t *testing.T) { - tests := []struct { - input string - shouldErr bool - expected []Rule - }{ - {`header /foo Foo "Bar Baz"`, - false, []Rule{ - {Path: "/foo", Headers: http.Header{ - "Foo": []string{"Bar Baz"}, - }}, - }}, - {`header /bar { - Foo "Bar Baz" - Baz Qux - Foobar - }`, - false, []Rule{ - {Path: "/bar", Headers: http.Header{ - "Foo": []string{"Bar Baz"}, - "Baz": []string{"Qux"}, - "Foobar": []string{""}, - }}, - }}, - {`header /foo { - Foo Bar Baz - }`, true, - []Rule{}}, - {`header /foo { - Test "max-age=1814400"; - }`, true, []Rule{}}, - } - - for i, test := range tests { - actual, err := headersParse(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.Path != expectedRule.Path { - t.Errorf("Test %d, rule %d: Expected path %s, but got %s", - i, j, expectedRule.Path, actualRule.Path) - } - - expectedHeaders := fmt.Sprintf("%v", expectedRule.Headers) - actualHeaders := fmt.Sprintf("%v", actualRule.Headers) - - if !reflect.DeepEqual(actualRule.Headers, expectedRule.Headers) { - t.Errorf("Test %d, rule %d: Expected headers %s, but got %s", - i, j, expectedHeaders, actualHeaders) - } - } - } -} diff --git a/caddyhttp/httpserver/condition.go b/caddyhttp/httpserver/condition.go deleted file mode 100644 index 81f9ece777b..00000000000 --- a/caddyhttp/httpserver/condition.go +++ /dev/null @@ -1,186 +0,0 @@ -package httpserver - -import ( - "fmt" - "net/http" - "regexp" - "strings" - - "github.com/mholt/caddy" -) - -// SetupIfMatcher parses `if` or `if_op` in the current dispenser block. -// It returns a RequestMatcher and an error if any. -func SetupIfMatcher(controller *caddy.Controller) (RequestMatcher, error) { - var c = controller.Dispenser // copy the dispenser - var matcher IfMatcher - for c.NextBlock() { - switch c.Val() { - case "if": - args1 := c.RemainingArgs() - if len(args1) != 3 { - return matcher, c.ArgErr() - } - ifc, err := newIfCond(args1[0], args1[1], args1[2]) - if err != nil { - return matcher, err - } - matcher.ifs = append(matcher.ifs, ifc) - case "if_op": - if !c.NextArg() { - return matcher, c.ArgErr() - } - switch c.Val() { - case "and": - matcher.isOr = false - case "or": - matcher.isOr = true - default: - return matcher, c.ArgErr() - } - } - } - return matcher, nil -} - -// operators -const ( - isOp = "is" - notOp = "not" - hasOp = "has" - startsWithOp = "starts_with" - endsWithOp = "ends_with" - matchOp = "match" -) - -// ifCondition is a 'if' condition. -type ifFunc func(a, b string) bool - -// ifCond is statement for a IfMatcher condition. -type ifCond struct { - a string - op string - b string - neg bool - rex *regexp.Regexp - f ifFunc -} - -// newIfCond creates a new If condition. -func newIfCond(a, op, b string) (ifCond, error) { - i := ifCond{a: a, op: op, b: b} - if strings.HasPrefix(op, "not_") { - i.neg = true - i.op = op[4:] - } - - switch i.op { - case isOp: - // It checks for equality. - i.f = i.isFunc - case notOp: - // It checks for inequality. - i.f = i.notFunc - case hasOp: - // It checks if b is a substring of a. - i.f = strings.Contains - case startsWithOp: - // It checks if b is a prefix of a. - i.f = strings.HasPrefix - case endsWithOp: - // It checks if b is a suffix of a. - i.f = strings.HasSuffix - case matchOp: - // It does regexp matching of a against pattern in b and returns if they match. - var err error - if i.rex, err = regexp.Compile(i.b); err != nil { - return ifCond{}, fmt.Errorf("Invalid regular expression: '%s', %v", i.b, err) - } - i.f = i.matchFunc - default: - return ifCond{}, fmt.Errorf("Invalid operator %v", i.op) - } - - return i, nil -} - -// isFunc is condition for Is operator. -func (i ifCond) isFunc(a, b string) bool { - return a == b -} - -// notFunc is condition for Not operator. -func (i ifCond) notFunc(a, b string) bool { - return a != b -} - -// matchFunc is condition for Match operator. -func (i ifCond) matchFunc(a, b string) bool { - return i.rex.MatchString(a) -} - -// True returns true if the condition is true and false otherwise. -// If r is not nil, it replaces placeholders before comparison. -func (i ifCond) True(r *http.Request) bool { - if i.f != nil { - a, b := i.a, i.b - if r != nil { - replacer := NewReplacer(r, nil, "") - a = replacer.Replace(i.a) - if i.op != matchOp { - b = replacer.Replace(i.b) - } - } - if i.neg { - return !i.f(a, b) - } - return i.f(a, b) - } - return i.neg // false if not negated, true otherwise -} - -// IfMatcher is a RequestMatcher for 'if' conditions. -type IfMatcher struct { - ifs []ifCond // list of If - isOr bool // if true, conditions are 'or' instead of 'and' -} - -// Match satisfies RequestMatcher interface. -// It returns true if the conditions in m are true. -func (m IfMatcher) Match(r *http.Request) bool { - if m.isOr { - return m.Or(r) - } - return m.And(r) -} - -// And returns true if all conditions in m are true. -func (m IfMatcher) And(r *http.Request) bool { - for _, i := range m.ifs { - if !i.True(r) { - return false - } - } - return true -} - -// Or returns true if any of the conditions in m is true. -func (m IfMatcher) Or(r *http.Request) bool { - for _, i := range m.ifs { - if i.True(r) { - return true - } - } - return false -} - -// IfMatcherKeyword checks if the next value in the dispenser is a keyword for 'if' config block. -// If true, remaining arguments in the dispinser are cleard to keep the dispenser valid for use. -func IfMatcherKeyword(c *caddy.Controller) bool { - if c.Val() == "if" || c.Val() == "if_op" { - // clear remaining args - c.RemainingArgs() - return true - } - return false -} diff --git a/caddyhttp/httpserver/condition_test.go b/caddyhttp/httpserver/condition_test.go deleted file mode 100644 index a63cc997b36..00000000000 --- a/caddyhttp/httpserver/condition_test.go +++ /dev/null @@ -1,354 +0,0 @@ -package httpserver - -import ( - "context" - "net/http" - "regexp" - "strings" - "testing" - - "github.com/mholt/caddy" -) - -func TestConditions(t *testing.T) { - tests := []struct { - condition string - isTrue bool - shouldErr bool - }{ - {"a is b", false, false}, - {"a is a", true, false}, - {"a not b", true, false}, - {"a not a", false, false}, - {"a has a", true, false}, - {"a has b", false, false}, - {"ba has b", true, false}, - {"bab has b", true, false}, - {"bab has bb", false, false}, - {"a not_has a", false, false}, - {"a not_has b", true, false}, - {"ba not_has b", false, false}, - {"bab not_has b", false, false}, - {"bab not_has bb", true, false}, - {"bab starts_with bb", false, false}, - {"bab starts_with ba", true, false}, - {"bab starts_with bab", true, false}, - {"bab not_starts_with bb", true, false}, - {"bab not_starts_with ba", false, false}, - {"bab not_starts_with bab", false, false}, - {"bab ends_with bb", false, false}, - {"bab ends_with bab", true, false}, - {"bab ends_with ab", true, false}, - {"bab not_ends_with bb", true, false}, - {"bab not_ends_with ab", false, false}, - {"bab not_ends_with bab", false, false}, - {"a match *", false, true}, - {"a match a", true, false}, - {"a match .*", true, false}, - {"a match a.*", true, false}, - {"a match b.*", false, false}, - {"ba match b.*", true, false}, - {"ba match b[a-z]", true, false}, - {"b0 match b[a-z]", false, false}, - {"b0a match b[a-z]", false, false}, - {"b0a match b[a-z]+", false, false}, - {"b0a match b[a-z0-9]+", true, false}, - {"bac match b[a-z]{2}", true, false}, - {"a not_match *", false, true}, - {"a not_match a", false, false}, - {"a not_match .*", false, false}, - {"a not_match a.*", false, false}, - {"a not_match b.*", true, false}, - {"ba not_match b.*", false, false}, - {"ba not_match b[a-z]", false, false}, - {"b0 not_match b[a-z]", true, false}, - {"b0a not_match b[a-z]", true, false}, - {"b0a not_match b[a-z]+", true, false}, - {"b0a not_match b[a-z0-9]+", false, false}, - {"bac not_match b[a-z]{2}", false, false}, - } - - for i, test := range tests { - str := strings.Fields(test.condition) - ifCond, err := newIfCond(str[0], str[1], str[2]) - if err != nil { - if !test.shouldErr { - t.Error(err) - } - continue - } - isTrue := ifCond.True(nil) - if isTrue != test.isTrue { - t.Errorf("Test %d: '%s' expected %v found %v", i, test.condition, test.isTrue, isTrue) - } - } - - invalidOperators := []string{"ss", "and", "if"} - for _, op := range invalidOperators { - _, err := newIfCond("a", op, "b") - if err == nil { - t.Errorf("Invalid operator %v used, expected error.", op) - } - } - - replaceTests := []struct { - url string - condition string - isTrue bool - }{ - {"/home", "{uri} match /home", true}, - {"/hom", "{uri} match /home", false}, - {"/hom", "{uri} starts_with /home", false}, - {"/hom", "{uri} starts_with /h", true}, - {"/home/.hiddenfile", `{uri} match \/\.(.*)`, true}, - {"/home/.hiddendir/afile", `{uri} match \/\.(.*)`, true}, - } - - for i, test := range replaceTests { - r, err := http.NewRequest("GET", test.url, nil) - if err != nil { - t.Errorf("Test %d: failed to create request: %v", i, err) - continue - } - ctx := context.WithValue(r.Context(), OriginalURLCtxKey, *r.URL) - r = r.WithContext(ctx) - str := strings.Fields(test.condition) - ifCond, err := newIfCond(str[0], str[1], str[2]) - if err != nil { - t.Errorf("Test %d: failed to create 'if' condition %v", i, err) - continue - } - isTrue := ifCond.True(r) - if isTrue != test.isTrue { - t.Errorf("Test %v: expected %v found %v", i, test.isTrue, isTrue) - continue - } - } -} - -func TestIfMatcher(t *testing.T) { - tests := []struct { - conditions []string - isOr bool - isTrue bool - }{ - { - []string{ - "a is a", - "b is b", - "c is c", - }, - false, - true, - }, - { - []string{ - "a is b", - "b is c", - "c is c", - }, - true, - true, - }, - { - []string{ - "a is a", - "b is a", - "c is c", - }, - false, - false, - }, - { - []string{ - "a is b", - "b is c", - "c is a", - }, - true, - false, - }, - { - []string{}, - false, - true, - }, - { - []string{}, - true, - false, - }, - } - - for i, test := range tests { - matcher := IfMatcher{isOr: test.isOr} - for _, condition := range test.conditions { - str := strings.Fields(condition) - ifCond, err := newIfCond(str[0], str[1], str[2]) - if err != nil { - t.Error(err) - } - matcher.ifs = append(matcher.ifs, ifCond) - } - isTrue := matcher.Match(nil) - if isTrue != test.isTrue { - t.Errorf("Test %d: expected %v found %v", i, test.isTrue, isTrue) - } - } -} - -func TestSetupIfMatcher(t *testing.T) { - rex_b, _ := regexp.Compile("b") - tests := []struct { - input string - shouldErr bool - expected IfMatcher - }{ - {`test { - if a match b - }`, false, IfMatcher{ - ifs: []ifCond{ - {a: "a", op: "match", b: "b", neg: false, rex: rex_b}, - }, - }}, - {`test { - if a match b - if_op or - }`, false, IfMatcher{ - ifs: []ifCond{ - {a: "a", op: "match", b: "b", neg: false, rex: rex_b}, - }, - isOr: true, - }}, - {`test { - if a match - }`, true, IfMatcher{}, - }, - {`test { - if a isn't b - }`, true, IfMatcher{}, - }, - {`test { - if a match b c - }`, true, IfMatcher{}, - }, - {`test { - if goal has go - if cook not_has go - }`, false, IfMatcher{ - ifs: []ifCond{ - {a: "goal", op: "has", b: "go", neg: false}, - {a: "cook", op: "has", b: "go", neg: true}, - }, - }}, - {`test { - if goal has go - if cook not_has go - if_op and - }`, false, IfMatcher{ - ifs: []ifCond{ - {a: "goal", op: "has", b: "go", neg: false}, - {a: "cook", op: "has", b: "go", neg: true}, - }, - }}, - {`test { - if goal has go - if cook not_has go - if_op not - }`, true, IfMatcher{}, - }, - } - - for i, test := range tests { - c := caddy.NewTestController("http", test.input) - c.Next() - - matcher, err := SetupIfMatcher(c) - if err == nil && test.shouldErr { - t.Errorf("Test %d didn't error, but it should have", i) - } else if err != nil && !test.shouldErr { - t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err) - } else if err != nil && test.shouldErr { - continue - } - - test_if, ok := matcher.(IfMatcher) - if !ok { - t.Error("RequestMatcher should be of type IfMatcher") - } - - if err != nil { - t.Errorf("Expected no error, but got: %v", err) - } - - if len(test_if.ifs) != len(test.expected.ifs) { - t.Errorf("Test %d: Expected %d ifConditions, found %v", i, - len(test.expected.ifs), len(test_if.ifs)) - } - - for j, if_c := range test_if.ifs { - expected_c := test.expected.ifs[j] - - if if_c.a != expected_c.a { - t.Errorf("Test %d, ifCond %d: Expected A=%s, got %s", - i, j, if_c.a, expected_c.a) - } - - if if_c.op != expected_c.op { - t.Errorf("Test %d, ifCond %d: Expected Op=%s, got %s", - i, j, if_c.op, expected_c.op) - } - - if if_c.b != expected_c.b { - t.Errorf("Test %d, ifCond %d: Expected B=%s, got %s", - i, j, if_c.b, expected_c.b) - } - - if if_c.neg != expected_c.neg { - t.Errorf("Test %d, ifCond %d: Expected Neg=%v, got %v", - i, j, if_c.neg, expected_c.neg) - } - - if expected_c.rex != nil && if_c.rex == nil { - t.Errorf("Test %d, ifCond %d: Expected Rex=%v, got ", - i, j, expected_c.rex) - } - - if expected_c.rex == nil && if_c.rex != nil { - t.Errorf("Test %d, ifCond %d: Expected Rex=, got %v", - i, j, if_c.rex) - } - - if expected_c.rex != nil && if_c.rex != nil { - if if_c.rex.String() != expected_c.rex.String() { - t.Errorf("Test %d, ifCond %d: Expected Rex=%v, got %v", - i, j, if_c.rex, expected_c.rex) - } - } - } - } -} - -func TestIfMatcherKeyword(t *testing.T) { - tests := []struct { - keyword string - expected bool - }{ - {"if", true}, - {"ifs", false}, - {"tls", false}, - {"http", false}, - {"if_op", true}, - {"if_type", false}, - {"if_cond", false}, - } - - for i, test := range tests { - c := caddy.NewTestController("http", test.keyword) - c.Next() - valid := IfMatcherKeyword(c) - if valid != test.expected { - t.Errorf("Test %d: expected %v found %v", i, test.expected, valid) - } - } -} diff --git a/caddyhttp/httpserver/error.go b/caddyhttp/httpserver/error.go deleted file mode 100644 index 2fbd486cfd1..00000000000 --- a/caddyhttp/httpserver/error.go +++ /dev/null @@ -1,56 +0,0 @@ -package httpserver - -import ( - "fmt" -) - -var ( - _ error = NonHijackerError{} - _ error = NonFlusherError{} - _ error = NonCloseNotifierError{} - _ error = NonPusherError{} -) - -// NonHijackerError is more descriptive error caused by a non hijacker -type NonHijackerError struct { - // underlying type which doesn't implement Hijack - Underlying interface{} -} - -// Implement Error -func (h NonHijackerError) Error() string { - return fmt.Sprintf("%T is not a hijacker", h.Underlying) -} - -// NonFlusherError is more descriptive error caused by a non flusher -type NonFlusherError struct { - // underlying type which doesn't implement Flush - Underlying interface{} -} - -// Implement Error -func (f NonFlusherError) Error() string { - return fmt.Sprintf("%T is not a flusher", f.Underlying) -} - -// NonCloseNotifierError is more descriptive error caused by a non closeNotifier -type NonCloseNotifierError struct { - // underlying type which doesn't implement CloseNotify - Underlying interface{} -} - -// Implement Error -func (c NonCloseNotifierError) Error() string { - return fmt.Sprintf("%T is not a closeNotifier", c.Underlying) -} - -// NonPusherError is more descriptive error caused by a non pusher -type NonPusherError struct { - // underlying type which doesn't implement pusher - Underlying interface{} -} - -// Implement Error -func (c NonPusherError) Error() string { - return fmt.Sprintf("%T is not a pusher", c.Underlying) -} diff --git a/caddyhttp/httpserver/https.go b/caddyhttp/httpserver/https.go deleted file mode 100644 index c35c93ab165..00000000000 --- a/caddyhttp/httpserver/https.go +++ /dev/null @@ -1,183 +0,0 @@ -package httpserver - -import ( - "fmt" - "net" - "net/http" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddytls" -) - -func activateHTTPS(cctx caddy.Context) error { - operatorPresent := !caddy.Started() - - if !caddy.Quiet && operatorPresent { - fmt.Print("Activating privacy features...") - } - - ctx := cctx.(*httpContext) - - // pre-screen each config and earmark the ones that qualify for managed TLS - markQualifiedForAutoHTTPS(ctx.siteConfigs) - - // place certificates and keys on disk - for _, c := range ctx.siteConfigs { - if c.TLS.OnDemand { - continue // obtain these certificates on-demand instead - } - err := c.TLS.ObtainCert(c.TLS.Hostname, operatorPresent) - if err != nil { - return err - } - } - - // update TLS configurations - err := enableAutoHTTPS(ctx.siteConfigs, true) - if err != nil { - return err - } - - // set up redirects - ctx.siteConfigs = makePlaintextRedirects(ctx.siteConfigs) - - // renew all relevant certificates that need renewal. this is important - // to do right away so we guarantee that renewals aren't missed, and - // also the user can respond to any potential errors that occur. - // (skip if upgrading, because the parent process is likely already listening - // on the ports we'd need to do ACME before we finish starting; parent process - // already running renewal ticker, so renewal won't be missed anyway.) - if !caddy.IsUpgrade() { - err = caddytls.RenewManagedCertificates(true) - if err != nil { - return err - } - } - - if !caddy.Quiet && operatorPresent { - fmt.Println(" done.") - } - - return nil -} - -// markQualifiedForAutoHTTPS scans each config and, if it -// qualifies for managed TLS, it sets the Managed field of -// the TLS config to true. -func markQualifiedForAutoHTTPS(configs []*SiteConfig) { - for _, cfg := range configs { - if caddytls.QualifiesForManagedTLS(cfg) && cfg.Addr.Scheme != "http" { - cfg.TLS.Managed = true - } - } -} - -// enableAutoHTTPS configures each config to use TLS according to default settings. -// It will only change configs that are marked as managed but not on-demand, and -// assumes that certificates and keys are already on disk. If loadCertificates is -// true, the certificates will be loaded from disk into the cache for this process -// to use. If false, TLS will still be enabled and configured with default settings, -// but no certificates will be parsed loaded into the cache, and the returned error -// value will always be nil. -func enableAutoHTTPS(configs []*SiteConfig, loadCertificates bool) error { - for _, cfg := range configs { - if cfg == nil || cfg.TLS == nil || !cfg.TLS.Managed || cfg.TLS.OnDemand { - continue - } - cfg.TLS.Enabled = true - cfg.Addr.Scheme = "https" - if loadCertificates && caddytls.HostQualifies(cfg.Addr.Host) { - _, err := cfg.TLS.CacheManagedCertificate(cfg.Addr.Host) - if err != nil { - return err - } - } - - // Make sure any config values not explicitly set are set to default - caddytls.SetDefaultTLSParams(cfg.TLS) - - // Set default port of 443 if not explicitly set - if cfg.Addr.Port == "" && - cfg.TLS.Enabled && - (!cfg.TLS.Manual || cfg.TLS.OnDemand) && - cfg.Addr.Host != "localhost" { - cfg.Addr.Port = HTTPSPort - } - } - return nil -} - -// makePlaintextRedirects sets up redirects from port 80 to the relevant HTTPS -// hosts. You must pass in all configs, not just configs that qualify, since -// we must know whether the same host already exists on port 80, and those would -// not be in a list of configs that qualify for automatic HTTPS. This function will -// only set up redirects for configs that qualify. It returns the updated list of -// all configs. -func makePlaintextRedirects(allConfigs []*SiteConfig) []*SiteConfig { - for i, cfg := range allConfigs { - if cfg.TLS.Managed && - !hostHasOtherPort(allConfigs, i, HTTPPort) && - (cfg.Addr.Port == HTTPSPort || !hostHasOtherPort(allConfigs, i, HTTPSPort)) { - allConfigs = append(allConfigs, redirPlaintextHost(cfg)) - } - } - return allConfigs -} - -// hostHasOtherPort returns true if there is another config in the list with the same -// hostname that has port otherPort, or false otherwise. All the configs are checked -// against the hostname of allConfigs[thisConfigIdx]. -func hostHasOtherPort(allConfigs []*SiteConfig, thisConfigIdx int, otherPort string) bool { - for i, otherCfg := range allConfigs { - if i == thisConfigIdx { - continue // has to be a config OTHER than the one we're comparing against - } - if otherCfg.Addr.Host == allConfigs[thisConfigIdx].Addr.Host && - otherCfg.Addr.Port == otherPort { - return true - } - } - return false -} - -// redirPlaintextHost returns a new plaintext HTTP configuration for -// a virtualHost that simply redirects to cfg, which is assumed to -// be the HTTPS configuration. The returned configuration is set -// to listen on HTTPPort. The TLS field of cfg must not be nil. -func redirPlaintextHost(cfg *SiteConfig) *SiteConfig { - redirPort := cfg.Addr.Port - if redirPort == DefaultHTTPSPort { - redirPort = "" // default port is redundant - } - redirMiddleware := func(next Handler) Handler { - return HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - // Construct the URL to which to redirect. Note that the Host in a request might - // contain a port, but we just need the hostname; we'll set the port if needed. - toURL := "https://" - requestHost, _, err := net.SplitHostPort(r.Host) - if err != nil { - requestHost = r.Host // Host did not contain a port; great - } - if redirPort == "" { - toURL += requestHost - } else { - toURL += net.JoinHostPort(requestHost, redirPort) - } - toURL += r.URL.RequestURI() - - w.Header().Set("Connection", "close") - http.Redirect(w, r, toURL, http.StatusMovedPermanently) - return 0, nil - }) - } - host := cfg.Addr.Host - port := HTTPPort - addr := net.JoinHostPort(host, port) - return &SiteConfig{ - Addr: Address{Original: addr, Host: host, Port: port}, - ListenHost: cfg.ListenHost, - middleware: []Middleware{redirMiddleware}, - TLS: &caddytls.Config{AltHTTPPort: cfg.TLS.AltHTTPPort, AltTLSSNIPort: cfg.TLS.AltTLSSNIPort}, - Timeouts: cfg.Timeouts, - } -} diff --git a/caddyhttp/httpserver/https_test.go b/caddyhttp/httpserver/https_test.go deleted file mode 100644 index 82a12700269..00000000000 --- a/caddyhttp/httpserver/https_test.go +++ /dev/null @@ -1,212 +0,0 @@ -package httpserver - -import ( - "fmt" - "net" - "net/http" - "net/http/httptest" - "testing" - - "github.com/mholt/caddy/caddytls" -) - -func TestRedirPlaintextHost(t *testing.T) { - for i, testcase := range []struct { - Host string // used for the site config - Port string - ListenHost string - RequestHost string // if different from Host - }{ - { - Host: "foohost", - }, - { - Host: "foohost", - Port: "80", - }, - { - Host: "foohost", - Port: "1234", - }, - { - Host: "foohost", - ListenHost: "93.184.216.34", - }, - { - Host: "foohost", - Port: "1234", - ListenHost: "93.184.216.34", - }, - { - Host: "foohost", - Port: "443", // since this is the default HTTPS port, should not be included in Location value - }, - { - Host: "*.example.com", - RequestHost: "foo.example.com", - }, - { - Host: "*.example.com", - Port: "1234", - RequestHost: "foo.example.com:1234", - }, - } { - cfg := redirPlaintextHost(&SiteConfig{ - Addr: Address{ - Host: testcase.Host, - Port: testcase.Port, - }, - ListenHost: testcase.ListenHost, - TLS: new(caddytls.Config), - }) - - // Check host and port - if actual, expected := cfg.Addr.Host, testcase.Host; actual != expected { - t.Errorf("Test %d: Expected redir config to have host %s but got %s", i, expected, actual) - } - if actual, expected := cfg.ListenHost, testcase.ListenHost; actual != expected { - t.Errorf("Test %d: Expected redir config to have bindhost %s but got %s", i, expected, actual) - } - if actual, expected := cfg.Addr.Port, HTTPPort; actual != expected { - t.Errorf("Test %d: Expected redir config to have port '%s' but got '%s'", i, expected, actual) - } - - // Make sure redirect handler is set up properly - if cfg.middleware == nil || len(cfg.middleware) != 1 { - t.Fatalf("Test %d: Redir config middleware not set up properly; got: %#v", i, cfg.middleware) - } - - handler := cfg.middleware[0](nil) - - // Check redirect for correctness, first by inspecting error and status code - requestHost := testcase.Host // hostname of request might be different than in config (e.g. wildcards) - if testcase.RequestHost != "" { - requestHost = testcase.RequestHost - } - rec := httptest.NewRecorder() - req, err := http.NewRequest("GET", "http://"+requestHost+"/bar?q=1", nil) - if err != nil { - t.Fatalf("Test %d: %v", i, err) - } - status, err := handler.ServeHTTP(rec, req) - if status != 0 { - t.Errorf("Test %d: Expected status return to be 0, but was %d", i, status) - } - if err != nil { - t.Errorf("Test %d: Expected returned error to be nil, but was %v", i, err) - } - if rec.Code != http.StatusMovedPermanently { - t.Errorf("Test %d: Expected status %d but got %d", http.StatusMovedPermanently, i, rec.Code) - } - - // Now check the Location value. It should mirror the hostname and port of the request - // unless the port is redundant, in which case it should be dropped. - locationHost, _, err := net.SplitHostPort(requestHost) - if err != nil { - locationHost = requestHost - } - expectedLoc := fmt.Sprintf("https://%s/bar?q=1", locationHost) - if testcase.Port != "" && testcase.Port != DefaultHTTPSPort { - expectedLoc = fmt.Sprintf("https://%s:%s/bar?q=1", locationHost, testcase.Port) - } - if got, want := rec.Header().Get("Location"), expectedLoc; got != want { - t.Errorf("Test %d: Expected Location: '%s' but got '%s'", i, want, got) - } - } -} - -func TestHostHasOtherPort(t *testing.T) { - configs := []*SiteConfig{ - {Addr: Address{Host: "example.com", Port: "80"}}, - {Addr: Address{Host: "sub1.example.com", Port: "80"}}, - {Addr: Address{Host: "sub1.example.com", Port: "443"}}, - } - - if hostHasOtherPort(configs, 0, "80") { - t.Errorf(`Expected hostHasOtherPort(configs, 0, "80") to be false, but got true`) - } - if hostHasOtherPort(configs, 0, "443") { - t.Errorf(`Expected hostHasOtherPort(configs, 0, "443") to be false, but got true`) - } - if !hostHasOtherPort(configs, 1, "443") { - t.Errorf(`Expected hostHasOtherPort(configs, 1, "443") to be true, but got false`) - } -} - -func TestMakePlaintextRedirects(t *testing.T) { - configs := []*SiteConfig{ - // Happy path = standard redirect from 80 to 443 - {Addr: Address{Host: "example.com"}, TLS: &caddytls.Config{Managed: true}}, - - // Host on port 80 already defined; don't change it (no redirect) - {Addr: Address{Host: "sub1.example.com", Port: "80", Scheme: "http"}, TLS: new(caddytls.Config)}, - {Addr: Address{Host: "sub1.example.com"}, TLS: &caddytls.Config{Managed: true}}, - - // Redirect from port 80 to port 5000 in this case - {Addr: Address{Host: "sub2.example.com", Port: "5000"}, TLS: &caddytls.Config{Managed: true}}, - - // Can redirect from 80 to either 443 or 5001, but choose 443 - {Addr: Address{Host: "sub3.example.com", Port: "443"}, TLS: &caddytls.Config{Managed: true}}, - {Addr: Address{Host: "sub3.example.com", Port: "5001", Scheme: "https"}, TLS: &caddytls.Config{Managed: true}}, - } - - result := makePlaintextRedirects(configs) - expectedRedirCount := 3 - - if len(result) != len(configs)+expectedRedirCount { - t.Errorf("Expected %d redirect(s) to be added, but got %d", - expectedRedirCount, len(result)-len(configs)) - } -} - -func TestEnableAutoHTTPS(t *testing.T) { - configs := []*SiteConfig{ - {Addr: Address{Host: "example.com"}, TLS: &caddytls.Config{Managed: true}}, - {}, // not managed - no changes! - } - - enableAutoHTTPS(configs, false) - - if !configs[0].TLS.Enabled { - t.Errorf("Expected config 0 to have TLS.Enabled == true, but it was false") - } - if configs[0].Addr.Scheme != "https" { - t.Errorf("Expected config 0 to have Addr.Scheme == \"https\", but it was \"%s\"", - configs[0].Addr.Scheme) - } - if configs[1].TLS != nil && configs[1].TLS.Enabled { - t.Errorf("Expected config 1 to have TLS.Enabled == false, but it was true") - } -} - -func TestMarkQualifiedForAutoHTTPS(t *testing.T) { - // TODO: caddytls.TestQualifiesForManagedTLS and this test share nearly the same config list... - configs := []*SiteConfig{ - {Addr: Address{Host: ""}, TLS: new(caddytls.Config)}, - {Addr: Address{Host: "localhost"}, TLS: new(caddytls.Config)}, - {Addr: Address{Host: "123.44.3.21"}, TLS: new(caddytls.Config)}, - {Addr: Address{Host: "example.com"}, TLS: new(caddytls.Config)}, - {Addr: Address{Host: "example.com"}, TLS: &caddytls.Config{Manual: true}}, - {Addr: Address{Host: "example.com"}, TLS: &caddytls.Config{ACMEEmail: "off"}}, - {Addr: Address{Host: "example.com"}, TLS: &caddytls.Config{ACMEEmail: "foo@bar.com"}}, - {Addr: Address{Host: "example.com", Scheme: "http"}, TLS: new(caddytls.Config)}, - {Addr: Address{Host: "example.com", Port: "80"}, TLS: new(caddytls.Config)}, - {Addr: Address{Host: "example.com", Port: "1234"}, TLS: new(caddytls.Config)}, - {Addr: Address{Host: "example.com", Scheme: "https"}, TLS: new(caddytls.Config)}, - {Addr: Address{Host: "example.com", Port: "80", Scheme: "https"}, TLS: new(caddytls.Config)}, - } - expectedManagedCount := 4 - - markQualifiedForAutoHTTPS(configs) - - count := 0 - for _, cfg := range configs { - if cfg.TLS.Managed { - count++ - } - } - - if count != expectedManagedCount { - t.Errorf("Expected %d managed configs, but got %d", expectedManagedCount, count) - } -} diff --git a/caddyhttp/httpserver/logger.go b/caddyhttp/httpserver/logger.go deleted file mode 100644 index 29e888a5cf0..00000000000 --- a/caddyhttp/httpserver/logger.go +++ /dev/null @@ -1,150 +0,0 @@ -package httpserver - -import ( - "bytes" - "io" - "log" - "os" - "strings" - "sync" - - "github.com/hashicorp/go-syslog" - "github.com/mholt/caddy" -) - -var remoteSyslogPrefixes = map[string]string{ - "syslog+tcp://": "tcp", - "syslog+udp://": "udp", - "syslog://": "udp", -} - -// Logger is shared between errors and log plugins and supports both logging to -// a file (with an optional file roller), local and remote syslog servers. -type Logger struct { - Output string - *log.Logger - Roller *LogRoller - writer io.Writer - fileMu *sync.RWMutex -} - -// NewTestLogger creates logger suitable for testing purposes -func NewTestLogger(buffer *bytes.Buffer) *Logger { - return &Logger{ - Logger: log.New(buffer, "", 0), - fileMu: new(sync.RWMutex), - } -} - -// Println wraps underlying logger with mutex -func (l Logger) Println(args ...interface{}) { - l.fileMu.RLock() - l.Logger.Println(args...) - l.fileMu.RUnlock() -} - -// Printf wraps underlying logger with mutex -func (l Logger) Printf(format string, args ...interface{}) { - l.fileMu.RLock() - l.Logger.Printf(format, args...) - l.fileMu.RUnlock() -} - -// Attach binds logger Start and Close functions to -// controller's OnStartup and OnShutdown hooks. -func (l *Logger) Attach(controller *caddy.Controller) { - if controller != nil { - // Opens file or connect to local/remote syslog - controller.OnStartup(l.Start) - - // Closes file or disconnects from local/remote syslog - controller.OnShutdown(l.Close) - } -} - -type syslogAddress struct { - network string - address string -} - -func parseSyslogAddress(location string) *syslogAddress { - for prefix, network := range remoteSyslogPrefixes { - if strings.HasPrefix(location, prefix) { - return &syslogAddress{ - network: network, - address: strings.TrimPrefix(location, prefix), - } - } - } - - return nil -} - -// Start initializes logger opening files or local/remote syslog connections -func (l *Logger) Start() error { - // initialize mutex on start - l.fileMu = new(sync.RWMutex) - - var err error - -selectwriter: - switch l.Output { - case "", "stderr": - l.writer = os.Stderr - case "stdout": - l.writer = os.Stdout - case "syslog": - l.writer, err = gsyslog.NewLogger(gsyslog.LOG_ERR, "LOCAL0", "caddy") - if err != nil { - return err - } - default: - if address := parseSyslogAddress(l.Output); address != nil { - l.writer, err = gsyslog.DialLogger(address.network, address.address, gsyslog.LOG_ERR, "LOCAL0", "caddy") - - if err != nil { - return err - } - - break selectwriter - } - - var file *os.File - - file, err = os.OpenFile(l.Output, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) - if err != nil { - return err - } - - if l.Roller != nil { - file.Close() - l.Roller.Filename = l.Output - l.writer = l.Roller.GetLogWriter() - } else { - l.writer = file - } - } - - l.Logger = log.New(l.writer, "", 0) - - return nil - -} - -// Close closes open log files or connections to syslog. -func (l *Logger) Close() error { - // don't close stdout or stderr - if l.writer == os.Stdout || l.writer == os.Stderr { - return nil - } - - // Will close local/remote syslog connections too :) - if closer, ok := l.writer.(io.WriteCloser); ok { - l.fileMu.Lock() - err := closer.Close() - l.fileMu.Unlock() - return err - } - - return nil -} diff --git a/caddyhttp/httpserver/logger_test.go b/caddyhttp/httpserver/logger_test.go deleted file mode 100644 index 0ef08c939e9..00000000000 --- a/caddyhttp/httpserver/logger_test.go +++ /dev/null @@ -1,212 +0,0 @@ -//+build linux darwin - -package httpserver - -import ( - "bytes" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - "sync" - "testing" - - syslog "gopkg.in/mcuadros/go-syslog.v2" - "gopkg.in/mcuadros/go-syslog.v2/format" -) - -func TestLoggingToStdout(t *testing.T) { - testCases := []struct { - Output string - ExpectedOutput string - }{ - { - Output: "stdout", - ExpectedOutput: "Hello world logged to stdout", - }, - } - - for i, testCase := range testCases { - output := captureStdout(func() { - logger := Logger{Output: testCase.Output, fileMu: new(sync.RWMutex)} - - if err := logger.Start(); err != nil { - t.Fatalf("Got unexpected error: %v", err) - } - - logger.Println(testCase.ExpectedOutput) - }) - - if !strings.Contains(output, testCase.ExpectedOutput) { - t.Fatalf("Test #%d: Expected output to contain: %s, got: %s", i, testCase.ExpectedOutput, output) - } - } -} - -func TestLoggingToStderr(t *testing.T) { - - testCases := []struct { - Output string - ExpectedOutput string - }{ - { - Output: "stderr", - ExpectedOutput: "Hello world logged to stderr", - }, - { - Output: "", - ExpectedOutput: "Hello world logged to stderr #2", - }, - } - - for i, testCase := range testCases { - output := captureStderr(func() { - logger := Logger{Output: testCase.Output, fileMu: new(sync.RWMutex)} - - if err := logger.Start(); err != nil { - t.Fatalf("Got unexpected error: %v", err) - } - - logger.Println(testCase.ExpectedOutput) - }) - - if !strings.Contains(output, testCase.ExpectedOutput) { - t.Fatalf("Test #%d: Expected output to contain: %s, got: %s", i, testCase.ExpectedOutput, output) - } - } -} - -func TestLoggingToFile(t *testing.T) { - file := filepath.Join(os.TempDir(), "access.log") - expectedOutput := "Hello world written to file" - - logger := Logger{Output: file} - - if err := logger.Start(); err != nil { - t.Fatalf("Got unexpected error during logger start: %v", err) - } - - logger.Print(expectedOutput) - - content, err := ioutil.ReadFile(file) - if err != nil { - t.Fatalf("Could not read log file content: %v", err) - } - - if !bytes.Contains(content, []byte(expectedOutput)) { - t.Fatalf("Expected log file to contain: %s, got: %s", expectedOutput, string(content)) - } - - os.Remove(file) -} - -func TestLoggingToSyslog(t *testing.T) { - - testCases := []struct { - Output string - ExpectedOutput string - }{ - { - Output: "syslog://127.0.0.1:5660", - ExpectedOutput: "Hello world! Test #1 over tcp", - }, - { - Output: "syslog+tcp://127.0.0.1:5661", - ExpectedOutput: "Hello world! Test #2 over tcp", - }, - { - Output: "syslog+udp://127.0.0.1:5662", - ExpectedOutput: "Hello world! Test #3 over udp", - }, - } - - for i, testCase := range testCases { - - ch := make(chan format.LogParts, 256) - server, err := bootServer(testCase.Output, ch) - defer server.Kill() - - if err != nil { - t.Errorf("Test #%d: expected no error during syslog server boot, got: %v", i, err) - } - - logger := Logger{Output: testCase.Output, fileMu: new(sync.RWMutex)} - - if err := logger.Start(); err != nil { - t.Errorf("Test #%d: expected no error during logger start, got: %v", i, err) - } - - defer logger.Close() - - logger.Print(testCase.ExpectedOutput) - - actual := <-ch - - if content, ok := actual["content"].(string); ok { - if !strings.Contains(content, testCase.ExpectedOutput) { - t.Errorf("Test #%d: expected server to capture content: %s, but got: %s", i, testCase.ExpectedOutput, content) - } - } else { - t.Errorf("Test #%d: expected server to capture content but got: %v", i, actual) - } - } -} - -func bootServer(location string, ch chan format.LogParts) (*syslog.Server, error) { - address := parseSyslogAddress(location) - - if address == nil { - return nil, fmt.Errorf("Could not parse syslog address: %s", location) - } - - server := syslog.NewServer() - server.SetFormat(syslog.Automatic) - - switch address.network { - case "tcp": - server.ListenTCP(address.address) - case "udp": - server.ListenUDP(address.address) - } - - server.SetHandler(syslog.NewChannelHandler(ch)) - - if err := server.Boot(); err != nil { - return nil, err - } - - return server, nil -} - -func captureStdout(f func()) string { - original := os.Stdout - r, w, _ := os.Pipe() - - os.Stdout = w - - f() - - w.Close() - - written, _ := ioutil.ReadAll(r) - os.Stdout = original - - return string(written) -} - -func captureStderr(f func()) string { - original := os.Stderr - r, w, _ := os.Pipe() - - os.Stderr = w - - f() - - w.Close() - - written, _ := ioutil.ReadAll(r) - os.Stderr = original - - return string(written) -} diff --git a/caddyhttp/httpserver/middleware.go b/caddyhttp/httpserver/middleware.go deleted file mode 100644 index 48e45a1491b..00000000000 --- a/caddyhttp/httpserver/middleware.go +++ /dev/null @@ -1,211 +0,0 @@ -package httpserver - -import ( - "fmt" - "net/http" - "os" - "path" - "time" - - "github.com/mholt/caddy" -) - -func init() { - initCaseSettings() -} - -type ( - // Middleware is the middle layer which represents the traditional - // idea of middleware: it chains one Handler to the next by being - // passed the next Handler in the chain. - Middleware func(Handler) Handler - - // ListenerMiddleware is similar to the Middleware type, except it - // chains one net.Listener to the next. - ListenerMiddleware func(caddy.Listener) caddy.Listener - - // Handler is like http.Handler except ServeHTTP may return a status - // code and/or error. - // - // If ServeHTTP writes the response header, it should return a status - // code of 0. This signals to other handlers before it that the response - // is already handled, and that they should not write to it also. Keep - // in mind that writing to the response body writes the header, too. - // - // If ServeHTTP encounters an error, it should return the error value - // so it can be logged by designated error-handling middleware. - // - // If writing a response after calling the next ServeHTTP method, the - // returned status code SHOULD be used when writing the response. - // - // If handling errors after calling the next ServeHTTP method, the - // returned error value SHOULD be logged or handled accordingly. - // - // Otherwise, return values should be propagated down the middleware - // chain by returning them unchanged. - Handler interface { - ServeHTTP(http.ResponseWriter, *http.Request) (int, error) - } - - // HandlerFunc is a convenience type like http.HandlerFunc, except - // ServeHTTP returns a status code and an error. See Handler - // documentation for more information. - HandlerFunc func(http.ResponseWriter, *http.Request) (int, error) - - // RequestMatcher checks to see if current request should be handled - // by underlying handler. - RequestMatcher interface { - Match(r *http.Request) bool - } - - // HandlerConfig is a middleware configuration. - // This makes it possible for middlewares to have a common - // configuration interface. - // - // TODO The long term plan is to get all middleware implement this - // interface for configurations. - HandlerConfig interface { - RequestMatcher - BasePath() string - } - - // ConfigSelector selects a configuration. - ConfigSelector []HandlerConfig -) - -// ServeHTTP implements the Handler interface. -func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - return f(w, r) -} - -// Select selects a Config. -// This chooses the config with the longest length. -func (c ConfigSelector) Select(r *http.Request) (config HandlerConfig) { - for i := range c { - if !c[i].Match(r) { - continue - } - if config == nil || len(c[i].BasePath()) > len(config.BasePath()) { - config = c[i] - } - } - return config -} - -// IndexFile looks for a file in /root/fpath/indexFile for each string -// in indexFiles. If an index file is found, it returns the root-relative -// path to the file and true. If no index file is found, empty string -// and false is returned. fpath must end in a forward slash '/' -// otherwise no index files will be tried (directory paths must end -// in a forward slash according to HTTP). -// -// All paths passed into and returned from this function use '/' as the -// path separator, just like URLs. IndexFle handles path manipulation -// internally for systems that use different path separators. -func IndexFile(root http.FileSystem, fpath string, indexFiles []string) (string, bool) { - if fpath[len(fpath)-1] != '/' || root == nil { - return "", false - } - for _, indexFile := range indexFiles { - // func (http.FileSystem).Open wants all paths separated by "/", - // regardless of operating system convention, so use - // path.Join instead of filepath.Join - fp := path.Join(fpath, indexFile) - f, err := root.Open(fp) - if err == nil { - f.Close() - return fp, true - } - } - return "", false -} - -// SetLastModifiedHeader checks if the provided modTime is valid and if it is sets it -// as a Last-Modified header to the ResponseWriter. If the modTime is in the future -// the current time is used instead. -func SetLastModifiedHeader(w http.ResponseWriter, modTime time.Time) { - if modTime.IsZero() || modTime.Equal(time.Unix(0, 0)) { - // the time does not appear to be valid. Don't put it in the response - return - } - - // RFC 2616 - Section 14.29 - Last-Modified: - // An origin server MUST NOT send a Last-Modified date which is later than the - // server's time of message origination. In such cases, where the resource's last - // modification would indicate some time in the future, the server MUST replace - // that date with the message origination date. - now := currentTime() - if modTime.After(now) { - modTime = now - } - - w.Header().Set("Last-Modified", modTime.UTC().Format(http.TimeFormat)) -} - -// CaseSensitivePath determines if paths should be case sensitive. -// This is configurable via CASE_SENSITIVE_PATH environment variable. -var CaseSensitivePath = true - -const caseSensitivePathEnv = "CASE_SENSITIVE_PATH" - -// initCaseSettings loads case sensitivity config from environment variable. -// -// This could have been in init, but init cannot be called from tests. -func initCaseSettings() { - switch os.Getenv(caseSensitivePathEnv) { - case "0", "false": - CaseSensitivePath = false - default: - CaseSensitivePath = true - } -} - -// MergeRequestMatchers merges multiple RequestMatchers into one. -// This allows a middleware to use multiple RequestMatchers. -func MergeRequestMatchers(matchers ...RequestMatcher) RequestMatcher { - return requestMatchers(matchers) -} - -type requestMatchers []RequestMatcher - -// Match satisfies RequestMatcher interface. -func (m requestMatchers) Match(r *http.Request) bool { - for _, matcher := range m { - if !matcher.Match(r) { - return false - } - } - return true -} - -// currentTime, as it is defined here, returns time.Now(). -// It's defined as a variable for mocking time in tests. -var currentTime = func() time.Time { return time.Now() } - -// EmptyNext is a no-op function that can be passed into -// Middleware functions so that the assignment to the -// Next field of the Handler can be tested. -// -// Used primarily for testing but needs to be exported so -// plugins can use this as a convenience. -var EmptyNext = HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { return 0, nil }) - -// SameNext does a pointer comparison between next1 and next2. -// -// Used primarily for testing but needs to be exported so -// plugins can use this as a convenience. -func SameNext(next1, next2 Handler) bool { - return fmt.Sprintf("%v", next1) == fmt.Sprintf("%v", next2) -} - -// Context key constants. -const ( - // RemoteUserCtxKey is the key for the remote user of the request, if any (basicauth). - RemoteUserCtxKey caddy.CtxKey = "remote_user" - - // MitmCtxKey is the key for the result of MITM detection - MitmCtxKey caddy.CtxKey = "mitm" - - // RequestIDCtxKey is the key for the U4 UUID value - RequestIDCtxKey caddy.CtxKey = "request_id" -) diff --git a/caddyhttp/httpserver/middleware_test.go b/caddyhttp/httpserver/middleware_test.go deleted file mode 100644 index 2f75c8bb9da..00000000000 --- a/caddyhttp/httpserver/middleware_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package httpserver - -import ( - "os" - "testing" -) - -func TestPathCaseSensitivity(t *testing.T) { - tests := []struct { - basePath string - path string - caseSensitive bool - expected bool - }{ - {"/", "/file", true, true}, - {"/a", "/file", true, false}, - {"/f", "/file", true, true}, - {"/f", "/File", true, false}, - {"/f", "/File", false, true}, - {"/file", "/file", true, true}, - {"/file", "/file", false, true}, - {"/files", "/file", false, false}, - {"/files", "/file", true, false}, - {"/folder", "/folder/file.txt", true, true}, - {"/folders", "/folder/file.txt", true, false}, - {"/folder", "/Folder/file.txt", false, true}, - {"/folders", "/Folder/file.txt", false, false}, - } - - for i, test := range tests { - CaseSensitivePath = test.caseSensitive - valid := Path(test.path).Matches(test.basePath) - if test.expected != valid { - t.Errorf("Test %d: Expected %v, found %v", i, test.expected, valid) - } - } -} - -func TestPathCaseSensitiveEnv(t *testing.T) { - tests := []struct { - envValue string - expected bool - }{ - {"1", true}, - {"0", false}, - {"false", false}, - {"true", true}, - {"", true}, - } - - for i, test := range tests { - os.Setenv(caseSensitivePathEnv, test.envValue) - initCaseSettings() - if test.expected != CaseSensitivePath { - t.Errorf("Test %d: Expected %v, found %v", i, test.expected, CaseSensitivePath) - } - } -} diff --git a/caddyhttp/httpserver/mitm.go b/caddyhttp/httpserver/mitm.go deleted file mode 100644 index d058f37c892..00000000000 --- a/caddyhttp/httpserver/mitm.go +++ /dev/null @@ -1,748 +0,0 @@ -package httpserver - -import ( - "bytes" - "context" - "crypto/tls" - "io" - "net" - "net/http" - "strconv" - "strings" - "sync" -) - -// tlsHandler is a http.Handler that will inject a value -// into the request context indicating if the TLS -// connection is likely being intercepted. -type tlsHandler struct { - next http.Handler - listener *tlsHelloListener - closeOnMITM bool // whether to close connection on MITM; TODO: expose through new directive -} - -// ServeHTTP checks the User-Agent. For the four main browsers (Chrome, -// Edge, Firefox, and Safari) indicated by the User-Agent, the properties -// of the TLS Client Hello will be compared. The context value "mitm" will -// be set to a value indicating if it is likely that the underlying TLS -// connection is being intercepted. -// -// Note that due to Microsoft's decision to intentionally make IE/Edge -// user agents obscure (and look like other browsers), this may offer -// less accuracy for IE/Edge clients. -// -// This MITM detection capability is based on research done by Durumeric, -// Halderman, et. al. in "The Security Impact of HTTPS Interception" (NDSS '17): -// https://jhalderm.com/pub/papers/interception-ndss17.pdf -func (h *tlsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if h.listener == nil { - h.next.ServeHTTP(w, r) - return - } - - h.listener.helloInfosMu.RLock() - info := h.listener.helloInfos[r.RemoteAddr] - h.listener.helloInfosMu.RUnlock() - - ua := r.Header.Get("User-Agent") - - var checked, mitm bool - if r.Header.Get("X-BlueCoat-Via") != "" || // Blue Coat (masks User-Agent header to generic values) - r.Header.Get("X-FCCKV2") != "" || // Fortinet - info.advertisesHeartbeatSupport() { // no major browsers have ever implemented Heartbeat - checked = true - mitm = true - } else if strings.Contains(ua, "Edge") || strings.Contains(ua, "MSIE") || - strings.Contains(ua, "Trident") { - checked = true - mitm = !info.looksLikeEdge() - } else if strings.Contains(ua, "Chrome") { - checked = true - mitm = !info.looksLikeChrome() - } else if strings.Contains(ua, "CriOS") { - // Chrome on iOS sometimes uses iOS-provided TLS stack (which looks exactly like Safari) - // but for connections that don't render a web page (favicon, etc.) it uses its own... - checked = true - mitm = !info.looksLikeChrome() && !info.looksLikeSafari() - } else if strings.Contains(ua, "Firefox") { - checked = true - if strings.Contains(ua, "Windows") { - ver := getVersion(ua, "Firefox") - if ver == 45.0 || ver == 52.0 { - mitm = !info.looksLikeTor() - } else { - mitm = !info.looksLikeFirefox() - } - } else { - mitm = !info.looksLikeFirefox() - } - } else if strings.Contains(ua, "Safari") { - checked = true - mitm = !info.looksLikeSafari() - } - - if checked { - r = r.WithContext(context.WithValue(r.Context(), MitmCtxKey, mitm)) - } - - if mitm && h.closeOnMITM { - // TODO: This termination might need to happen later in the middleware - // chain in order to be picked up by the log directive, in case the site - // owner still wants to log this event. It'll probably require a new - // directive. If this feature is useful, we can finish implementing this. - r.Close = true - return - } - - h.next.ServeHTTP(w, r) -} - -// getVersion returns a (possibly simplified) representation of the version string -// from a UserAgent string. It returns a float, so it can represent major and minor -// versions; the rest of the version is just tacked on behind the decimal point. -// The purpose of this is to stay simple while allowing for basic, fast comparisons. -// If the version for softwareName is not found in ua, -1 is returned. -func getVersion(ua, softwareName string) float64 { - search := softwareName + "/" - start := strings.Index(ua, search) - if start < 0 { - return -1 - } - start += len(search) - end := strings.Index(ua[start:], " ") - if end < 0 { - end = len(ua) - } else { - end += start - } - strVer := strings.Replace(ua[start:end], "-", "", -1) - firstDot := strings.Index(strVer, ".") - if firstDot >= 0 { - strVer = strVer[:firstDot+1] + strings.Replace(strVer[firstDot+1:], ".", "", -1) - } - ver, err := strconv.ParseFloat(strVer, 64) - if err != nil { - return -1 - } - return ver -} - -// clientHelloConn reads the ClientHello -// and stores it in the attached listener. -type clientHelloConn struct { - net.Conn - listener *tlsHelloListener - readHello bool // whether ClientHello has been read - buf *bytes.Buffer -} - -// Read reads from c.Conn (by letting the standard library -// do the reading off the wire), with the exception of -// getting a copy of the ClientHello so it can parse it. -func (c *clientHelloConn) Read(b []byte) (n int, err error) { - // if we've already read the ClientHello, pass thru - if c.readHello { - return c.Conn.Read(b) - } - - // we let the standard lib read off the wire for us, and - // tee that into our buffer so we can read the ClientHello - tee := io.TeeReader(c.Conn, c.buf) - n, err = tee.Read(b) - if err != nil { - return - } - if c.buf.Len() < 5 { - return // need to read more bytes for header - } - - // read the header bytes - hdr := make([]byte, 5) - _, err = io.ReadFull(c.buf, hdr) - if err != nil { - return // this would be highly unusual and sad - } - - // get length of the ClientHello message and read it - length := int(uint16(hdr[3])<<8 | uint16(hdr[4])) - if c.buf.Len() < length { - return // need to read more bytes - } - hello := make([]byte, length) - _, err = io.ReadFull(c.buf, hello) - if err != nil { - return - } - bufpool.Put(c.buf) // buffer no longer needed - - // parse the ClientHello and store it in the map - rawParsed := parseRawClientHello(hello) - c.listener.helloInfosMu.Lock() - c.listener.helloInfos[c.Conn.RemoteAddr().String()] = rawParsed - c.listener.helloInfosMu.Unlock() - - c.readHello = true - return -} - -// parseRawClientHello parses data which contains the raw -// TLS Client Hello message. It extracts relevant information -// into info. Any error reading the Client Hello (such as -// insufficient length or invalid length values) results in -// a silent error and an incomplete info struct, since there -// is no good way to handle an error like this during Accept(). -// The data is expected to contain the whole ClientHello and -// ONLY the ClientHello. -// -// The majority of this code is borrowed from the Go standard -// library, which is (c) The Go Authors. It has been modified -// to fit this use case. -func parseRawClientHello(data []byte) (info rawHelloInfo) { - if len(data) < 42 { - return - } - sessionIDLen := int(data[38]) - if sessionIDLen > 32 || len(data) < 39+sessionIDLen { - return - } - data = data[39+sessionIDLen:] - if len(data) < 2 { - return - } - // cipherSuiteLen is the number of bytes of cipher suite numbers. Since - // they are uint16s, the number must be even. - cipherSuiteLen := int(data[0])<<8 | int(data[1]) - if cipherSuiteLen%2 == 1 || len(data) < 2+cipherSuiteLen { - return - } - numCipherSuites := cipherSuiteLen / 2 - // read in the cipher suites - info.cipherSuites = make([]uint16, numCipherSuites) - for i := 0; i < numCipherSuites; i++ { - info.cipherSuites[i] = uint16(data[2+2*i])<<8 | uint16(data[3+2*i]) - } - data = data[2+cipherSuiteLen:] - if len(data) < 1 { - return - } - // read in the compression methods - compressionMethodsLen := int(data[0]) - if len(data) < 1+compressionMethodsLen { - return - } - info.compressionMethods = data[1 : 1+compressionMethodsLen] - - data = data[1+compressionMethodsLen:] - - // ClientHello is optionally followed by extension data - if len(data) < 2 { - return - } - extensionsLength := int(data[0])<<8 | int(data[1]) - data = data[2:] - if extensionsLength != len(data) { - return - } - - // read in each extension, and extract any relevant information - // from extensions we care about - for len(data) != 0 { - if len(data) < 4 { - return - } - extension := uint16(data[0])<<8 | uint16(data[1]) - length := int(data[2])<<8 | int(data[3]) - data = data[4:] - if len(data) < length { - return - } - - // record that the client advertised support for this extension - info.extensions = append(info.extensions, extension) - - switch extension { - case extensionSupportedCurves: - // http://tools.ietf.org/html/rfc4492#section-5.5.1 - if length < 2 { - return - } - l := int(data[0])<<8 | int(data[1]) - if l%2 == 1 || length != l+2 { - return - } - numCurves := l / 2 - info.curves = make([]tls.CurveID, numCurves) - d := data[2:] - for i := 0; i < numCurves; i++ { - info.curves[i] = tls.CurveID(d[0])<<8 | tls.CurveID(d[1]) - d = d[2:] - } - case extensionSupportedPoints: - // http://tools.ietf.org/html/rfc4492#section-5.5.2 - if length < 1 { - return - } - l := int(data[0]) - if length != l+1 { - return - } - info.points = make([]uint8, l) - copy(info.points, data[1:]) - } - - data = data[length:] - } - - return -} - -// newTLSListener returns a new tlsHelloListener that wraps ln. -func newTLSListener(ln net.Listener, config *tls.Config) *tlsHelloListener { - return &tlsHelloListener{ - Listener: ln, - config: config, - helloInfos: make(map[string]rawHelloInfo), - } -} - -// tlsHelloListener is a TLS listener that is specially designed -// to read the ClientHello manually so we can extract necessary -// information from it. Each ClientHello message is mapped by -// the remote address of the client, which must be removed when -// the connection is closed (use ConnState). -type tlsHelloListener struct { - net.Listener - config *tls.Config - helloInfos map[string]rawHelloInfo - helloInfosMu sync.RWMutex -} - -// Accept waits for and returns the next connection to the listener. -// After it accepts the underlying connection, it reads the -// ClientHello message and stores the parsed data into a map on l. -func (l *tlsHelloListener) Accept() (net.Conn, error) { - conn, err := l.Listener.Accept() - if err != nil { - return nil, err - } - buf := bufpool.Get().(*bytes.Buffer) - buf.Reset() - helloConn := &clientHelloConn{Conn: conn, listener: l, buf: buf} - return tls.Server(helloConn, l.config), nil -} - -// rawHelloInfo contains the "raw" data parsed from the TLS -// Client Hello. No interpretation is done on the raw data. -// -// The methods on this type implement heuristics described -// by Durumeric, Halderman, et. al. in -// "The Security Impact of HTTPS Interception": -// https://jhalderm.com/pub/papers/interception-ndss17.pdf -type rawHelloInfo struct { - cipherSuites []uint16 - extensions []uint16 - compressionMethods []byte - curves []tls.CurveID - points []uint8 -} - -// advertisesHeartbeatSupport returns true if info indicates -// that the client supports the Heartbeat extension. -func (info rawHelloInfo) advertisesHeartbeatSupport() bool { - for _, ext := range info.extensions { - if ext == extensionHeartbeat { - return true - } - } - return false -} - -// looksLikeFirefox returns true if info looks like a handshake -// from a modern version of Firefox. -func (info rawHelloInfo) looksLikeFirefox() bool { - // "To determine whether a Firefox session has been - // intercepted, we check for the presence and order - // of extensions, cipher suites, elliptic curves, - // EC point formats, and handshake compression methods." (early 2016) - - // We check for the presence and order of the extensions. - // Note: Sometimes 0x15 (21, padding) is present, sometimes not. - // Note: Firefox 51+ does not advertise 0x3374 (13172, NPN). - // Note: Firefox doesn't advertise 0x0 (0, SNI) when connecting to IP addresses. - // Note: Firefox 55+ doesn't appear to advertise 0xFF03 (65283, short headers). It used to be between 5 and 13. - // Note: Firefox on Fedora (or RedHat) doesn't include ECC suites because of patent liability. - requiredExtensionsOrder := []uint16{23, 65281, 10, 11, 35, 16, 5, 13} - if !assertPresenceAndOrdering(requiredExtensionsOrder, info.extensions, true) { - return false - } - - // We check for both presence of curves and their ordering. - requiredCurves := []tls.CurveID{29, 23, 24, 25} - if len(info.curves) < len(requiredCurves) { - return false - } - for i := range requiredCurves { - if info.curves[i] != requiredCurves[i] { - return false - } - } - if len(info.curves) > len(requiredCurves) { - // newer Firefox (55 Nightly?) may have additional curves at end of list - allowedCurves := []tls.CurveID{256, 257} - for i := range allowedCurves { - if info.curves[len(requiredCurves)+i] != allowedCurves[i] { - return false - } - } - } - - if hasGreaseCiphers(info.cipherSuites) { - return false - } - - // We check for order of cipher suites but not presence, since - // according to the paper, cipher suites may be not be added - // or reordered by the user, but they may be disabled. - expectedCipherSuiteOrder := []uint16{ - TLS_AES_128_GCM_SHA256, // 0x1301 - TLS_CHACHA20_POLY1305_SHA256, // 0x1303 - TLS_AES_256_GCM_SHA384, // 0x1302 - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, // 0xc02b - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, // 0xc02f - tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, // 0xcca9 - tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, // 0xcca8 - tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, // 0xc02c - tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, // 0xc030 - tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, // 0xc00a - tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, // 0xc009 - tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, // 0xc013 - tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, // 0xc014 - TLS_DHE_RSA_WITH_AES_128_CBC_SHA, // 0x33 - TLS_DHE_RSA_WITH_AES_256_CBC_SHA, // 0x39 - tls.TLS_RSA_WITH_AES_128_CBC_SHA, // 0x2f - tls.TLS_RSA_WITH_AES_256_CBC_SHA, // 0x35 - tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, // 0xa - } - return assertPresenceAndOrdering(expectedCipherSuiteOrder, info.cipherSuites, false) -} - -// looksLikeChrome returns true if info looks like a handshake -// from a modern version of Chrome. -func (info rawHelloInfo) looksLikeChrome() bool { - // "We check for ciphers and extensions that Chrome is known - // to not support, but do not check for the inclusion of - // specific ciphers or extensions, nor do we validate their - // order. When appropriate, we check the presence and order - // of elliptic curves, compression methods, and EC point formats." (early 2016) - - // Not in Chrome 56, but present in Safari 10 (Feb. 2017): - // TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 (0xc024) - // TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 (0xc023) - // TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA (0xc00a) - // TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (0xc009) - // TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 (0xc028) - // TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 (0xc027) - // TLS_RSA_WITH_AES_256_CBC_SHA256 (0x3d) - // TLS_RSA_WITH_AES_128_CBC_SHA256 (0x3c) - - // Not in Chrome 56, but present in Firefox 51 (Feb. 2017): - // TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA (0xc00a) - // TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (0xc009) - // TLS_DHE_RSA_WITH_AES_128_CBC_SHA (0x33) - // TLS_DHE_RSA_WITH_AES_256_CBC_SHA (0x39) - - // Selected ciphers present in Chrome mobile (Feb. 2017): - // 0xc00a, 0xc014, 0xc009, 0x9c, 0x9d, 0x2f, 0x35, 0xa - - chromeCipherExclusions := map[uint16]struct{}{ - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: {}, // 0xc024 - tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: {}, // 0xc023 - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: {}, // 0xc028 - tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: {}, // 0xc027 - TLS_RSA_WITH_AES_256_CBC_SHA256: {}, // 0x3d - tls.TLS_RSA_WITH_AES_128_CBC_SHA256: {}, // 0x3c - TLS_DHE_RSA_WITH_AES_128_CBC_SHA: {}, // 0x33 - TLS_DHE_RSA_WITH_AES_256_CBC_SHA: {}, // 0x39 - } - for _, ext := range info.cipherSuites { - if _, ok := chromeCipherExclusions[ext]; ok { - return false - } - } - - // Chrome does not include curve 25 (CurveP521) (as of Chrome 56, Feb. 2017). - for _, curve := range info.curves { - if curve == 25 { - return false - } - } - - if !hasGreaseCiphers(info.cipherSuites) { - return false - } - - return true -} - -// looksLikeEdge returns true if info looks like a handshake -// from a modern version of MS Edge. -func (info rawHelloInfo) looksLikeEdge() bool { - // "SChannel connections can by uniquely identified because SChannel - // is the only TLS library we tested that includes the OCSP status - // request extension before the supported groups and EC point formats - // extensions." (early 2016) - // - // More specifically, the OCSP status request extension appears - // *directly* before the other two extensions, which occur in that - // order. (I contacted the authors for clarification and verified it.) - for i, ext := range info.extensions { - if ext == extensionOCSPStatusRequest { - if len(info.extensions) <= i+2 { - return false - } - if info.extensions[i+1] != extensionSupportedCurves || - info.extensions[i+2] != extensionSupportedPoints { - return false - } - } - } - - for _, cs := range info.cipherSuites { - // As of Feb. 2017, Edge does not have 0xff, but Avast adds it - if cs == scsvRenegotiation { - return false - } - // Edge and modern IE do not have 0x4 or 0x5, but Blue Coat does - if cs == TLS_RSA_WITH_RC4_128_MD5 || cs == tls.TLS_RSA_WITH_RC4_128_SHA { - return false - } - } - - if hasGreaseCiphers(info.cipherSuites) { - return false - } - - return true -} - -// looksLikeSafari returns true if info looks like a handshake -// from a modern version of MS Safari. -func (info rawHelloInfo) looksLikeSafari() bool { - // "One unique aspect of Secure Transport is that it includes - // the TLS_EMPTY_RENEGOTIATION_INFO_SCSV (0xff) cipher first, - // whereas the other libraries we investigated include the - // cipher last. Similar to Microsoft, Apple has changed - // TLS behavior in minor OS updates, which are not indicated - // in the HTTP User-Agent header. We allow for any of the - // updates when validating handshakes, and we check for the - // presence and ordering of ciphers, extensions, elliptic - // curves, and compression methods." (early 2016) - - // Note that any C lib (e.g. curl) compiled on macOS - // will probably use Secure Transport which will also - // share the TLS handshake characteristics of Safari. - - // We check for the presence and order of the extensions. - requiredExtensionsOrder := []uint16{10, 11, 13, 13172, 16, 5, 18, 23} - if !assertPresenceAndOrdering(requiredExtensionsOrder, info.extensions, true) { - // Safari on iOS 11 (beta) uses different set/ordering of extensions - requiredExtensionsOrderiOS11 := []uint16{65281, 0, 23, 13, 5, 13172, 18, 16, 11, 10} - if !assertPresenceAndOrdering(requiredExtensionsOrderiOS11, info.extensions, true) { - return false - } - } else { - // For these versions of Safari, expect TLS_EMPTY_RENEGOTIATION_INFO_SCSV first. - if len(info.cipherSuites) < 1 { - return false - } - if info.cipherSuites[0] != scsvRenegotiation { - return false - } - } - - if hasGreaseCiphers(info.cipherSuites) { - return false - } - - // We check for order and presence of cipher suites - expectedCipherSuiteOrder := []uint16{ - tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, // 0xc02c - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, // 0xc02b - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, // 0xc024 - tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, // 0xc023 - tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, // 0xc00a - tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, // 0xc009 - tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, // 0xc030 - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, // 0xc02f - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, // 0xc028 - tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, // 0xc027 - tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, // 0xc014 - tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, // 0xc013 - tls.TLS_RSA_WITH_AES_256_GCM_SHA384, // 0x9d - tls.TLS_RSA_WITH_AES_128_GCM_SHA256, // 0x9c - TLS_RSA_WITH_AES_256_CBC_SHA256, // 0x3d - tls.TLS_RSA_WITH_AES_128_CBC_SHA256, // 0x3c - tls.TLS_RSA_WITH_AES_256_CBC_SHA, // 0x35 - tls.TLS_RSA_WITH_AES_128_CBC_SHA, // 0x2f - } - return assertPresenceAndOrdering(expectedCipherSuiteOrder, info.cipherSuites, true) -} - -// looksLikeTor returns true if the info looks like a ClientHello from Tor browser -// (based on Firefox). -func (info rawHelloInfo) looksLikeTor() bool { - requiredExtensionsOrder := []uint16{10, 11, 16, 5, 13} - if !assertPresenceAndOrdering(requiredExtensionsOrder, info.extensions, true) { - return false - } - - // check for session tickets support; Tor doesn't support them to prevent tracking - for _, ext := range info.extensions { - if ext == 35 { - return false - } - } - - // We check for both presence of curves and their ordering, including - // an optional curve at the beginning (for Tor based on Firefox 52) - infoCurves := info.curves - if len(info.curves) == 4 { - if info.curves[0] != 29 { - return false - } - infoCurves = info.curves[1:] - } - requiredCurves := []tls.CurveID{23, 24, 25} - if len(infoCurves) < len(requiredCurves) { - return false - } - for i := range requiredCurves { - if infoCurves[i] != requiredCurves[i] { - return false - } - } - - if hasGreaseCiphers(info.cipherSuites) { - return false - } - - // We check for order of cipher suites but not presence, since - // according to the paper, cipher suites may be not be added - // or reordered by the user, but they may be disabled. - expectedCipherSuiteOrder := []uint16{ - TLS_AES_128_GCM_SHA256, // 0x1301 - TLS_CHACHA20_POLY1305_SHA256, // 0x1303 - TLS_AES_256_GCM_SHA384, // 0x1302 - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, // 0xc02b - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, // 0xc02f - tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, // 0xcca9 - tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, // 0xcca8 - tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, // 0xc02c - tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, // 0xc030 - tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, // 0xc00a - tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, // 0xc009 - tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, // 0xc013 - tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, // 0xc014 - TLS_DHE_RSA_WITH_AES_128_CBC_SHA, // 0x33 - TLS_DHE_RSA_WITH_AES_256_CBC_SHA, // 0x39 - tls.TLS_RSA_WITH_AES_128_CBC_SHA, // 0x2f - tls.TLS_RSA_WITH_AES_256_CBC_SHA, // 0x35 - tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, // 0xa - } - return assertPresenceAndOrdering(expectedCipherSuiteOrder, info.cipherSuites, false) -} - -// assertPresenceAndOrdering will return true if candidateList contains -// the items in requiredItems in the same order as requiredItems. -// -// If requiredIsSubset is true, then all items in requiredItems must be -// present in candidateList. If requiredIsSubset is false, then requiredItems -// may contain items that are not in candidateList. -// -// In all cases, the order of requiredItems is enforced. -func assertPresenceAndOrdering(requiredItems, candidateList []uint16, requiredIsSubset bool) bool { - superset := requiredItems - subset := candidateList - if requiredIsSubset { - superset = candidateList - subset = requiredItems - } - - var j int - for _, item := range subset { - var found bool - for j < len(superset) { - if superset[j] == item { - found = true - break - } - j++ - } - if j == len(superset) && !found { - return false - } - } - return true -} - -func hasGreaseCiphers(cipherSuites []uint16) bool { - for _, cipher := range cipherSuites { - if _, ok := greaseCiphers[cipher]; ok { - return true - } - } - return false -} - -// pool buffers so we can reuse allocations over time -var bufpool = sync.Pool{ - New: func() interface{} { - return new(bytes.Buffer) - }, -} - -var greaseCiphers = map[uint16]struct{}{ - 0x0A0A: {}, - 0x1A1A: {}, - 0x2A2A: {}, - 0x3A3A: {}, - 0x4A4A: {}, - 0x5A5A: {}, - 0x6A6A: {}, - 0x7A7A: {}, - 0x8A8A: {}, - 0x9A9A: {}, - 0xAAAA: {}, - 0xBABA: {}, - 0xCACA: {}, - 0xDADA: {}, - 0xEAEA: {}, - 0xFAFA: {}, -} - -// Define variables used for TLS communication -const ( - extensionOCSPStatusRequest = 5 - extensionSupportedCurves = 10 // also called "SupportedGroups" - extensionSupportedPoints = 11 - extensionHeartbeat = 15 - - scsvRenegotiation = 0xff - - // cipher suites missing from the crypto/tls package, - // in no particular order here - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xc024 - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xc028 - TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x3d - TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x33 - TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x39 - TLS_RSA_WITH_RC4_128_MD5 = 0x4 - - // new PSK ciphers introduced by TLS 1.3, not (yet) in crypto/tls - // https://tlswg.github.io/tls13-spec/#rfc.appendix.A.4) - TLS_AES_128_GCM_SHA256 = 0x1301 - TLS_AES_256_GCM_SHA384 = 0x1302 - TLS_CHACHA20_POLY1305_SHA256 = 0x1303 - TLS_AES_128_CCM_SHA256 = 0x1304 - TLS_AES_128_CCM_8_SHA256 = 0x1305 -) diff --git a/caddyhttp/httpserver/mitm_test.go b/caddyhttp/httpserver/mitm_test.go deleted file mode 100644 index 82df34af623..00000000000 --- a/caddyhttp/httpserver/mitm_test.go +++ /dev/null @@ -1,399 +0,0 @@ -package httpserver - -import ( - "crypto/tls" - "encoding/hex" - "net/http" - "net/http/httptest" - "reflect" - "testing" -) - -func TestParseClientHello(t *testing.T) { - for i, test := range []struct { - inputHex string - expected rawHelloInfo - }{ - { - // curl 7.51.0 (x86_64-apple-darwin16.0) libcurl/7.51.0 SecureTransport zlib/1.2.8 - inputHex: `010000a6030358a28c73a71bdfc1f09dee13fecdc58805dcce42ac44254df548f14645f7dc2c00004400ffc02cc02bc024c023c00ac009c008c030c02fc028c027c014c013c012009f009e006b0067003900330016009d009c003d003c0035002f000a00af00ae008d008c008b01000039000a00080006001700180019000b00020100000d00120010040102010501060104030203050306030005000501000000000012000000170000`, - expected: rawHelloInfo{ - cipherSuites: []uint16{255, 49196, 49195, 49188, 49187, 49162, 49161, 49160, 49200, 49199, 49192, 49191, 49172, 49171, 49170, 159, 158, 107, 103, 57, 51, 22, 157, 156, 61, 60, 53, 47, 10, 175, 174, 141, 140, 139}, - extensions: []uint16{10, 11, 13, 5, 18, 23}, - compressionMethods: []byte{0}, - curves: []tls.CurveID{23, 24, 25}, - points: []uint8{0}, - }, - }, - { - // Chrome 56 - inputHex: `010000c003031dae75222dae1433a5a283ddcde8ddabaefbf16d84f250eee6fdff48cdfff8a00000201a1ac02bc02fc02cc030cca9cca8cc14cc13c013c014009c009d002f0035000a010000777a7a0000ff010001000000000e000c0000096c6f63616c686f73740017000000230000000d00140012040308040401050308050501080606010201000500050100000000001200000010000e000c02683208687474702f312e3175500000000b00020100000a000a0008aaaa001d001700182a2a000100`, - expected: rawHelloInfo{ - cipherSuites: []uint16{6682, 49195, 49199, 49196, 49200, 52393, 52392, 52244, 52243, 49171, 49172, 156, 157, 47, 53, 10}, - extensions: []uint16{31354, 65281, 0, 23, 35, 13, 5, 18, 16, 30032, 11, 10, 10794}, - compressionMethods: []byte{0}, - curves: []tls.CurveID{43690, 29, 23, 24}, - points: []uint8{0}, - }, - }, - { - // Firefox 51 - inputHex: `010000bd030375f9022fc3a6562467f3540d68013b2d0b961979de6129e944efe0b35531323500001ec02bc02fcca9cca8c02cc030c00ac009c013c01400330039002f0035000a010000760000000e000c0000096c6f63616c686f737400170000ff01000100000a000a0008001d001700180019000b00020100002300000010000e000c02683208687474702f312e31000500050100000000ff030000000d0020001e040305030603020308040805080604010501060102010402050206020202`, - expected: rawHelloInfo{ - cipherSuites: []uint16{49195, 49199, 52393, 52392, 49196, 49200, 49162, 49161, 49171, 49172, 51, 57, 47, 53, 10}, - extensions: []uint16{0, 23, 65281, 10, 11, 35, 16, 5, 65283, 13}, - compressionMethods: []byte{0}, - curves: []tls.CurveID{29, 23, 24, 25}, - points: []uint8{0}, - }, - }, - { - // openssl s_client (OpenSSL 0.9.8zh 14 Jan 2016) - inputHex: `0100012b03035d385236b8ca7b7946fa0336f164e76bf821ed90e8de26d97cc677671b6f36380000acc030c02cc028c024c014c00a00a500a300a1009f006b006a0069006800390038003700360088008700860085c032c02ec02ac026c00fc005009d003d00350084c02fc02bc027c023c013c00900a400a200a0009e00670040003f003e0033003200310030009a0099009800970045004400430042c031c02dc029c025c00ec004009c003c002f009600410007c011c007c00cc00200050004c012c008001600130010000dc00dc003000a00ff0201000055000b000403000102000a001c001a00170019001c001b0018001a0016000e000d000b000c0009000a00230000000d0020001e060106020603050105020503040104020403030103020303020102020203000f000101`, - expected: rawHelloInfo{ - cipherSuites: []uint16{49200, 49196, 49192, 49188, 49172, 49162, 165, 163, 161, 159, 107, 106, 105, 104, 57, 56, 55, 54, 136, 135, 134, 133, 49202, 49198, 49194, 49190, 49167, 49157, 157, 61, 53, 132, 49199, 49195, 49191, 49187, 49171, 49161, 164, 162, 160, 158, 103, 64, 63, 62, 51, 50, 49, 48, 154, 153, 152, 151, 69, 68, 67, 66, 49201, 49197, 49193, 49189, 49166, 49156, 156, 60, 47, 150, 65, 7, 49169, 49159, 49164, 49154, 5, 4, 49170, 49160, 22, 19, 16, 13, 49165, 49155, 10, 255}, - extensions: []uint16{11, 10, 35, 13, 15}, - compressionMethods: []byte{1, 0}, - curves: []tls.CurveID{23, 25, 28, 27, 24, 26, 22, 14, 13, 11, 12, 9, 10}, - points: []uint8{0, 1, 2}, - }, - }, - } { - data, err := hex.DecodeString(test.inputHex) - if err != nil { - t.Fatalf("Test %d: Could not decode hex data: %v", i, err) - } - actual := parseRawClientHello(data) - if !reflect.DeepEqual(test.expected, actual) { - t.Errorf("Test %d: Expected %+v; got %+v", i, test.expected, actual) - } - } -} - -func TestHeuristicFunctionsAndHandler(t *testing.T) { - // To test the heuristics, we assemble a collection of real - // ClientHello messages from various TLS clients, both genuine - // and intercepted. Please be sure to hex-encode them and - // document the User-Agent associated with the connection - // as well as any intercepting proxy as thoroughly as possible. - // - // If the TLS client used is not an HTTP client (e.g. s_client), - // you can leave the userAgent blank, but please use a comment - // to document crucial missing information such as client name, - // version, and platform, maybe even the date you collected - // the sample! Please group similar clients together, ordered - // by version for convenience. - - // clientHello pairs a User-Agent string to its ClientHello message. - type clientHello struct { - userAgent string - helloHex string // do NOT include the header, just the ClientHello message - interception bool // if test case shows an interception, set to true - reqHeaders http.Header // if the request should set any headers to imitate a browser or proxy - } - - // clientHellos groups samples of true (real) ClientHellos by the - // name of the browser that produced them. We limit the set of - // browsers to those we are programmed to protect, as well as a - // category for "Other" which contains real ClientHello messages - // from clients that we do not recognize, which may be used to - // test or imitate interception scenarios. - // - // Please group similar clients and order by version for convenience - // when adding to the test cases. - clientHellos := map[string][]clientHello{ - "Chrome": { - { - userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", - helloHex: `010000c003031dae75222dae1433a5a283ddcde8ddabaefbf16d84f250eee6fdff48cdfff8a00000201a1ac02bc02fc02cc030cca9cca8cc14cc13c013c014009c009d002f0035000a010000777a7a0000ff010001000000000e000c0000096c6f63616c686f73740017000000230000000d00140012040308040401050308050501080606010201000500050100000000001200000010000e000c02683208687474702f312e3175500000000b00020100000a000a0008aaaa001d001700182a2a000100`, - interception: false, - }, - { - // Chrome on iOS will use iOS' TLS stack for requests that load - // the web page (apparently required by the dev ToS) but will use its - // own TLS stack for everything else, it seems. - - // Chrome on iOS - userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/56.0.2924.79 Mobile/14A456 Safari/602.1", - helloHex: `010000de030358b062c509b21410a6496b5a82bfec74436cdecebe8ea1da29799939bbd3c17200002c00ffc02cc02bc024c023c00ac009c008c030c02fc028c027c014c013c012009d009c003d003c0035002f000a0100008900000014001200000f66696e6572706978656c732e636f6d000a00080006001700180019000b00020100000d00120010040102010501060104030203050306033374000000100030002e0268320568322d31360568322d31350568322d313408737064792f332e3106737064792f3308687474702f312e310005000501000000000012000000170000`, - }, - { - // Chrome on iOS (requesting favicon) - userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/56.0.2924.79 Mobile/14A456 Safari/602.1", - helloHex: `010000c20303863eb64788e3b9638c261300318411cbdd8f09576d58eec1e744b6ce944f574f0000208a8acca9cca8cc14cc13c02bc02fc02cc030c013c014009c009d002f0035000a01000079baba0000ff0100010000000014001200000f66696e6572706978656c732e636f6d0017000000230000000d00140012040308040401050308050501080606010201000500050100000000001200000010000e000c02683208687474702f312e31000b00020100000a000a00083a3a001d001700184a4a000100`, - }, - { - userAgent: "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", - helloHex: `010000c603036f717a88212c3e9e41940f82c42acb3473e0e4a64e8f52d9af33d34e972e08a30000206a6ac02bc02fc02cc030cca9cca8cc14cc13c013c014009c009d002f0035000a0100007d7a7a0000ff0100010000000014001200000f66696e6572706978656c732e636f6d0017000000230000000d00140012040308040401050308050501080606010201000500050100000000001200000010000e000c02683208687474702f312e3175500000000b00020100000a000a00087a7a001d001700188a8a000100`, - interception: false, - }, - { - userAgent: "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", - helloHex: `010001fc030383141d213d1bf069171843489faf808028d282c9828e1ba87637c863833c730720a67e76e152f4b704523b72317ef4587e231f02e2395e0ecac6be9f28c35e6ce600208a8ac02bc02fc02cc030cca9cca8cc14cc13c013c014009c009d002f0035000a010001931a1a0000ff0100010000000014001200000f66696e6572706978656c732e636f6d00170000002300785e85429bf1764f33111cd3ad5d1c56d765976fd962b49dbecbb6f7865e2a8d8536ad854f1fa99a8bbbf998814fee54a63a0bf162869d2bba37e9778304e7c4140825718e191b574c6246a0611de6447bdd80417f83ff9d9b7124069a9f74b90394ecb89bec5f6a1a67c1b89e50b8674782f53dd51807651a000d00140012040308040401050308050501080606010201000500050100000000001200000010000e000c02683208687474702f312e3175500000000b00020100000a000a00081a1a001d001700182a2a0001000015009a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000`, - interception: false, - }, - { - userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36", - helloHex: `010000c203034166c97e2016046e0c88ad867c410d0aee470f4d9b4ec8fe41a751d2a6348e3100001c4a4ac02bc02fc02cc030cca9cca8c013c014009c009d002f0035000a0100007dcaca0000ff0100010000000014001200000f66696e6572706978656c732e636f6d0017000000230000000d00140012040308040401050308050501080606010201000500050100000000001200000010000e000c02683208687474702f312e3175500000000b00020100000a000a00086a6a001d001700187a7a000100`, - interception: false, - }, - { - userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36", - helloHex: `010000c203037741795e73cd5b4949f79a0dc9cccc8b006e4c0ec324f965c6fe9f0833909f0100001c7a7ac02bc02fc02cc030cca9cca8c013c014009c009d002f0035000a0100007d7a7a0000ff0100010000000014001200000f66696e6572706978656c732e636f6d0017000000230000000d00140012040308040401050308050501080606010201000500050100000000001200000010000e000c02683208687474702f312e3175500000000b00020100000a000a00084a4a001d001700185a5a000100`, - interception: false, - }, - }, - "Firefox": { - { - userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:51.0) Gecko/20100101 Firefox/51.0", - helloHex: `010000bd030375f9022fc3a6562467f3540d68013b2d0b961979de6129e944efe0b35531323500001ec02bc02fcca9cca8c02cc030c00ac009c013c01400330039002f0035000a010000760000000e000c0000096c6f63616c686f737400170000ff01000100000a000a0008001d001700180019000b00020100002300000010000e000c02683208687474702f312e31000500050100000000ff030000000d0020001e040305030603020308040805080604010501060102010402050206020202`, - interception: false, - }, - { - userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0) Gecko/20100101 Firefox/53.0", - helloHex: `010001fc0303c99d54ae0628bbb9fea3833a4244c6a712cac9d7738f4930b8b9d8e2f6bd578220f7936cedb48907981c9292fb08ceee6f59bd6fddb3d4271ccd7c12380c5038ab001ec02bc02fcca9cca8c02cc030c00ac009c013c01400330039002f0035000a01000195001500af000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000c0000096c6f63616c686f737400170000ff01000100000a000a0008001d001700180019000b000201000023007886da2d41843ff42131b856982c19a545837b70e604325423a817d925e9d95bd084737682cea6b804dfb7cbe336a3b27b8d520d57520c29cfe5f4f3d3236183b84b05c18f0ca30bf598111e390086fea00d9631f1f78527277eb7838b86e73c4e5d15b55d086b1a4a8aa29f12a55126c6274bcd499bbeb23a0010000e000c02683208687474702f312e31000500050100000000000d0018001604030503060308040805080604010501060102030201`, - interception: false, - }, - { - userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0) Gecko/20100101 Firefox/53.0", - helloHex: `010000b1030365d899820b999245d571c2f7d6b850f63ad931d3c68ceb9cf5a508421a871dc500001ec02bc02fcca9cca8c02cc030c00ac009c013c01400330039002f0035000a0100006a0000000e000c0000096c6f63616c686f737400170000ff01000100000a000a0008001d001700180019000b00020100002300000010000e000c02683208687474702f312e31000500050100000000000d0018001604030503060308040805080604010501060102030201`, - interception: false, - }, - { - // this was a Nightly release at the time - userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:55.0) Gecko/20100101 Firefox/55.0", - helloHex: `010001fc030331e380b7d12018e1202ef3327607203df5c5732b4fa5ab5abaf0b60034c2fb662070c836b9b89123e37f4f1074d152df438fa8ee8a0f89b036fd952f4fcc0b994f001c130113031302c02bc02fcca9cca8c02cc030c013c014002f0035000a0100019700000014001200000f63616464797365727665722e636f6d00170000ff01000100000a000e000c001d00170018001901000101000b0002010000230078c97e7716a041e2ea824571bef26a3dff2bf50a883cd15d904ab2d17deb514f6e0a079ee7c212c000178387ffafc2e530b6df6662f570aae134330f13c458a0eaad5a96a9696f572110918740b15db1143d19aaaa706942030b433a7e6150f62b443c0564e5b8f7ee9577bf3bf7faec8c67425b648ab54d880010000e000c02683208687474702f312e310005000501000000000028006b0069001d0020aee6e596155ee6f79f943e81ceabe0979d27fbbb8b9189ccb2ebc75226351f32001700410421875a44e510decac11ef1d7cfddd4dfe105d5cd3a2d42fba03ebde23e51e8ce65bda1b48be82d4848d1db2bfce68e94092e925a9ce0dbf5df35479558108489002b0009087f12030303020301000d0018001604030503060308040805080604010501060102030201002d000201010015002500000000000000000000000000000000000000000000000000000000000000000000000000`, - interception: false, - }, - { - // Firefox on Fedora (RedHat) doesn't include ECC ciphers because of patent liabilities - userAgent: "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:53.0) Gecko/20100101 Firefox/53.0", - helloHex: `010000b70303f5280b74d617d42e39fd77b78a2b537b1d7787ce4fcbcf3604c9fbcd677c6c5500001ec02bc02fcca9cca8c02cc030c00ac009c013c01400330039002f0035000a0100007000000014001200000f66696e6572706978656c732e636f6d00170000ff01000100000a000a0008001d001700180019000b00020100002300000010000e000c02683208687474702f312e31000500050100000000000d0018001604030503060308040805080604010501060102030201`, - interception: false, - }, - }, - "Edge": { - { - userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393", - helloHex: `010000bd030358a3c9bf05f734842e189fb6ce653b67b846e990bc1fc5fb8c397874d06020f1000038c02cc02bc030c02f009f009ec024c023c028c027c00ac009c014c01300390033009d009c003d003c0035002f000a006a00400038003200130100005c000500050100000000000a00080006001d00170018000b00020100000d00140012040105010201040305030203020206010603002300000010000e000c02683208687474702f312e310017000055000006000100020002ff01000100`, - interception: false, - }, - }, - "Safari": { - { - userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/602.4.8 (KHTML, like Gecko) Version/10.0.3 Safari/602.4.8", - helloHex: `010000d2030358a295b513c8140c6ff880f4a8a73cc830ed2dab2c4f2068eb365228d828732e00002600ffc02cc02bc024c023c00ac009c030c02fc028c027c014c013009d009c003d003c0035002f010000830000000e000c0000096c6f63616c686f7374000a00080006001700180019000b00020100000d00120010040102010501060104030203050306033374000000100030002e0268320568322d31360568322d31350568322d313408737064792f332e3106737064792f3308687474702f312e310005000501000000000012000000170000`, - interception: false, - }, - { - userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.28 (KHTML, like Gecko) Version/11.0 Mobile/15A5318g Safari/604.1", - helloHex: `010000e10303be294e11847ba01301e0bb6129f4a0d66344602141a8f0a1ab0750a1db145755000028c02cc02bc024c023cca9c00ac009c030c02fc028c027cca8c014c013009d009c003d003c0035002f01000090ff0100010000000014001200000f66696e6572706978656c732e636f6d00170000000d00140012040308040401050308050501080606010201000500050100000000337400000012000000100030002e0268320568322d31360568322d31350568322d313408737064792f332e3106737064792f3308687474702f312e31000b00020100000a00080006001d00170018`, - interception: false, - }, - }, - "Tor": { - { - userAgent: "Mozilla/5.0 (Windows NT 6.1; rv:45.0) Gecko/20100101 Firefox/45.0", - helloHex: `010000a40303137f05d4151f2d9095aee4254416d9dce73d6a1d857e8097ea20d021c04a7a81000016c02bc02fc00ac009c013c01400330039002f0035000a0100006500000014001200000f66696e6572706978656c732e636f6dff01000100000a00080006001700180019000b00020100337400000010000b000908687474702f312e31000500050100000000000d001600140401050106010201040305030603020304020202`, - interception: false, - }, - { - userAgent: "Mozilla/5.0 (Windows NT 6.1; rv:52.0) Gecko/20100101 Firefox/52.0", - helloHex: `010000b4030322e1f3aff4c37caba303c2ce53ba1689b3e70117a46f413d44f70a74cb6a496100001ec02bc02fcca9cca8c02cc030c00ac009c013c01400330039002f0035000a0100006d00000014001200000f66696e6572706978656c732e636f6d00170000ff01000100000a000a0008001d001700180019000b000201000010000b000908687474702f312e31000500050100000000ff030000000d0018001604030503060308040805080604010501060102030201`, - interception: false, - }, - }, - "Other": { // these are either non-browser clients or intercepted client hellos - { - // openssl s_client (OpenSSL 0.9.8zh 14 Jan 2016) - NOT an interception, but not a browser either - helloHex: `0100012b03035d385236b8ca7b7946fa0336f164e76bf821ed90e8de26d97cc677671b6f36380000acc030c02cc028c024c014c00a00a500a300a1009f006b006a0069006800390038003700360088008700860085c032c02ec02ac026c00fc005009d003d00350084c02fc02bc027c023c013c00900a400a200a0009e00670040003f003e0033003200310030009a0099009800970045004400430042c031c02dc029c025c00ec004009c003c002f009600410007c011c007c00cc00200050004c012c008001600130010000dc00dc003000a00ff0201000055000b000403000102000a001c001a00170019001c001b0018001a0016000e000d000b000c0009000a00230000000d0020001e060106020603050105020503040104020403030103020303020102020203000f000101`, - // NOTE: This test case is not actually an interception, but s_client is not a browser - // or any client we support MITM checking for, either. Since it advertises heartbeat, - // our heuristics still flag it as a MITM. - interception: true, - }, - { - // curl 7.51.0 (x86_64-apple-darwin16.0) libcurl/7.51.0 SecureTransport zlib/1.2.8 - userAgent: "curl/7.51.0", - helloHex: `010000a6030358a28c73a71bdfc1f09dee13fecdc58805dcce42ac44254df548f14645f7dc2c00004400ffc02cc02bc024c023c00ac009c008c030c02fc028c027c014c013c012009f009e006b0067003900330016009d009c003d003c0035002f000a00af00ae008d008c008b01000039000a00080006001700180019000b00020100000d00120010040102010501060104030203050306030005000501000000000012000000170000`, - interception: false, - }, - { - // Avast 17.1.2286 (Feb. 2017) on Windows 10 x64 build 14393, intercepting Edge - userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393", - helloHex: `010000ce0303b418fdc4b6cf6436a5e2bfb06b96ed5faa7285c20c7b49341a78be962a9dc40000003ac02cc02bc030c02f009f009ec024c023c028c027c00ac009c014c01300390033009d009c003d003c0035002f000a006a004000380032001300ff0100006b00000014001200000f66696e6572706978656c732e636f6d000b000403000102000a00080006001d0017001800230000000d001400120401050102010403050302030202060106030005000501000000000010000e000c02683208687474702f312e310016000000170000`, - interception: true, - }, - { - // Kaspersky Internet Security 17.0.0.611 on Windows 10 x64 build 14393, intercepting Edge - userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393", - helloHex: `010000eb030361ce302bf4b0d5adf1ff30b2cf433c4a4b68f33e07b2651695e7ae6ec3cf126400003ac02cc02bc030c02f009f009ec024c023c028c027c00ac009c014c01300390033009d009c003d003c0035002f000a006a004000380032001300ff0100008800000014001200000f66696e6572706978656c732e636f6d000b000403000102000a001c001a00170019001c001b0018001a0016000e000d000b000c0009000a00230000000d0020001e060106020603050105020503040104020403030103020303020102020203000500050100000000000f0001010010000e000c02683208687474702f312e31`, - interception: true, - }, - { - // Kaspersky Internet Security 17.0.0.611 on Windows 10 x64 build 14393, intercepting Firefox 51 - userAgent: "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:51.0) Gecko/20100101 Firefox/51.0", - helloHex: `010001fc0303768e3f9ea75194c7cb03d23e8e6371b95fb696d339b797be57a634309ec98a42200f2a7554098364b7f05d21a8c7f43f31a893a4fc5670051020408c8e4dc234dd001cc02bc02fc02cc030c00ac009c013c01400330039002f0035000a00ff0100019700000014001200000f66696e6572706978656c732e636f6d000b000403000102000a001c001a00170019001c001b0018001a0016000e000d000b000c0009000a00230078bf4e244d4de3d53c6331edda9672dfc4a17aae92b671e86da1368b1b5ae5324372817d8f3b7ffe1a7a1537a5049b86cd7c44863978c1e615b005942755da20fc3a4e34a16f78034aa3b1cffcef95f81a0995c522a53b0e95a4f98db84c43359d93d8647b2de2a69f3ebdcfc6bca452730cbd00179226dedf000d0020001e060106020603050105020503040104020403030103020303020102020203000500050100000000000f0001010010000e000c02683208687474702f312e3100150093000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000`, - interception: true, - }, - { - // Kaspersky Internet Security 17.0.0.611 on Windows 10 x64 build 14393, intercepting Chrome 56 - userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", - helloHex: `010000c903033481e7af24e647ba5a79ec97e9264c1a1f990cf842f50effe22be52130d5af82000018c02bc02fc02cc030c013c014009c009d002f0035000a00ff0100008800000014001200000f66696e6572706978656c732e636f6d000b000403000102000a001c001a00170019001c001b0018001a0016000e000d000b000c0009000a00230000000d0020001e060106020603050105020503040104020403030103020303020102020203000500050100000000000f0001010010000e000c02683208687474702f312e31`, - interception: true, - }, - { - // AVG 17.1.3006 (build 17.1.3354.20) on Windows 10 x64 build 14393, intercepting Edge - userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393", - helloHex: `010000ca0303fd83091207161eca6b4887db50587109c50e463beb190362736b1fcf9e05f807000036c02cc02bc030c02f009f009ec024c023c028c027c00ac009c014c01300390033009d009c003d003c0035002f006a00400038003200ff0100006b00000014001200000f66696e6572706978656c732e636f6d000b000403000102000a00080006001d0017001800230000000d001400120401050102010403050302030202060106030005000501000000000010000e000c02683208687474702f312e310016000000170000`, - interception: true, - }, - { - // IE 11 on Windows 7, this connection was intercepted by Blue Coat - // no sensible User-Agent value, since Blue Coat changes it to something super generic - // By the way, here's another reason we hate Blue Coat: they break TLS 1.3: - // https://twitter.com/FiloSottile/status/835269932929667072 - helloHex: `010000b1030358a3f3bae627f464da8cb35976b88e9119640032d41e62a107d608ed8d3e62b9000034c028c027c014c013009f009e009d009cc02cc02bc024c023c00ac009003d003c0035002f006a004000380032000a0013000500040100005400000014001200000f66696e6572706978656c732e636f6d000500050100000000000a00080006001700180019000b00020100000d0014001206010603040105010201040305030203020200170000ff01000100`, - interception: true, - reqHeaders: http.Header{"X-Bluecoat-Via": {"66808702E9A2CF4"}}, // actual field name would be "X-BlueCoat-Via" but Go canonicalizes field names - }, - { - // Firefox 51.0.1 being intercepted by burp 1.7.17 - userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:51.0) Gecko/20100101 Firefox/51.0", - helloHex: `010000d8030358a92f4daca95acc2f6a10a9c50d736135eae39406d3090238464540d482677600003ac023c027003cc025c02900670040c009c013002fc004c00e00330032c02bc02f009cc02dc031009e00a2c008c012000ac003c00d0016001300ff01000075000a0034003200170001000300130015000600070009000a0018000b000c0019000d000e000f001000110002001200040005001400080016000b00020100000d00180016060306010503050104030401040202030201020201010000001700150000126a61677561722e6b796877616e612e6f7267`, - interception: true, - }, - { - // Chrome 56 on Windows 10 being intercepted by Fortigate (on some public school network); note: I had to enable TLS 1.0 for this test (proxy was issuing a SHA-1 cert to client) - userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", - helloHex: `010000e5030158ac612125c83bae95282113b2a4c572cf613c160d234350fb6d0ddce879ffec000064003300320039003800160013c013c009c014c00ac012c008002f0035000a00150012003d003c00670040006b006ac011c0070096009a009900410084004500440088008700ba00be00bd00c000c400c3c03cc044c042c03dc045c04300090005000400ff01000058000a003600340000000100020003000400050006000700080009000a000b000c000d000e000f0010001100120013001400150016001700180019000b0002010000000014001200000f66696e6572706978656c732e636f6d`, - interception: true, - }, - { - // IE 11 on Windows 10, intercepted by Fortigate (same firewall as above) - userAgent: "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko", - helloHex: `010000e5030158ac634c5278d7b17421f23a64cc91d68c470c6b247322fe867ba035b373d05c000064003300320039003800160013c013c009c014c00ac012c008002f0035000a00150012003d003c00670040006b006ac011c0070096009a009900410084004500440088008700ba00be00bd00c000c400c3c03cc044c042c03dc045c04300090005000400ff01000058000a003600340000000100020003000400050006000700080009000a000b000c000d000e000f0010001100120013001400150016001700180019000b0002010000000014001200000f66696e6572706978656c732e636f6d`, - interception: true, - }, - { - // Edge 38.14393.0.0 on Windows 10, intercepted by Fortigate (same as above) - userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393", - helloHex: `010000e5030158ac6421a45794b8ade6a0ac6c910cde0f99c49bb1ba737b88638ec8dcf0d077000064003300320039003800160013c013c009c014c00ac012c008002f0035000a00150012003d003c00670040006b006ac011c0070096009a009900410084004500440088008700ba00be00bd00c000c400c3c03cc044c042c03dc045c04300090005000400ff01000058000a003600340000000100020003000400050006000700080009000a000b000c000d000e000f0010001100120013001400150016001700180019000b0002010000000014001200000f66696e6572706978656c732e636f6d`, - interception: true, - }, - { - // Firefox 50.0.1 on Windows 10, intercepted by Fortigate (same as above) - userAgent: "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0", - helloHex: `010000e5030158ac64e40495e77b7baf2031281451620bfe354b0c37521ebc0a40f5dc0c0cb6000064003300320039003800160013c013c009c014c00ac012c008002f0035000a00150012003d003c00670040006b006ac011c0070096009a009900410084004500440088008700ba00be00bd00c000c400c3c03cc044c042c03dc045c04300090005000400ff01000058000a003600340000000100020003000400050006000700080009000a000b000c000d000e000f0010001100120013001400150016001700180019000b0002010000000014001200000f66696e6572706978656c732e636f6d`, - interception: true, - }, - }, - } - - for client, chs := range clientHellos { - for i, ch := range chs { - hello, err := hex.DecodeString(ch.helloHex) - if err != nil { - t.Errorf("[%s] Test %d: Error decoding ClientHello: %v", client, i, err) - continue - } - parsed := parseRawClientHello(hello) - - isChrome := parsed.looksLikeChrome() - isFirefox := parsed.looksLikeFirefox() - isSafari := parsed.looksLikeSafari() - isEdge := parsed.looksLikeEdge() - isTor := parsed.looksLikeTor() - - // we want each of the heuristic functions to be as - // exclusive but as low-maintenance as possible; - // in other words, if one returns true, the others - // should return false, with as little logic as possible, - // but with enough logic to force TLS proxies to do a - // good job preserving characterstics of the handshake. - if (isChrome && (isFirefox || isSafari || isEdge || isTor)) || - (isFirefox && (isChrome || isSafari || isEdge || isTor)) || - (isSafari && (isChrome || isFirefox || isEdge || isTor)) || - (isEdge && (isChrome || isFirefox || isSafari || isTor)) || - (isTor && (isChrome || isFirefox || isSafari || isEdge)) { - t.Errorf("[%s] Test %d: Multiple fingerprinting functions matched: "+ - "Chrome=%v Firefox=%v Safari=%v Edge=%v Tor=%v\n\tparsed hello dec: %+v\n\tparsed hello hex: %#x\n", - client, i, isChrome, isFirefox, isSafari, isEdge, isTor, parsed, parsed) - } - - // test the handler and detection results - var got, checked bool - want := ch.interception - handler := &tlsHandler{ - next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - got, checked = r.Context().Value(MitmCtxKey).(bool) - }), - listener: newTLSListener(nil, nil), - } - handler.listener.helloInfos[""] = parsed - w := httptest.NewRecorder() - r, err := http.NewRequest("GET", "/", nil) - if err != nil { - t.Fatal(err) - } - r.Header.Set("User-Agent", ch.userAgent) - if ch.reqHeaders != nil { - for field, values := range ch.reqHeaders { - r.Header[field] = values // NOTE: field names not standardized when setting directly like this! - } - } - handler.ServeHTTP(w, r) - if got != want { - t.Errorf("[%s] Test %d: Expected MITM=%v but got %v (type assertion OK (checked)=%v)", - client, i, want, got, checked) - t.Errorf("[%s] Test %d: Looks like Chrome=%v Firefox=%v Safari=%v Edge=%v Tor=%v\n\tparsed hello dec: %+v\n\tparsed hello hex: %#x\n", - client, i, isChrome, isFirefox, isSafari, isEdge, isTor, parsed, parsed) - } - } - } -} - -func TestGetVersion(t *testing.T) { - for i, test := range []struct { - UserAgent string - SoftwareName string - Version float64 - }{ - { - UserAgent: "Mozilla/5.0 (Windows NT 6.1; rv:45.0) Gecko/20100101 Firefox/45.0", - SoftwareName: "Firefox", - Version: 45.0, - }, - { - UserAgent: "Mozilla/5.0 (Windows NT 6.1; rv:45.0) Gecko/20100101 Firefox/45.0 more_stuff_here", - SoftwareName: "Firefox", - Version: 45.0, - }, - { - UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393", - SoftwareName: "Safari", - Version: 537.36, - }, - { - UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393", - SoftwareName: "Chrome", - Version: 51.0270479, - }, - { - UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393", - SoftwareName: "Mozilla", - Version: 5.0, - }, - { - UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393", - SoftwareName: "curl", - Version: -1, - }, - } { - actual := getVersion(test.UserAgent, test.SoftwareName) - if actual != test.Version { - t.Errorf("Test [%d]: Expected version=%f, got version=%f for %s in '%s'", - i, test.Version, actual, test.SoftwareName, test.UserAgent) - } - } -} diff --git a/caddyhttp/httpserver/path.go b/caddyhttp/httpserver/path.go deleted file mode 100644 index 92136395d97..00000000000 --- a/caddyhttp/httpserver/path.go +++ /dev/null @@ -1,35 +0,0 @@ -package httpserver - -import ( - "net/http" - "strings" -) - -// Path represents a URI path. It should usually be -// set to the value of a request path. -type Path string - -// Matches checks to see if base matches p. The correct -// usage of this method sets p as the request path, and -// base as a Caddyfile (user-defined) rule path. -// -// Path matching will probably not always be a direct -// comparison; this method assures that paths can be -// easily and consistently matched. -func (p Path) Matches(base string) bool { - if base == "/" { - return true - } - if CaseSensitivePath { - return strings.HasPrefix(string(p), base) - } - return strings.HasPrefix(strings.ToLower(string(p)), strings.ToLower(base)) -} - -// PathMatcher is a Path RequestMatcher. -type PathMatcher string - -// Match satisfies RequestMatcher. -func (p PathMatcher) Match(r *http.Request) bool { - return Path(r.URL.Path).Matches(string(p)) -} diff --git a/caddyhttp/httpserver/path_test.go b/caddyhttp/httpserver/path_test.go deleted file mode 100644 index 6ae92e8f1d1..00000000000 --- a/caddyhttp/httpserver/path_test.go +++ /dev/null @@ -1,96 +0,0 @@ -package httpserver - -import "testing" - -func TestPathMatches(t *testing.T) { - for i, testcase := range []struct { - reqPath Path - rulePath string // or "base path" as in Caddyfile docs - shouldMatch bool - caseInsensitive bool - }{ - { - reqPath: "/", - rulePath: "/", - shouldMatch: true, - }, - { - reqPath: "/foo/bar", - rulePath: "/foo", - shouldMatch: true, - }, - { - reqPath: "/foobar", - rulePath: "/foo/", - shouldMatch: false, - }, - { - reqPath: "/foobar", - rulePath: "/foo/bar", - shouldMatch: false, - }, - { - reqPath: "/Foobar", - rulePath: "/Foo", - shouldMatch: true, - }, - { - - reqPath: "/FooBar", - rulePath: "/Foo", - shouldMatch: true, - }, - { - reqPath: "/foobar", - rulePath: "/FooBar", - shouldMatch: true, - caseInsensitive: true, - }, - { - reqPath: "", - rulePath: "/", // a lone forward slash means to match all requests (see issue #1645) - many future test cases related to this issue - shouldMatch: true, - }, - { - reqPath: "foobar.php", - rulePath: "/", - shouldMatch: true, - }, - { - reqPath: "", - rulePath: "", - shouldMatch: true, - }, - { - reqPath: "/foo/bar", - rulePath: "", - shouldMatch: true, - }, - { - reqPath: "/foo/bar", - rulePath: "", - shouldMatch: true, - }, - { - reqPath: "no/leading/slash", - rulePath: "/", - shouldMatch: true, - }, - { - reqPath: "no/leading/slash", - rulePath: "/no/leading/slash", - shouldMatch: false, - }, - { - reqPath: "no/leading/slash", - rulePath: "", - shouldMatch: true, - }, - } { - CaseSensitivePath = !testcase.caseInsensitive - if got, want := testcase.reqPath.Matches(testcase.rulePath), testcase.shouldMatch; got != want { - t.Errorf("Test %d: For request path '%s' and other path '%s': expected %v, got %v", - i, testcase.reqPath, testcase.rulePath, want, got) - } - } -} diff --git a/caddyhttp/httpserver/plugin.go b/caddyhttp/httpserver/plugin.go deleted file mode 100644 index 65ccab737ef..00000000000 --- a/caddyhttp/httpserver/plugin.go +++ /dev/null @@ -1,545 +0,0 @@ -package httpserver - -import ( - "flag" - "fmt" - "log" - "net" - "net/url" - "os" - "path/filepath" - "strings" - "time" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyfile" - "github.com/mholt/caddy/caddytls" -) - -const serverType = "http" - -func init() { - flag.StringVar(&HTTPPort, "http-port", HTTPPort, "Default port to use for HTTP") - flag.StringVar(&HTTPSPort, "https-port", HTTPSPort, "Default port to use for HTTPS") - flag.StringVar(&Host, "host", DefaultHost, "Default host") - flag.StringVar(&Port, "port", DefaultPort, "Default port") - flag.StringVar(&Root, "root", DefaultRoot, "Root path of default site") - flag.DurationVar(&GracefulTimeout, "grace", 5*time.Second, "Maximum duration of graceful shutdown") - flag.BoolVar(&HTTP2, "http2", true, "Use HTTP/2") - flag.BoolVar(&QUIC, "quic", false, "Use experimental QUIC") - - caddy.RegisterServerType(serverType, caddy.ServerType{ - Directives: func() []string { return directives }, - DefaultInput: func() caddy.Input { - if Port == DefaultPort && Host != "" { - // by leaving the port blank in this case we give auto HTTPS - // a chance to set the port to 443 for us - return caddy.CaddyfileInput{ - Contents: []byte(fmt.Sprintf("%s\nroot %s", Host, Root)), - ServerTypeName: serverType, - } - } - return caddy.CaddyfileInput{ - Contents: []byte(fmt.Sprintf("%s:%s\nroot %s", Host, Port, Root)), - ServerTypeName: serverType, - } - }, - NewContext: newContext, - }) - caddy.RegisterCaddyfileLoader("short", caddy.LoaderFunc(shortCaddyfileLoader)) - caddy.RegisterParsingCallback(serverType, "root", hideCaddyfile) - caddy.RegisterParsingCallback(serverType, "tls", activateHTTPS) - caddytls.RegisterConfigGetter(serverType, func(c *caddy.Controller) *caddytls.Config { return GetConfig(c).TLS }) -} - -// hideCaddyfile hides the source/origin Caddyfile if it is within the -// site root. This function should be run after parsing the root directive. -func hideCaddyfile(cctx caddy.Context) error { - ctx := cctx.(*httpContext) - for _, cfg := range ctx.siteConfigs { - // if no Caddyfile exists exit. - if cfg.originCaddyfile == "" { - return nil - } - absRoot, err := filepath.Abs(cfg.Root) - if err != nil { - return err - } - absOriginCaddyfile, err := filepath.Abs(cfg.originCaddyfile) - if err != nil { - return err - } - if strings.HasPrefix(absOriginCaddyfile, absRoot) { - cfg.HiddenFiles = append(cfg.HiddenFiles, filepath.ToSlash(strings.TrimPrefix(absOriginCaddyfile, absRoot))) - } - } - return nil -} - -func newContext() caddy.Context { - return &httpContext{keysToSiteConfigs: make(map[string]*SiteConfig)} -} - -type httpContext struct { - // keysToSiteConfigs maps an address at the top of a - // server block (a "key") to its SiteConfig. Not all - // SiteConfigs will be represented here, only ones - // that appeared in the Caddyfile. - keysToSiteConfigs map[string]*SiteConfig - - // siteConfigs is the master list of all site configs. - siteConfigs []*SiteConfig -} - -func (h *httpContext) saveConfig(key string, cfg *SiteConfig) { - h.siteConfigs = append(h.siteConfigs, cfg) - h.keysToSiteConfigs[key] = cfg -} - -// InspectServerBlocks make sure that everything checks out before -// executing directives and otherwise prepares the directives to -// be parsed and executed. -func (h *httpContext) InspectServerBlocks(sourceFile string, serverBlocks []caddyfile.ServerBlock) ([]caddyfile.ServerBlock, error) { - // For each address in each server block, make a new config - for _, sb := range serverBlocks { - for _, key := range sb.Keys { - key = strings.ToLower(key) - if _, dup := h.keysToSiteConfigs[key]; dup { - return serverBlocks, fmt.Errorf("duplicate site address: %s", key) - } - addr, err := standardizeAddress(key) - if err != nil { - return serverBlocks, err - } - - // Fill in address components from command line so that middleware - // have access to the correct information during setup - if addr.Host == "" && Host != DefaultHost { - addr.Host = Host - } - if addr.Port == "" && Port != DefaultPort { - addr.Port = Port - } - - // If default HTTP or HTTPS ports have been customized, - // make sure the ACME challenge ports match - var altHTTPPort, altTLSSNIPort string - if HTTPPort != DefaultHTTPPort { - altHTTPPort = HTTPPort - } - if HTTPSPort != DefaultHTTPSPort { - altTLSSNIPort = HTTPSPort - } - - // Save the config to our master list, and key it for lookups - cfg := &SiteConfig{ - Addr: addr, - Root: Root, - TLS: &caddytls.Config{ - Hostname: addr.Host, - AltHTTPPort: altHTTPPort, - AltTLSSNIPort: altTLSSNIPort, - }, - originCaddyfile: sourceFile, - } - h.saveConfig(key, cfg) - } - } - - // For sites that have gzip (which gets chained in - // before the error handler) we should ensure that the - // errors directive also appears so error pages aren't - // written after the gzip writer is closed. See #616. - for _, sb := range serverBlocks { - _, hasGzip := sb.Tokens["gzip"] - _, hasErrors := sb.Tokens["errors"] - if hasGzip && !hasErrors { - sb.Tokens["errors"] = []caddyfile.Token{{Text: "errors"}} - } - } - - return serverBlocks, nil -} - -// MakeServers uses the newly-created siteConfigs to -// create and return a list of server instances. -func (h *httpContext) MakeServers() ([]caddy.Server, error) { - // make sure TLS is disabled for explicitly-HTTP sites - // (necessary when HTTP address shares a block containing tls) - for _, cfg := range h.siteConfigs { - if !cfg.TLS.Enabled { - continue - } - if cfg.Addr.Port == HTTPPort || cfg.Addr.Scheme == "http" { - cfg.TLS.Enabled = false - log.Printf("[WARNING] TLS disabled for %s", cfg.Addr) - } else if cfg.Addr.Scheme == "" { - // set scheme to https ourselves, since TLS is enabled - // and it was not explicitly set to something else. this - // makes it appear as "https" when we print the list of - // running sites; otherwise "http" would be assumed which - // is incorrect for this site. - cfg.Addr.Scheme = "https" - } - if cfg.Addr.Port == "" && ((!cfg.TLS.Manual && !cfg.TLS.SelfSigned) || cfg.TLS.OnDemand) { - // this is vital, otherwise the function call below that - // sets the listener address will use the default port - // instead of 443 because it doesn't know about TLS. - cfg.Addr.Port = HTTPSPort - } - } - - // we must map (group) each config to a bind address - groups, err := groupSiteConfigsByListenAddr(h.siteConfigs) - if err != nil { - return nil, err - } - - // then we create a server for each group - var servers []caddy.Server - for addr, group := range groups { - s, err := NewServer(addr, group) - if err != nil { - return nil, err - } - servers = append(servers, s) - } - - return servers, nil -} - -// GetConfig gets the SiteConfig that corresponds to c. -// If none exist (should only happen in tests), then a -// new, empty one will be created. -func GetConfig(c *caddy.Controller) *SiteConfig { - ctx := c.Context().(*httpContext) - key := strings.ToLower(c.Key) - if cfg, ok := ctx.keysToSiteConfigs[key]; ok { - return cfg - } - // we should only get here during tests because directive - // actions typically skip the server blocks where we make - // the configs - cfg := &SiteConfig{Root: Root, TLS: new(caddytls.Config)} - ctx.saveConfig(key, cfg) - return cfg -} - -// shortCaddyfileLoader loads a Caddyfile if positional arguments are -// detected, or, in other words, if un-named arguments are provided to -// the program. A "short Caddyfile" is one in which each argument -// is a line of the Caddyfile. The default host and port are prepended -// according to the Host and Port values. -func shortCaddyfileLoader(serverType string) (caddy.Input, error) { - if flag.NArg() > 0 && serverType == "http" { - confBody := fmt.Sprintf("%s:%s\n%s", Host, Port, strings.Join(flag.Args(), "\n")) - return caddy.CaddyfileInput{ - Contents: []byte(confBody), - Filepath: "args", - ServerTypeName: serverType, - }, nil - } - return nil, nil -} - -// groupSiteConfigsByListenAddr groups site configs by their listen -// (bind) address, so sites that use the same listener can be served -// on the same server instance. The return value maps the listen -// address (what you pass into net.Listen) to the list of site configs. -// This function does NOT vet the configs to ensure they are compatible. -func groupSiteConfigsByListenAddr(configs []*SiteConfig) (map[string][]*SiteConfig, error) { - groups := make(map[string][]*SiteConfig) - - for _, conf := range configs { - // We would add a special case here so that localhost addresses - // bind to 127.0.0.1 if conf.ListenHost is not already set, which - // would prevent outsiders from even connecting; but that was problematic: - // https://caddy.community/t/wildcard-virtual-domains-with-wildcard-roots/221/5?u=matt - - if conf.Addr.Port == "" { - conf.Addr.Port = Port - } - addr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(conf.ListenHost, conf.Addr.Port)) - if err != nil { - return nil, err - } - addrstr := addr.String() - groups[addrstr] = append(groups[addrstr], conf) - } - - return groups, 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. -type Address struct { - Original, Scheme, Host, Port, Path string -} - -// String returns a human-friendly print of the address. -func (a Address) String() string { - if a.Host == "" && a.Port == "" { - return "" - } - scheme := a.Scheme - if scheme == "" { - if a.Port == HTTPSPort { - scheme = "https" - } else { - scheme = "http" - } - } - s := scheme - if s != "" { - s += "://" - } - s += a.Host - if a.Port != "" && - ((scheme == "https" && a.Port != DefaultHTTPSPort) || - (scheme == "http" && a.Port != DefaultHTTPPort)) { - s += ":" + a.Port - } - if a.Path != "" { - s += a.Path - } - return s -} - -// VHost returns a sensible concatenation of Host:Port/Path from a. -// It's basically the a.Original but without the scheme. -func (a Address) VHost() string { - if idx := strings.Index(a.Original, "://"); idx > -1 { - return a.Original[idx+3:] - } - return a.Original -} - -// standardizeAddress parses an address string into a structured format with separate -// scheme, host, port, and path portions, as well as the original input string. -func standardizeAddress(str string) (Address, error) { - input := str - - // Split input into components (prepend with // to assert host by default) - if !strings.Contains(str, "//") && !strings.HasPrefix(str, "/") { - str = "//" + str - } - u, err := url.Parse(str) - if err != nil { - return Address{}, err - } - - // separate host and port - host, port, err := net.SplitHostPort(u.Host) - if err != nil { - host, port, err = net.SplitHostPort(u.Host + ":") - if err != nil { - host = u.Host - } - } - - // see if we can set port based off scheme - if port == "" { - if u.Scheme == "http" { - port = HTTPPort - } else if u.Scheme == "https" { - port = HTTPSPort - } - } - - // repeated or conflicting scheme is confusing, so error - if u.Scheme != "" && (port == "http" || port == "https") { - return Address{}, fmt.Errorf("[%s] scheme specified twice in address", input) - } - - // error if scheme and port combination violate convention - if (u.Scheme == "http" && port == HTTPSPort) || (u.Scheme == "https" && port == HTTPPort) { - return Address{}, fmt.Errorf("[%s] scheme and port violate convention", input) - } - - // standardize http and https ports to their respective port numbers - if port == "http" { - u.Scheme = "http" - port = HTTPPort - } else if port == "https" { - u.Scheme = "https" - port = HTTPSPort - } - - return Address{Original: input, Scheme: u.Scheme, Host: host, Port: port, Path: u.Path}, err -} - -// RegisterDevDirective splices name into the list of directives -// immediately before another directive. This function is ONLY -// for plugin development purposes! NEVER use it for a plugin -// that you are not currently building. If before is empty, -// the directive will be appended to the end of the list. -// -// It is imperative that directives execute in the proper -// order, and hard-coding the list of directives guarantees -// a correct, absolute order every time. This function is -// convenient when developing a plugin, but it does not -// guarantee absolute ordering. Multiple plugins registering -// directives with this function will lead to non- -// deterministic builds and buggy software. -// -// Directive names must be lower-cased and unique. Any errors -// here are fatal, and even successful calls print a message -// to stdout as a reminder to use it only in development. -func RegisterDevDirective(name, before string) { - if name == "" { - fmt.Println("[FATAL] Cannot register empty directive name") - os.Exit(1) - } - if strings.ToLower(name) != name { - fmt.Printf("[FATAL] %s: directive name must be lowercase\n", name) - os.Exit(1) - } - for _, dir := range directives { - if dir == name { - fmt.Printf("[FATAL] %s: directive name already exists\n", name) - os.Exit(1) - } - } - if before == "" { - directives = append(directives, name) - } else { - var found bool - for i, dir := range directives { - if dir == before { - directives = append(directives[:i], append([]string{name}, directives[i:]...)...) - found = true - break - } - } - if !found { - fmt.Printf("[FATAL] %s: directive not found\n", before) - os.Exit(1) - } - } - msg := fmt.Sprintf("Registered directive '%s' ", name) - if before == "" { - msg += "at end of list" - } else { - msg += fmt.Sprintf("before '%s'", before) - } - fmt.Printf("[DEV NOTICE] %s\n", msg) -} - -// directives is the list of all directives known to exist for the -// http server type, including non-standard (3rd-party) directives. -// The ordering of this list is important. -var directives = []string{ - // primitive actions that set up the fundamental vitals of each config - "root", - "index", - "bind", - "limits", - "timeouts", - "tls", - - // services/utilities, or other directives that don't necessarily inject handlers - "startup", - "shutdown", - "request_id", - "realip", // github.com/captncraig/caddy-realip - "git", // github.com/abiosoft/caddy-git - - // directives that add listener middleware to the stack - "proxyprotocol", // github.com/mastercactapus/caddy-proxyprotocol - - // directives that add middleware to the stack - "locale", // github.com/simia-tech/caddy-locale - "log", - "cache", // github.com/nicolasazrak/caddy-cache - "rewrite", - "ext", - "gzip", - "header", - "errors", - "authz", // github.com/casbin/caddy-authz - "filter", // github.com/echocat/caddy-filter - "minify", // github.com/hacdias/caddy-minify - "ipfilter", // github.com/pyed/ipfilter - "ratelimit", // github.com/xuqingfeng/caddy-rate-limit - "search", // github.com/pedronasser/caddy-search - "expires", // github.com/epicagency/caddy-expires - "basicauth", - "redir", - "status", - "cors", // github.com/captncraig/cors/caddy - "nobots", // github.com/Xumeiquer/nobots - "mime", - "login", // github.com/tarent/loginsrv/caddy - "reauth", // github.com/freman/caddy-reauth - "jwt", // github.com/BTBurke/caddy-jwt - "jsonp", // github.com/pschlump/caddy-jsonp - "upload", // blitznote.com/src/caddy.upload - "multipass", // github.com/namsral/multipass/caddy - "internal", - "pprof", - "expvar", - "push", - "datadog", // github.com/payintech/caddy-datadog - "prometheus", // github.com/miekg/caddy-prometheus - "proxy", - "fastcgi", - "cgi", // github.com/jung-kurt/caddy-cgi - "websocket", - "filemanager", // github.com/hacdias/filemanager/caddy/filemanager - "webdav", // github.com/hacdias/caddy-webdav - "markdown", - "templates", - "browse", - "jekyll", // github.com/hacdias/filemanager/caddy/jekyll - "hugo", // github.com/hacdias/filemanager/caddy/hugo - "mailout", // github.com/SchumacherFM/mailout - "awses", // github.com/miquella/caddy-awses - "awslambda", // github.com/coopernurse/caddy-awslambda - "grpc", // github.com/pieterlouw/caddy-grpc - "gopkg", // github.com/zikes/gopkg - "restic", // github.com/restic/caddy -} - -const ( - // DefaultHost is the default host. - DefaultHost = "" - // DefaultPort is the default port. - DefaultPort = "2015" - // DefaultRoot is the default root folder. - DefaultRoot = "." - // DefaultHTTPPort is the default port for HTTP. - DefaultHTTPPort = "80" - // DefaultHTTPSPort is the default port for HTTPS. - DefaultHTTPSPort = "443" -) - -// These "soft defaults" are configurable by -// command line flags, etc. -var ( - // Root is the site root - Root = DefaultRoot - - // Host is the site host - Host = DefaultHost - - // Port is the site port - Port = DefaultPort - - // GracefulTimeout is the maximum duration of a graceful shutdown. - GracefulTimeout time.Duration - - // HTTP2 indicates whether HTTP2 is enabled or not. - HTTP2 bool - - // QUIC indicates whether QUIC is enabled or not. - QUIC bool - - // HTTPPort is the port to use for HTTP. - HTTPPort = DefaultHTTPPort - - // HTTPSPort is the port to use for HTTPS. - HTTPSPort = DefaultHTTPSPort -) diff --git a/caddyhttp/httpserver/plugin_test.go b/caddyhttp/httpserver/plugin_test.go deleted file mode 100644 index 34781388414..00000000000 --- a/caddyhttp/httpserver/plugin_test.go +++ /dev/null @@ -1,235 +0,0 @@ -package httpserver - -import ( - "strings" - "testing" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyfile" -) - -func TestStandardizeAddress(t *testing.T) { - for i, test := range []struct { - input string - scheme, host, port, path string - shouldErr bool - }{ - {`localhost`, "", "localhost", "", "", false}, - {`localhost:1234`, "", "localhost", "1234", "", false}, - {`localhost:`, "", "localhost", "", "", false}, - {`0.0.0.0`, "", "0.0.0.0", "", "", false}, - {`127.0.0.1:1234`, "", "127.0.0.1", "1234", "", false}, - {`:1234`, "", "", "1234", "", false}, - {`[::1]`, "", "::1", "", "", false}, - {`[::1]:1234`, "", "::1", "1234", "", false}, - {`:`, "", "", "", "", false}, - {`localhost:http`, "http", "localhost", "80", "", false}, - {`localhost:https`, "https", "localhost", "443", "", false}, - {`:http`, "http", "", "80", "", false}, - {`:https`, "https", "", "443", "", false}, - {`http://localhost:https`, "", "", "", "", true}, // conflict - {`http://localhost:http`, "", "", "", "", true}, // repeated scheme - {`http://localhost:443`, "", "", "", "", true}, // not conventional - {`https://localhost:80`, "", "", "", "", true}, // not conventional - {`http://localhost`, "http", "localhost", "80", "", false}, - {`https://localhost`, "https", "localhost", "443", "", false}, - {`http://127.0.0.1`, "http", "127.0.0.1", "80", "", false}, - {`https://127.0.0.1`, "https", "127.0.0.1", "443", "", false}, - {`http://[::1]`, "http", "::1", "80", "", 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", "", "", true}, - {`localhost::`, "", "localhost::", "", "", true}, - {`#$%@`, "", "", "", "", true}, - {`host/path`, "", "host", "", "/path", false}, - {`http://host/`, "http", "host", "80", "/", false}, - {`//asdf`, "", "asdf", "", "", false}, - {`:1234/asdf`, "", "", "1234", "/asdf", false}, - {`http://host/path`, "http", "host", "80", "/path", false}, - {`https://host:443/path/foo`, "https", "host", "443", "/path/foo", false}, - {`host:80/path`, "", "host", "80", "/path", false}, - {`host:https/path`, "https", "host", "443", "/path", false}, - {`/path`, "", "", "", "/path", false}, - } { - actual, err := standardizeAddress(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", i, test.input) - } - - 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 TestAddressVHost(t *testing.T) { - for i, test := range []struct { - addr Address - expected string - }{ - {Address{Original: "host:1234"}, "host:1234"}, - {Address{Original: "host:1234/foo"}, "host:1234/foo"}, - {Address{Original: "host/foo"}, "host/foo"}, - {Address{Original: "http://host/foo"}, "host/foo"}, - {Address{Original: "https://host/foo"}, "host/foo"}, - } { - actual := test.addr.VHost() - if actual != test.expected { - t.Errorf("Test %d: expected '%s' but got '%s'", i, test.expected, actual) - } - } -} - -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 TestInspectServerBlocksWithCustomDefaultPort(t *testing.T) { - Port = "9999" - filename := "Testfile" - ctx := newContext().(*httpContext) - input := strings.NewReader(`localhost`) - sblocks, err := caddyfile.Parse(filename, input, nil) - if err != nil { - t.Fatalf("Expected no error setting up test, got: %v", err) - } - _, err = ctx.InspectServerBlocks(filename, sblocks) - if err != nil { - t.Fatalf("Didn't expect an error, but got: %v", err) - } - addr := ctx.keysToSiteConfigs["localhost"].Addr - if addr.Port != Port { - t.Errorf("Expected the port on the address to be set, but got: %#v", addr) - } -} - -func TestInspectServerBlocksCaseInsensitiveKey(t *testing.T) { - filename := "Testfile" - ctx := newContext().(*httpContext) - input := strings.NewReader("localhost {\n}\nLOCALHOST {\n}") - sblocks, err := caddyfile.Parse(filename, input, nil) - if err != nil { - t.Fatalf("Expected no error setting up test, got: %v", err) - } - _, err = ctx.InspectServerBlocks(filename, sblocks) - if err == nil { - t.Error("Expected an error because keys on this server type are case-insensitive (so these are duplicated), but didn't get an error") - } -} - -func TestGetConfig(t *testing.T) { - // case insensitivity for key - con := caddy.NewTestController("http", "") - con.Key = "foo" - cfg := GetConfig(con) - con.Key = "FOO" - cfg2 := GetConfig(con) - if cfg != cfg2 { - t.Errorf("Expected same config using same key with different case; got %p and %p", cfg, cfg2) - } - - // make sure different key returns different config - con.Key = "foobar" - cfg3 := GetConfig(con) - if cfg == cfg3 { - t.Errorf("Expected different configs using when key is different; got %p and %p", cfg, cfg3) - } -} - -func TestDirectivesList(t *testing.T) { - for i, dir1 := range directives { - if dir1 == "" { - t.Errorf("directives[%d]: empty directive name", i) - continue - } - if got, want := dir1, strings.ToLower(dir1); got != want { - t.Errorf("directives[%d]: %s should be lower-cased", i, dir1) - continue - } - for j := i + 1; j < len(directives); j++ { - dir2 := directives[j] - if dir1 == dir2 { - t.Errorf("directives[%d] (%s) is a duplicate of directives[%d] (%s)", - j, dir2, i, dir1) - } - } - } -} - -func TestContextSaveConfig(t *testing.T) { - ctx := newContext().(*httpContext) - ctx.saveConfig("foo", new(SiteConfig)) - if _, ok := ctx.keysToSiteConfigs["foo"]; !ok { - t.Error("Expected config to be saved, but it wasn't") - } - if got, want := len(ctx.siteConfigs), 1; got != want { - t.Errorf("Expected len(siteConfigs) == %d, but was %d", want, got) - } - ctx.saveConfig("Foobar", new(SiteConfig)) - if _, ok := ctx.keysToSiteConfigs["foobar"]; ok { - t.Error("Did not expect to get config with case-insensitive key, but did") - } - if got, want := len(ctx.siteConfigs), 2; got != want { - t.Errorf("Expected len(siteConfigs) == %d, but was %d", want, got) - } -} - -// Test to make sure we are correctly hiding the Caddyfile -func TestHideCaddyfile(t *testing.T) { - ctx := newContext().(*httpContext) - ctx.saveConfig("test", &SiteConfig{ - Root: Root, - originCaddyfile: "Testfile", - }) - err := hideCaddyfile(ctx) - if err != nil { - t.Fatalf("Failed to hide Caddyfile, got: %v", err) - return - } - if len(ctx.siteConfigs[0].HiddenFiles) == 0 { - t.Fatal("Failed to add Caddyfile to HiddenFiles.") - return - } - for _, file := range ctx.siteConfigs[0].HiddenFiles { - if file == "/Testfile" { - return - } - } - t.Fatal("Caddyfile missing from HiddenFiles") -} diff --git a/caddyhttp/httpserver/recorder.go b/caddyhttp/httpserver/recorder.go deleted file mode 100644 index da89056cf4f..00000000000 --- a/caddyhttp/httpserver/recorder.go +++ /dev/null @@ -1,70 +0,0 @@ -package httpserver - -import ( - "net/http" - "time" -) - -// ResponseRecorder is a type of http.ResponseWriter that captures -// the status code written to it and also the size of the body -// written in the response. A status code does not have -// to be written, however, in which case 200 must be assumed. -// It is best to have the constructor initialize this type -// with that default status code. -// -// Setting the Replacer field allows middlewares to type-assert -// the http.ResponseWriter to ResponseRecorder and set their own -// placeholder values for logging utilities to use. -// -// Beware when accessing the Replacer value; it may be nil! -type ResponseRecorder struct { - *ResponseWriterWrapper - Replacer Replacer - status int - size int - start time.Time -} - -// NewResponseRecorder makes and returns a new responseRecorder, -// which captures the HTTP Status code from the ResponseWriter -// and also the length of the response body written through it. -// Because a status is not set unless WriteHeader is called -// explicitly, this constructor initializes with a status code -// of 200 to cover the default case. -func NewResponseRecorder(w http.ResponseWriter) *ResponseRecorder { - return &ResponseRecorder{ - ResponseWriterWrapper: &ResponseWriterWrapper{ResponseWriter: w}, - status: http.StatusOK, - start: time.Now(), - } -} - -// WriteHeader records the status code and calls the -// underlying ResponseWriter's WriteHeader method. -func (r *ResponseRecorder) WriteHeader(status int) { - r.status = status - r.ResponseWriterWrapper.WriteHeader(status) -} - -// Write is a wrapper that records the size of the body -// that gets written. -func (r *ResponseRecorder) Write(buf []byte) (int, error) { - n, err := r.ResponseWriterWrapper.Write(buf) - if err == nil { - r.size += n - } - return n, err -} - -// Size is a Getter to size property -func (r *ResponseRecorder) Size() int { - return r.size -} - -// Status is a Getter to status property -func (r *ResponseRecorder) Status() int { - return r.status -} - -// Interface guards -var _ HTTPInterfaces = (*ResponseRecorder)(nil) diff --git a/caddyhttp/httpserver/recorder_test.go b/caddyhttp/httpserver/recorder_test.go deleted file mode 100644 index 0772d669f4d..00000000000 --- a/caddyhttp/httpserver/recorder_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package httpserver - -import ( - "net/http" - "net/http/httptest" - "testing" -) - -func TestNewResponseRecorder(t *testing.T) { - w := httptest.NewRecorder() - recordRequest := NewResponseRecorder(w) - if !(recordRequest.ResponseWriter == w) { - t.Fatalf("Expected Response writer in the Recording to be same as the one sent\n") - } - if recordRequest.status != http.StatusOK { - t.Fatalf("Expected recorded status to be http.StatusOK (%d) , but found %d\n ", http.StatusOK, recordRequest.status) - } -} -func TestWriteHeader(t *testing.T) { - w := httptest.NewRecorder() - recordRequest := NewResponseRecorder(w) - recordRequest.WriteHeader(401) - if w.Code != 401 || recordRequest.status != 401 { - t.Fatalf("Expected Response status to be set to 401, but found %d\n", recordRequest.status) - } -} - -func TestWrite(t *testing.T) { - w := httptest.NewRecorder() - responseTestString := "test" - recordRequest := NewResponseRecorder(w) - buf := []byte(responseTestString) - recordRequest.Write(buf) - if recordRequest.size != len(buf) { - t.Fatalf("Expected the bytes written counter to be %d, but instead found %d\n", len(buf), recordRequest.size) - } - if w.Body.String() != responseTestString { - t.Fatalf("Expected Response Body to be %s , but found %s\n", responseTestString, w.Body.String()) - } -} diff --git a/caddyhttp/httpserver/replacer.go b/caddyhttp/httpserver/replacer.go deleted file mode 100644 index 24757a2756d..00000000000 --- a/caddyhttp/httpserver/replacer.go +++ /dev/null @@ -1,367 +0,0 @@ -package httpserver - -import ( - "bytes" - "io" - "io/ioutil" - "net" - "net/http" - "net/http/httputil" - "net/url" - "os" - "path" - "strconv" - "strings" - "time" - - "github.com/mholt/caddy" -) - -// requestReplacer is a strings.Replacer which is used to -// encode literal \r and \n characters and keep everything -// on one line -var requestReplacer = strings.NewReplacer( - "\r", "\\r", - "\n", "\\n", -) - -var now = time.Now - -// Replacer is a type which can replace placeholder -// substrings in a string with actual values from a -// http.Request and ResponseRecorder. Always use -// NewReplacer to get one of these. Any placeholders -// made with Set() should overwrite existing values if -// the key is already used. -type Replacer interface { - Replace(string) string - Set(key, value string) -} - -// replacer implements Replacer. customReplacements -// is used to store custom replacements created with -// Set() until the time of replacement, at which point -// they will be used to overwrite other replacements -// if there is a name conflict. -type replacer struct { - customReplacements map[string]string - emptyValue string - responseRecorder *ResponseRecorder - request *http.Request - requestBody *limitWriter -} - -type limitWriter struct { - w bytes.Buffer - remain int -} - -func newLimitWriter(max int) *limitWriter { - return &limitWriter{ - w: bytes.Buffer{}, - remain: max, - } -} - -func (lw *limitWriter) Write(p []byte) (int, error) { - // skip if we are full - if lw.remain <= 0 { - return len(p), nil - } - if n := len(p); n > lw.remain { - p = p[:lw.remain] - } - n, err := lw.w.Write(p) - lw.remain -= n - return n, err -} - -func (lw *limitWriter) String() string { - return lw.w.String() -} - -// NewReplacer makes a new replacer based on r and rr which -// are used for request and response placeholders, respectively. -// Request placeholders are created immediately, whereas -// response placeholders are not created until Replace() -// is invoked. rr may be nil if it is not available. -// emptyValue should be the string that is used in place -// of empty string (can still be empty string). -func NewReplacer(r *http.Request, rr *ResponseRecorder, emptyValue string) Replacer { - rb := newLimitWriter(MaxLogBodySize) - if r.Body != nil { - r.Body = struct { - io.Reader - io.Closer - }{io.TeeReader(r.Body, rb), io.Closer(r.Body)} - } - return &replacer{ - request: r, - requestBody: rb, - responseRecorder: rr, - customReplacements: make(map[string]string), - emptyValue: emptyValue, - } -} - -func canLogRequest(r *http.Request) bool { - if r.Method == "POST" || r.Method == "PUT" { - for _, cType := range r.Header[headerContentType] { - // the cType could have charset and other info - if strings.Contains(cType, contentTypeJSON) || strings.Contains(cType, contentTypeXML) { - return true - } - } - } - return false -} - -// Replace performs a replacement of values on s and returns -// the string with the replaced values. -func (r *replacer) Replace(s string) string { - // Do not attempt replacements if no placeholder is found. - if !strings.ContainsAny(s, "{}") { - return s - } - - result := "" - for { - idxStart := strings.Index(s, "{") - if idxStart == -1 { - // no placeholder anymore - break - } - idxEnd := strings.Index(s[idxStart:], "}") - if idxEnd == -1 { - // unpaired placeholder - break - } - idxEnd += idxStart - - // get a replacement - placeholder := s[idxStart : idxEnd+1] - replacement := r.getSubstitution(placeholder) - - // append prefix + replacement - result += s[:idxStart] + replacement - - // strip out scanned parts - s = s[idxEnd+1:] - } - - // append unscanned parts - return result + s -} - -func roundDuration(d time.Duration) time.Duration { - if d >= time.Millisecond { - return round(d, time.Millisecond) - } else if d >= time.Microsecond { - return round(d, time.Microsecond) - } - - return d -} - -// round rounds d to the nearest r -func round(d, r time.Duration) time.Duration { - if r <= 0 { - return d - } - neg := d < 0 - if neg { - d = -d - } - if m := d % r; m+m < r { - d = d - m - } else { - d = d + r - m - } - if neg { - return -d - } - return d -} - -// getSubstitution retrieves value from corresponding key -func (r *replacer) getSubstitution(key string) string { - // search custom replacements first - if value, ok := r.customReplacements[key]; ok { - return value - } - - // search request headers then - if key[1] == '>' { - want := key[2 : len(key)-1] - for key, values := range r.request.Header { - // Header placeholders (case-insensitive) - if strings.EqualFold(key, want) { - return strings.Join(values, ",") - } - } - } - // next check for cookies - if key[1] == '~' { - name := key[2 : len(key)-1] - if cookie, err := r.request.Cookie(name); err == nil { - return cookie.Value - } - } - // next check for query argument - if key[1] == '?' { - query := r.request.URL.Query() - name := key[2 : len(key)-1] - return query.Get(name) - } - - // search default replacements in the end - switch key { - case "{method}": - return r.request.Method - case "{scheme}": - if r.request.TLS != nil { - return "https" - } - return "http" - case "{hostname}": - name, err := os.Hostname() - if err != nil { - return r.emptyValue - } - return name - case "{host}": - return r.request.Host - case "{hostonly}": - host, _, err := net.SplitHostPort(r.request.Host) - if err != nil { - return r.request.Host - } - return host - case "{path}": - u, _ := r.request.Context().Value(OriginalURLCtxKey).(url.URL) - return u.Path - case "{path_escaped}": - u, _ := r.request.Context().Value(OriginalURLCtxKey).(url.URL) - return url.QueryEscape(u.Path) - case "{request_id}": - reqid, _ := r.request.Context().Value(RequestIDCtxKey).(string) - return reqid - case "{rewrite_path}": - return r.request.URL.Path - case "{rewrite_path_escaped}": - return url.QueryEscape(r.request.URL.Path) - case "{query}": - u, _ := r.request.Context().Value(OriginalURLCtxKey).(url.URL) - return u.RawQuery - case "{query_escaped}": - u, _ := r.request.Context().Value(OriginalURLCtxKey).(url.URL) - return url.QueryEscape(u.RawQuery) - case "{fragment}": - u, _ := r.request.Context().Value(OriginalURLCtxKey).(url.URL) - return u.Fragment - case "{proto}": - return r.request.Proto - case "{remote}": - host, _, err := net.SplitHostPort(r.request.RemoteAddr) - if err != nil { - return r.request.RemoteAddr - } - return host - case "{port}": - _, port, err := net.SplitHostPort(r.request.RemoteAddr) - if err != nil { - return r.emptyValue - } - return port - case "{uri}": - u, _ := r.request.Context().Value(OriginalURLCtxKey).(url.URL) - return u.RequestURI() - case "{uri_escaped}": - u, _ := r.request.Context().Value(OriginalURLCtxKey).(url.URL) - return url.QueryEscape(u.RequestURI()) - case "{rewrite_uri}": - return r.request.URL.RequestURI() - case "{rewrite_uri_escaped}": - return url.QueryEscape(r.request.URL.RequestURI()) - case "{when}": - return now().Format(timeFormat) - case "{when_iso}": - return now().UTC().Format(timeFormatISOUTC) - case "{when_unix}": - return strconv.FormatInt(now().Unix(), 10) - case "{file}": - _, file := path.Split(r.request.URL.Path) - return file - case "{dir}": - dir, _ := path.Split(r.request.URL.Path) - return dir - case "{request}": - dump, err := httputil.DumpRequest(r.request, false) - if err != nil { - return r.emptyValue - } - return requestReplacer.Replace(string(dump)) - case "{request_body}": - if !canLogRequest(r.request) { - return r.emptyValue - } - _, err := ioutil.ReadAll(r.request.Body) - if err != nil { - if err == ErrMaxBytesExceeded { - return r.emptyValue - } - } - return requestReplacer.Replace(r.requestBody.String()) - case "{mitm}": - if val, ok := r.request.Context().Value(caddy.CtxKey("mitm")).(bool); ok { - if val { - return "likely" - } - return "unlikely" - } - return "unknown" - case "{status}": - if r.responseRecorder == nil { - return r.emptyValue - } - return strconv.Itoa(r.responseRecorder.status) - case "{size}": - if r.responseRecorder == nil { - return r.emptyValue - } - return strconv.Itoa(r.responseRecorder.size) - case "{latency}": - if r.responseRecorder == nil { - return r.emptyValue - } - return roundDuration(time.Since(r.responseRecorder.start)).String() - case "{latency_ms}": - if r.responseRecorder == nil { - return r.emptyValue - } - elapsedDuration := time.Since(r.responseRecorder.start) - return strconv.FormatInt(convertToMilliseconds(elapsedDuration), 10) - } - - return r.emptyValue -} - -//convertToMilliseconds returns the number of milliseconds in the given duration -func convertToMilliseconds(d time.Duration) int64 { - return d.Nanoseconds() / 1e6 -} - -// Set sets key to value in the r.customReplacements map. -func (r *replacer) Set(key, value string) { - r.customReplacements["{"+key+"}"] = value -} - -const ( - timeFormat = "02/Jan/2006:15:04:05 -0700" - timeFormatISOUTC = "2006-01-02T15:04:05Z" // ISO 8601 with timezone to be assumed as UTC - headerContentType = "Content-Type" - contentTypeJSON = "application/json" - contentTypeXML = "application/xml" - // MaxLogBodySize limits the size of logged request's body - MaxLogBodySize = 100 * 1024 -) diff --git a/caddyhttp/httpserver/replacer_test.go b/caddyhttp/httpserver/replacer_test.go deleted file mode 100644 index 4b6a2e44239..00000000000 --- a/caddyhttp/httpserver/replacer_test.go +++ /dev/null @@ -1,228 +0,0 @@ -package httpserver - -import ( - "context" - "net/http" - "net/http/httptest" - "os" - "strings" - "testing" - "time" -) - -func TestNewReplacer(t *testing.T) { - w := httptest.NewRecorder() - recordRequest := NewResponseRecorder(w) - reader := strings.NewReader(`{"username": "dennis"}`) - - request, err := http.NewRequest("POST", "http://localhost", reader) - if err != nil { - t.Fatal("Request Formation Failed\n") - } - rep := NewReplacer(request, recordRequest, "") - - switch v := rep.(type) { - case *replacer: - if v.getSubstitution("{host}") != "localhost" { - t.Error("Expected host to be localhost") - } - if v.getSubstitution("{method}") != "POST" { - t.Error("Expected request method to be POST") - } - default: - t.Fatalf("Expected *replacer underlying Replacer type, got: %#v", rep) - } -} - -func TestReplace(t *testing.T) { - w := httptest.NewRecorder() - recordRequest := NewResponseRecorder(w) - reader := strings.NewReader(`{"username": "dennis"}`) - - request, err := http.NewRequest("POST", "http://localhost/?foo=bar", reader) - if err != nil { - t.Fatalf("Failed to make request: %v", err) - } - ctx := context.WithValue(request.Context(), OriginalURLCtxKey, *request.URL) - request = request.WithContext(ctx) - - request.Header.Set("Custom", "foobarbaz") - request.Header.Set("ShorterVal", "1") - repl := NewReplacer(request, recordRequest, "-") - // add some headers after creating replacer - request.Header.Set("CustomAdd", "caddy") - request.Header.Set("Cookie", "foo=bar; taste=delicious") - - hostname, err := os.Hostname() - if err != nil { - t.Fatalf("Failed to determine hostname: %v", err) - } - - old := now - now = func() time.Time { - return time.Date(2006, 1, 2, 15, 4, 5, 02, time.FixedZone("hardcoded", -7)) - } - defer func() { - now = old - }() - testCases := []struct { - template string - expect string - }{ - {"This hostname is {hostname}", "This hostname is " + hostname}, - {"This host is {host}.", "This host is localhost."}, - {"This request method is {method}.", "This request method is POST."}, - {"The response status is {status}.", "The response status is 200."}, - {"{when}", "02/Jan/2006:15:04:05 +0000"}, - {"{when_iso}", "2006-01-02T15:04:12Z"}, - {"{when_unix}", "1136214252"}, - {"The Custom header is {>Custom}.", "The Custom header is foobarbaz."}, - {"The CustomAdd header is {>CustomAdd}.", "The CustomAdd header is caddy."}, - {"The request is {request}.", "The request is POST /?foo=bar HTTP/1.1\\r\\nHost: localhost\\r\\n" + - "Cookie: foo=bar; taste=delicious\\r\\nCustom: foobarbaz\\r\\nCustomadd: caddy\\r\\n" + - "Shorterval: 1\\r\\n\\r\\n."}, - {"The cUsToM header is {>cUsToM}...", "The cUsToM header is foobarbaz..."}, - {"The Non-Existent header is {>Non-Existent}.", "The Non-Existent header is -."}, - {"Bad {host placeholder...", "Bad {host placeholder..."}, - {"Bad {>Custom placeholder", "Bad {>Custom placeholder"}, - {"Bad {>Custom placeholder {>ShorterVal}", "Bad -"}, - {"Bad {}", "Bad -"}, - {"Cookies are {~taste}", "Cookies are delicious"}, - {"Missing cookie is {~missing}", "Missing cookie is -"}, - {"Query string is {query}", "Query string is foo=bar"}, - {"Query string value for foo is {?foo}", "Query string value for foo is bar"}, - {"Missing query string argument is {?missing}", "Missing query string argument is "}, - } - - for _, c := range testCases { - if expected, actual := c.expect, repl.Replace(c.template); expected != actual { - t.Errorf("for template '%s', expected '%s', got '%s'", c.template, expected, actual) - } - } - - complexCases := []struct { - template string - replacements map[string]string - expect string - }{ - { - "/a{1}/{2}", - map[string]string{ - "{1}": "12", - "{2}": "", - }, - "/a12/"}, - } - - for _, c := range complexCases { - repl := &replacer{ - customReplacements: c.replacements, - } - if expected, actual := c.expect, repl.Replace(c.template); expected != actual { - t.Errorf("for template '%s', expected '%s', got '%s'", c.template, expected, actual) - } - } -} - -func TestSet(t *testing.T) { - w := httptest.NewRecorder() - recordRequest := NewResponseRecorder(w) - reader := strings.NewReader(`{"username": "dennis"}`) - - request, err := http.NewRequest("POST", "http://localhost", reader) - if err != nil { - t.Fatalf("Request Formation Failed: %s\n", err.Error()) - } - repl := NewReplacer(request, recordRequest, "") - - repl.Set("host", "getcaddy.com") - repl.Set("method", "GET") - repl.Set("status", "201") - repl.Set("variable", "value") - - if repl.Replace("This host is {host}") != "This host is getcaddy.com" { - t.Error("Expected host replacement failed") - } - if repl.Replace("This request method is {method}") != "This request method is GET" { - t.Error("Expected method replacement failed") - } - if repl.Replace("The response status is {status}") != "The response status is 201" { - t.Error("Expected status replacement failed") - } - if repl.Replace("The value of variable is {variable}") != "The value of variable is value" { - t.Error("Expected variable replacement failed") - } -} - -// Test function to test that various placeholders hold correct values after a rewrite -// has been performed. The NewRequest actually contains the rewritten value. -func TestPathRewrite(t *testing.T) { - w := httptest.NewRecorder() - recordRequest := NewResponseRecorder(w) - reader := strings.NewReader(`{"username": "dennis"}`) - - request, err := http.NewRequest("POST", "http://getcaddy.com/index.php?key=value", reader) - if err != nil { - t.Fatalf("Request Formation Failed: %s\n", err.Error()) - } - urlCopy := *request.URL - urlCopy.Path = "a/custom/path.php" - ctx := context.WithValue(request.Context(), OriginalURLCtxKey, urlCopy) - request = request.WithContext(ctx) - - repl := NewReplacer(request, recordRequest, "") - - if got, want := repl.Replace("This path is '{path}'"), "This path is 'a/custom/path.php'"; got != want { - t.Errorf("{path} replacement failed; got '%s', want '%s'", got, want) - } - - if got, want := repl.Replace("This path is {rewrite_path}"), "This path is /index.php"; got != want { - t.Errorf("{rewrite_path} replacement failed; got '%s', want '%s'", got, want) - } - if got, want := repl.Replace("This path is '{uri}'"), "This path is 'a/custom/path.php?key=value'"; got != want { - t.Errorf("{uri} replacement failed; got '%s', want '%s'", got, want) - } - - if got, want := repl.Replace("This path is {rewrite_uri}"), "This path is /index.php?key=value"; got != want { - t.Errorf("{rewrite_uri} replacement failed; got '%s', want '%s'", got, want) - } - -} - -func TestRound(t *testing.T) { - var tests = map[time.Duration]time.Duration{ - // 599.935µs -> 560µs - 559935 * time.Nanosecond: 560 * time.Microsecond, - // 1.55ms -> 2ms - 1550 * time.Microsecond: 2 * time.Millisecond, - // 1.5555s -> 1.556s - 1555500 * time.Microsecond: 1556 * time.Millisecond, - // 1m2.0035s -> 1m2.004s - 62003500 * time.Microsecond: 62004 * time.Millisecond, - } - - for dur, expected := range tests { - rounded := roundDuration(dur) - if rounded != expected { - t.Errorf("Expected %v, Got %v", expected, rounded) - } - } -} - -func TestMillisecondConverstion(t *testing.T) { - var testCases = map[time.Duration]int64{ - 2 * time.Second: 2000, - 9039492 * time.Nanosecond: 9, - 1000 * time.Microsecond: 1, - 127 * time.Nanosecond: 0, - 0 * time.Millisecond: 0, - 255 * time.Millisecond: 255, - } - - for dur, expected := range testCases { - numMillisecond := convertToMilliseconds(dur) - if numMillisecond != expected { - t.Errorf("Expected %v. Got %v", expected, numMillisecond) - } - } -} diff --git a/caddyhttp/httpserver/responsewriterwrapper.go b/caddyhttp/httpserver/responsewriterwrapper.go deleted file mode 100644 index 350d0e6c979..00000000000 --- a/caddyhttp/httpserver/responsewriterwrapper.go +++ /dev/null @@ -1,65 +0,0 @@ -package httpserver - -import ( - "bufio" - "net" - "net/http" -) - -// ResponseWriterWrapper wrappers underlying ResponseWriter -// and inherits its Hijacker/Pusher/CloseNotifier/Flusher as well. -type ResponseWriterWrapper struct { - http.ResponseWriter -} - -// Hijack implements http.Hijacker. It simply wraps the underlying -// ResponseWriter's Hijack method if there is one, or returns an error. -func (rww *ResponseWriterWrapper) Hijack() (net.Conn, *bufio.ReadWriter, error) { - if hj, ok := rww.ResponseWriter.(http.Hijacker); ok { - return hj.Hijack() - } - return nil, nil, NonHijackerError{Underlying: rww.ResponseWriter} -} - -// Flush implements http.Flusher. It simply wraps the underlying -// ResponseWriter's Flush method if there is one, or panics. -func (rww *ResponseWriterWrapper) Flush() { - if f, ok := rww.ResponseWriter.(http.Flusher); ok { - f.Flush() - } else { - panic(NonFlusherError{Underlying: rww.ResponseWriter}) - } -} - -// CloseNotify implements http.CloseNotifier. -// It just inherits the underlying ResponseWriter's CloseNotify method. -// It panics if the underlying ResponseWriter is not a CloseNotifier. -func (rww *ResponseWriterWrapper) CloseNotify() <-chan bool { - if cn, ok := rww.ResponseWriter.(http.CloseNotifier); ok { - return cn.CloseNotify() - } - panic(NonCloseNotifierError{Underlying: rww.ResponseWriter}) -} - -// Push implements http.Pusher. -// It just inherits the underlying ResponseWriter's Push method. -// It panics if the underlying ResponseWriter is not a Pusher. -func (rww *ResponseWriterWrapper) Push(target string, opts *http.PushOptions) error { - if pusher, hasPusher := rww.ResponseWriter.(http.Pusher); hasPusher { - return pusher.Push(target, opts) - } - - return NonPusherError{Underlying: rww.ResponseWriter} -} - -// HTTPInterfaces mix all the interfaces that middleware ResponseWriters need to support. -type HTTPInterfaces interface { - http.ResponseWriter - http.Pusher - http.Flusher - http.CloseNotifier - http.Hijacker -} - -// Interface guards -var _ HTTPInterfaces = (*ResponseWriterWrapper)(nil) diff --git a/caddyhttp/httpserver/roller.go b/caddyhttp/httpserver/roller.go deleted file mode 100644 index 8f0823bee7d..00000000000 --- a/caddyhttp/httpserver/roller.go +++ /dev/null @@ -1,124 +0,0 @@ -package httpserver - -import ( - "errors" - "io" - "path/filepath" - "strconv" - - "gopkg.in/natefinch/lumberjack.v2" -) - -// LogRoller implements a type that provides a rolling logger. -type LogRoller struct { - Filename string - MaxSize int - MaxAge int - MaxBackups int - Compress bool - LocalTime bool -} - -// GetLogWriter returns an io.Writer that writes to a rolling logger. -// This should be called only from the main goroutine (like during -// server setup) because this method is not thread-safe; it is careful -// to create only one log writer per log file, even if the log file -// is shared by different sites or middlewares. This ensures that -// rolling is synchronized, since a process (or multiple processes) -// should not create more than one roller on the same file at the -// same time. See issue #1363. -func (l LogRoller) GetLogWriter() io.Writer { - absPath, err := filepath.Abs(l.Filename) - if err != nil { - absPath = l.Filename // oh well, hopefully they're consistent in how they specify the filename - } - lj, has := lumberjacks[absPath] - if !has { - lj = &lumberjack.Logger{ - Filename: l.Filename, - MaxSize: l.MaxSize, - MaxAge: l.MaxAge, - MaxBackups: l.MaxBackups, - Compress: l.Compress, - LocalTime: l.LocalTime, - } - lumberjacks[absPath] = lj - } - return lj -} - -// IsLogRollerSubdirective is true if the subdirective is for the log roller. -func IsLogRollerSubdirective(subdir string) bool { - return subdir == directiveRotateSize || - subdir == directiveRotateAge || - subdir == directiveRotateKeep || - subdir == directiveRotateCompress -} - -var invalidRollerParameterErr = errors.New("invalid roller parameter") - -// ParseRoller parses roller contents out of c. -func ParseRoller(l *LogRoller, what string, where ...string) error { - if l == nil { - l = DefaultLogRoller() - } - - // rotate_compress doesn't accept any parameters. - // others only accept one parameter - if (what == directiveRotateCompress && len(where) != 0) || - (what != directiveRotateCompress && len(where) != 1) { - return invalidRollerParameterErr - } - - var ( - value int - err error - ) - if what != directiveRotateCompress { - value, err = strconv.Atoi(where[0]) - if err != nil { - return err - } - } - - switch what { - case directiveRotateSize: - l.MaxSize = value - case directiveRotateAge: - l.MaxAge = value - case directiveRotateKeep: - l.MaxBackups = value - case directiveRotateCompress: - l.Compress = true - } - return nil -} - -// DefaultLogRoller will roll logs by default. -func DefaultLogRoller() *LogRoller { - return &LogRoller{ - MaxSize: defaultRotateSize, - MaxAge: defaultRotateAge, - MaxBackups: defaultRotateKeep, - Compress: false, - LocalTime: true, - } -} - -const ( - // defaultRotateSize is 100 MB. - defaultRotateSize = 100 - // defaultRotateAge is 14 days. - defaultRotateAge = 14 - // defaultRotateKeep is 10 files. - defaultRotateKeep = 10 - - directiveRotateSize = "rotate_size" - directiveRotateAge = "rotate_age" - directiveRotateKeep = "rotate_keep" - directiveRotateCompress = "rotate_compress" -) - -// lumberjacks maps log filenames to the logger -// that is being used to keep them rolled/maintained. -var lumberjacks = make(map[string]*lumberjack.Logger) diff --git a/caddyhttp/httpserver/server.go b/caddyhttp/httpserver/server.go deleted file mode 100644 index 6a51aa9c088..00000000000 --- a/caddyhttp/httpserver/server.go +++ /dev/null @@ -1,541 +0,0 @@ -// Package httpserver implements an HTTP server on top of Caddy. -package httpserver - -import ( - "context" - "crypto/tls" - "errors" - "fmt" - "log" - "net" - "net/http" - "net/url" - "os" - "path" - "path/filepath" - "runtime" - "strings" - "sync" - "time" - - "github.com/lucas-clemente/quic-go/h2quic" - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/staticfiles" - "github.com/mholt/caddy/caddytls" -) - -// Server is the HTTP server implementation. -type Server struct { - Server *http.Server - quicServer *h2quic.Server - listener net.Listener - listenerMu sync.Mutex - sites []*SiteConfig - connTimeout time.Duration // max time to wait for a connection before force stop - tlsGovChan chan struct{} // close to stop the TLS maintenance goroutine - vhosts *vhostTrie -} - -// ensure it satisfies the interface -var _ caddy.GracefulServer = new(Server) - -var defaultALPN = []string{"h2", "http/1.1"} - -// makeTLSConfig extracts TLS settings from each site config to -// build a tls.Config usable in Caddy HTTP servers. The returned -// config will be nil if TLS is disabled for these sites. -func makeTLSConfig(group []*SiteConfig) (*tls.Config, error) { - var tlsConfigs []*caddytls.Config - for i := range group { - if HTTP2 && len(group[i].TLS.ALPN) == 0 { - // if no application-level protocol was configured up to now, - // default to HTTP/2, then HTTP/1.1 if necessary - group[i].TLS.ALPN = defaultALPN - } - tlsConfigs = append(tlsConfigs, group[i].TLS) - } - return caddytls.MakeTLSConfig(tlsConfigs) -} - -func getFallbacks(sites []*SiteConfig) []string { - fallbacks := []string{} - for _, sc := range sites { - if sc.FallbackSite { - fallbacks = append(fallbacks, sc.Addr.Host) - } - } - return fallbacks -} - -// NewServer creates a new Server instance that will listen on addr -// and will serve the sites configured in group. -func NewServer(addr string, group []*SiteConfig) (*Server, error) { - s := &Server{ - Server: makeHTTPServerWithTimeouts(addr, group), - vhosts: newVHostTrie(), - sites: group, - connTimeout: GracefulTimeout, - } - s.vhosts.fallbackHosts = append(s.vhosts.fallbackHosts, getFallbacks(group)...) - s.Server = makeHTTPServerWithHeaderLimit(s.Server, group) - s.Server.Handler = s // this is weird, but whatever - - // extract TLS settings from each site config to build - // a tls.Config, which will not be nil if TLS is enabled - tlsConfig, err := makeTLSConfig(group) - if err != nil { - return nil, err - } - s.Server.TLSConfig = tlsConfig - - // if TLS is enabled, make sure we prepare the Server accordingly - if s.Server.TLSConfig != nil { - // enable QUIC if desired (requires HTTP/2) - if HTTP2 && QUIC { - s.quicServer = &h2quic.Server{Server: s.Server} - s.Server.Handler = s.wrapWithSvcHeaders(s.Server.Handler) - } - - // wrap the HTTP handler with a handler that does MITM detection - tlsh := &tlsHandler{next: s.Server.Handler} - s.Server.Handler = tlsh // this needs to be the "outer" handler when Serve() is called, for type assertion - - // when Serve() creates the TLS listener later, that listener should - // be adding a reference the ClientHello info to a map; this callback - // will be sure to clear out that entry when the connection closes. - s.Server.ConnState = func(c net.Conn, cs http.ConnState) { - // when a connection closes or is hijacked, delete its entry - // in the map, because we are done with it. - if tlsh.listener != nil { - if cs == http.StateHijacked || cs == http.StateClosed { - tlsh.listener.helloInfosMu.Lock() - delete(tlsh.listener.helloInfos, c.RemoteAddr().String()) - tlsh.listener.helloInfosMu.Unlock() - } - } - } - - // As of Go 1.7, if the Server's TLSConfig is not nil, HTTP/2 is enabled only - // if TLSConfig.NextProtos includes the string "h2" - if HTTP2 && len(s.Server.TLSConfig.NextProtos) == 0 { - // some experimenting shows that this NextProtos must have at least - // one value that overlaps with the NextProtos of any other tls.Config - // that is returned from GetConfigForClient; if there is no overlap, - // the connection will fail (as of Go 1.8, Feb. 2017). - s.Server.TLSConfig.NextProtos = defaultALPN - } - } - - // Compile custom middleware for every site (enables virtual hosting) - for _, site := range group { - stack := Handler(staticfiles.FileServer{Root: http.Dir(site.Root), Hide: site.HiddenFiles}) - for i := len(site.middleware) - 1; i >= 0; i-- { - stack = site.middleware[i](stack) - } - site.middlewareChain = stack - s.vhosts.Insert(site.Addr.VHost(), site) - } - - return s, nil -} - -// makeHTTPServerWithHeaderLimit apply minimum header limit within a group to given http.Server -func makeHTTPServerWithHeaderLimit(s *http.Server, group []*SiteConfig) *http.Server { - var min int64 - for _, cfg := range group { - limit := cfg.Limits.MaxRequestHeaderSize - if limit == 0 { - continue - } - - // not set yet - if min == 0 { - min = limit - } - - // find a better one - if limit < min { - min = limit - } - } - - if min > 0 { - s.MaxHeaderBytes = int(min) - } - return s -} - -// makeHTTPServerWithTimeouts makes an http.Server from the group of -// configs in a way that configures timeouts (or, if not set, it uses -// the default timeouts) by combining the configuration of each -// SiteConfig in the group. (Timeouts are important for mitigating -// slowloris attacks.) -func makeHTTPServerWithTimeouts(addr string, group []*SiteConfig) *http.Server { - // find the minimum duration configured for each timeout - var min Timeouts - for _, cfg := range group { - if cfg.Timeouts.ReadTimeoutSet && - (!min.ReadTimeoutSet || cfg.Timeouts.ReadTimeout < min.ReadTimeout) { - min.ReadTimeoutSet = true - min.ReadTimeout = cfg.Timeouts.ReadTimeout - } - if cfg.Timeouts.ReadHeaderTimeoutSet && - (!min.ReadHeaderTimeoutSet || cfg.Timeouts.ReadHeaderTimeout < min.ReadHeaderTimeout) { - min.ReadHeaderTimeoutSet = true - min.ReadHeaderTimeout = cfg.Timeouts.ReadHeaderTimeout - } - if cfg.Timeouts.WriteTimeoutSet && - (!min.WriteTimeoutSet || cfg.Timeouts.WriteTimeout < min.WriteTimeout) { - min.WriteTimeoutSet = true - min.WriteTimeout = cfg.Timeouts.WriteTimeout - } - if cfg.Timeouts.IdleTimeoutSet && - (!min.IdleTimeoutSet || cfg.Timeouts.IdleTimeout < min.IdleTimeout) { - min.IdleTimeoutSet = true - min.IdleTimeout = cfg.Timeouts.IdleTimeout - } - } - - // for the values that were not set, use defaults - if !min.ReadTimeoutSet { - min.ReadTimeout = defaultTimeouts.ReadTimeout - } - if !min.ReadHeaderTimeoutSet { - min.ReadHeaderTimeout = defaultTimeouts.ReadHeaderTimeout - } - if !min.WriteTimeoutSet { - min.WriteTimeout = defaultTimeouts.WriteTimeout - } - if !min.IdleTimeoutSet { - min.IdleTimeout = defaultTimeouts.IdleTimeout - } - - // set the final values on the server and return it - return &http.Server{ - Addr: addr, - ReadTimeout: min.ReadTimeout, - ReadHeaderTimeout: min.ReadHeaderTimeout, - WriteTimeout: min.WriteTimeout, - IdleTimeout: min.IdleTimeout, - } -} - -func (s *Server) wrapWithSvcHeaders(previousHandler http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - s.quicServer.SetQuicHeaders(w.Header()) - previousHandler.ServeHTTP(w, r) - } -} - -// Listen creates an active listener for s that can be -// used to serve requests. -func (s *Server) Listen() (net.Listener, error) { - if s.Server == nil { - return nil, fmt.Errorf("Server field is nil") - } - - ln, err := net.Listen("tcp", s.Server.Addr) - if err != nil { - var succeeded bool - if runtime.GOOS == "windows" { - // Windows has been known to keep sockets open even after closing the listeners. - // Tests reveal this error case easily because they call Start() then Stop() - // in succession. TODO: Better way to handle this? And why limit this to Windows? - for i := 0; i < 20; i++ { - time.Sleep(100 * time.Millisecond) - ln, err = net.Listen("tcp", s.Server.Addr) - if err == nil { - succeeded = true - break - } - } - } - if !succeeded { - return nil, err - } - } - - if tcpLn, ok := ln.(*net.TCPListener); ok { - ln = tcpKeepAliveListener{TCPListener: tcpLn} - } - - cln := ln.(caddy.Listener) - for _, site := range s.sites { - for _, m := range site.listenerMiddleware { - cln = m(cln) - } - } - - // Very important to return a concrete caddy.Listener - // implementation for graceful restarts. - return cln.(caddy.Listener), nil -} - -// ListenPacket creates udp connection for QUIC if it is enabled, -func (s *Server) ListenPacket() (net.PacketConn, error) { - if QUIC { - udpAddr, err := net.ResolveUDPAddr("udp", s.Server.Addr) - if err != nil { - return nil, err - } - return net.ListenUDP("udp", udpAddr) - } - return nil, nil -} - -// Serve serves requests on ln. It blocks until ln is closed. -func (s *Server) Serve(ln net.Listener) error { - s.listenerMu.Lock() - s.listener = ln - s.listenerMu.Unlock() - - if s.Server.TLSConfig != nil { - // Create TLS listener - note that we do not replace s.listener - // with this TLS listener; tls.listener is unexported and does - // not implement the File() method we need for graceful restarts - // on POSIX systems. - // TODO: Is this ^ still relevant anymore? Maybe we can now that it's a net.Listener... - ln = newTLSListener(ln, s.Server.TLSConfig) - if handler, ok := s.Server.Handler.(*tlsHandler); ok { - handler.listener = ln.(*tlsHelloListener) - } - - // Rotate TLS session ticket keys - s.tlsGovChan = caddytls.RotateSessionTicketKeys(s.Server.TLSConfig) - } - - err := s.Server.Serve(ln) - if s.quicServer != nil { - s.quicServer.Close() - } - return err -} - -// ServePacket serves QUIC requests on pc until it is closed. -func (s *Server) ServePacket(pc net.PacketConn) error { - if s.quicServer != nil { - err := s.quicServer.Serve(pc.(*net.UDPConn)) - return fmt.Errorf("serving QUIC connections: %v", err) - } - return nil -} - -// ServeHTTP is the entry point of all HTTP requests. -func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { - defer func() { - // We absolutely need to be sure we stay alive up here, - // even though, in theory, the errors middleware does this. - if rec := recover(); rec != nil { - log.Printf("[PANIC] %v", rec) - DefaultErrorFunc(w, r, http.StatusInternalServerError) - } - }() - - // copy the original, unchanged URL into the context - // so it can be referenced by middlewares - urlCopy := *r.URL - if r.URL.User != nil { - userInfo := new(url.Userinfo) - *userInfo = *r.URL.User - urlCopy.User = userInfo - } - c := context.WithValue(r.Context(), OriginalURLCtxKey, urlCopy) - r = r.WithContext(c) - - w.Header().Set("Server", caddy.AppName) - - status, _ := s.serveHTTP(w, r) - - // Fallback error response in case error handling wasn't chained in - if status >= 400 { - DefaultErrorFunc(w, r, status) - } -} - -func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - // strip out the port because it's not used in virtual - // hosting; the port is irrelevant because each listener - // is on a different port. - hostname, _, err := net.SplitHostPort(r.Host) - if err != nil { - hostname = r.Host - } - - // look up the virtualhost; if no match, serve error - vhost, pathPrefix := s.vhosts.Match(hostname + r.URL.Path) - c := context.WithValue(r.Context(), caddy.CtxKey("path_prefix"), pathPrefix) - r = r.WithContext(c) - - if vhost == nil { - // check for ACME challenge even if vhost is nil; - // could be a new host coming online soon - if caddytls.HTTPChallengeHandler(w, r, "localhost", caddytls.DefaultHTTPAlternatePort) { - return 0, nil - } - // otherwise, log the error and write a message to the client - remoteHost, _, err := net.SplitHostPort(r.RemoteAddr) - if err != nil { - remoteHost = r.RemoteAddr - } - WriteSiteNotFound(w, r) // don't add headers outside of this function - log.Printf("[INFO] %s - No such site at %s (Remote: %s, Referer: %s)", - hostname, s.Server.Addr, remoteHost, r.Header.Get("Referer")) - return 0, nil - } - - // we still check for ACME challenge if the vhost exists, - // because we must apply its HTTP challenge config settings - if s.proxyHTTPChallenge(vhost, w, r) { - return 0, nil - } - - // trim the path portion of the site address from the beginning of - // the URL path, so a request to example.com/foo/blog on the site - // defined as example.com/foo appears as /blog instead of /foo/blog. - if pathPrefix != "/" { - r.URL.Path = strings.TrimPrefix(r.URL.Path, pathPrefix) - if !strings.HasPrefix(r.URL.Path, "/") { - r.URL.Path = "/" + r.URL.Path - } - } - - return vhost.middlewareChain.ServeHTTP(w, r) -} - -// proxyHTTPChallenge solves the ACME HTTP challenge if r is the HTTP -// request for the challenge. If it is, and if the request has been -// fulfilled (response written), true is returned; false otherwise. -// If you don't have a vhost, just call the challenge handler directly. -func (s *Server) proxyHTTPChallenge(vhost *SiteConfig, w http.ResponseWriter, r *http.Request) bool { - if vhost.Addr.Port != caddytls.HTTPChallengePort { - return false - } - if vhost.TLS != nil && vhost.TLS.Manual { - return false - } - altPort := caddytls.DefaultHTTPAlternatePort - if vhost.TLS != nil && vhost.TLS.AltHTTPPort != "" { - altPort = vhost.TLS.AltHTTPPort - } - return caddytls.HTTPChallengeHandler(w, r, vhost.ListenHost, altPort) -} - -// Address returns the address s was assigned to listen on. -func (s *Server) Address() string { - return s.Server.Addr -} - -// Stop stops s gracefully (or forcefully after timeout) and -// closes its listener. -func (s *Server) Stop() error { - ctx, cancel := context.WithTimeout(context.Background(), s.connTimeout) - defer cancel() - - err := s.Server.Shutdown(ctx) - if err != nil { - return err - } - - // signal any TLS governor goroutines to exit - if s.tlsGovChan != nil { - close(s.tlsGovChan) - } - - return nil -} - -// OnStartupComplete lists the sites served by this server -// and any relevant information, assuming caddy.Quiet == false. -func (s *Server) OnStartupComplete() { - if caddy.Quiet { - return - } - for _, site := range s.sites { - output := site.Addr.String() - if caddy.IsLoopback(s.Address()) && !caddy.IsLoopback(site.Addr.Host) { - output += " (only accessible on this machine)" - } - fmt.Println(output) - log.Println(output) - } -} - -// defaultTimeouts stores the default timeout values to use -// if left unset by user configuration. NOTE: Most default -// timeouts are disabled (see issues #1464 and #1733). -var defaultTimeouts = Timeouts{IdleTimeout: 5 * time.Minute} - -// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted -// connections. It's used by ListenAndServe and ListenAndServeTLS so -// dead TCP connections (e.g. closing laptop mid-download) eventually -// go away. -// -// Borrowed from the Go standard library. -type tcpKeepAliveListener struct { - *net.TCPListener -} - -// Accept accepts the connection with a keep-alive enabled. -func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { - tc, err := ln.AcceptTCP() - if err != nil { - return - } - tc.SetKeepAlive(true) - tc.SetKeepAlivePeriod(3 * time.Minute) - return tc, nil -} - -// File implements caddy.Listener; it returns the underlying file of the listener. -func (ln tcpKeepAliveListener) File() (*os.File, error) { - return ln.TCPListener.File() -} - -// ErrMaxBytesExceeded is the error returned by MaxBytesReader -// when the request body exceeds the limit imposed -var ErrMaxBytesExceeded = errors.New("http: request body too large") - -// DefaultErrorFunc responds to an HTTP request with a simple description -// of the specified HTTP status code. -func DefaultErrorFunc(w http.ResponseWriter, r *http.Request, status int) { - WriteTextResponse(w, status, fmt.Sprintf("%d %s\n", status, http.StatusText(status))) -} - -const httpStatusMisdirectedRequest = 421 // RFC 7540, 9.1.2 - -// WriteSiteNotFound writes appropriate error code to w, signaling that -// requested host is not served by Caddy on a given port. -func WriteSiteNotFound(w http.ResponseWriter, r *http.Request) { - status := http.StatusNotFound - if r.ProtoMajor >= 2 { - // TODO: use http.StatusMisdirectedRequest when it gets defined - status = httpStatusMisdirectedRequest - } - WriteTextResponse(w, status, fmt.Sprintf("%d Site %s is not served on this interface\n", status, r.Host)) -} - -// WriteTextResponse writes body with code status to w. The body will -// be interpreted as plain text. -func WriteTextResponse(w http.ResponseWriter, status int, body string) { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.Header().Set("X-Content-Type-Options", "nosniff") - w.WriteHeader(status) - w.Write([]byte(body)) -} - -// SafePath joins siteRoot and reqPath and converts it to a path that can -// be used to access a path on the local disk. It ensures the path does -// not traverse outside of the site root. -// -// If opening a file, use http.Dir instead. -func SafePath(siteRoot, reqPath string) string { - reqPath = filepath.ToSlash(reqPath) - reqPath = strings.Replace(reqPath, "\x00", "", -1) // NOTE: Go 1.9 checks for null bytes in the syscall package - if siteRoot == "" { - siteRoot = "." - } - return filepath.Join(siteRoot, filepath.FromSlash(path.Clean("/"+reqPath))) -} - -// OriginalURLCtxKey is the key for accessing the original, incoming URL on an HTTP request. -const OriginalURLCtxKey = caddy.CtxKey("original_url") diff --git a/caddyhttp/httpserver/server_test.go b/caddyhttp/httpserver/server_test.go deleted file mode 100644 index 036ee3dd81a..00000000000 --- a/caddyhttp/httpserver/server_test.go +++ /dev/null @@ -1,146 +0,0 @@ -package httpserver - -import ( - "net/http" - "testing" - "time" -) - -func TestAddress(t *testing.T) { - addr := "127.0.0.1:9005" - srv := &Server{Server: &http.Server{Addr: addr}} - - if got, want := srv.Address(), addr; got != want { - t.Errorf("Expected '%s' but got '%s'", want, got) - } -} - -func TestMakeHTTPServerWithTimeouts(t *testing.T) { - for i, tc := range []struct { - group []*SiteConfig - expected Timeouts - }{ - { - group: []*SiteConfig{{Timeouts: Timeouts{}}}, - expected: Timeouts{ - ReadTimeout: defaultTimeouts.ReadTimeout, - ReadHeaderTimeout: defaultTimeouts.ReadHeaderTimeout, - WriteTimeout: defaultTimeouts.WriteTimeout, - IdleTimeout: defaultTimeouts.IdleTimeout, - }, - }, - { - group: []*SiteConfig{{Timeouts: Timeouts{ - ReadTimeout: 1 * time.Second, - ReadTimeoutSet: true, - ReadHeaderTimeout: 2 * time.Second, - ReadHeaderTimeoutSet: true, - }}}, - expected: Timeouts{ - ReadTimeout: 1 * time.Second, - ReadHeaderTimeout: 2 * time.Second, - WriteTimeout: defaultTimeouts.WriteTimeout, - IdleTimeout: defaultTimeouts.IdleTimeout, - }, - }, - { - group: []*SiteConfig{{Timeouts: Timeouts{ - ReadTimeoutSet: true, - WriteTimeoutSet: true, - }}}, - expected: Timeouts{ - ReadTimeout: 0, - ReadHeaderTimeout: defaultTimeouts.ReadHeaderTimeout, - WriteTimeout: 0, - IdleTimeout: defaultTimeouts.IdleTimeout, - }, - }, - { - group: []*SiteConfig{ - {Timeouts: Timeouts{ - ReadTimeout: 2 * time.Second, - ReadTimeoutSet: true, - WriteTimeout: 2 * time.Second, - WriteTimeoutSet: true, - }}, - {Timeouts: Timeouts{ - ReadTimeout: 1 * time.Second, - ReadTimeoutSet: true, - WriteTimeout: 1 * time.Second, - WriteTimeoutSet: true, - }}, - }, - expected: Timeouts{ - ReadTimeout: 1 * time.Second, - ReadHeaderTimeout: defaultTimeouts.ReadHeaderTimeout, - WriteTimeout: 1 * time.Second, - IdleTimeout: defaultTimeouts.IdleTimeout, - }, - }, - { - group: []*SiteConfig{{Timeouts: Timeouts{ - ReadHeaderTimeout: 5 * time.Second, - ReadHeaderTimeoutSet: true, - IdleTimeout: 10 * time.Second, - IdleTimeoutSet: true, - }}}, - expected: Timeouts{ - ReadTimeout: defaultTimeouts.ReadTimeout, - ReadHeaderTimeout: 5 * time.Second, - WriteTimeout: defaultTimeouts.WriteTimeout, - IdleTimeout: 10 * time.Second, - }, - }, - } { - actual := makeHTTPServerWithTimeouts("127.0.0.1:9005", tc.group) - - if got, want := actual.Addr, "127.0.0.1:9005"; got != want { - t.Errorf("Test %d: Expected Addr=%s, but was %s", i, want, got) - } - if got, want := actual.ReadTimeout, tc.expected.ReadTimeout; got != want { - t.Errorf("Test %d: Expected ReadTimeout=%v, but was %v", i, want, got) - } - if got, want := actual.ReadHeaderTimeout, tc.expected.ReadHeaderTimeout; got != want { - t.Errorf("Test %d: Expected ReadHeaderTimeout=%v, but was %v", i, want, got) - } - if got, want := actual.WriteTimeout, tc.expected.WriteTimeout; got != want { - t.Errorf("Test %d: Expected WriteTimeout=%v, but was %v", i, want, got) - } - if got, want := actual.IdleTimeout, tc.expected.IdleTimeout; got != want { - t.Errorf("Test %d: Expected IdleTimeout=%v, but was %v", i, want, got) - } - } -} - -func TestMakeHTTPServerWithHeaderLimit(t *testing.T) { - for name, c := range map[string]struct { - group []*SiteConfig - expect int - }{ - "disable": { - group: []*SiteConfig{{}}, - expect: 0, - }, - "oneSite": { - group: []*SiteConfig{{Limits: Limits{ - MaxRequestHeaderSize: 100, - }}}, - expect: 100, - }, - "multiSites": { - group: []*SiteConfig{ - {Limits: Limits{MaxRequestHeaderSize: 100}}, - {Limits: Limits{MaxRequestHeaderSize: 50}}, - }, - expect: 50, - }, - } { - c := c - t.Run(name, func(t *testing.T) { - actual := makeHTTPServerWithHeaderLimit(&http.Server{}, c.group) - if got := actual.MaxHeaderBytes; got != c.expect { - t.Errorf("Expect %d, but got %d", c.expect, got) - } - }) - } -} diff --git a/caddyhttp/httpserver/siteconfig.go b/caddyhttp/httpserver/siteconfig.go deleted file mode 100644 index 2d2dced2b1d..00000000000 --- a/caddyhttp/httpserver/siteconfig.go +++ /dev/null @@ -1,124 +0,0 @@ -package httpserver - -import ( - "time" - - "github.com/mholt/caddy/caddytls" -) - -// SiteConfig contains information about a site -// (also known as a virtual host). -type SiteConfig struct { - // The address of the site - Addr Address - - // The hostname to bind listener to; - // defaults to Addr.Host - ListenHost string - - // TLS configuration - TLS *caddytls.Config - - // Uncompiled middleware stack - middleware []Middleware - - // Compiled middleware stack - middlewareChain Handler - - // listener middleware stack - listenerMiddleware []ListenerMiddleware - - // Directory from which to serve files - Root string - - // A list of files to hide (for example, the - // source Caddyfile). TODO: Enforcing this - // should be centralized, for example, a - // standardized way of loading files from disk - // for a request. - HiddenFiles []string - - // Max request's header/body size - Limits Limits - - // The path to the Caddyfile used to generate this site config - originCaddyfile string - - // These timeout values are used, in conjunction with other - // site configs on the same server instance, to set the - // respective timeout values on the http.Server that - // is created. Sensible values will mitigate slowloris - // attacks and overcome faulty networks, while still - // preserving functionality needed for proxying, - // websockets, etc. - Timeouts Timeouts - - // If true, any requests not matching other site definitions - // may be served by this site. - FallbackSite bool -} - -// Timeouts specify various timeouts for a server to use. -// If the assocated bool field is true, then the duration -// value should be treated literally (i.e. a zero-value -// duration would mean "no timeout"). If false, the duration -// was left unset, so a zero-value duration would mean to -// use a default value (even if default is non-zero). -type Timeouts struct { - ReadTimeout time.Duration - ReadTimeoutSet bool - ReadHeaderTimeout time.Duration - ReadHeaderTimeoutSet bool - WriteTimeout time.Duration - WriteTimeoutSet bool - IdleTimeout time.Duration - IdleTimeoutSet bool -} - -// Limits specify size limit of request's header and body. -type Limits struct { - MaxRequestHeaderSize int64 - MaxRequestBodySizes []PathLimit -} - -// PathLimit is a mapping from a site's path to its corresponding -// maximum request body size (in bytes) -type PathLimit struct { - Path string - Limit int64 -} - -// AddMiddleware adds a middleware to a site's middleware stack. -func (s *SiteConfig) AddMiddleware(m Middleware) { - s.middleware = append(s.middleware, m) -} - -// AddListenerMiddleware adds a listener middleware to a site's listenerMiddleware stack. -func (s *SiteConfig) AddListenerMiddleware(l ListenerMiddleware) { - s.listenerMiddleware = append(s.listenerMiddleware, l) -} - -// TLSConfig returns s.TLS. -func (s SiteConfig) TLSConfig() *caddytls.Config { - return s.TLS -} - -// Host returns s.Addr.Host. -func (s SiteConfig) Host() string { - return s.Addr.Host -} - -// Port returns s.Addr.Port. -func (s SiteConfig) Port() string { - return s.Addr.Port -} - -// Middleware returns s.middleware (useful for tests). -func (s SiteConfig) Middleware() []Middleware { - return s.middleware -} - -// ListenerMiddleware returns s.listenerMiddleware -func (s SiteConfig) ListenerMiddleware() []ListenerMiddleware { - return s.listenerMiddleware -} diff --git a/caddyhttp/httpserver/tplcontext.go b/caddyhttp/httpserver/tplcontext.go deleted file mode 100644 index 92256ebf818..00000000000 --- a/caddyhttp/httpserver/tplcontext.go +++ /dev/null @@ -1,446 +0,0 @@ -package httpserver - -import ( - "bytes" - "crypto/rand" - "fmt" - "io/ioutil" - mathrand "math/rand" - "net" - "net/http" - "net/url" - "path" - "strings" - "sync" - "text/template" - "time" - - "os" - - "github.com/russross/blackfriday" -) - -// This file contains the context and functions available for -// use in the templates. - -// Context is the context with which Caddy templates are executed. -type Context struct { - Root http.FileSystem - Req *http.Request - URL *url.URL - Args []interface{} // defined by arguments to .Include - - // just used for adding preload links for server push - responseHeader http.Header -} - -// NewContextWithHeader creates a context with given response header. -// -// To plugin developer: -// The returned context's exported fileds remain empty, -// you should then initialize them if you want. -func NewContextWithHeader(rh http.Header) Context { - return Context{ - responseHeader: rh, - } -} - -// Include returns the contents of filename relative to the site root. -func (c Context) Include(filename string, args ...interface{}) (string, error) { - c.Args = args - return ContextInclude(filename, c, c.Root) -} - -// Now returns the current timestamp in the specified format. -func (c Context) Now(format string) string { - return time.Now().Format(format) -} - -// NowDate returns the current date/time that can be used -// in other time functions. -func (c Context) NowDate() time.Time { - return time.Now() -} - -// Cookie gets the value of a cookie with name name. -func (c Context) Cookie(name string) string { - cookies := c.Req.Cookies() - for _, cookie := range cookies { - if cookie.Name == name { - return cookie.Value - } - } - return "" -} - -// Header gets the value of a request header with field name. -func (c Context) Header(name string) string { - return c.Req.Header.Get(name) -} - -// Hostname gets the (remote) hostname of the client making the request. -func (c Context) Hostname() string { - ip := c.IP() - - hostnameList, err := net.LookupAddr(ip) - if err != nil || len(hostnameList) == 0 { - return c.Req.RemoteAddr - } - - return hostnameList[0] -} - -// Env gets a map of the environment variables. -func (c Context) Env() map[string]string { - osEnv := os.Environ() - envVars := make(map[string]string, len(osEnv)) - for _, env := range osEnv { - data := strings.SplitN(env, "=", 2) - if len(data) == 2 && len(data[0]) > 0 { - envVars[data[0]] = data[1] - } - } - return envVars -} - -// IP gets the (remote) IP address of the client making the request. -func (c Context) IP() string { - ip, _, err := net.SplitHostPort(c.Req.RemoteAddr) - if err != nil { - return c.Req.RemoteAddr - } - return ip -} - -// To mock the net.InterfaceAddrs from the test. -var networkInterfacesFn = net.InterfaceAddrs - -// ServerIP gets the (local) IP address of the server. -// TODO: The bind directive should be honored in this method (see PR #1474). -func (c Context) ServerIP() string { - addrs, err := networkInterfacesFn() - if err != nil { - return "" - } - - for _, address := range addrs { - // Validate the address and check if it's not a loopback - if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { - if ipnet.IP.To4() != nil || ipnet.IP.To16() != nil { - return ipnet.IP.String() - } - } - } - - return "" -} - -// URI returns the raw, unprocessed request URI (including query -// string and hash) obtained directly from the Request-Line of -// the HTTP request. -func (c Context) URI() string { - return c.Req.RequestURI -} - -// Host returns the hostname portion of the Host header -// from the HTTP request. -func (c Context) Host() (string, error) { - host, _, err := net.SplitHostPort(c.Req.Host) - if err != nil { - if !strings.Contains(c.Req.Host, ":") { - // common with sites served on the default port 80 - return c.Req.Host, nil - } - return "", err - } - return host, nil -} - -// Port returns the port portion of the Host header if specified. -func (c Context) Port() (string, error) { - _, port, err := net.SplitHostPort(c.Req.Host) - if err != nil { - if !strings.Contains(c.Req.Host, ":") { - // common with sites served on the default port 80 - return HTTPPort, nil - } - return "", err - } - return port, nil -} - -// Method returns the method (GET, POST, etc.) of the request. -func (c Context) Method() string { - return c.Req.Method -} - -// PathMatches returns true if the path portion of the request -// URL matches pattern. -func (c Context) PathMatches(pattern string) bool { - return Path(c.Req.URL.Path).Matches(pattern) -} - -// Truncate truncates the input string to the given length. -// If length is negative, it returns that many characters -// starting from the end of the string. If the absolute value -// of length is greater than len(input), the whole input is -// returned. -func (c Context) Truncate(input string, length int) string { - if length < 0 && len(input)+length > 0 { - return input[len(input)+length:] - } - if length >= 0 && len(input) > length { - return input[:length] - } - return input -} - -// StripHTML returns s without HTML tags. It is fairly naive -// but works with most valid HTML inputs. -func (c Context) StripHTML(s string) string { - var buf bytes.Buffer - var inTag, inQuotes bool - var tagStart int - for i, ch := range s { - if inTag { - if ch == '>' && !inQuotes { - inTag = false - } else if ch == '<' && !inQuotes { - // false start - buf.WriteString(s[tagStart:i]) - tagStart = i - } else if ch == '"' { - inQuotes = !inQuotes - } - continue - } - if ch == '<' { - inTag = true - tagStart = i - continue - } - buf.WriteRune(ch) - } - if inTag { - // false start - buf.WriteString(s[tagStart:]) - } - return buf.String() -} - -// Ext returns the suffix beginning at the final dot in the final -// slash-separated element of the pathStr (or in other words, the -// file extension). -func (c Context) Ext(pathStr string) string { - return path.Ext(pathStr) -} - -// StripExt returns the input string without the extension, -// which is the suffix starting with the final '.' character -// but not before the final path separator ('/') character. -// If there is no extension, the whole input is returned. -func (c Context) StripExt(path string) string { - for i := len(path) - 1; i >= 0 && path[i] != '/'; i-- { - if path[i] == '.' { - return path[:i] - } - } - return path -} - -// Replace replaces instances of find in input with replacement. -func (c Context) Replace(input, find, replacement string) string { - return strings.Replace(input, find, replacement, -1) -} - -// Markdown returns the HTML contents of the markdown contained in filename -// (relative to the site root). -func (c Context) Markdown(filename string) (string, error) { - body, err := c.Include(filename) - if err != nil { - return "", err - } - renderer := blackfriday.HtmlRenderer(0, "", "") - extns := 0 - extns |= blackfriday.EXTENSION_TABLES - extns |= blackfriday.EXTENSION_FENCED_CODE - extns |= blackfriday.EXTENSION_STRIKETHROUGH - extns |= blackfriday.EXTENSION_DEFINITION_LISTS - markdown := blackfriday.Markdown([]byte(body), renderer, extns) - - return string(markdown), nil -} - -// ContextInclude opens filename using fs and executes a template with the context ctx. -// This does the same thing that Context.Include() does, but with the ability to provide -// your own context so that the included files can have access to additional fields your -// type may provide. You can embed Context in your type, then override its Include method -// to call this function with ctx being the instance of your type, and fs being Context.Root. -func ContextInclude(filename string, ctx interface{}, fs http.FileSystem) (string, error) { - file, err := fs.Open(filename) - if err != nil { - return "", err - } - defer file.Close() - - body, err := ioutil.ReadAll(file) - if err != nil { - return "", err - } - - tpl, err := template.New(filename).Funcs(TemplateFuncs).Parse(string(body)) - if err != nil { - return "", err - } - - buf := includeBufs.Get().(*bytes.Buffer) - buf.Reset() - defer includeBufs.Put(buf) - err = tpl.Execute(buf, ctx) - if err != nil { - return "", err - } - - return buf.String(), nil -} - -// ToLower will convert the given string to lower case. -func (c Context) ToLower(s string) string { - return strings.ToLower(s) -} - -// ToUpper will convert the given string to upper case. -func (c Context) ToUpper(s string) string { - return strings.ToUpper(s) -} - -// Split is a pass-through to strings.Split. It will split the first argument at each instance of the separator and return a slice of strings. -func (c Context) Split(s string, sep string) []string { - return strings.Split(s, sep) -} - -// Join is a pass-through to strings.Join. It will join the first argument slice with the separator in the second argument and return the result. -func (c Context) Join(a []string, sep string) string { - return strings.Join(a, sep) -} - -// Slice will convert the given arguments into a slice. -func (c Context) Slice(elems ...interface{}) []interface{} { - return elems -} - -// Map will convert the arguments into a map. It expects alternating string keys and values. This is useful for building more complicated data structures -// if you are using subtemplates or things like that. -func (c Context) Map(values ...interface{}) (map[string]interface{}, error) { - if len(values)%2 != 0 { - return nil, fmt.Errorf("Map expects an even number of arguments") - } - dict := make(map[string]interface{}, len(values)/2) - for i := 0; i < len(values); i += 2 { - key, ok := values[i].(string) - if !ok { - return nil, fmt.Errorf("Map keys must be strings") - } - dict[key] = values[i+1] - } - return dict, nil -} - -// Files reads and returns a slice of names from the given directory -// relative to the root of Context c. -func (c Context) Files(name string) ([]string, error) { - dir, err := c.Root.Open(path.Clean(name)) - if err != nil { - return nil, err - } - defer dir.Close() - - stat, err := dir.Stat() - if err != nil { - return nil, err - } - if !stat.IsDir() { - return nil, fmt.Errorf("%v is not a directory", name) - } - - dirInfo, err := dir.Readdir(0) - if err != nil { - return nil, err - } - - names := make([]string, len(dirInfo)) - for i, fileInfo := range dirInfo { - names[i] = fileInfo.Name() - } - - return names, nil -} - -// IsMITM returns true if it seems likely that the TLS connection -// is being intercepted. -func (c Context) IsMITM() bool { - if val, ok := c.Req.Context().Value(MitmCtxKey).(bool); ok { - return val - } - return false -} - -// RandomString generates a random string of random length given -// length bounds. Thanks to http://stackoverflow.com/a/35615565/1048862 -// for the clever technique that is fairly fast, secure, and maintains -// proper distributions over the dictionary. -func (c Context) RandomString(minLen, maxLen int) string { - const ( - letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - letterIdxBits = 6 // 6 bits to represent 64 possibilities (indexes) - letterIdxMask = 1<\n
  • str1
  • \n
  • str2
  • \n\n", - }, - } - - for i, test := range tests { - testPrefix := getTestPrefix(i) - - // WriteFile truncates the contentt - err := ioutil.WriteFile(absInFilePath, []byte(test.fileContent), os.ModePerm) - if err != nil { - t.Fatal(testPrefix+"Failed to create test file. Error was: %v", err) - } - - content, _ := context.Markdown(inputFilename) - if content != test.expectedContent { - t.Errorf(testPrefix+"Expected content [%s] but found [%s]. Input file was: %s", test.expectedContent, content, inputFilename) - } - } -} - -func TestCookie(t *testing.T) { - - tests := []struct { - cookie *http.Cookie - cookieName string - expectedValue string - }{ - // Test 0 - happy path - { - cookie: &http.Cookie{Name: "cookieName", Value: "cookieValue"}, - cookieName: "cookieName", - expectedValue: "cookieValue", - }, - // Test 1 - try to get a non-existing cookie - { - cookie: &http.Cookie{Name: "cookieName", Value: "cookieValue"}, - cookieName: "notExisting", - expectedValue: "", - }, - // Test 2 - partial name match - { - cookie: &http.Cookie{Name: "cookie", Value: "cookieValue"}, - cookieName: "cook", - expectedValue: "", - }, - // Test 3 - cookie with optional fields - { - cookie: &http.Cookie{Name: "cookie", Value: "cookieValue", Path: "/path", Domain: "https://localhost", Expires: (time.Now().Add(10 * time.Minute)), MaxAge: 120}, - cookieName: "cookie", - expectedValue: "cookieValue", - }, - } - - for i, test := range tests { - testPrefix := getTestPrefix(i) - - // reinitialize the context for each test - context := getContextOrFail(t) - - context.Req.AddCookie(test.cookie) - - actualCookieVal := context.Cookie(test.cookieName) - - if actualCookieVal != test.expectedValue { - t.Errorf(testPrefix+"Expected cookie value [%s] but found [%s] for cookie with name %s", test.expectedValue, actualCookieVal, test.cookieName) - } - } -} - -func TestCookieMultipleCookies(t *testing.T) { - context := getContextOrFail(t) - - cookieNameBase, cookieValueBase := "cookieName", "cookieValue" - - // make sure that there's no state and multiple requests for different cookies return the correct result - for i := 0; i < 10; i++ { - context.Req.AddCookie(&http.Cookie{Name: fmt.Sprintf("%s%d", cookieNameBase, i), Value: fmt.Sprintf("%s%d", cookieValueBase, i)}) - } - - for i := 0; i < 10; i++ { - expectedCookieVal := fmt.Sprintf("%s%d", cookieValueBase, i) - actualCookieVal := context.Cookie(fmt.Sprintf("%s%d", cookieNameBase, i)) - if actualCookieVal != expectedCookieVal { - t.Fatalf("Expected cookie value %s, found %s", expectedCookieVal, actualCookieVal) - } - } -} - -func TestHeader(t *testing.T) { - context := getContextOrFail(t) - - headerKey, headerVal := "Header1", "HeaderVal1" - context.Req.Header.Add(headerKey, headerVal) - - actualHeaderVal := context.Header(headerKey) - if actualHeaderVal != headerVal { - t.Errorf("Expected header %s, found %s", headerVal, actualHeaderVal) - } - - missingHeaderVal := context.Header("not-existing") - if missingHeaderVal != "" { - t.Errorf("Expected empty header value, found %s", missingHeaderVal) - } -} - -func TestHostname(t *testing.T) { - context := getContextOrFail(t) - - tests := []struct { - inputRemoteAddr string - expectedHostname string - }{ - // TODO(mholt): Fix these tests, they're not portable. i.e. my resolver - // returns "fwdr-8.fwdr-8.fwdr-8.fwdr-8." instead of these google ones. - // Test 0 - ipv4 with port - // {"8.8.8.8:1111", "google-public-dns-a.google.com."}, - // // Test 1 - ipv4 without port - // {"8.8.8.8", "google-public-dns-a.google.com."}, - // // Test 2 - ipv6 with port - // {"[2001:4860:4860::8888]:11", "google-public-dns-a.google.com."}, - // // Test 3 - ipv6 without port and brackets - // {"2001:4860:4860::8888", "google-public-dns-a.google.com."}, - // Test 4 - no hostname available - {"1.1.1.1", "1.1.1.1"}, - } - - for i, test := range tests { - testPrefix := getTestPrefix(i) - - context.Req.RemoteAddr = test.inputRemoteAddr - actualHostname := context.Hostname() - - if actualHostname != test.expectedHostname { - t.Errorf(testPrefix+"Expected hostname %s, found %s", test.expectedHostname, actualHostname) - } - } -} - -func TestEnv(t *testing.T) { - context := getContextOrFail(t) - - name := "ENV_TEST_NAME" - testValue := "TEST_VALUE" - os.Setenv(name, testValue) - - notExisting := "ENV_TEST_NOT_EXISTING" - os.Unsetenv(notExisting) - - invalidName := "ENV_TEST_INVALID_NAME" - os.Setenv("="+invalidName, testValue) - - env := context.Env() - if value := env[name]; value != testValue { - t.Errorf("Expected env-variable %s value '%s', found '%s'", - name, testValue, value) - } - - if value, ok := env[notExisting]; ok { - t.Errorf("Expected empty env-variable %s, found '%s'", - notExisting, value) - } - - for k, v := range env { - if strings.Contains(k, invalidName) { - t.Errorf("Expected invalid name not to be included in Env %s, found in key '%s'", invalidName, k) - } - if strings.Contains(v, invalidName) { - t.Errorf("Expected invalid name not be be included in Env %s, found in value '%s'", invalidName, v) - } - } - - os.Unsetenv("=" + invalidName) -} - -func TestIP(t *testing.T) { - context := getContextOrFail(t) - - tests := []struct { - inputRemoteAddr string - expectedIP string - }{ - // Test 0 - ipv4 with port - {"1.1.1.1:1111", "1.1.1.1"}, - // Test 1 - ipv4 without port - {"1.1.1.1", "1.1.1.1"}, - // Test 2 - ipv6 with port - {"[::1]:11", "::1"}, - // Test 3 - ipv6 without port and brackets - {"[2001:db8:a0b:12f0::1]", "[2001:db8:a0b:12f0::1]"}, - // Test 4 - ipv6 with zone and port - {`[fe80:1::3%eth0]:44`, `fe80:1::3%eth0`}, - } - - for i, test := range tests { - testPrefix := getTestPrefix(i) - - context.Req.RemoteAddr = test.inputRemoteAddr - actualIP := context.IP() - - if actualIP != test.expectedIP { - t.Errorf(testPrefix+"Expected IP %s, found %s", test.expectedIP, actualIP) - } - } -} - -type myIP string - -func (ip myIP) mockInterfaces() ([]net.Addr, error) { - a := net.ParseIP(string(ip)) - - return []net.Addr{ - &net.IPNet{IP: a, Mask: nil}, - }, nil -} - -func TestServerIP(t *testing.T) { - context := getContextOrFail(t) - - tests := []string{ - // Test 0 - ipv4 - "1.1.1.1", - // Test 1 - ipv6 - "2001:db8:a0b:12f0::1", - } - - for i, expectedIP := range tests { - testPrefix := getTestPrefix(i) - - // Mock the network interface - ip := myIP(expectedIP) - networkInterfacesFn = ip.mockInterfaces - defer func() { - networkInterfacesFn = net.InterfaceAddrs - }() - - actualIP := context.ServerIP() - - if actualIP != expectedIP { - t.Errorf("%sExpected IP \"%s\", found \"%s\".", testPrefix, expectedIP, actualIP) - } - } -} - -func TestURL(t *testing.T) { - context := getContextOrFail(t) - - inputURL := "http://localhost" - context.Req.RequestURI = inputURL - - if inputURL != context.URI() { - t.Errorf("Expected url %s, found %s", inputURL, context.URI()) - } -} - -func TestHost(t *testing.T) { - tests := []struct { - input string - expectedHost string - shouldErr bool - }{ - { - input: "localhost:123", - expectedHost: "localhost", - shouldErr: false, - }, - { - input: "localhost", - expectedHost: "localhost", - shouldErr: false, - }, - { - input: "[::]", - expectedHost: "", - shouldErr: true, - }, - } - - for _, test := range tests { - testHostOrPort(t, true, test.input, test.expectedHost, test.shouldErr) - } -} - -func TestPort(t *testing.T) { - tests := []struct { - input string - expectedPort string - shouldErr bool - }{ - { - input: "localhost:123", - expectedPort: "123", - shouldErr: false, - }, - { - input: "localhost", - expectedPort: "80", // assuming 80 is the default port - shouldErr: false, - }, - { - input: ":8080", - expectedPort: "8080", - shouldErr: false, - }, - { - input: "[::]", - expectedPort: "", - shouldErr: true, - }, - } - - for _, test := range tests { - testHostOrPort(t, false, test.input, test.expectedPort, test.shouldErr) - } -} - -func testHostOrPort(t *testing.T, isTestingHost bool, input, expectedResult string, shouldErr bool) { - context := getContextOrFail(t) - - context.Req.Host = input - var actualResult, testedObject string - var err error - - if isTestingHost { - actualResult, err = context.Host() - testedObject = "host" - } else { - actualResult, err = context.Port() - testedObject = "port" - } - - if shouldErr && err == nil { - t.Errorf("Expected error, found nil!") - return - } - - if !shouldErr && err != nil { - t.Errorf("Expected no error, found %s", err) - return - } - - if actualResult != expectedResult { - t.Errorf("Expected %s %s, found %s", testedObject, expectedResult, actualResult) - } -} - -func TestMethod(t *testing.T) { - context := getContextOrFail(t) - - method := "POST" - context.Req.Method = method - - if method != context.Method() { - t.Errorf("Expected method %s, found %s", method, context.Method()) - } - -} - -func TestContextPathMatches(t *testing.T) { - context := getContextOrFail(t) - - tests := []struct { - urlStr string - pattern string - shouldMatch bool - }{ - // Test 0 - { - urlStr: "http://localhost/", - pattern: "", - shouldMatch: true, - }, - // Test 1 - { - urlStr: "http://localhost", - pattern: "", - shouldMatch: true, - }, - // Test 1 - { - urlStr: "http://localhost/", - pattern: "/", - shouldMatch: true, - }, - // Test 3 - { - urlStr: "http://localhost/?param=val", - pattern: "/", - shouldMatch: true, - }, - // Test 4 - { - urlStr: "http://localhost/dir1/dir2", - pattern: "/dir2", - shouldMatch: false, - }, - // Test 5 - { - urlStr: "http://localhost/dir1/dir2", - pattern: "/dir1", - shouldMatch: true, - }, - // Test 6 - { - urlStr: "http://localhost:444/dir1/dir2", - pattern: "/dir1", - shouldMatch: true, - }, - // Test 7 - { - urlStr: "http://localhost/dir1/dir2", - pattern: "*/dir2", - shouldMatch: false, - }, - } - - for i, test := range tests { - testPrefix := getTestPrefix(i) - var err error - context.Req.URL, err = url.Parse(test.urlStr) - if err != nil { - t.Fatalf("Failed to prepare test URL from string %s! Error was: %s", test.urlStr, err) - } - - matches := context.PathMatches(test.pattern) - if matches != test.shouldMatch { - t.Errorf(testPrefix+"Expected and actual result differ: expected to match [%t], actual matches [%t]", test.shouldMatch, matches) - } - } -} - -func TestTruncate(t *testing.T) { - context := getContextOrFail(t) - tests := []struct { - inputString string - inputLength int - expected string - }{ - // Test 0 - small length - { - inputString: "string", - inputLength: 1, - expected: "s", - }, - // Test 1 - exact length - { - inputString: "string", - inputLength: 6, - expected: "string", - }, - // Test 2 - bigger length - { - inputString: "string", - inputLength: 10, - expected: "string", - }, - // Test 3 - zero length - { - inputString: "string", - inputLength: 0, - expected: "", - }, - // Test 4 - negative, smaller length - { - inputString: "string", - inputLength: -5, - expected: "tring", - }, - // Test 5 - negative, exact length - { - inputString: "string", - inputLength: -6, - expected: "string", - }, - // Test 6 - negative, bigger length - { - inputString: "string", - inputLength: -7, - expected: "string", - }, - } - - for i, test := range tests { - actual := context.Truncate(test.inputString, test.inputLength) - if actual != test.expected { - t.Errorf(getTestPrefix(i)+"Expected '%s', found '%s'. Input was Truncate(%q, %d)", test.expected, actual, test.inputString, test.inputLength) - } - } -} - -func TestStripHTML(t *testing.T) { - context := getContextOrFail(t) - tests := []struct { - input string - expected string - }{ - // Test 0 - no tags - { - input: `h1`, - expected: `h1`, - }, - // Test 1 - happy path - { - input: `

    h1

    `, - expected: `h1`, - }, - // Test 2 - tag in quotes - { - input: `">h1`, - expected: `h1`, - }, - // Test 3 - multiple tags - { - input: `

    h1

    `, - expected: `h1`, - }, - // Test 4 - tags not closed - { - input: `hi`, - expected: ` 0 && !reflect.DeepEqual(test.fileNames, actual) { - t.Errorf(testPrefix+"Expected files %v, got: %v", - test.fileNames, actual) - } - } - } - - if dirPath != "" { - if err := os.RemoveAll(dirPath); err != nil && !os.IsNotExist(err) { - t.Fatalf(testPrefix+"Expected no error removing directory, got: '%s'", err.Error()) - } - } - } -} - -func TestAddLink(t *testing.T) { - for name, c := range map[string]struct { - input string - expectLinks []string - }{ - "oneLink": { - input: `{{.AddLink "; rel=preload"}}`, - expectLinks: []string{"; rel=preload"}, - }, - "multipleLinks": { - input: `{{.AddLink "; rel=preload"}} {{.AddLink "; rel=meta"}}`, - expectLinks: []string{"; rel=preload", "; rel=meta"}, - }, - } { - c := c - t.Run(name, func(t *testing.T) { - ctx := getContextOrFail(t) - tmpl, err := template.New("").Parse(c.input) - if err != nil { - t.Fatal(err) - } - err = tmpl.Execute(ioutil.Discard, ctx) - if err != nil { - t.Fatal(err) - } - if got := ctx.responseHeader["Link"]; !reflect.DeepEqual(got, c.expectLinks) { - t.Errorf("Result not match: expect %v, but got %v", c.expectLinks, got) - } - }) - } -} diff --git a/caddyhttp/httpserver/vhosttrie.go b/caddyhttp/httpserver/vhosttrie.go deleted file mode 100644 index d49347078f3..00000000000 --- a/caddyhttp/httpserver/vhosttrie.go +++ /dev/null @@ -1,161 +0,0 @@ -package httpserver - -import ( - "net" - "strings" -) - -// vhostTrie facilitates virtual hosting. It matches -// requests first by hostname (with support for -// wildcards as TLS certificates support them), then -// by longest matching path. -type vhostTrie struct { - fallbackHosts []string - edges map[string]*vhostTrie - site *SiteConfig // site to match on this node; also known as a virtual host - path string // the path portion of the key for the associated site -} - -// newVHostTrie returns a new vhostTrie. -func newVHostTrie() *vhostTrie { - return &vhostTrie{edges: make(map[string]*vhostTrie), fallbackHosts: []string{"0.0.0.0", ""}} -} - -// Insert adds stack to t keyed by key. The key should be -// a valid "host/path" combination (or just host). -func (t *vhostTrie) Insert(key string, site *SiteConfig) { - host, path := t.splitHostPath(key) - if _, ok := t.edges[host]; !ok { - t.edges[host] = newVHostTrie() - } - t.edges[host].insertPath(path, path, site) -} - -// insertPath expects t to be a host node (not a root node), -// and inserts site into the t according to remainingPath. -func (t *vhostTrie) insertPath(remainingPath, originalPath string, site *SiteConfig) { - if remainingPath == "" { - t.site = site - t.path = originalPath - return - } - ch := string(remainingPath[0]) - if _, ok := t.edges[ch]; !ok { - t.edges[ch] = newVHostTrie() - } - t.edges[ch].insertPath(remainingPath[1:], originalPath, site) -} - -// Match returns the virtual host (site) in v with -// the closest match to key. If there was a match, -// it returns the SiteConfig and the path portion of -// the key used to make the match. The matched path -// would be a prefix of the path portion of the -// key, if not the whole path portion of the key. -// If there is no match, nil and empty string will -// be returned. -// -// A typical key will be in the form "host" or "host/path". -func (t *vhostTrie) Match(key string) (*SiteConfig, string) { - host, path := t.splitHostPath(key) - // try the given host, then, if no match, try fallback hosts - branch := t.matchHost(host) - for _, h := range t.fallbackHosts { - if branch != nil { - break - } - branch = t.matchHost(h) - } - if branch == nil { - return nil, "" - } - node := branch.matchPath(path) - if node == nil { - return nil, "" - } - return node.site, node.path -} - -// matchHost returns the vhostTrie matching host. The matching -// algorithm is the same as used to match certificates to host -// with SNI during TLS handshakes. In other words, it supports, -// to some degree, the use of wildcard (*) characters. -func (t *vhostTrie) matchHost(host string) *vhostTrie { - // try exact match - if subtree, ok := t.edges[host]; ok { - return subtree - } - - // then try replacing labels in the host - // with wildcards until we get a match - labels := strings.Split(host, ".") - for i := range labels { - labels[i] = "*" - candidate := strings.Join(labels, ".") - if subtree, ok := t.edges[candidate]; ok { - return subtree - } - } - - return nil -} - -// matchPath traverses t until it finds the longest key matching -// remainingPath, and returns its node. -func (t *vhostTrie) matchPath(remainingPath string) *vhostTrie { - var longestMatch *vhostTrie - for len(remainingPath) > 0 { - ch := string(remainingPath[0]) - next, ok := t.edges[ch] - if !ok { - break - } - if next.site != nil { - longestMatch = next - } - t = next - remainingPath = remainingPath[1:] - } - return longestMatch -} - -// splitHostPath separates host from path in key. -func (t *vhostTrie) splitHostPath(key string) (host, path string) { - parts := strings.SplitN(key, "/", 2) - host, path = strings.ToLower(parts[0]), "/" - if len(parts) > 1 { - path += parts[1] - } - // strip out the port (if present) from the host, since - // each port has its own socket, and each socket has its - // own listener, and each listener has its own server - // instance, and each server instance has its own vhosts. - // removing the port is a simple way to standardize so - // when requests come in, we can be sure to get a match. - hostname, _, err := net.SplitHostPort(host) - if err == nil { - host = hostname - } - return -} - -// String returns a list of all the entries in t; assumes that -// t is a root node. -func (t *vhostTrie) String() string { - var s string - for host, edge := range t.edges { - s += edge.str(host) - } - return s -} - -func (t *vhostTrie) str(prefix string) string { - var s string - for key, edge := range t.edges { - if edge.site != nil { - s += prefix + key + "\n" - } - s += edge.str(prefix + key) - } - return s -} diff --git a/caddyhttp/httpserver/vhosttrie_test.go b/caddyhttp/httpserver/vhosttrie_test.go deleted file mode 100644 index 95ef1fba57f..00000000000 --- a/caddyhttp/httpserver/vhosttrie_test.go +++ /dev/null @@ -1,141 +0,0 @@ -package httpserver - -import ( - "net/http" - "net/http/httptest" - "testing" -) - -func TestVHostTrie(t *testing.T) { - trie := newVHostTrie() - populateTestTrie(trie, []string{ - "example", - "example.com", - "*.example.com", - "example.com/foo", - "example.com/foo/bar", - "*.example.com/test", - }) - assertTestTrie(t, trie, []vhostTrieTest{ - {"not-in-trie.com", false, "", "/"}, - {"example", true, "example", "/"}, - {"example.com", true, "example.com", "/"}, - {"example.com/test", true, "example.com", "/"}, - {"example.com/foo", true, "example.com/foo", "/foo"}, - {"example.com/foo/", true, "example.com/foo", "/foo"}, - {"EXAMPLE.COM/foo", true, "example.com/foo", "/foo"}, - {"EXAMPLE.COM/Foo", true, "example.com", "/"}, - {"example.com/foo/bar", true, "example.com/foo/bar", "/foo/bar"}, - {"example.com/foo/bar/baz", true, "example.com/foo/bar", "/foo/bar"}, - {"example.com/foo/other", true, "example.com/foo", "/foo"}, - {"foo.example.com", true, "*.example.com", "/"}, - {"foo.example.com/else", true, "*.example.com", "/"}, - }, false) -} - -func TestVHostTrieWildcard1(t *testing.T) { - trie := newVHostTrie() - populateTestTrie(trie, []string{ - "example.com", - "", - }) - assertTestTrie(t, trie, []vhostTrieTest{ - {"not-in-trie.com", true, "", "/"}, - {"example.com", true, "example.com", "/"}, - {"example.com/foo", true, "example.com", "/"}, - {"not-in-trie.com/asdf", true, "", "/"}, - }, true) -} - -func TestVHostTrieWildcard2(t *testing.T) { - trie := newVHostTrie() - populateTestTrie(trie, []string{ - "0.0.0.0/asdf", - }) - assertTestTrie(t, trie, []vhostTrieTest{ - {"example.com/asdf/foo", true, "0.0.0.0/asdf", "/asdf"}, - {"example.com/foo", false, "", "/"}, - {"host/asdf", true, "0.0.0.0/asdf", "/asdf"}, - }, true) -} - -func TestVHostTrieWildcard3(t *testing.T) { - trie := newVHostTrie() - populateTestTrie(trie, []string{ - "*/foo", - }) - assertTestTrie(t, trie, []vhostTrieTest{ - {"example.com/foo", true, "*/foo", "/foo"}, - {"example.com", false, "", "/"}, - }, true) -} - -func TestVHostTriePort(t *testing.T) { - // Make sure port is stripped out - trie := newVHostTrie() - populateTestTrie(trie, []string{ - "example.com:1234", - }) - assertTestTrie(t, trie, []vhostTrieTest{ - {"example.com/foo", true, "example.com:1234", "/"}, - }, true) -} - -func populateTestTrie(trie *vhostTrie, keys []string) { - for _, key := range keys { - // we wrap this in a func, passing in the key, otherwise the - // handler always writes the last key to the response, even - // if the handler is actually from one of the earlier keys. - func(key string) { - site := &SiteConfig{ - middlewareChain: HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - w.Write([]byte(key)) - return 0, nil - }), - } - trie.Insert(key, site) - }(key) - } -} - -type vhostTrieTest struct { - query string - expectMatch bool - expectedKey string - matchedPrefix string // the path portion of a key that is expected to be matched -} - -func assertTestTrie(t *testing.T, trie *vhostTrie, tests []vhostTrieTest, hasWildcardHosts bool) { - for i, test := range tests { - site, pathPrefix := trie.Match(test.query) - - if !test.expectMatch { - if site != nil { - // If not expecting a value, then just make sure we didn't get one - t.Errorf("Test %d: Expected no matches, but got %v", i, site) - } - continue - } - - // Otherwise, we must assert we got a value - if site == nil { - t.Errorf("Test %d: Expected non-nil return value, but got: %v", i, site) - continue - } - - // And it must be the correct value - resp := httptest.NewRecorder() - site.middlewareChain.ServeHTTP(resp, nil) - actualHandlerKey := resp.Body.String() - if actualHandlerKey != test.expectedKey { - t.Errorf("Test %d: Expected match '%s' but matched '%s'", - i, test.expectedKey, actualHandlerKey) - } - - // The path prefix must also be correct - if test.matchedPrefix != pathPrefix { - t.Errorf("Test %d: Expected matched path prefix to be '%s', got '%s'", - i, test.matchedPrefix, pathPrefix) - } - } -} diff --git a/caddyhttp/index/index.go b/caddyhttp/index/index.go deleted file mode 100644 index 743f6b9e088..00000000000 --- a/caddyhttp/index/index.go +++ /dev/null @@ -1,33 +0,0 @@ -package index - -import ( - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/staticfiles" -) - -func init() { - caddy.RegisterPlugin("index", caddy.Plugin{ - ServerType: "http", - Action: setupIndex, - }) -} - -func setupIndex(c *caddy.Controller) error { - var index []string - - for c.Next() { - args := c.RemainingArgs() - - if len(args) == 0 { - return c.Errf("Expected at least one index") - } - - for _, in := range args { - index = append(index, in) - } - - staticfiles.IndexPages = index - } - - return nil -} diff --git a/caddyhttp/index/index_test.go b/caddyhttp/index/index_test.go deleted file mode 100644 index dfd879f396e..00000000000 --- a/caddyhttp/index/index_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package index - -import ( - "testing" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/staticfiles" -) - -func TestIndexIncompleteParams(t *testing.T) { - c := caddy.NewTestController("", "index") - - err := setupIndex(c) - if err == nil { - t.Error("Expected an error, but didn't get one") - } -} - -func TestIndex(t *testing.T) { - c := caddy.NewTestController("", "index a.html b.html c.html") - - err := setupIndex(c) - if err != nil { - t.Errorf("Expected no errors, got: %v", err) - } - - expectedIndex := []string{"a.html", "b.html", "c.html"} - - if len(staticfiles.IndexPages) != 3 { - t.Errorf("Expected 3 values, got %v", len(staticfiles.IndexPages)) - } - - // Ensure ordering is correct - for i, actual := range staticfiles.IndexPages { - if actual != expectedIndex[i] { - t.Errorf("Expected value in position %d to be %v, got %v", i, expectedIndex[i], actual) - } - } -} diff --git a/caddyhttp/internalsrv/internal.go b/caddyhttp/internalsrv/internal.go deleted file mode 100644 index e698ca37234..00000000000 --- a/caddyhttp/internalsrv/internal.go +++ /dev/null @@ -1,97 +0,0 @@ -// Package internalsrv provides a simple middleware that (a) prevents access -// to internal locations and (b) allows to return files from internal location -// by setting a special header, e.g. in a proxy response. -// -// The package is named internalsrv so as not to conflict with Go tooling -// convention which treats folders called "internal" differently. -package internalsrv - -import ( - "net/http" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -// Internal middleware protects internal locations from external requests - -// but allows access from the inside by using a special HTTP header. -type Internal struct { - Next httpserver.Handler - Paths []string -} - -const ( - redirectHeader string = "X-Accel-Redirect" - contentLengthHeader string = "Content-Length" - contentEncodingHeader string = "Content-Encoding" - maxRedirectCount int = 10 -) - -func isInternalRedirect(w http.ResponseWriter) bool { - return w.Header().Get(redirectHeader) != "" -} - -// ServeHTTP implements the httpserver.Handler interface. -func (i Internal) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - // Internal location requested? -> Not found. - for _, prefix := range i.Paths { - if httpserver.Path(r.URL.Path).Matches(prefix) { - return http.StatusNotFound, nil - } - } - - // Use internal response writer to ignore responses that will be - // redirected to internal locations - iw := internalResponseWriter{ResponseWriterWrapper: &httpserver.ResponseWriterWrapper{ResponseWriter: w}} - status, err := i.Next.ServeHTTP(iw, r) - - for c := 0; c < maxRedirectCount && isInternalRedirect(iw); c++ { - // Redirect - adapt request URL path and send it again - // "down the chain" - r.URL.Path = iw.Header().Get(redirectHeader) - iw.ClearHeader() - status, err = i.Next.ServeHTTP(iw, r) - } - - if isInternalRedirect(iw) { - // Too many redirect cycles - iw.ClearHeader() - return http.StatusInternalServerError, nil - } - - return status, err -} - -// internalResponseWriter wraps the underlying http.ResponseWriter and ignores -// calls to Write and WriteHeader if the response should be redirected to an -// internal location. -type internalResponseWriter struct { - *httpserver.ResponseWriterWrapper -} - -// ClearHeader removes script headers that would interfere with follow up -// redirect requests. -func (w internalResponseWriter) ClearHeader() { - w.Header().Del(redirectHeader) - w.Header().Del(contentLengthHeader) - w.Header().Del(contentEncodingHeader) -} - -// WriteHeader ignores the call if the response should be redirected to an -// internal location. -func (w internalResponseWriter) WriteHeader(code int) { - if !isInternalRedirect(w) { - w.ResponseWriterWrapper.WriteHeader(code) - } -} - -// Write ignores the call if the response should be redirected to an internal -// location. -func (w internalResponseWriter) Write(b []byte) (int, error) { - if isInternalRedirect(w) { - return 0, nil - } - return w.ResponseWriterWrapper.Write(b) -} - -// Interface guards -var _ httpserver.HTTPInterfaces = internalResponseWriter{} diff --git a/caddyhttp/internalsrv/internal_test.go b/caddyhttp/internalsrv/internal_test.go deleted file mode 100644 index ea071044887..00000000000 --- a/caddyhttp/internalsrv/internal_test.go +++ /dev/null @@ -1,123 +0,0 @@ -package internalsrv - -import ( - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "strconv" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -const ( - internalProtectedData = "~~~protected-data~~~" - contentTypeOctetStream = "application/octet-stream" -) - -func TestInternal(t *testing.T) { - im := Internal{ - Next: httpserver.HandlerFunc(internalTestHandlerFunc), - Paths: []string{"/internal"}, - } - - tests := []struct { - url string - expectedCode int - expectedBody string - }{ - {"/internal", http.StatusNotFound, ""}, - - {"/public", 0, "/public"}, - {"/public/internal", 0, "/public/internal"}, - - {"/redirect", 0, "/internal"}, - - {"/cycle", http.StatusInternalServerError, ""}, - } - - var i int - for i, test := range tests { - req, err := http.NewRequest("GET", test.url, nil) - if err != nil { - t.Fatalf("Test %d: Could not create HTTP request: %v", i, err) - } - - rec := httptest.NewRecorder() - code, _ := im.ServeHTTP(rec, req) - - if code != test.expectedCode { - t.Errorf("Test %d: Expected status code %d for %s, but got %d", - i, test.expectedCode, test.url, code) - } - if rec.Body.String() != test.expectedBody { - t.Errorf("Test %d: Expected body '%s' for %s, but got '%s'", - i, test.expectedBody, test.url, rec.Body.String()) - } - } - - { - req, err := http.NewRequest("GET", "/download", nil) - if err != nil { - t.Fatalf("Test %d: Could not create HTTP request: %v", i, err) - } - - rec := httptest.NewRecorder() - code, _ := im.ServeHTTP(rec, req) - - if code != 0 { - t.Errorf("Test %d: Expected status code %d for %s, but got %d", - i, 0, "/download", code) - } - if rec.Body.String() != internalProtectedData { - t.Errorf("Test %d: Expected body '%s' for %s, but got '%s'", - i, internalProtectedData, "/download", rec.Body.String()) - } - contentLength, err := strconv.Atoi(rec.Header().Get("Content-Length")) - if err != nil || contentLength != len(internalProtectedData) { - t.Errorf("Test %d: Expected content-length %d for %s, but got %d", - i, len(internalProtectedData), "/download", contentLength) - } - if val := rec.Header().Get("Content-Type"); val != contentTypeOctetStream { - t.Errorf("Test %d: Expected content-type '%s' header for %s, but got '%s'", - i, contentTypeOctetStream, "/download", val) - } - if val := rec.Header().Get("Content-Disposition"); val == "" { - t.Errorf("Test %d: Expected content-disposition header for %s", - i, "/download") - } - if val := rec.Header().Get("Content-Encoding"); val != "" { - t.Errorf("Test %d: Expected removal of content-encoding header for %s", - i, "/download") - } - } -} - -func internalTestHandlerFunc(w http.ResponseWriter, r *http.Request) (int, error) { - switch r.URL.Path { - case "/redirect": - w.Header().Set("X-Accel-Redirect", "/internal") - - case "/cycle": - w.Header().Set("X-Accel-Redirect", "/cycle") - - case "/download": - w.Header().Set("X-Accel-Redirect", "/internal/data") - w.Header().Set("Content-Disposition", "attachment; filename=test") - w.Header().Set("Content-Encoding", "magic") - w.Header().Set("Content-Length", "999") - - case "/internal/data": - w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Type", contentTypeOctetStream) - w.Header().Set("Content-Length", strconv.Itoa(len(internalProtectedData))) - w.Write([]byte(internalProtectedData)) - return 0, nil - } - - w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, r.URL.String()) - - return 0, nil -} diff --git a/caddyhttp/internalsrv/setup.go b/caddyhttp/internalsrv/setup.go deleted file mode 100644 index 990fafafd29..00000000000 --- a/caddyhttp/internalsrv/setup.go +++ /dev/null @@ -1,42 +0,0 @@ -package internalsrv - -import ( - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func init() { - caddy.RegisterPlugin("internal", caddy.Plugin{ - ServerType: "http", - Action: setup, - }) -} - -// Internal configures a new Internal middleware instance. -func setup(c *caddy.Controller) error { - paths, err := internalParse(c) - if err != nil { - return err - } - - httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler { - return Internal{Next: next, Paths: paths} - }) - - return nil -} - -func internalParse(c *caddy.Controller) ([]string, error) { - var paths []string - - for c.Next() { - if c.NextArg() { - paths = append(paths, c.Val()) - } - if c.NextArg() { - return nil, c.ArgErr() - } - } - - return paths, nil -} diff --git a/caddyhttp/internalsrv/setup_test.go b/caddyhttp/internalsrv/setup_test.go deleted file mode 100644 index 7c2b71e8dfa..00000000000 --- a/caddyhttp/internalsrv/setup_test.go +++ /dev/null @@ -1,74 +0,0 @@ -package internalsrv - -import ( - "testing" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestSetup(t *testing.T) { - c := caddy.NewTestController("http", `internal /internal`) - err := setup(c) - if err != nil { - t.Errorf("Expected no errors, 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.(Internal) - - if !ok { - t.Fatalf("Expected handler to be type Internal, got: %#v", handler) - } - - if myHandler.Paths[0] != "/internal" { - t.Errorf("Expected internal in the list of internal Paths") - } - - if !httpserver.SameNext(myHandler.Next, httpserver.EmptyNext) { - t.Error("'Next' field of handler was not set properly") - } - -} - -func TestInternalParse(t *testing.T) { - tests := []struct { - inputInternalPaths string - shouldErr bool - expectedInternalPaths []string - }{ - {`internal`, false, []string{}}, - - {`internal /internal`, false, []string{"/internal"}}, - - {`internal /internal1 - internal /internal2`, false, []string{"/internal1", "/internal2"}}, - - {`internal /internal1 /internal2`, true, nil}, - } - for i, test := range tests { - actualInternalPaths, err := internalParse(caddy.NewTestController("http", test.inputInternalPaths)) - - 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(actualInternalPaths) != len(test.expectedInternalPaths) { - t.Fatalf("Test %d expected %d InternalPaths, but got %d", - i, len(test.expectedInternalPaths), len(actualInternalPaths)) - } - for j, actualInternalPath := range actualInternalPaths { - if actualInternalPath != test.expectedInternalPaths[j] { - t.Fatalf("Test %d expected %dth Internal Path to be %s , but got %s", - i, j, test.expectedInternalPaths[j], actualInternalPath) - } - } - } - -} diff --git a/caddyhttp/limits/handler.go b/caddyhttp/limits/handler.go deleted file mode 100644 index 6bcd12c5c29..00000000000 --- a/caddyhttp/limits/handler.go +++ /dev/null @@ -1,90 +0,0 @@ -package limits - -import ( - "io" - "net/http" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -// Limit is a middleware to control request body size -type Limit struct { - Next httpserver.Handler - BodyLimits []httpserver.PathLimit -} - -func (l Limit) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - if r.Body == nil { - return l.Next.ServeHTTP(w, r) - } - - // apply the path-based request body size limit. - for _, bl := range l.BodyLimits { - if httpserver.Path(r.URL.Path).Matches(bl.Path) { - r.Body = MaxBytesReader(w, r.Body, bl.Limit) - break - } - } - - return l.Next.ServeHTTP(w, r) -} - -// MaxBytesReader and its associated methods are borrowed from the -// Go Standard library (comments intact). The only difference is that -// it returns a ErrMaxBytesExceeded error instead of a generic error message -// when the request body has exceeded the requested limit -func MaxBytesReader(w http.ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser { - return &maxBytesReader{w: w, r: r, n: n} -} - -type maxBytesReader struct { - w http.ResponseWriter - r io.ReadCloser // underlying reader - n int64 // max bytes remaining - err error // sticky error -} - -func (l *maxBytesReader) Read(p []byte) (n int, err error) { - if l.err != nil { - return 0, l.err - } - if len(p) == 0 { - return 0, nil - } - // If they asked for a 32KB byte read but only 5 bytes are - // remaining, no need to read 32KB. 6 bytes will answer the - // question of the whether we hit the limit or go past it. - if int64(len(p)) > l.n+1 { - p = p[:l.n+1] - } - n, err = l.r.Read(p) - - if int64(n) <= l.n { - l.n -= int64(n) - l.err = err - return n, err - } - - n = int(l.n) - l.n = 0 - - // The server code and client code both use - // maxBytesReader. This "requestTooLarge" check is - // only used by the server code. To prevent binaries - // which only using the HTTP Client code (such as - // cmd/go) from also linking in the HTTP server, don't - // use a static type assertion to the server - // "*response" type. Check this interface instead: - type requestTooLarger interface { - requestTooLarge() - } - if res, ok := l.w.(requestTooLarger); ok { - res.requestTooLarge() - } - l.err = httpserver.ErrMaxBytesExceeded - return n, l.err -} - -func (l *maxBytesReader) Close() error { - return l.r.Close() -} diff --git a/caddyhttp/limits/handler_test.go b/caddyhttp/limits/handler_test.go deleted file mode 100644 index aae20ba7b18..00000000000 --- a/caddyhttp/limits/handler_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package limits - -import ( - "io/ioutil" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestBodySizeLimit(t *testing.T) { - var ( - gotContent []byte - gotError error - expectContent = "hello" - ) - l := Limit{ - Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - gotContent, gotError = ioutil.ReadAll(r.Body) - return 0, nil - }), - BodyLimits: []httpserver.PathLimit{{Path: "/", Limit: int64(len(expectContent))}}, - } - - r := httptest.NewRequest("GET", "/", strings.NewReader(expectContent+expectContent)) - l.ServeHTTP(httptest.NewRecorder(), r) - if got := string(gotContent); got != expectContent { - t.Errorf("expected content[%s], got[%s]", expectContent, got) - } - if gotError != httpserver.ErrMaxBytesExceeded { - t.Errorf("expect error %v, got %v", httpserver.ErrMaxBytesExceeded, gotError) - } -} diff --git a/caddyhttp/limits/setup.go b/caddyhttp/limits/setup.go deleted file mode 100644 index 1c75153f2cd..00000000000 --- a/caddyhttp/limits/setup.go +++ /dev/null @@ -1,219 +0,0 @@ -package limits - -import ( - "errors" - "sort" - "strconv" - "strings" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -const ( - serverType = "http" - pluginName = "limits" -) - -func init() { - caddy.RegisterPlugin(pluginName, caddy.Plugin{ - ServerType: serverType, - Action: setupLimits, - }) -} - -// pathLimitUnparsed is a PathLimit before it's parsed -type pathLimitUnparsed struct { - Path string - Limit string -} - -func setupLimits(c *caddy.Controller) error { - bls, err := parseLimits(c) - if err != nil { - return err - } - - httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler { - return Limit{Next: next, BodyLimits: bls} - }) - return nil -} - -func parseLimits(c *caddy.Controller) ([]httpserver.PathLimit, error) { - config := httpserver.GetConfig(c) - - if !c.Next() { - return nil, c.ArgErr() - } - - args := c.RemainingArgs() - argList := []pathLimitUnparsed{} - headerLimit := "" - - switch len(args) { - case 0: - // Format: limits { - // header - // body - // body - // ... - // } - for c.NextBlock() { - kind := c.Val() - pathOrLimit := c.RemainingArgs() - switch kind { - case "header": - if len(pathOrLimit) != 1 { - return nil, c.ArgErr() - } - headerLimit = pathOrLimit[0] - case "body": - if len(pathOrLimit) == 1 { - argList = append(argList, pathLimitUnparsed{ - Path: "/", - Limit: pathOrLimit[0], - }) - break - } - - if len(pathOrLimit) == 2 { - argList = append(argList, pathLimitUnparsed{ - Path: pathOrLimit[0], - Limit: pathOrLimit[1], - }) - break - } - - fallthrough - default: - return nil, c.ArgErr() - } - } - case 1: - // Format: limits - headerLimit = args[0] - argList = []pathLimitUnparsed{{ - Path: "/", - Limit: args[0], - }} - default: - return nil, c.ArgErr() - } - - if headerLimit != "" { - size := parseSize(headerLimit) - if size < 1 { // also disallow size = 0 - return nil, c.ArgErr() - } - config.Limits.MaxRequestHeaderSize = size - } - - if len(argList) > 0 { - pathLimit, err := parseArguments(argList) - if err != nil { - return nil, c.ArgErr() - } - SortPathLimits(pathLimit) - config.Limits.MaxRequestBodySizes = pathLimit - } - - return config.Limits.MaxRequestBodySizes, nil -} - -func parseArguments(args []pathLimitUnparsed) ([]httpserver.PathLimit, error) { - pathLimit := []httpserver.PathLimit{} - - for _, pair := range args { - size := parseSize(pair.Limit) - if size < 1 { // also disallow size = 0 - return pathLimit, errors.New("Parse failed") - } - pathLimit = addPathLimit(pathLimit, pair.Path, size) - } - return pathLimit, nil -} - -var validUnits = []struct { - symbol string - multiplier int64 -}{ - {"KB", 1024}, - {"MB", 1024 * 1024}, - {"GB", 1024 * 1024 * 1024}, - {"B", 1}, - {"", 1}, // defaulting to "B" -} - -// parseSize parses the given string as size limit -// Size are positive numbers followed by a unit (case insensitive) -// Allowed units: "B" (bytes), "KB" (kilo), "MB" (mega), "GB" (giga) -// If the unit is omitted, "b" is assumed -// Returns the parsed size in bytes, or -1 if cannot parse -func parseSize(sizeStr string) int64 { - sizeStr = strings.ToUpper(sizeStr) - - for _, unit := range validUnits { - if strings.HasSuffix(sizeStr, unit.symbol) { - size, err := strconv.ParseInt(sizeStr[0:len(sizeStr)-len(unit.symbol)], 10, 64) - if err != nil { - return -1 - } - return size * unit.multiplier - } - } - - // Unreachable code - return -1 -} - -// addPathLimit appends the path-to-request body limit mapping to pathLimit -// Slashes are checked and added to path if necessary. Duplicates are ignored. -func addPathLimit(pathLimit []httpserver.PathLimit, path string, limit int64) []httpserver.PathLimit { - // Enforces preceding slash - if path[0] != '/' { - path = "/" + path - } - - // Use the last value if there are duplicates - for i, p := range pathLimit { - if p.Path == path { - pathLimit[i].Limit = limit - return pathLimit - } - } - - return append(pathLimit, httpserver.PathLimit{Path: path, Limit: limit}) -} - -// SortPathLimits sort pathLimits by their paths length, longest first -func SortPathLimits(pathLimits []httpserver.PathLimit) { - sorter := &pathLimitSorter{ - pathLimits: pathLimits, - by: LengthDescending, - } - sort.Sort(sorter) -} - -// structs and methods implementing the sorting interfaces for PathLimit -type pathLimitSorter struct { - pathLimits []httpserver.PathLimit - by func(p1, p2 *httpserver.PathLimit) bool -} - -func (s *pathLimitSorter) Len() int { - return len(s.pathLimits) -} - -func (s *pathLimitSorter) Swap(i, j int) { - s.pathLimits[i], s.pathLimits[j] = s.pathLimits[j], s.pathLimits[i] -} - -func (s *pathLimitSorter) Less(i, j int) bool { - return s.by(&s.pathLimits[i], &s.pathLimits[j]) -} - -// LengthDescending is the comparator for SortPathLimits -func LengthDescending(p1, p2 *httpserver.PathLimit) bool { - return len(p1.Path) > len(p2.Path) -} diff --git a/caddyhttp/limits/setup_test.go b/caddyhttp/limits/setup_test.go deleted file mode 100644 index 08a36f901e8..00000000000 --- a/caddyhttp/limits/setup_test.go +++ /dev/null @@ -1,224 +0,0 @@ -package limits - -import ( - "reflect" - "testing" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -const ( - KB = 1024 - MB = 1024 * 1024 - GB = 1024 * 1024 * 1024 -) - -func TestParseLimits(t *testing.T) { - for name, c := range map[string]struct { - input string - shouldErr bool - expect httpserver.Limits - }{ - "catchAll": { - input: `limits 2kb`, - expect: httpserver.Limits{ - MaxRequestHeaderSize: 2 * KB, - MaxRequestBodySizes: []httpserver.PathLimit{{Path: "/", Limit: 2 * KB}}, - }, - }, - "onlyHeader": { - input: `limits { - header 2kb - }`, - expect: httpserver.Limits{ - MaxRequestHeaderSize: 2 * KB, - }, - }, - "onlyBody": { - input: `limits { - body 2kb - }`, - expect: httpserver.Limits{ - MaxRequestBodySizes: []httpserver.PathLimit{{Path: "/", Limit: 2 * KB}}, - }, - }, - "onlyBodyWithPath": { - input: `limits { - body /test 2kb - }`, - expect: httpserver.Limits{ - MaxRequestBodySizes: []httpserver.PathLimit{{Path: "/test", Limit: 2 * KB}}, - }, - }, - "mixture": { - input: `limits { - header 1kb - body 2kb - body /bar 3kb - }`, - expect: httpserver.Limits{ - MaxRequestHeaderSize: 1 * KB, - MaxRequestBodySizes: []httpserver.PathLimit{ - {Path: "/bar", Limit: 3 * KB}, - {Path: "/", Limit: 2 * KB}, - }, - }, - }, - "invalidFormat": { - input: `limits a b`, - shouldErr: true, - }, - "invalidHeaderFormat": { - input: `limits { - header / 100 - }`, - shouldErr: true, - }, - "invalidBodyFormat": { - input: `limits { - body / 100 200 - }`, - shouldErr: true, - }, - "invalidKind": { - input: `limits { - head 100 - }`, - shouldErr: true, - }, - "invalidLimitSize": { - input: `limits 10bk`, - shouldErr: true, - }, - } { - c := c - t.Run(name, func(t *testing.T) { - controller := caddy.NewTestController("", c.input) - _, err := parseLimits(controller) - if c.shouldErr && err == nil { - t.Error("failed to get expected error") - } - if !c.shouldErr && err != nil { - t.Errorf("got unexpected error: %v", err) - } - if got := httpserver.GetConfig(controller).Limits; !reflect.DeepEqual(got, c.expect) { - t.Errorf("expect %#v, but got %#v", c.expect, got) - } - }) - } -} - -func TestParseArguments(t *testing.T) { - cases := []struct { - arguments []pathLimitUnparsed - expected []httpserver.PathLimit - hasError bool - }{ - // Parse errors - {arguments: []pathLimitUnparsed{{"/", "123.5"}}, expected: []httpserver.PathLimit{}, hasError: true}, - {arguments: []pathLimitUnparsed{{"/", "200LB"}}, expected: []httpserver.PathLimit{}, hasError: true}, - {arguments: []pathLimitUnparsed{{"/", "path:999MB"}}, expected: []httpserver.PathLimit{}, hasError: true}, - {arguments: []pathLimitUnparsed{{"/", "1_234_567"}}, expected: []httpserver.PathLimit{}, hasError: true}, - {arguments: []pathLimitUnparsed{{"/", "0MB"}}, expected: []httpserver.PathLimit{}, hasError: true}, - - // Valid results - {arguments: []pathLimitUnparsed{}, expected: []httpserver.PathLimit{}, hasError: false}, - { - arguments: []pathLimitUnparsed{{"/", "100"}}, - expected: []httpserver.PathLimit{{Path: "/", Limit: 100}}, - hasError: false, - }, - { - arguments: []pathLimitUnparsed{{"/", "100KB"}}, - expected: []httpserver.PathLimit{{Path: "/", Limit: 100 * KB}}, - hasError: false, - }, - { - arguments: []pathLimitUnparsed{{"/", "100MB"}}, - expected: []httpserver.PathLimit{{Path: "/", Limit: 100 * MB}}, - hasError: false, - }, - { - arguments: []pathLimitUnparsed{{"/", "100GB"}}, - expected: []httpserver.PathLimit{{Path: "/", Limit: 100 * GB}}, - hasError: false, - }, - { - arguments: []pathLimitUnparsed{{"index", "100"}}, - expected: []httpserver.PathLimit{{Path: "/index", Limit: 100}}, - hasError: false, - }, - { - arguments: []pathLimitUnparsed{{"/home", "100MB"}, {"/upload/images", "500GB"}}, - expected: []httpserver.PathLimit{ - {Path: "/home", Limit: 100 * MB}, - {Path: "/upload/images", Limit: 500 * GB}, - }, - hasError: false}, - { - arguments: []pathLimitUnparsed{{"/", "999"}, {"/home", "12345MB"}}, - expected: []httpserver.PathLimit{ - {Path: "/", Limit: 999}, - {Path: "/home", Limit: 12345 * MB}, - }, - hasError: false, - }, - - // Duplicates - { - arguments: []pathLimitUnparsed{{"/home", "999"}, {"/home", "12345MB"}}, - expected: []httpserver.PathLimit{ - {Path: "/home", Limit: 12345 * MB}, - }, - hasError: false, - }, - } - - for caseNum, c := range cases { - output, err := parseArguments(c.arguments) - if c.hasError && (err == nil) { - t.Errorf("Expecting error for case %v but none encountered", caseNum) - } - if !c.hasError && (err != nil) { - t.Errorf("Expecting no error for case %v but encountered %v", caseNum, err) - } - - if !reflect.DeepEqual(c.expected, output) { - t.Errorf("Case %v is expecting: %v, actual %v", caseNum, c.expected, output) - } - } -} - -func TestSortPathLimits(t *testing.T) { - cases := []struct { - arguments []httpserver.PathLimit - expected []httpserver.PathLimit - }{ - // Parse errors - {arguments: []httpserver.PathLimit{}, expected: []httpserver.PathLimit{}}, - { - arguments: []httpserver.PathLimit{{Path: "/index", Limit: 100}}, - expected: []httpserver.PathLimit{{Path: "/index", Limit: 100}}, - }, - { - arguments: []httpserver.PathLimit{ - {Path: "/static", Limit: 1}, - {Path: "/static/images", Limit: 100}, - {Path: "/index", Limit: 200}, - }, - expected: []httpserver.PathLimit{ - {Path: "/static/images", Limit: 100}, - {Path: "/static", Limit: 1}, - {Path: "/index", Limit: 200}}, - }, - } - - for caseNum, c := range cases { - output := append([]httpserver.PathLimit{}, c.arguments...) - SortPathLimits(output) - if !reflect.DeepEqual(c.expected, output) { - t.Errorf("Case %v is expecting: %v, actual %v", caseNum, c.expected, output) - } - } -} diff --git a/caddyhttp/log/log.go b/caddyhttp/log/log.go deleted file mode 100644 index d0a0fd695c4..00000000000 --- a/caddyhttp/log/log.go +++ /dev/null @@ -1,87 +0,0 @@ -// Package log implements request (access) logging middleware. -package log - -import ( - "fmt" - "net/http" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func init() { - caddy.RegisterPlugin("log", caddy.Plugin{ - ServerType: "http", - Action: setup, - }) -} - -// Logger is a basic request logging middleware. -type Logger struct { - Next httpserver.Handler - Rules []*Rule - ErrorFunc func(http.ResponseWriter, *http.Request, int) // failover error handler -} - -func (l Logger) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - for _, rule := range l.Rules { - if httpserver.Path(r.URL.Path).Matches(rule.PathScope) { - // Record the response - responseRecorder := httpserver.NewResponseRecorder(w) - - // Attach the Replacer we'll use so that other middlewares can - // set their own placeholders if they want to. - rep := httpserver.NewReplacer(r, responseRecorder, CommonLogEmptyValue) - responseRecorder.Replacer = rep - - // Bon voyage, request! - status, err := l.Next.ServeHTTP(responseRecorder, r) - - if status >= 400 { - // There was an error up the chain, but no response has been written yet. - // The error must be handled here so the log entry will record the response size. - if l.ErrorFunc != nil { - l.ErrorFunc(responseRecorder, r, status) - } else { - // Default failover error handler - responseRecorder.WriteHeader(status) - fmt.Fprintf(responseRecorder, "%d %s", status, http.StatusText(status)) - } - status = 0 - } - - // Write log entries - for _, e := range rule.Entries { - e.Log.Println(rep.Replace(e.Format)) - } - - return status, err - } - } - return l.Next.ServeHTTP(w, r) -} - -// Entry represents a log entry under a path scope -type Entry struct { - Format string - Log *httpserver.Logger -} - -// Rule configures the logging middleware. -type Rule struct { - PathScope string - Entries []*Entry -} - -const ( - // DefaultLogFilename is the default log filename. - DefaultLogFilename = "access.log" - // CommonLogFormat is the common log format. - CommonLogFormat = `{remote} ` + CommonLogEmptyValue + " " + CommonLogEmptyValue + ` [{when}] "{method} {uri} {proto}" {status} {size}` - // CommonLogEmptyValue is the common empty log value. - CommonLogEmptyValue = "-" - // CombinedLogFormat is the combined log format. - CombinedLogFormat = CommonLogFormat + ` "{>Referer}" "{>User-Agent}"` - // DefaultLogFormat is the default log format. - DefaultLogFormat = CommonLogFormat -) diff --git a/caddyhttp/log/log_test.go b/caddyhttp/log/log_test.go deleted file mode 100644 index 2cf46afd952..00000000000 --- a/caddyhttp/log/log_test.go +++ /dev/null @@ -1,165 +0,0 @@ -package log - -import ( - "bytes" - "io/ioutil" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -type erroringMiddleware struct{} - -func (erroringMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - if rr, ok := w.(*httpserver.ResponseRecorder); ok { - rr.Replacer.Set("testval", "foobar") - } - return http.StatusNotFound, nil -} - -func TestLoggedStatus(t *testing.T) { - var f bytes.Buffer - var next erroringMiddleware - rule := Rule{ - PathScope: "/", - Entries: []*Entry{{ - Format: DefaultLogFormat + " {testval}", - Log: httpserver.NewTestLogger(&f), - }}, - } - - logger := Logger{ - Rules: []*Rule{&rule}, - Next: next, - } - - r, err := http.NewRequest("GET", "/", nil) - if err != nil { - t.Fatal(err) - } - - rec := httptest.NewRecorder() - - status, err := logger.ServeHTTP(rec, r) - if status != 0 { - t.Errorf("Expected status to be 0, but was %d", status) - } - - if err != nil { - t.Errorf("Expected error to be nil, instead got: %v", err) - } - - logged := f.String() - if !strings.Contains(logged, "404 13") { - t.Errorf("Expected log entry to contain '404 13', but it didn't: %s", logged) - } - - // check custom placeholder - if !strings.Contains(logged, "foobar") { - t.Errorf("Expected the log entry to contain 'foobar' (custom placeholder), but it didn't: %s", logged) - } -} - -func TestLogRequestBody(t *testing.T) { - var got bytes.Buffer - logger := Logger{ - Rules: []*Rule{{ - PathScope: "/", - Entries: []*Entry{{ - Format: "{request_body}", - Log: httpserver.NewTestLogger(&got), - }}, - }}, - Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - // drain up body - ioutil.ReadAll(r.Body) - return 0, nil - }), - } - - for i, c := range []struct { - body string - expect string - }{ - {"", "\n"}, - {"{hello} world!", "{hello} world!\n"}, - {func() string { - length := httpserver.MaxLogBodySize + 100 - b := make([]byte, length) - for i := 0; i < length; i++ { - b[i] = 0xab - } - return string(b) - }(), func() string { - b := make([]byte, httpserver.MaxLogBodySize) - for i := 0; i < httpserver.MaxLogBodySize; i++ { - b[i] = 0xab - } - return string(b) + "\n" - }(), - }, - } { - got.Reset() - r := httptest.NewRequest("POST", "/", bytes.NewBufferString(c.body)) - r.Header.Set("Content-Type", "application/json") - status, err := logger.ServeHTTP(httptest.NewRecorder(), r) - if status != 0 { - t.Errorf("case %d: Expected status to be 0, but was %d", i, status) - } - if err != nil { - t.Errorf("case %d: Expected error to be nil, instead got: %v", i, err) - } - if got.String() != c.expect { - t.Errorf("case %d: Expected body %q, but got %q", i, c.expect, got.String()) - } - } -} - -func TestMultiEntries(t *testing.T) { - var ( - got1 bytes.Buffer - got2 bytes.Buffer - ) - logger := Logger{ - Rules: []*Rule{{ - PathScope: "/", - Entries: []*Entry{ - { - Format: "foo {request_body}", - Log: httpserver.NewTestLogger(&got1), - }, - { - Format: "{method} {request_body}", - Log: httpserver.NewTestLogger(&got2), - }, - }, - }}, - Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - // drain up body - ioutil.ReadAll(r.Body) - return 0, nil - }), - } - - r, err := http.NewRequest("POST", "/", bytes.NewBufferString("hello world")) - if err != nil { - t.Fatal(err) - } - r.Header.Set("Content-Type", "application/json") - status, err := logger.ServeHTTP(httptest.NewRecorder(), r) - if status != 0 { - t.Errorf("Expected status to be 0, but was %d", status) - } - if err != nil { - t.Errorf("Expected error to be nil, instead got: %v", err) - } - if got, expect := got1.String(), "foo hello world\n"; got != expect { - t.Errorf("Expected %q, but got %q", expect, got) - } - if got, expect := got2.String(), "POST hello world\n"; got != expect { - t.Errorf("Expected %q, but got %q", expect, got) - } -} diff --git a/caddyhttp/log/setup.go b/caddyhttp/log/setup.go deleted file mode 100644 index 90177ab0784..00000000000 --- a/caddyhttp/log/setup.go +++ /dev/null @@ -1,102 +0,0 @@ -package log - -import ( - "strings" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -// setup sets up the logging middleware. -func setup(c *caddy.Controller) error { - rules, err := logParse(c) - if err != nil { - return err - } - - for _, rule := range rules { - for _, entry := range rule.Entries { - entry.Log.Attach(c) - } - } - - httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler { - return Logger{Next: next, Rules: rules, ErrorFunc: httpserver.DefaultErrorFunc} - }) - - return nil -} - -func logParse(c *caddy.Controller) ([]*Rule, error) { - var rules []*Rule - - for c.Next() { - args := c.RemainingArgs() - - var logRoller *httpserver.LogRoller - logRoller = httpserver.DefaultLogRoller() - - for c.NextBlock() { - what := c.Val() - where := c.RemainingArgs() - - // only support roller related options inside a block - if !httpserver.IsLogRollerSubdirective(what) { - return nil, c.ArgErr() - } - - if err := httpserver.ParseRoller(logRoller, what, where...); err != nil { - return nil, err - } - } - - path := "/" - format := DefaultLogFormat - output := DefaultLogFilename - - switch len(args) { - case 0: - // nothing to change - case 1: - // Only an output file specified - output = args[0] - case 2, 3: - // Path scope, output file, and maybe a format specified - path = args[0] - output = args[1] - if len(args) > 2 { - format = strings.Replace(args[2], "{common}", CommonLogFormat, -1) - format = strings.Replace(format, "{combined}", CombinedLogFormat, -1) - } - default: - // Maximum number of args in log directive is 3. - return nil, c.ArgErr() - } - - rules = appendEntry(rules, path, &Entry{ - Log: &httpserver.Logger{ - Output: output, - Roller: logRoller, - }, - Format: format, - }) - } - - return rules, nil -} - -func appendEntry(rules []*Rule, pathScope string, entry *Entry) []*Rule { - for _, rule := range rules { - if rule.PathScope == pathScope { - rule.Entries = append(rule.Entries, entry) - return rules - } - } - - rules = append(rules, &Rule{ - PathScope: pathScope, - Entries: []*Entry{entry}, - }) - - return rules -} diff --git a/caddyhttp/log/setup_test.go b/caddyhttp/log/setup_test.go deleted file mode 100644 index 9ed7a76df99..00000000000 --- a/caddyhttp/log/setup_test.go +++ /dev/null @@ -1,278 +0,0 @@ -package log - -import ( - "testing" - - "reflect" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestSetup(t *testing.T) { - c := caddy.NewTestController("http", `log`) - err := setup(c) - if err != nil { - t.Errorf("Expected no errors, got: %v", err) - } - cfg := httpserver.GetConfig(c) - mids := cfg.Middleware() - if mids == nil { - t.Fatal("Expected middleware, was nil instead") - } - - handler := mids[0](httpserver.EmptyNext) - myHandler, ok := handler.(Logger) - - if !ok { - t.Fatalf("Expected handler to be type Logger, got: %#v", handler) - } - - if myHandler.Rules[0].PathScope != "/" { - t.Errorf("Expected / as the default PathScope") - } - - expectedLogger := &httpserver.Logger{ - Output: DefaultLogFilename, - Roller: httpserver.DefaultLogRoller(), - } - - if !reflect.DeepEqual(myHandler.Rules[0].Entries[0].Log, expectedLogger) { - t.Errorf("Expected %v as the default Log, got: %v", expectedLogger, myHandler.Rules[0].Entries[0].Log) - } - if myHandler.Rules[0].Entries[0].Format != DefaultLogFormat { - t.Errorf("Expected %s as the default Log Format", DefaultLogFormat) - } - if !httpserver.SameNext(myHandler.Next, httpserver.EmptyNext) { - t.Error("'Next' field of handler was not set properly") - } -} - -func TestLogParse(t *testing.T) { - tests := []struct { - inputLogRules string - shouldErr bool - expectedLogRules []Rule - }{ - {`log`, false, []Rule{{ - PathScope: "/", - Entries: []*Entry{{ - Log: &httpserver.Logger{ - Output: DefaultLogFilename, - Roller: httpserver.DefaultLogRoller(), - }, - Format: DefaultLogFormat, - }}, - }}}, - {`log log.txt`, false, []Rule{{ - PathScope: "/", - Entries: []*Entry{{ - Log: &httpserver.Logger{ - Output: "log.txt", - Roller: httpserver.DefaultLogRoller(), - }, - Format: DefaultLogFormat, - }}, - }}}, - {`log syslog://127.0.0.1:5000`, false, []Rule{{ - PathScope: "/", - Entries: []*Entry{{ - Log: &httpserver.Logger{ - Output: "syslog://127.0.0.1:5000", - Roller: httpserver.DefaultLogRoller(), - }, - Format: DefaultLogFormat, - }}, - }}}, - {`log syslog+tcp://127.0.0.1:5000`, false, []Rule{{ - PathScope: "/", - Entries: []*Entry{{ - Log: &httpserver.Logger{ - Output: "syslog+tcp://127.0.0.1:5000", - Roller: httpserver.DefaultLogRoller(), - }, - Format: DefaultLogFormat, - }}, - }}}, - {`log /api log.txt`, false, []Rule{{ - PathScope: "/api", - Entries: []*Entry{{ - Log: &httpserver.Logger{ - Output: "log.txt", - Roller: httpserver.DefaultLogRoller(), - }, - Format: DefaultLogFormat, - }}, - }}}, - {`log /serve stdout`, false, []Rule{{ - PathScope: "/serve", - Entries: []*Entry{{ - Log: &httpserver.Logger{ - Output: "stdout", - Roller: httpserver.DefaultLogRoller(), - }, - Format: DefaultLogFormat, - }}, - }}}, - {`log /myapi log.txt {common}`, false, []Rule{{ - PathScope: "/myapi", - Entries: []*Entry{{ - Log: &httpserver.Logger{ - Output: "log.txt", - Roller: httpserver.DefaultLogRoller(), - }, - Format: CommonLogFormat, - }}, - }}}, - {`log /myapi log.txt "prefix {common} suffix"`, false, []Rule{{ - PathScope: "/myapi", - Entries: []*Entry{{ - Log: &httpserver.Logger{ - Output: "log.txt", - Roller: httpserver.DefaultLogRoller(), - }, - Format: "prefix " + CommonLogFormat + " suffix", - }}, - }}}, - {`log /test accesslog.txt {combined}`, false, []Rule{{ - PathScope: "/test", - Entries: []*Entry{{ - Log: &httpserver.Logger{ - Output: "accesslog.txt", - Roller: httpserver.DefaultLogRoller(), - }, - Format: CombinedLogFormat, - }}, - }}}, - {`log /test accesslog.txt "prefix {combined} suffix"`, false, []Rule{{ - PathScope: "/test", - Entries: []*Entry{{ - Log: &httpserver.Logger{ - Output: "accesslog.txt", - Roller: httpserver.DefaultLogRoller(), - }, - Format: "prefix " + CombinedLogFormat + " suffix", - }}, - }}}, - {`log /api1 log.txt - log /api2 accesslog.txt {combined}`, false, []Rule{{ - PathScope: "/api1", - Entries: []*Entry{{ - Log: &httpserver.Logger{ - Output: "log.txt", - Roller: httpserver.DefaultLogRoller(), - }, - Format: DefaultLogFormat, - }}, - }, { - PathScope: "/api2", - Entries: []*Entry{{ - Log: &httpserver.Logger{ - Output: "accesslog.txt", - Roller: httpserver.DefaultLogRoller(), - }, - Format: CombinedLogFormat, - }}, - }}}, - {`log /api3 stdout {host} - log /api4 log.txt {when}`, false, []Rule{{ - PathScope: "/api3", - Entries: []*Entry{{ - Log: &httpserver.Logger{ - Output: "stdout", - Roller: httpserver.DefaultLogRoller(), - }, - Format: "{host}", - }}, - }, { - PathScope: "/api4", - Entries: []*Entry{{ - Log: &httpserver.Logger{ - Output: "log.txt", - Roller: httpserver.DefaultLogRoller(), - }, - Format: "{when}", - }}, - }}}, - {`log access.log { - rotate_size 2 - rotate_age 10 - rotate_keep 3 - rotate_compress - }`, false, []Rule{{ - PathScope: "/", - Entries: []*Entry{{ - Log: &httpserver.Logger{ - Output: "access.log", - Roller: &httpserver.LogRoller{ - MaxSize: 2, - MaxAge: 10, - MaxBackups: 3, - Compress: true, - LocalTime: true, - }}, - Format: DefaultLogFormat, - }}, - }}}, - {`log / stdout {host} - log / log.txt {when}`, false, []Rule{{ - PathScope: "/", - Entries: []*Entry{{ - Log: &httpserver.Logger{ - Output: "stdout", - Roller: httpserver.DefaultLogRoller(), - }, - Format: "{host}", - }, { - Log: &httpserver.Logger{ - Output: "log.txt", - Roller: httpserver.DefaultLogRoller(), - }, - Format: "{when}", - }}, - }}}, - {`log access.log { rotate_size 2 rotate_age 10 rotate_keep 3 }`, true, nil}, - {`log access.log { rotate_compress invalid }`, true, nil}, - {`log access.log { rotate_size }`, true, nil}, - {`log access.log { invalid_option 1 }`, true, nil}, - {`log / acccess.log "{remote} - [{when}] "{method} {port}" {scheme} {mitm} "`, true, nil}, - } - for i, test := range tests { - c := caddy.NewTestController("http", test.inputLogRules) - actualLogRules, err := logParse(c) - - if err == nil && test.shouldErr { - t.Errorf("Test %d didn't error, but it should have", i) - } else if err != nil && !test.shouldErr { - t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err) - } - if len(actualLogRules) != len(test.expectedLogRules) { - t.Fatalf("Test %d expected %d no of Log rules, but got %d ", - i, len(test.expectedLogRules), len(actualLogRules)) - } - for j, actualLogRule := range actualLogRules { - - if actualLogRule.PathScope != test.expectedLogRules[j].PathScope { - t.Errorf("Test %d expected %dth LogRule PathScope to be %s , but got %s", - i, j, test.expectedLogRules[j].PathScope, actualLogRule.PathScope) - } - - if got, expect := len(actualLogRule.Entries), len(test.expectedLogRules[j].Entries); got != expect { - t.Fatalf("Test %d expected %dth LogRule with %d no of Log entries, but got %d ", - i, j, expect, got) - } - - for k, actualEntry := range actualLogRule.Entries { - if !reflect.DeepEqual(actualEntry.Log, test.expectedLogRules[j].Entries[k].Log) { - t.Errorf("Test %d expected %dth LogRule Log to be %v , but got %v", - i, j, test.expectedLogRules[j].Entries[k].Log, actualEntry.Log) - } - - if actualEntry.Format != test.expectedLogRules[j].Entries[k].Format { - t.Errorf("Test %d expected %dth LogRule Format to be %s , but got %s", - i, j, test.expectedLogRules[j].Entries[k].Format, actualEntry.Format) - } - } - } - } -} diff --git a/caddyhttp/markdown/markdown.go b/caddyhttp/markdown/markdown.go deleted file mode 100644 index 12c2ec61b55..00000000000 --- a/caddyhttp/markdown/markdown.go +++ /dev/null @@ -1,173 +0,0 @@ -// Package markdown is middleware to render markdown files as HTML -// on-the-fly. -package markdown - -import ( - "net/http" - "os" - "path" - "strconv" - "strings" - "text/template" - "time" - - "github.com/mholt/caddy/caddyhttp/httpserver" - "github.com/russross/blackfriday" -) - -// Markdown implements a layer of middleware that serves -// markdown as HTML. -type Markdown struct { - // Server root - Root string - - // Jail the requests to site root with a mock file system - FileSys http.FileSystem - - // Next HTTP handler in the chain - Next httpserver.Handler - - // The list of markdown configurations - Configs []*Config -} - -// Config stores markdown middleware configurations. -type Config struct { - // Markdown renderer - Renderer blackfriday.Renderer - - // Base path to match - PathScope string - - // List of extensions to consider as markdown files - Extensions map[string]struct{} - - // List of style sheets to load for each markdown file - Styles []string - - // List of JavaScript files to load for each markdown file - Scripts []string - - // The list of index files to try - IndexFiles []string - - // Template(s) to render with - Template *template.Template - - // a pair of template's name and its underlying file path - TemplateFiles map[string]string -} - -// ServeHTTP implements the http.Handler interface. -func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - var cfg *Config - for _, c := range md.Configs { - if httpserver.Path(r.URL.Path).Matches(c.PathScope) { // not negated - cfg = c - break // or goto - } - } - if cfg == nil { - return md.Next.ServeHTTP(w, r) // exit early - } - - // We only deal with HEAD/GET - switch r.Method { - case http.MethodGet, http.MethodHead: - default: - return http.StatusMethodNotAllowed, nil - } - - var dirents []os.FileInfo - var lastModTime time.Time - fpath := r.URL.Path - if idx, ok := httpserver.IndexFile(md.FileSys, fpath, cfg.IndexFiles); ok { - // We're serving a directory index file, which may be a markdown - // file with a template. Let's grab a list of files this directory - // URL points to, and pass that in to any possible template invocations, - // so that templates can customize the look and feel of a directory. - fdp, err := md.FileSys.Open(fpath) - switch { - case err == nil: // nop - case os.IsPermission(err): - return http.StatusForbidden, err - case os.IsExist(err): - return http.StatusNotFound, nil - default: // did we run out of FD? - return http.StatusInternalServerError, err - } - defer fdp.Close() - - // Grab a possible set of directory entries. Note, we do not check - // for errors here (unreadable directory, for example). It may - // still be useful to have a directory template file, without the - // directory contents being present. Note, the directory's last - // modification is also present here (entry "."). - dirents, _ = fdp.Readdir(-1) - for _, d := range dirents { - lastModTime = latest(lastModTime, d.ModTime()) - } - - // Set path to found index file - fpath = idx - } - - // If not supported extension, pass on it - if _, ok := cfg.Extensions[path.Ext(fpath)]; !ok { - return md.Next.ServeHTTP(w, r) - } - - // At this point we have a supported extension/markdown - f, err := md.FileSys.Open(fpath) - switch { - case err == nil: // nop - case os.IsPermission(err): - return http.StatusForbidden, err - case os.IsExist(err): - return http.StatusNotFound, nil - default: // did we run out of FD? - return http.StatusInternalServerError, err - } - defer f.Close() - - fs, err := f.Stat() - if err != nil { - return http.StatusGone, nil - } - lastModTime = latest(lastModTime, fs.ModTime()) - - ctx := httpserver.NewContextWithHeader(w.Header()) - ctx.Root = md.FileSys - ctx.Req = r - ctx.URL = r.URL - html, err := cfg.Markdown(title(fpath), f, dirents, ctx) - if err != nil { - return http.StatusInternalServerError, err - } - - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.Header().Set("Content-Length", strconv.Itoa(len(html))) - httpserver.SetLastModifiedHeader(w, lastModTime) - if r.Method == http.MethodGet { - w.Write(html) - } - return http.StatusOK, nil -} - -// latest returns the latest time.Time -func latest(t ...time.Time) time.Time { - var last time.Time - - for _, tt := range t { - if tt.After(last) { - last = tt - } - } - - return last -} - -// title gives a backup generated title for a page -func title(p string) string { - return strings.TrimSuffix(path.Base(p), path.Ext(p)) -} diff --git a/caddyhttp/markdown/markdown_test.go b/caddyhttp/markdown/markdown_test.go deleted file mode 100644 index 44eb81d2e39..00000000000 --- a/caddyhttp/markdown/markdown_test.go +++ /dev/null @@ -1,251 +0,0 @@ -package markdown - -import ( - "io/ioutil" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "testing" - "text/template" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" - "github.com/russross/blackfriday" -) - -func TestMarkdown(t *testing.T) { - rootDir := "./testdata" - - f := func(filename string) string { - return filepath.ToSlash(rootDir + string(filepath.Separator) + filename) - } - - md := Markdown{ - Root: rootDir, - FileSys: http.Dir(rootDir), - Configs: []*Config{ - { - Renderer: blackfriday.HtmlRenderer(0, "", ""), - PathScope: "/blog", - Extensions: map[string]struct{}{ - ".md": {}, - }, - IndexFiles: []string{"index.md"}, - Styles: []string{}, - Scripts: []string{}, - Template: setDefaultTemplate(f("markdown_tpl.html")), - }, - { - Renderer: blackfriday.HtmlRenderer(0, "", ""), - PathScope: "/docflags", - Extensions: map[string]struct{}{ - ".md": {}, - }, - IndexFiles: []string{"index.md"}, - Styles: []string{}, - Scripts: []string{}, - Template: setDefaultTemplate(f("docflags/template.txt")), - }, - { - Renderer: blackfriday.HtmlRenderer(0, "", ""), - PathScope: "/log", - Extensions: map[string]struct{}{ - ".md": {}, - }, - IndexFiles: []string{"index.md"}, - Styles: []string{"/resources/css/log.css", "/resources/css/default.css"}, - Scripts: []string{"/resources/js/log.js", "/resources/js/default.js"}, - Template: GetDefaultTemplate(), - }, - { - Renderer: blackfriday.HtmlRenderer(0, "", ""), - PathScope: "/og", - Extensions: map[string]struct{}{ - ".md": {}, - }, - IndexFiles: []string{"index.md"}, - Styles: []string{}, - Scripts: []string{}, - Template: setDefaultTemplate(f("markdown_tpl.html")), - }, - }, - - Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - t.Fatalf("Next shouldn't be called") - return 0, nil - }), - } - - get := func(url string) string { - req, err := http.NewRequest("GET", url, nil) - if err != nil { - t.Fatalf("Could not create HTTP request: %v", err) - } - rec := httptest.NewRecorder() - code, err := md.ServeHTTP(rec, req) - if err != nil { - t.Fatal(err) - } - if code != http.StatusOK { - t.Fatalf("Wrong status, expected: %d and got %d", http.StatusOK, code) - } - return rec.Body.String() - } - - respBody := get("/blog/test.md") - expectedBody := ` - - -Markdown test 1 - - -

    Header for: Markdown test 1

    -Welcome to A Caddy website! -

    Welcome on the blog

    - -

    Body

    - -
    func getTrue() bool {
    -    return true
    -}
    -
    - - - -` - if respBody != expectedBody { - t.Fatalf("Expected body:\n%q\ngot:\n%q", expectedBody, respBody) - } - - respBody = get("/docflags/test.md") - expectedBody = `Doc.var_string hello -Doc.var_bool true -` - - if respBody != expectedBody { - t.Fatalf("Expected body:\n%q\ngot:\n%q", expectedBody, respBody) - } - - respBody = get("/log/test.md") - expectedBody = ` - - - Markdown test 2 - - - - - - - - -

    Welcome on the blog

    - -

    Body

    - -
    func getTrue() bool {
    -    return true
    -}
    -
    - - -` - - if respBody != expectedBody { - t.Fatalf("Expected body:\n%q\ngot:\n%q", expectedBody, respBody) - } - - respBody = get("/og/first.md") - expectedBody = ` - - -first_post - - -

    Header for: first_post

    -Welcome to title! -

    Test h1

    - - - -` - - if respBody != expectedBody { - t.Fatalf("Expected body:\n%q\ngot:\n%q", expectedBody, respBody) - } -} - -func setDefaultTemplate(filename string) *template.Template { - buf, err := ioutil.ReadFile(filename) - if err != nil { - return nil - } - - return template.Must(GetDefaultTemplate().Parse(string(buf))) -} - -func TestTemplateReload(t *testing.T) { - const ( - templateFile = "testdata/test.html" - targetFile = "testdata/hello.md" - ) - c := caddy.NewTestController("http", `markdown { - template `+templateFile+` - }`) - - err := ioutil.WriteFile(templateFile, []byte("hello {{.Doc.body}}"), 0644) - if err != nil { - t.Fatal(err) - } - err = ioutil.WriteFile(targetFile, []byte("caddy"), 0644) - if err != nil { - t.Fatal(err) - } - defer func() { - os.Remove(templateFile) - os.Remove(targetFile) - }() - - config, err := markdownParse(c) - if err != nil { - t.Fatal(err) - } - md := Markdown{ - Root: "./testdata", - FileSys: http.Dir("./testdata"), - Configs: config, - Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - t.Fatalf("Next shouldn't be called") - return 0, nil - }), - } - - req := httptest.NewRequest("GET", "/hello.md", nil) - get := func() string { - rec := httptest.NewRecorder() - code, err := md.ServeHTTP(rec, req) - if err != nil { - t.Fatal(err) - } - if code != http.StatusOK { - t.Fatalf("Wrong status, expected: %d and got %d", http.StatusOK, code) - } - return rec.Body.String() - } - - if expect, got := "hello

    caddy

    \n", get(); expect != got { - t.Fatalf("Expected body:\n%q\nbut got:\n%q", expect, got) - } - - // update template - err = ioutil.WriteFile(templateFile, []byte("hi {{.Doc.body}}"), 0644) - if err != nil { - t.Fatal(err) - } - - if expect, got := "hi

    caddy

    \n", get(); expect != got { - t.Fatalf("Expected body:\n%q\nbut got:\n%q", expect, got) - } - -} diff --git a/caddyhttp/markdown/metadata/metadata.go b/caddyhttp/markdown/metadata/metadata.go deleted file mode 100644 index a5e1c370685..00000000000 --- a/caddyhttp/markdown/metadata/metadata.go +++ /dev/null @@ -1,146 +0,0 @@ -package metadata - -import ( - "bufio" - "bytes" - "time" -) - -var ( - // Date format YYYY-MM-DD HH:MM:SS or YYYY-MM-DD - timeLayout = []string{ - `2006-01-02 15:04:05-0700`, - `2006-01-02 15:04:05`, - `2006-01-02`, - } -) - -// Metadata stores a page's metadata -type Metadata struct { - // Page title - Title string - - // Page template - Template string - - // Publish date - Date time.Time - - // Variables to be used with Template - Variables map[string]interface{} -} - -// NewMetadata returns a new Metadata struct, loaded with the given map -func NewMetadata(parsedMap map[string]interface{}) Metadata { - md := Metadata{ - Variables: make(map[string]interface{}), - } - md.load(parsedMap) - - return md -} - -// load loads parsed values in parsedMap into Metadata -func (m *Metadata) load(parsedMap map[string]interface{}) { - - // Pull top level things out - if title, ok := parsedMap["title"]; ok { - m.Title, _ = title.(string) - } - if template, ok := parsedMap["template"]; ok { - m.Template, _ = template.(string) - } - if date, ok := parsedMap["date"].(string); ok { - for _, layout := range timeLayout { - if t, err := time.Parse(layout, date); err == nil { - m.Date = t - break - } - } - } - - m.Variables = parsedMap -} - -// Parser is a an interface that must be satisfied by each parser -type Parser interface { - // Initialize a parser - Init(b *bytes.Buffer) bool - - // Type of metadata - Type() string - - // Parsed metadata. - Metadata() Metadata - - // Raw markdown. - Markdown() []byte -} - -// GetParser returns a parser for the given data -func GetParser(buf []byte) Parser { - for _, p := range parsers() { - b := bytes.NewBuffer(buf) - if p.Init(b) { - return p - } - } - - return nil -} - -// parsers returns all available parsers -func parsers() []Parser { - return []Parser{ - &TOMLParser{}, - &YAMLParser{}, - &JSONParser{}, - - // This one must be last - &NoneParser{}, - } -} - -// Split out prefixed/suffixed metadata with given delimiter -func splitBuffer(b *bytes.Buffer, delim string) (*bytes.Buffer, *bytes.Buffer) { - scanner := bufio.NewScanner(b) - - // Read and check first line - if !scanner.Scan() { - return nil, nil - } - if string(bytes.TrimSpace(scanner.Bytes())) != delim { - return nil, nil - } - - // Accumulate metadata, until delimiter - meta := bytes.NewBuffer(nil) - for scanner.Scan() { - if string(bytes.TrimSpace(scanner.Bytes())) == delim { - break - } - if _, err := meta.Write(scanner.Bytes()); err != nil { - return nil, nil - } - if _, err := meta.WriteRune('\n'); err != nil { - return nil, nil - } - } - // Make sure we saw closing delimiter - if string(bytes.TrimSpace(scanner.Bytes())) != delim { - return nil, nil - } - - // The rest is markdown - markdown := new(bytes.Buffer) - for scanner.Scan() { - if _, err := markdown.Write(scanner.Bytes()); err != nil { - return nil, nil - } - if _, err := markdown.WriteRune('\n'); err != nil { - return nil, nil - } - } - - return meta, markdown -} diff --git a/caddyhttp/markdown/metadata/metadata_json.go b/caddyhttp/markdown/metadata/metadata_json.go deleted file mode 100644 index 61343f287ac..00000000000 --- a/caddyhttp/markdown/metadata/metadata_json.go +++ /dev/null @@ -1,56 +0,0 @@ -package metadata - -import ( - "bytes" - "encoding/json" -) - -// JSONParser is the MetadataParser for JSON -type JSONParser struct { - metadata Metadata - markdown *bytes.Buffer -} - -// Type returns the kind of metadata parser implemented by this struct. -func (j *JSONParser) Type() string { - return "JSON" -} - -// Init prepares the metadata metadata/markdown file and parses it -func (j *JSONParser) Init(b *bytes.Buffer) bool { - m := make(map[string]interface{}) - - err := json.Unmarshal(b.Bytes(), &m) - if err != nil { - var offset int - - jerr, ok := err.(*json.SyntaxError) - if !ok { - return false - } - - offset = int(jerr.Offset) - - m = make(map[string]interface{}) - err = json.Unmarshal(b.Next(offset-1), &m) - if err != nil { - return false - } - } - - j.metadata = NewMetadata(m) - j.markdown = bytes.NewBuffer(b.Bytes()) - - return true -} - -// Metadata returns parsed metadata. It should be called -// only after a call to Parse returns without error. -func (j *JSONParser) Metadata() Metadata { - return j.metadata -} - -// Markdown returns the markdown text. It should be called only after a call to Parse returns without error. -func (j *JSONParser) Markdown() []byte { - return j.markdown.Bytes() -} diff --git a/caddyhttp/markdown/metadata/metadata_none.go b/caddyhttp/markdown/metadata/metadata_none.go deleted file mode 100644 index e87bfb43e69..00000000000 --- a/caddyhttp/markdown/metadata/metadata_none.go +++ /dev/null @@ -1,42 +0,0 @@ -package metadata - -import ( - "bytes" -) - -// NoneParser is the parser for plaintext markdown with no metadata. -type NoneParser struct { - metadata Metadata - markdown *bytes.Buffer -} - -// Type returns the kind of parser this struct is. -func (n *NoneParser) Type() string { - return "None" -} - -// Init prepases and parses the metadata and markdown file -func (n *NoneParser) Init(b *bytes.Buffer) bool { - m := make(map[string]interface{}) - n.metadata = NewMetadata(m) - n.markdown = bytes.NewBuffer(b.Bytes()) - - return true -} - -// Parse the metadata -func (n *NoneParser) Parse(b []byte) ([]byte, error) { - return nil, nil -} - -// Metadata returns parsed metadata. It should be called -// only after a call to Parse returns without error. -func (n *NoneParser) Metadata() Metadata { - return n.metadata -} - -// Markdown returns parsed markdown. It should be called -// only after a call to Parse returns without error. -func (n *NoneParser) Markdown() []byte { - return n.markdown.Bytes() -} diff --git a/caddyhttp/markdown/metadata/metadata_test.go b/caddyhttp/markdown/metadata/metadata_test.go deleted file mode 100644 index a5663d55f8c..00000000000 --- a/caddyhttp/markdown/metadata/metadata_test.go +++ /dev/null @@ -1,283 +0,0 @@ -package metadata - -import ( - "bytes" - "fmt" - "strings" - "testing" -) - -func check(t *testing.T, err error) { - if err != nil { - t.Fatal(err) - } -} - -var TOML = [5]string{` -title = "A title" -template = "default" -name = "value" -positive = true -negative = false -number = 1410 -float = 1410.07 -`, - `+++ -title = "A title" -template = "default" -name = "value" -positive = true -negative = false -number = 1410 -float = 1410.07 -+++ -Page content - `, - `+++ -title = "A title" -template = "default" -name = "value" -positive = true -negative = false -number = 1410 -float = 1410.07 - `, - `title = "A title" template = "default" [variables] name = "value"`, - `+++ -title = "A title" -template = "default" -name = "value" -positive = true -negative = false -number = 1410 -float = 1410.07 -+++ -`, -} - -var YAML = [5]string{` -title : A title -template : default -name : value -positive : true -negative : false -number : 1410 -float : 1410.07 -`, - `--- -title : A title -template : default -name : value -positive : true -negative : false -number : 1410 -float : 1410.07 ---- - Page content - `, - `--- -title : A title -template : default -name : value -number : 1410 -float : 1410.07 - `, - `title : A title template : default variables : name : value : positive : true : negative : false`, - `--- -title : A title -template : default -name : value -positive : true -negative : false -number : 1410 -float : 1410.07 ---- -`, -} - -var JSON = [5]string{` - "title" : "A title", - "template" : "default", - "name" : "value", - "positive" : true, - "negative" : false, - "number": 1410, - "float": 1410.07 -`, - `{ - "title" : "A title", - "template" : "default", - "name" : "value", - "positive" : true, - "negative" : false, - "number" : 1410, - "float": 1410.07 -} -Page content - `, - ` -{ - "title" : "A title", - "template" : "default", - "name" : "value", - "positive" : true, - "negative" : false, - "number" : 1410, - "float": 1410.07 - `, - ` -{ - "title" :: "A title", - "template" : "default", - "name" : "value", - "positive" : true, - "negative" : false, - "number" : 1410, - "float": 1410.07 -} - `, - `{ - "title" : "A title", - "template" : "default", - "name" : "value", - "positive" : true, - "negative" : false, - "number" : 1410, - "float": 1410.07 -} -`, -} - -func TestParsers(t *testing.T) { - expected := Metadata{ - Title: "A title", - Template: "default", - Variables: map[string]interface{}{ - "name": "value", - "title": "A title", - "template": "default", - "number": 1410, - "float": 1410.07, - "positive": true, - "negative": false, - }, - } - compare := func(m Metadata) bool { - if m.Title != expected.Title { - return false - } - if m.Template != expected.Template { - return false - } - for k, v := range m.Variables { - if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", expected.Variables[k]) { - return false - } - } - - varLenOK := len(m.Variables) == len(expected.Variables) - return varLenOK - } - - data := []struct { - parser Parser - testData [5]string - name string - }{ - {&JSONParser{}, JSON, "JSON"}, - {&YAMLParser{}, YAML, "YAML"}, - {&TOMLParser{}, TOML, "TOML"}, - } - - for _, v := range data { - // metadata without identifiers - if v.parser.Init(bytes.NewBufferString(v.testData[0])) { - t.Fatalf("Expected error for invalid metadata for %v", v.name) - } - - // metadata with identifiers - if !v.parser.Init(bytes.NewBufferString(v.testData[1])) { - t.Fatalf("Metadata failed to initialize, type %v", v.parser.Type()) - } - md := v.parser.Markdown() - if !compare(v.parser.Metadata()) { - t.Fatalf("Expected %v, found %v for %v", expected, v.parser.Metadata(), v.name) - } - if "Page content" != strings.TrimSpace(string(md)) { - t.Fatalf("Expected %v, found %v for %v", "Page content", string(md), v.name) - } - // Check that we find the correct metadata parser type - if p := GetParser([]byte(v.testData[1])); p.Type() != v.name { - t.Fatalf("Wrong parser found, expected %v, found %v", v.name, p.Type()) - } - - // metadata without closing identifier - if v.parser.Init(bytes.NewBufferString(v.testData[2])) { - t.Fatalf("Expected error for missing closing identifier for %v parser", v.name) - } - - // invalid metadata - if v.parser.Init(bytes.NewBufferString(v.testData[3])) { - t.Fatalf("Expected error for invalid metadata for %v", v.name) - } - - // front matter but no body - if !v.parser.Init(bytes.NewBufferString(v.testData[4])) { - t.Fatalf("Unexpected error for valid metadata but no body for %v", v.name) - } - } -} - -func TestLargeBody(t *testing.T) { - - var JSON = `{ -"template": "chapter" -} - -Mycket olika byggnader har man i de nordiska rikena: pyramidformiga, kilformiga, välvda, runda och fyrkantiga. De pyramidformiga består helt enkelt av träribbor, som upptill löper samman och nedtill bildar en vidare krets; de är avsedda att användas av hantverkarna under sommaren, för att de inte ska plågas av solen, på samma gång som de besväras av rök och eld. De kilformiga husen är i regel försedda med höga tak, för att de täta och tunga snömassorna fortare ska kunna blåsa av och inte tynga ned taken. Dessa är täckta av björknäver, tegel eller kluvet spån av furu - för kådans skull -, gran, ek eller bok; taken på de förmögnas hus däremot med plåtar av koppar eller bly, i likhet med kyrktaken. Valvbyggnaderna uppförs ganska konstnärligt till skydd mot våldsamma vindar och snöfall, görs av sten eller trä, och är avsedda för olika alldagliga viktiga ändamål. Liknande byggnader kan finnas i stormännens gårdar där de används som förvaringsrum för husgeråd och jordbruksredskap. De runda byggnaderna - som för övrigt är de högst sällsynta - används av konstnärer, som vid sitt arbete behöver ett jämnt fördelat ljus från taket. Vanligast är de fyrkantiga husen, vars grova bjälkar är synnerligen väl hopfogade i hörnen - ett sant mästerverk av byggnadskonst; även dessa har fönster högt uppe i taken, för att dagsljuset skall kunna strömma in och ge alla därinne full belysning. Stenhusen har dörröppningar i förhållande till byggnadens storlek, men smala fönstergluggar, som skydd mot den stränga kölden, frosten och snön. Vore de större och vidare, såsom fönstren i Italien, skulle husen i följd av den fint yrande snön, som röres upp av den starka blåsten, precis som dammet av virvelvinden, snart nog fyllas med massor av snö och inte kunna stå emot dess tryck, utan störta samman. - - ` - var TOML = `+++ -template = "chapter" -+++ - -Mycket olika byggnader har man i de nordiska rikena: pyramidformiga, kilformiga, välvda, runda och fyrkantiga. De pyramidformiga består helt enkelt av träribbor, som upptill löper samman och nedtill bildar en vidare krets; de är avsedda att användas av hantverkarna under sommaren, för att de inte ska plågas av solen, på samma gång som de besväras av rök och eld. De kilformiga husen är i regel försedda med höga tak, för att de täta och tunga snömassorna fortare ska kunna blåsa av och inte tynga ned taken. Dessa är täckta av björknäver, tegel eller kluvet spån av furu - för kådans skull -, gran, ek eller bok; taken på de förmögnas hus däremot med plåtar av koppar eller bly, i likhet med kyrktaken. Valvbyggnaderna uppförs ganska konstnärligt till skydd mot våldsamma vindar och snöfall, görs av sten eller trä, och är avsedda för olika alldagliga viktiga ändamål. Liknande byggnader kan finnas i stormännens gårdar där de används som förvaringsrum för husgeråd och jordbruksredskap. De runda byggnaderna - som för övrigt är de högst sällsynta - används av konstnärer, som vid sitt arbete behöver ett jämnt fördelat ljus från taket. Vanligast är de fyrkantiga husen, vars grova bjälkar är synnerligen väl hopfogade i hörnen - ett sant mästerverk av byggnadskonst; även dessa har fönster högt uppe i taken, för att dagsljuset skall kunna strömma in och ge alla därinne full belysning. Stenhusen har dörröppningar i förhållande till byggnadens storlek, men smala fönstergluggar, som skydd mot den stränga kölden, frosten och snön. Vore de större och vidare, såsom fönstren i Italien, skulle husen i följd av den fint yrande snön, som röres upp av den starka blåsten, precis som dammet av virvelvinden, snart nog fyllas med massor av snö och inte kunna stå emot dess tryck, utan störta samman. - - ` - var YAML = `--- -template : chapter ---- - -Mycket olika byggnader har man i de nordiska rikena: pyramidformiga, kilformiga, välvda, runda och fyrkantiga. De pyramidformiga består helt enkelt av träribbor, som upptill löper samman och nedtill bildar en vidare krets; de är avsedda att användas av hantverkarna under sommaren, för att de inte ska plågas av solen, på samma gång som de besväras av rök och eld. De kilformiga husen är i regel försedda med höga tak, för att de täta och tunga snömassorna fortare ska kunna blåsa av och inte tynga ned taken. Dessa är täckta av björknäver, tegel eller kluvet spån av furu - för kådans skull -, gran, ek eller bok; taken på de förmögnas hus däremot med plåtar av koppar eller bly, i likhet med kyrktaken. Valvbyggnaderna uppförs ganska konstnärligt till skydd mot våldsamma vindar och snöfall, görs av sten eller trä, och är avsedda för olika alldagliga viktiga ändamål. Liknande byggnader kan finnas i stormännens gårdar där de används som förvaringsrum för husgeråd och jordbruksredskap. De runda byggnaderna - som för övrigt är de högst sällsynta - används av konstnärer, som vid sitt arbete behöver ett jämnt fördelat ljus från taket. Vanligast är de fyrkantiga husen, vars grova bjälkar är synnerligen väl hopfogade i hörnen - ett sant mästerverk av byggnadskonst; även dessa har fönster högt uppe i taken, för att dagsljuset skall kunna strömma in och ge alla därinne full belysning. Stenhusen har dörröppningar i förhållande till byggnadens storlek, men smala fönstergluggar, som skydd mot den stränga kölden, frosten och snön. Vore de större och vidare, såsom fönstren i Italien, skulle husen i följd av den fint yrande snön, som röres upp av den starka blåsten, precis som dammet av virvelvinden, snart nog fyllas med massor av snö och inte kunna stå emot dess tryck, utan störta samman. - - ` - var NONE = ` - -Mycket olika byggnader har man i de nordiska rikena: pyramidformiga, kilformiga, välvda, runda och fyrkantiga. De pyramidformiga består helt enkelt av träribbor, som upptill löper samman och nedtill bildar en vidare krets; de är avsedda att användas av hantverkarna under sommaren, för att de inte ska plågas av solen, på samma gång som de besväras av rök och eld. De kilformiga husen är i regel försedda med höga tak, för att de täta och tunga snömassorna fortare ska kunna blåsa av och inte tynga ned taken. Dessa är täckta av björknäver, tegel eller kluvet spån av furu - för kådans skull -, gran, ek eller bok; taken på de förmögnas hus däremot med plåtar av koppar eller bly, i likhet med kyrktaken. Valvbyggnaderna uppförs ganska konstnärligt till skydd mot våldsamma vindar och snöfall, görs av sten eller trä, och är avsedda för olika alldagliga viktiga ändamål. Liknande byggnader kan finnas i stormännens gårdar där de används som förvaringsrum för husgeråd och jordbruksredskap. De runda byggnaderna - som för övrigt är de högst sällsynta - används av konstnärer, som vid sitt arbete behöver ett jämnt fördelat ljus från taket. Vanligast är de fyrkantiga husen, vars grova bjälkar är synnerligen väl hopfogade i hörnen - ett sant mästerverk av byggnadskonst; även dessa har fönster högt uppe i taken, för att dagsljuset skall kunna strömma in och ge alla därinne full belysning. Stenhusen har dörröppningar i förhållande till byggnadens storlek, men smala fönstergluggar, som skydd mot den stränga kölden, frosten och snön. Vore de större och vidare, såsom fönstren i Italien, skulle husen i följd av den fint yrande snön, som röres upp av den starka blåsten, precis som dammet av virvelvinden, snart nog fyllas med massor av snö och inte kunna stå emot dess tryck, utan störta samman. - - ` - var expectedBody = `Mycket olika byggnader har man i de nordiska rikena: pyramidformiga, kilformiga, välvda, runda och fyrkantiga. De pyramidformiga består helt enkelt av träribbor, som upptill löper samman och nedtill bildar en vidare krets; de är avsedda att användas av hantverkarna under sommaren, för att de inte ska plågas av solen, på samma gång som de besväras av rök och eld. De kilformiga husen är i regel försedda med höga tak, för att de täta och tunga snömassorna fortare ska kunna blåsa av och inte tynga ned taken. Dessa är täckta av björknäver, tegel eller kluvet spån av furu - för kådans skull -, gran, ek eller bok; taken på de förmögnas hus däremot med plåtar av koppar eller bly, i likhet med kyrktaken. Valvbyggnaderna uppförs ganska konstnärligt till skydd mot våldsamma vindar och snöfall, görs av sten eller trä, och är avsedda för olika alldagliga viktiga ändamål. Liknande byggnader kan finnas i stormännens gårdar där de används som förvaringsrum för husgeråd och jordbruksredskap. De runda byggnaderna - som för övrigt är de högst sällsynta - används av konstnärer, som vid sitt arbete behöver ett jämnt fördelat ljus från taket. Vanligast är de fyrkantiga husen, vars grova bjälkar är synnerligen väl hopfogade i hörnen - ett sant mästerverk av byggnadskonst; även dessa har fönster högt uppe i taken, för att dagsljuset skall kunna strömma in och ge alla därinne full belysning. Stenhusen har dörröppningar i förhållande till byggnadens storlek, men smala fönstergluggar, som skydd mot den stränga kölden, frosten och snön. Vore de större och vidare, såsom fönstren i Italien, skulle husen i följd av den fint yrande snön, som röres upp av den starka blåsten, precis som dammet av virvelvinden, snart nog fyllas med massor av snö och inte kunna stå emot dess tryck, utan störta samman. -` - - data := []struct { - pType string - testData string - }{ - {"JSON", JSON}, - {"TOML", TOML}, - {"YAML", YAML}, - {"None", NONE}, - } - for _, v := range data { - p := GetParser([]byte(v.testData)) - if v.pType != p.Type() { - t.Fatalf("Wrong parser type, expected %v, got %v", v.pType, p.Type()) - } - md := p.Markdown() - if strings.TrimSpace(string(md)) != strings.TrimSpace(expectedBody) { - t.Log("Provided:", v.testData) - t.Log("Returned:", p.Markdown()) - t.Fatalf("Error, mismatched body in expected type %v, matched type %v", v.pType, p.Type()) - } - } -} diff --git a/caddyhttp/markdown/metadata/metadata_toml.go b/caddyhttp/markdown/metadata/metadata_toml.go deleted file mode 100644 index 8f8800b2e5a..00000000000 --- a/caddyhttp/markdown/metadata/metadata_toml.go +++ /dev/null @@ -1,46 +0,0 @@ -package metadata - -import ( - "bytes" - - "github.com/naoina/toml" -) - -// TOMLParser is the Parser for TOML -type TOMLParser struct { - metadata Metadata - markdown *bytes.Buffer -} - -// Type returns the kind of parser this struct is. -func (t *TOMLParser) Type() string { - return "TOML" -} - -// Init prepares and parses the metadata and markdown file itself -func (t *TOMLParser) Init(b *bytes.Buffer) bool { - meta, data := splitBuffer(b, "+++") - if meta == nil || data == nil { - return false - } - t.markdown = data - - m := make(map[string]interface{}) - if err := toml.Unmarshal(meta.Bytes(), &m); err != nil { - return false - } - t.metadata = NewMetadata(m) - - return true -} - -// Metadata returns parsed metadata. It should be called -// only after a call to Parse returns without error. -func (t *TOMLParser) Metadata() Metadata { - return t.metadata -} - -// Markdown returns parser markdown. It should be called only after a call to Parse returns without error. -func (t *TOMLParser) Markdown() []byte { - return t.markdown.Bytes() -} diff --git a/caddyhttp/markdown/metadata/metadata_yaml.go b/caddyhttp/markdown/metadata/metadata_yaml.go deleted file mode 100644 index f8e9acfd342..00000000000 --- a/caddyhttp/markdown/metadata/metadata_yaml.go +++ /dev/null @@ -1,46 +0,0 @@ -package metadata - -import ( - "bytes" - - "gopkg.in/yaml.v2" -) - -// YAMLParser is the Parser for YAML -type YAMLParser struct { - metadata Metadata - markdown *bytes.Buffer -} - -// Type returns the kind of metadata parser. -func (y *YAMLParser) Type() string { - return "YAML" -} - -// Init prepares the metadata parser for parsing. -func (y *YAMLParser) Init(b *bytes.Buffer) bool { - meta, data := splitBuffer(b, "---") - if meta == nil || data == nil { - return false - } - y.markdown = data - - m := make(map[string]interface{}) - if err := yaml.Unmarshal(meta.Bytes(), &m); err != nil { - return false - } - y.metadata = NewMetadata(m) - - return true -} - -// Metadata returns parsed metadata. It should be called -// only after a call to Parse returns without error. -func (y *YAMLParser) Metadata() Metadata { - return y.metadata -} - -// Markdown renders the text as a byte array -func (y *YAMLParser) Markdown() []byte { - return y.markdown.Bytes() -} diff --git a/caddyhttp/markdown/process.go b/caddyhttp/markdown/process.go deleted file mode 100644 index 7d4f85ebcfd..00000000000 --- a/caddyhttp/markdown/process.go +++ /dev/null @@ -1,92 +0,0 @@ -package markdown - -import ( - "io" - "io/ioutil" - "os" - - "github.com/mholt/caddy/caddyhttp/httpserver" - "github.com/mholt/caddy/caddyhttp/markdown/metadata" - "github.com/mholt/caddy/caddyhttp/markdown/summary" - "github.com/russross/blackfriday" -) - -// FileInfo represents a file in a particular server context. It wraps the os.FileInfo struct. -type FileInfo struct { - os.FileInfo - ctx httpserver.Context -} - -var recognizedMetaTags = []string{ - "author", - "copyright", - "description", - "subject", -} - -// Summarize returns an abbreviated string representation of the markdown stored in this file. -// wordcount is the number of words returned in the summary. -func (f FileInfo) Summarize(wordcount int) (string, error) { - fp, err := f.ctx.Root.Open(f.Name()) - if err != nil { - return "", err - } - defer fp.Close() - - buf, err := ioutil.ReadAll(fp) - if err != nil { - return "", err - } - - return string(summary.Markdown(buf, wordcount)), nil -} - -// Markdown processes the contents of a page in b. It parses the metadata -// (if any) and uses the template (if found). -func (c *Config) Markdown(title string, r io.Reader, dirents []os.FileInfo, ctx httpserver.Context) ([]byte, error) { - body, err := ioutil.ReadAll(r) - if err != nil { - return nil, err - } - - parser := metadata.GetParser(body) - markdown := parser.Markdown() - mdata := parser.Metadata() - - // process markdown - extns := 0 - extns |= blackfriday.EXTENSION_TABLES - extns |= blackfriday.EXTENSION_FENCED_CODE - extns |= blackfriday.EXTENSION_STRIKETHROUGH - extns |= blackfriday.EXTENSION_DEFINITION_LISTS - html := blackfriday.Markdown(markdown, c.Renderer, extns) - - // set it as body for template - mdata.Variables["body"] = string(html) - - // fixup title - mdata.Variables["title"] = mdata.Title - if mdata.Variables["title"] == "" { - mdata.Variables["title"] = title - } - - // move available and valid front matters to the meta values - meta := make(map[string]string) - for _, val := range recognizedMetaTags { - if mVal, ok := mdata.Variables[val]; ok { - meta[val] = mVal.(string) - } - } - - // massage possible files - files := []FileInfo{} - for _, ent := range dirents { - file := FileInfo{ - FileInfo: ent, - ctx: ctx, - } - files = append(files, file) - } - - return execTemplate(c, mdata, meta, files, ctx) -} diff --git a/caddyhttp/markdown/process_test.go b/caddyhttp/markdown/process_test.go deleted file mode 100644 index fbafaf989c3..00000000000 --- a/caddyhttp/markdown/process_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package markdown - -import ( - "os" - "strings" - "testing" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestConfig_Markdown(t *testing.T) { - tests := []map[string]string{ - {"author": "authorVal"}, - {"copyright": "copyrightVal"}, - {"description": "descriptionVal"}, - {"subject": "subjectVal"}, - {"author": "authorVal", "copyright": "copyrightVal"}, - {"author": "authorVal", "copyright": "copyrightVal", "description": "descriptionVal"}, - {"author": "authorVal", "copyright": "copyrightVal", "description": "descriptionVal", "subject": "subjectVal"}, - } - - for i, meta := range tests { - config := &Config{ - Template: GetDefaultTemplate(), - } - - toml := "+++" - for key, val := range meta { - toml = toml + "\n" + key + "= \"" + val + "\"" - } - toml = toml + "\n+++" - - res, _ := config.Markdown("Test title", strings.NewReader(toml), []os.FileInfo{}, httpserver.Context{}) - sRes := string(res) - - for key, val := range meta { - c := strings.Contains(sRes, "") - if !c { - t.Error("Test case", i, "should contain meta", key, val) - } - } - } -} diff --git a/caddyhttp/markdown/setup.go b/caddyhttp/markdown/setup.go deleted file mode 100644 index 8792f85bc89..00000000000 --- a/caddyhttp/markdown/setup.go +++ /dev/null @@ -1,159 +0,0 @@ -package markdown - -import ( - "net/http" - "path/filepath" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" - "github.com/russross/blackfriday" -) - -func init() { - caddy.RegisterPlugin("markdown", caddy.Plugin{ - ServerType: "http", - Action: setup, - }) -} - -// setup configures a new Markdown middleware instance. -func setup(c *caddy.Controller) error { - mdconfigs, err := markdownParse(c) - if err != nil { - return err - } - - cfg := httpserver.GetConfig(c) - - md := Markdown{ - Root: cfg.Root, - FileSys: http.Dir(cfg.Root), - Configs: mdconfigs, - } - - cfg.AddMiddleware(func(next httpserver.Handler) httpserver.Handler { - md.Next = next - return md - }) - - return nil -} - -func markdownParse(c *caddy.Controller) ([]*Config, error) { - var mdconfigs []*Config - - for c.Next() { - md := &Config{ - Renderer: blackfriday.HtmlRenderer(0, "", ""), - Extensions: make(map[string]struct{}), - Template: GetDefaultTemplate(), - IndexFiles: []string{}, - TemplateFiles: make(map[string]string), - } - - // Get the path scope - args := c.RemainingArgs() - switch len(args) { - case 0: - md.PathScope = "/" - case 1: - md.PathScope = args[0] - default: - return mdconfigs, c.ArgErr() - } - - // Load any other configuration parameters - for c.NextBlock() { - if err := loadParams(c, md); err != nil { - return mdconfigs, err - } - } - - // If no extensions were specified, assume some defaults - if len(md.Extensions) == 0 { - md.Extensions[".md"] = struct{}{} - md.Extensions[".markdown"] = struct{}{} - md.Extensions[".mdown"] = struct{}{} - } - - // Make a list of index files to match extensions - for ext := range md.Extensions { - md.IndexFiles = append(md.IndexFiles, "index"+ext) - } - mdconfigs = append(mdconfigs, md) - } - - return mdconfigs, nil -} - -func loadParams(c *caddy.Controller, mdc *Config) error { - cfg := httpserver.GetConfig(c) - - switch c.Val() { - case "ext": - for _, ext := range c.RemainingArgs() { - mdc.Extensions[ext] = struct{}{} - } - return nil - case "css": - if !c.NextArg() { - return c.ArgErr() - } - mdc.Styles = append(mdc.Styles, c.Val()) - return nil - case "js": - if !c.NextArg() { - return c.ArgErr() - } - mdc.Scripts = append(mdc.Scripts, c.Val()) - return nil - case "template": - tArgs := c.RemainingArgs() - switch len(tArgs) { - default: - return c.ArgErr() - case 1: - fpath := filepath.ToSlash(filepath.Clean(cfg.Root + string(filepath.Separator) + tArgs[0])) - - if err := SetTemplate(mdc.Template, "", fpath); err != nil { - return c.Errf("default template parse error: %v", err) - } - - mdc.TemplateFiles[""] = fpath - return nil - case 2: - fpath := filepath.ToSlash(filepath.Clean(cfg.Root + string(filepath.Separator) + tArgs[1])) - - if err := SetTemplate(mdc.Template, tArgs[0], fpath); err != nil { - return c.Errf("template parse error: %v", err) - } - - mdc.TemplateFiles[tArgs[0]] = fpath - return nil - } - case "templatedir": - if !c.NextArg() { - return c.ArgErr() - } - - pattern := c.Val() - _, err := mdc.Template.ParseGlob(pattern) - if err != nil { - return c.Errf("template load error: %v", err) - } - if c.NextArg() { - return c.ArgErr() - } - - paths, err := filepath.Glob(pattern) - if err != nil { - return c.Errf("glob %q failed: %v", pattern, err) - } - for _, path := range paths { - mdc.TemplateFiles[filepath.Base(path)] = path - } - return nil - default: - return c.Err("Expected valid markdown configuration property") - } -} diff --git a/caddyhttp/markdown/setup_test.go b/caddyhttp/markdown/setup_test.go deleted file mode 100644 index 596ad84d2ec..00000000000 --- a/caddyhttp/markdown/setup_test.go +++ /dev/null @@ -1,158 +0,0 @@ -package markdown - -import ( - "bytes" - "fmt" - "net/http" - "reflect" - "testing" - "text/template" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestSetup(t *testing.T) { - c := caddy.NewTestController("http", `markdown /blog`) - err := setup(c) - if err != nil { - t.Errorf("Expected no errors, 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.(Markdown) - - if !ok { - t.Fatalf("Expected handler to be type Markdown, got: %#v", handler) - } - - if myHandler.Configs[0].PathScope != "/blog" { - t.Errorf("Expected /blog as the Path Scope") - } - if len(myHandler.Configs[0].Extensions) != 3 { - t.Error("Expected 3 markdown extensions") - } - for _, key := range []string{".md", ".markdown", ".mdown"} { - if ext, ok := myHandler.Configs[0].Extensions[key]; !ok { - t.Errorf("Expected extensions to contain %v", ext) - } - } -} - -func TestMarkdownParse(t *testing.T) { - tests := []struct { - inputMarkdownConfig string - shouldErr bool - expectedMarkdownConfig []Config - }{ - - {`markdown /blog { - ext .md .txt - css /resources/css/blog.css - js /resources/js/blog.js -}`, false, []Config{{ - PathScope: "/blog", - Extensions: map[string]struct{}{ - ".md": {}, - ".txt": {}, - }, - Styles: []string{"/resources/css/blog.css"}, - Scripts: []string{"/resources/js/blog.js"}, - Template: GetDefaultTemplate(), - TemplateFiles: make(map[string]string), - }}}, - {`markdown /blog { - ext .md - template tpl_with_include.html -}`, false, []Config{{ - PathScope: "/blog", - Extensions: map[string]struct{}{ - ".md": {}, - }, - Template: setDefaultTemplate("./testdata/tpl_with_include.html"), - TemplateFiles: map[string]string{ - "": "testdata/tpl_with_include.html", - }, - }}}, - } - - for i, test := range tests { - c := caddy.NewTestController("http", test.inputMarkdownConfig) - httpserver.GetConfig(c).Root = "./testdata" - actualMarkdownConfigs, err := markdownParse(c) - - if err == nil && test.shouldErr { - t.Errorf("Test %d didn't error, but it should have", i) - } else if err != nil && !test.shouldErr { - t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err) - } - if len(actualMarkdownConfigs) != len(test.expectedMarkdownConfig) { - t.Fatalf("Test %d expected %d no of WebSocket configs, but got %d ", - i, len(test.expectedMarkdownConfig), len(actualMarkdownConfigs)) - } - for j, actualMarkdownConfig := range actualMarkdownConfigs { - - if actualMarkdownConfig.PathScope != test.expectedMarkdownConfig[j].PathScope { - t.Errorf("Test %d expected %dth Markdown PathScope to be %s , but got %s", - i, j, test.expectedMarkdownConfig[j].PathScope, actualMarkdownConfig.PathScope) - } - - if fmt.Sprint(actualMarkdownConfig.Styles) != fmt.Sprint(test.expectedMarkdownConfig[j].Styles) { - t.Errorf("Test %d expected %dth Markdown Config Styles to be %s , but got %s", - i, j, fmt.Sprint(test.expectedMarkdownConfig[j].Styles), fmt.Sprint(actualMarkdownConfig.Styles)) - } - if fmt.Sprint(actualMarkdownConfig.Scripts) != fmt.Sprint(test.expectedMarkdownConfig[j].Scripts) { - t.Errorf("Test %d expected %dth Markdown Config Scripts to be %s , but got %s", - i, j, fmt.Sprint(test.expectedMarkdownConfig[j].Scripts), fmt.Sprint(actualMarkdownConfig.Scripts)) - } - if ok, tx, ty := equalTemplates(actualMarkdownConfig.Template, test.expectedMarkdownConfig[j].Template); !ok { - t.Errorf("Test %d the %dth Markdown Config Templates did not match, expected %s to be %s", i, j, tx, ty) - } - if expect, got := test.expectedMarkdownConfig[j].TemplateFiles, actualMarkdownConfig.TemplateFiles; !reflect.DeepEqual(expect, got) { - t.Errorf("Test %d the %d Markdown config TemplateFiles did not match, expect %v, but got %v", i, j, expect, got) - } - - } - } -} - -func equalTemplates(i, j *template.Template) (bool, string, string) { - // Just in case :) - if i == j { - return true, "", "" - } - - // We can't do much here, templates can't really be compared. However, - // we can execute the templates and compare their outputs to be reasonably - // sure that they're the same. - - // This is exceedingly ugly. - ctx := httpserver.Context{ - Root: http.Dir("./testdata"), - } - - md := Data{ - Context: ctx, - Doc: make(map[string]interface{}), - Styles: []string{"style1"}, - Scripts: []string{"js1"}, - } - md.Doc["title"] = "some title" - md.Doc["body"] = "some body" - - bufi := new(bytes.Buffer) - bufj := new(bytes.Buffer) - - if err := i.Execute(bufi, md); err != nil { - return false, fmt.Sprintf("%v", err), "" - } - if err := j.Execute(bufj, md); err != nil { - return false, "", fmt.Sprintf("%v", err) - } - - return bytes.Equal(bufi.Bytes(), bufj.Bytes()), string(bufi.Bytes()), string(bufj.Bytes()) -} diff --git a/caddyhttp/markdown/summary/render.go b/caddyhttp/markdown/summary/render.go deleted file mode 100644 index b23affbd189..00000000000 --- a/caddyhttp/markdown/summary/render.go +++ /dev/null @@ -1,153 +0,0 @@ -package summary - -import ( - "bytes" - - "github.com/russross/blackfriday" -) - -// Ensure we implement the Blackfriday Markdown Renderer interface -var _ blackfriday.Renderer = (*renderer)(nil) - -// renderer renders Markdown to plain-text meant for listings and excerpts, -// and implements the blackfriday.Renderer interface. -// -// Many of the methods are stubs with no output to prevent output of HTML markup. -type renderer struct{} - -// Blocklevel callbacks - -// BlockCode is the code tag callback. -func (r renderer) BlockCode(out *bytes.Buffer, text []byte, land string) {} - -// BlockQuote is the quote tag callback. -func (r renderer) BlockQuote(out *bytes.Buffer, text []byte) {} - -// BlockHtml is the HTML tag callback. -func (r renderer) BlockHtml(out *bytes.Buffer, text []byte) {} - -// Header is the header tag callback. -func (r renderer) Header(out *bytes.Buffer, text func() bool, level int, id string) {} - -// HRule is the horizontal rule tag callback. -func (r renderer) HRule(out *bytes.Buffer) {} - -// List is the list tag callback. -func (r renderer) List(out *bytes.Buffer, text func() bool, flags int) { - // TODO: This is not desired (we'd rather not write lists as part of summary), - // but see this issue: https://github.com/russross/blackfriday/issues/189 - marker := out.Len() - if !text() { - out.Truncate(marker) - } - out.Write([]byte{' '}) -} - -// ListItem is the list item tag callback. -func (r renderer) ListItem(out *bytes.Buffer, text []byte, flags int) {} - -// Paragraph is the paragraph tag callback. This renders simple paragraph text -// into plain text, such that summaries can be easily generated. -func (r renderer) Paragraph(out *bytes.Buffer, text func() bool) { - marker := out.Len() - if !text() { - out.Truncate(marker) - } - out.Write([]byte{' '}) -} - -// Table is the table tag callback. -func (r renderer) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {} - -// TableRow is the table row tag callback. -func (r renderer) TableRow(out *bytes.Buffer, text []byte) {} - -// TableHeaderCell is the table header cell tag callback. -func (r renderer) TableHeaderCell(out *bytes.Buffer, text []byte, flags int) {} - -// TableCell is the table cell tag callback. -func (r renderer) TableCell(out *bytes.Buffer, text []byte, flags int) {} - -// Footnotes is the foot notes tag callback. -func (r renderer) Footnotes(out *bytes.Buffer, text func() bool) {} - -// FootnoteItem is the footnote item tag callback. -func (r renderer) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {} - -// TitleBlock is the title tag callback. -func (r renderer) TitleBlock(out *bytes.Buffer, text []byte) {} - -// Spanlevel callbacks - -// AutoLink is the autolink tag callback. -func (r renderer) AutoLink(out *bytes.Buffer, link []byte, kind int) {} - -// CodeSpan is the code span tag callback. Outputs a simple Markdown version -// of the code span. -func (r renderer) CodeSpan(out *bytes.Buffer, text []byte) { - out.Write([]byte("`")) - out.Write(text) - out.Write([]byte("`")) -} - -// DoubleEmphasis is the double emphasis tag callback. Outputs a simple -// plain-text version of the input. -func (r renderer) DoubleEmphasis(out *bytes.Buffer, text []byte) { - out.Write(text) -} - -// Emphasis is the emphasis tag callback. Outputs a simple plain-text -// version of the input. -func (r renderer) Emphasis(out *bytes.Buffer, text []byte) { - out.Write(text) -} - -// Image is the image tag callback. -func (r renderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {} - -// LineBreak is the line break tag callback. -func (r renderer) LineBreak(out *bytes.Buffer) {} - -// Link is the link tag callback. Outputs a sipmle plain-text version -// of the input. -func (r renderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) { - out.Write(content) -} - -// RawHtmlTag is the raw HTML tag callback. -func (r renderer) RawHtmlTag(out *bytes.Buffer, tag []byte) {} - -// TripleEmphasis is the triple emphasis tag callback. Outputs a simple plain-text -// version of the input. -func (r renderer) TripleEmphasis(out *bytes.Buffer, text []byte) { - out.Write(text) -} - -// StrikeThrough is the strikethrough tag callback. -func (r renderer) StrikeThrough(out *bytes.Buffer, text []byte) {} - -// FootnoteRef is the footnote ref tag callback. -func (r renderer) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {} - -// Lowlevel callbacks - -// Entity callback. Outputs a simple plain-text version of the input. -func (r renderer) Entity(out *bytes.Buffer, entity []byte) { - out.Write(entity) -} - -// NormalText callback. Outputs a simple plain-text version of the input. -func (r renderer) NormalText(out *bytes.Buffer, text []byte) { - out.Write(text) -} - -// Header and footer - -// DocumentHeader callback. -func (r renderer) DocumentHeader(out *bytes.Buffer) {} - -// DocumentFooter callback. -func (r renderer) DocumentFooter(out *bytes.Buffer) {} - -// GetFlags returns zero. -func (r renderer) GetFlags() int { return 0 } diff --git a/caddyhttp/markdown/summary/summary.go b/caddyhttp/markdown/summary/summary.go deleted file mode 100644 index e55bba2c9b0..00000000000 --- a/caddyhttp/markdown/summary/summary.go +++ /dev/null @@ -1,17 +0,0 @@ -package summary - -import ( - "bytes" - - "github.com/russross/blackfriday" -) - -// Markdown formats input using a plain-text renderer, and -// then returns up to the first `wordcount` words as a summary. -func Markdown(input []byte, wordcount int) []byte { - words := bytes.Fields(blackfriday.Markdown(input, renderer{}, 0)) - if wordcount > len(words) { - wordcount = len(words) - } - return bytes.Join(words[0:wordcount], []byte{' '}) -} diff --git a/caddyhttp/markdown/summary/summary_test.go b/caddyhttp/markdown/summary/summary_test.go deleted file mode 100644 index f87c00db00c..00000000000 --- a/caddyhttp/markdown/summary/summary_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package summary - -import "testing" - -func TestMarkdown(t *testing.T) { - input := []byte(`Testing with just a few words.`) - got := string(Markdown(input, 3)) - if want := "Testing with just"; want != got { - t.Errorf("Expected '%s' but got '%s'", want, got) - } -} diff --git a/caddyhttp/markdown/template.go b/caddyhttp/markdown/template.go deleted file mode 100644 index b21e598499a..00000000000 --- a/caddyhttp/markdown/template.go +++ /dev/null @@ -1,104 +0,0 @@ -package markdown - -import ( - "bytes" - "io/ioutil" - "text/template" - - "github.com/mholt/caddy/caddyhttp/httpserver" - "github.com/mholt/caddy/caddyhttp/markdown/metadata" -) - -// Data represents a markdown document. -type Data struct { - httpserver.Context - Doc map[string]interface{} - Styles []string - Scripts []string - Meta map[string]string - Files []FileInfo -} - -// Include "overrides" the embedded httpserver.Context's Include() -// method so that included files have access to d's fields. -// Note: using {{template 'template-name' .}} instead might be better. -func (d Data) Include(filename string, args ...interface{}) (string, error) { - d.Args = args - return httpserver.ContextInclude(filename, d, d.Root) -} - -// execTemplate executes a template given a requestPath, template, and metadata -func execTemplate(c *Config, mdata metadata.Metadata, meta map[string]string, files []FileInfo, ctx httpserver.Context) ([]byte, error) { - mdData := Data{ - Context: ctx, - Doc: mdata.Variables, - Styles: c.Styles, - Scripts: c.Scripts, - Meta: meta, - Files: files, - } - - templateName := mdata.Template - // reload template on every request for now - // TODO: cache templates by a general plugin - if templateFile, ok := c.TemplateFiles[templateName]; ok { - err := SetTemplate(c.Template, templateName, templateFile) - if err != nil { - return nil, err - } - } - - b := new(bytes.Buffer) - if err := c.Template.ExecuteTemplate(b, templateName, mdData); err != nil { - return nil, err - } - - return b.Bytes(), nil -} - -// SetTemplate reads in the template with the filename provided. If the file does not exist or is not parsable, it will return an error. -func SetTemplate(t *template.Template, name, filename string) error { - - // Read template - buf, err := ioutil.ReadFile(filename) - if err != nil { - return err - } - - // Update if exists - if tt := t.Lookup(name); tt != nil { - _, err = tt.Parse(string(buf)) - return err - } - - // Allocate new name if not - _, err = t.New(name).Parse(string(buf)) - return err -} - -// GetDefaultTemplate returns the default template. -func GetDefaultTemplate() *template.Template { - return template.Must(template.New("").Parse(defaultTemplate)) -} - -const ( - defaultTemplate = ` - - - {{.Doc.title}} - - {{range $key, $val := .Meta}} - - {{end}} - {{- range .Styles}} - - {{- end}} - {{- range .Scripts}} - - {{- end}} - - - {{.Doc.body}} - -` -) diff --git a/caddyhttp/markdown/testdata/blog/test.md b/caddyhttp/markdown/testdata/blog/test.md deleted file mode 100644 index 93f07a49348..00000000000 --- a/caddyhttp/markdown/testdata/blog/test.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -title: Markdown test 1 -sitename: A Caddy website ---- - -## Welcome on the blog - -Body - -``` go -func getTrue() bool { - return true -} -``` diff --git a/caddyhttp/markdown/testdata/docflags/template.txt b/caddyhttp/markdown/testdata/docflags/template.txt deleted file mode 100644 index 2b001388f15..00000000000 --- a/caddyhttp/markdown/testdata/docflags/template.txt +++ /dev/null @@ -1,2 +0,0 @@ -Doc.var_string {{.Doc.var_string}} -Doc.var_bool {{.Doc.var_bool}} diff --git a/caddyhttp/markdown/testdata/docflags/test.md b/caddyhttp/markdown/testdata/docflags/test.md deleted file mode 100644 index 64ca7f78d5e..00000000000 --- a/caddyhttp/markdown/testdata/docflags/test.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -var_string: hello -var_bool: true ---- diff --git a/caddyhttp/markdown/testdata/header.html b/caddyhttp/markdown/testdata/header.html deleted file mode 100644 index cfbdc75b50a..00000000000 --- a/caddyhttp/markdown/testdata/header.html +++ /dev/null @@ -1 +0,0 @@ -

    Header for: {{.Doc.title}}

    \ No newline at end of file diff --git a/caddyhttp/markdown/testdata/log/test.md b/caddyhttp/markdown/testdata/log/test.md deleted file mode 100644 index 476ab3015c3..00000000000 --- a/caddyhttp/markdown/testdata/log/test.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -title: Markdown test 2 -sitename: A Caddy website ---- - -## Welcome on the blog - -Body - -``` go -func getTrue() bool { - return true -} -``` diff --git a/caddyhttp/markdown/testdata/markdown_tpl.html b/caddyhttp/markdown/testdata/markdown_tpl.html deleted file mode 100644 index 7c697850001..00000000000 --- a/caddyhttp/markdown/testdata/markdown_tpl.html +++ /dev/null @@ -1,11 +0,0 @@ - - - -{{.Doc.title}} - - -{{.Include "header.html"}} -Welcome to {{.Doc.sitename}}! -{{.Doc.body}} - - diff --git a/caddyhttp/markdown/testdata/og/first.md b/caddyhttp/markdown/testdata/og/first.md deleted file mode 100644 index 4d7a4251f6e..00000000000 --- a/caddyhttp/markdown/testdata/og/first.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: first_post -sitename: title ---- -# Test h1 diff --git a/caddyhttp/markdown/testdata/tpl_with_include.html b/caddyhttp/markdown/testdata/tpl_with_include.html deleted file mode 100644 index 68cc986cf45..00000000000 --- a/caddyhttp/markdown/testdata/tpl_with_include.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - {{.Doc.title}} - - - Welcome to {{.Doc.sitename}}! -

    - {{.Doc.body}} - - diff --git a/caddyhttp/mime/mime.go b/caddyhttp/mime/mime.go deleted file mode 100644 index b215fc8a061..00000000000 --- a/caddyhttp/mime/mime.go +++ /dev/null @@ -1,31 +0,0 @@ -package mime - -import ( - "net/http" - "path" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -// Config represent a mime config. Map from extension to mime-type. -// Note, this should be safe with concurrent read access, as this is -// not modified concurrently. -type Config map[string]string - -// Mime sets Content-Type header of requests based on configurations. -type Mime struct { - Next httpserver.Handler - Configs Config -} - -// ServeHTTP implements the httpserver.Handler interface. -func (e Mime) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - // Get a clean /-path, grab the extension - ext := path.Ext(path.Clean(r.URL.Path)) - - if contentType, ok := e.Configs[ext]; ok { - w.Header().Set("Content-Type", contentType) - } - - return e.Next.ServeHTTP(w, r) -} diff --git a/caddyhttp/mime/mime_test.go b/caddyhttp/mime/mime_test.go deleted file mode 100644 index f3e896dc6ed..00000000000 --- a/caddyhttp/mime/mime_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package mime - -import ( - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestMimeHandler(t *testing.T) { - mimes := Config{ - ".html": "text/html", - ".txt": "text/plain", - ".swf": "application/x-shockwave-flash", - } - - m := Mime{Configs: mimes} - - w := httptest.NewRecorder() - exts := []string{ - ".html", ".txt", ".swf", - } - for _, e := range exts { - url := "/file" + e - r, err := http.NewRequest("GET", url, nil) - if err != nil { - t.Error(err) - } - m.Next = nextFunc(true, mimes[e]) - _, err = m.ServeHTTP(w, r) - if err != nil { - t.Error(err) - } - } - - w = httptest.NewRecorder() - exts = []string{ - ".htm1", ".abc", ".mdx", - } - for _, e := range exts { - url := "/file" + e - r, err := http.NewRequest("GET", url, nil) - if err != nil { - t.Error(err) - } - m.Next = nextFunc(false, "") - _, err = m.ServeHTTP(w, r) - if err != nil { - t.Error(err) - } - } -} - -func nextFunc(shouldMime bool, contentType string) httpserver.Handler { - return httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - if shouldMime { - if w.Header().Get("Content-Type") != contentType { - return 0, fmt.Errorf("expected Content-Type: %v, found %v", contentType, w.Header().Get("Content-Type")) - } - return 0, nil - } - if w.Header().Get("Content-Type") != "" { - return 0, fmt.Errorf("Content-Type header not expected") - } - return 0, nil - }) -} diff --git a/caddyhttp/mime/setup.go b/caddyhttp/mime/setup.go deleted file mode 100644 index bca6224363c..00000000000 --- a/caddyhttp/mime/setup.go +++ /dev/null @@ -1,74 +0,0 @@ -package mime - -import ( - "fmt" - "strings" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func init() { - caddy.RegisterPlugin("mime", caddy.Plugin{ - ServerType: "http", - Action: setup, - }) -} - -// setup configures a new mime middleware instance. -func setup(c *caddy.Controller) error { - configs, err := mimeParse(c) - if err != nil { - return err - } - - httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler { - return Mime{Next: next, Configs: configs} - }) - - return nil -} - -func mimeParse(c *caddy.Controller) (Config, error) { - configs := Config{} - - for c.Next() { - // At least one extension is required - - args := c.RemainingArgs() - switch len(args) { - case 2: - if err := validateExt(configs, args[0]); err != nil { - return configs, err - } - configs[args[0]] = args[1] - case 1: - return configs, c.ArgErr() - case 0: - for c.NextBlock() { - ext := c.Val() - if err := validateExt(configs, ext); err != nil { - return configs, err - } - if !c.NextArg() { - return configs, c.ArgErr() - } - configs[ext] = c.Val() - } - } - - } - - return configs, nil -} - -// validateExt checks for valid file name extension. -func validateExt(configs Config, ext string) error { - if !strings.HasPrefix(ext, ".") { - return fmt.Errorf(`mime: invalid extension "%v" (must start with dot)`, ext) - } - if _, ok := configs[ext]; ok { - return fmt.Errorf(`mime: duplicate extension "%v" found`, ext) - } - return nil -} diff --git a/caddyhttp/mime/setup_test.go b/caddyhttp/mime/setup_test.go deleted file mode 100644 index 571acf17efe..00000000000 --- a/caddyhttp/mime/setup_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package mime - -import ( - "testing" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestSetup(t *testing.T) { - c := caddy.NewTestController("http", `mime .txt text/plain`) - 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, but had 0 instead") - } - - handler := mids[0](httpserver.EmptyNext) - myHandler, ok := handler.(Mime) - if !ok { - t.Fatalf("Expected handler to be type Mime, got: %#v", handler) - } - - if !httpserver.SameNext(myHandler.Next, httpserver.EmptyNext) { - t.Error("'Next' field of handler was not set properly") - } - - tests := []struct { - input string - shouldErr bool - }{ - {`mime {`, true}, - {`mime {}`, true}, - {`mime a b`, true}, - {`mime a {`, true}, - {`mime { txt f } `, true}, - {`mime { html } `, true}, - {`mime { - .html text/html - .txt text/plain - } `, false}, - {`mime { - .foo text/foo - .bar text/bar - .foo text/foobar - } `, true}, - {`mime { .html text/html } `, false}, - {`mime { .html - } `, true}, - {`mime .txt text/plain`, false}, - } - for i, test := range tests { - m, err := mimeParse(caddy.NewTestController("http", test.input)) - if test.shouldErr && err == nil { - t.Errorf("Test %v: Expected error but found nil %v", i, m) - } else if !test.shouldErr && err != nil { - t.Errorf("Test %v: Expected no error but found error: %v", i, err) - } - } -} diff --git a/caddyhttp/pprof/pprof.go b/caddyhttp/pprof/pprof.go deleted file mode 100644 index 3a0dbd93c1f..00000000000 --- a/caddyhttp/pprof/pprof.go +++ /dev/null @@ -1,47 +0,0 @@ -package pprof - -import ( - "net/http" - pp "net/http/pprof" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -// BasePath is the base path to match for all pprof requests. -const BasePath = "/debug/pprof" - -// Handler is a simple struct whose ServeHTTP will delegate pprof -// endpoints to their equivalent net/http/pprof handlers. -type Handler struct { - Next httpserver.Handler - Mux *http.ServeMux -} - -// ServeHTTP handles requests to BasePath with pprof, or passes -// all other requests up the chain. -func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - if httpserver.Path(r.URL.Path).Matches(BasePath) { - h.Mux.ServeHTTP(w, r) - return 0, nil - } - return h.Next.ServeHTTP(w, r) -} - -// NewMux returns a new http.ServeMux that routes pprof requests. -// It pretty much copies what the std lib pprof does on init: -// https://golang.org/src/net/http/pprof/pprof.go#L67 -func NewMux() *http.ServeMux { - mux := http.NewServeMux() - mux.HandleFunc(BasePath+"/", func(w http.ResponseWriter, r *http.Request) { - // this endpoint, as implemented in the standard library, doesn't set - // its Content-Type header, so using this can confuse clients, especially - // if gzipping... - w.Header().Set("Content-Type", "text/html; charset=utf-8") - pp.Index(w, r) - }) - mux.HandleFunc(BasePath+"/cmdline", pp.Cmdline) - mux.HandleFunc(BasePath+"/profile", pp.Profile) - mux.HandleFunc(BasePath+"/symbol", pp.Symbol) - mux.HandleFunc(BasePath+"/trace", pp.Trace) - return mux -} diff --git a/caddyhttp/pprof/pprof_test.go b/caddyhttp/pprof/pprof_test.go deleted file mode 100644 index 2b870eb8256..00000000000 --- a/caddyhttp/pprof/pprof_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package pprof - -import ( - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestServeHTTP(t *testing.T) { - h := Handler{ - Next: httpserver.HandlerFunc(nextHandler), - Mux: NewMux(), - } - - w := httptest.NewRecorder() - r, err := http.NewRequest("GET", "/debug/pprof", nil) - if err != nil { - t.Fatal(err) - } - status, err := h.ServeHTTP(w, r) - - if status != 0 { - t.Errorf("Expected status %d but got %d", 0, status) - } - if err != nil { - t.Errorf("Expected nil error, but got: %v", err) - } - if w.Body.String() == "content" { - t.Errorf("Expected pprof to handle request, but it didn't") - } - - w = httptest.NewRecorder() - r, err = http.NewRequest("GET", "/foo", nil) - if err != nil { - t.Fatal(err) - } - status, err = h.ServeHTTP(w, r) - if status != http.StatusNotFound { - t.Errorf("Test two: Expected status %d but got %d", http.StatusNotFound, status) - } - if err != nil { - t.Errorf("Test two: Expected nil error, but got: %v", err) - } - if w.Body.String() != "content" { - t.Errorf("Expected pprof to pass the request through, but it didn't; got: %s", w.Body.String()) - } -} - -func nextHandler(w http.ResponseWriter, r *http.Request) (int, error) { - fmt.Fprintf(w, "content") - return http.StatusNotFound, nil -} diff --git a/caddyhttp/pprof/setup.go b/caddyhttp/pprof/setup.go deleted file mode 100644 index 638d5e126e5..00000000000 --- a/caddyhttp/pprof/setup.go +++ /dev/null @@ -1,37 +0,0 @@ -package pprof - -import ( - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func init() { - caddy.RegisterPlugin("pprof", caddy.Plugin{ - ServerType: "http", - Action: setup, - }) -} - -// setup returns a new instance of a pprof handler. It accepts no arguments or options. -func setup(c *caddy.Controller) error { - found := false - - for c.Next() { - if found { - return c.Err("pprof can only be specified once") - } - if len(c.RemainingArgs()) != 0 { - return c.ArgErr() - } - if c.NextBlock() { - return c.ArgErr() - } - found = true - } - - httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler { - return &Handler{Next: next, Mux: NewMux()} - }) - - return nil -} diff --git a/caddyhttp/pprof/setup_test.go b/caddyhttp/pprof/setup_test.go deleted file mode 100644 index f51303ec8a9..00000000000 --- a/caddyhttp/pprof/setup_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package pprof - -import ( - "testing" - - "github.com/mholt/caddy" -) - -func TestSetup(t *testing.T) { - tests := []struct { - input string - shouldErr bool - }{ - {`pprof`, false}, - {`pprof {}`, true}, - {`pprof /foo`, true}, - {`pprof { - a b - }`, true}, - {`pprof - pprof`, true}, - } - for i, test := range tests { - c := caddy.NewTestController("http", test.input) - err := setup(c) - if test.shouldErr && err == nil { - t.Errorf("Test %v: Expected error but found nil", i) - } else if !test.shouldErr && err != nil { - t.Errorf("Test %v: Expected no error but found error: %v", i, err) - } - } -} diff --git a/caddyhttp/proxy/body.go b/caddyhttp/proxy/body.go deleted file mode 100644 index 38d0016596b..00000000000 --- a/caddyhttp/proxy/body.go +++ /dev/null @@ -1,40 +0,0 @@ -package proxy - -import ( - "bytes" - "io" - "io/ioutil" -) - -type bufferedBody struct { - *bytes.Reader -} - -func (*bufferedBody) Close() error { - return nil -} - -// rewind allows bufferedBody to be read again. -func (b *bufferedBody) rewind() error { - if b == nil { - return nil - } - _, err := b.Seek(0, io.SeekStart) - return err -} - -// newBufferedBody returns *bufferedBody to use in place of src. Closes src -// and returns Read error on src. All content from src is buffered. -func newBufferedBody(src io.ReadCloser) (*bufferedBody, error) { - if src == nil { - return nil, nil - } - b, err := ioutil.ReadAll(src) - src.Close() - if err != nil { - return nil, err - } - return &bufferedBody{ - Reader: bytes.NewReader(b), - }, nil -} diff --git a/caddyhttp/proxy/body_test.go b/caddyhttp/proxy/body_test.go deleted file mode 100644 index 5b72784cf29..00000000000 --- a/caddyhttp/proxy/body_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package proxy - -import ( - "bytes" - "io" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" -) - -func TestBodyRetry(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - io.Copy(w, r.Body) - r.Body.Close() - })) - defer ts.Close() - - testcase := "test content" - req, err := http.NewRequest(http.MethodPost, ts.URL, bytes.NewBufferString(testcase)) - if err != nil { - t.Fatal(err) - } - - body, err := newBufferedBody(req.Body) - if err != nil { - t.Fatal(err) - } - if body != nil { - req.Body = body - } - - // simulate fail request - host := req.URL.Host - req.URL.Host = "example.com" - body.rewind() - _, _ = http.DefaultTransport.RoundTrip(req) - - // retry request - req.URL.Host = host - body.rewind() - resp, err := http.DefaultTransport.RoundTrip(req) - if err != nil { - t.Fatal(err) - } - result, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - resp.Body.Close() - if string(result) != testcase { - t.Fatalf("result = %s, want %s", result, testcase) - } - - // try one more time for body reuse - body.rewind() - resp, err = http.DefaultTransport.RoundTrip(req) - if err != nil { - t.Fatal(err) - } - result, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - resp.Body.Close() - if string(result) != testcase { - t.Fatalf("result = %s, want %s", result, testcase) - } -} diff --git a/caddyhttp/proxy/policy.go b/caddyhttp/proxy/policy.go deleted file mode 100644 index f95c5b8f198..00000000000 --- a/caddyhttp/proxy/policy.go +++ /dev/null @@ -1,182 +0,0 @@ -package proxy - -import ( - "hash/fnv" - "math" - "math/rand" - "net" - "net/http" - "sync" -) - -// HostPool is a collection of UpstreamHosts. -type HostPool []*UpstreamHost - -// Policy decides how a host will be selected from a pool. -type Policy interface { - Select(pool HostPool, r *http.Request) *UpstreamHost -} - -func init() { - RegisterPolicy("random", func(arg string) Policy { return &Random{} }) - RegisterPolicy("least_conn", func(arg string) Policy { return &LeastConn{} }) - RegisterPolicy("round_robin", func(arg string) Policy { return &RoundRobin{} }) - RegisterPolicy("ip_hash", func(arg string) Policy { return &IPHash{} }) - RegisterPolicy("first", func(arg string) Policy { return &First{} }) - RegisterPolicy("uri_hash", func(arg string) Policy { return &URIHash{} }) - RegisterPolicy("header", func(arg string) Policy { return &Header{arg} }) -} - -// Random is a policy that selects up hosts from a pool at random. -type Random struct{} - -// Select selects an up host at random from the specified pool. -func (r *Random) Select(pool HostPool, request *http.Request) *UpstreamHost { - - // Because the number of available hosts isn't known - // up front, the host is selected via reservoir sampling - // https://en.wikipedia.org/wiki/Reservoir_sampling - var randHost *UpstreamHost - count := 0 - for _, host := range pool { - if !host.Available() { - continue - } - - // (n % 1 == 0) holds for all n, therefore randHost - // will always get assigned a value if there is - // at least 1 available host - count++ - if (rand.Int() % count) == 0 { - randHost = host - } - } - return randHost -} - -// LeastConn is a policy that selects the host with the least connections. -type LeastConn struct{} - -// Select selects the up host with the least number of connections in the -// pool. If more than one host has the same least number of connections, -// one of the hosts is chosen at random. -func (r *LeastConn) Select(pool HostPool, request *http.Request) *UpstreamHost { - var bestHost *UpstreamHost - count := 0 - leastConn := int64(math.MaxInt64) - for _, host := range pool { - if !host.Available() { - continue - } - - if host.Conns < leastConn { - leastConn = host.Conns - count = 0 - } - - // Among hosts with same least connections, perform a reservoir - // sample: https://en.wikipedia.org/wiki/Reservoir_sampling - if host.Conns == leastConn { - count++ - if (rand.Int() % count) == 0 { - bestHost = host - } - } - } - return bestHost -} - -// RoundRobin is a policy that selects hosts based on round-robin ordering. -type RoundRobin struct { - robin uint32 - mutex sync.Mutex -} - -// Select selects an up host from the pool using a round-robin ordering scheme. -func (r *RoundRobin) Select(pool HostPool, request *http.Request) *UpstreamHost { - poolLen := uint32(len(pool)) - r.mutex.Lock() - defer r.mutex.Unlock() - // Return next available host - for i := uint32(0); i < poolLen; i++ { - r.robin++ - host := pool[r.robin%poolLen] - if host.Available() { - return host - } - } - return nil -} - -// hostByHashing returns an available host from pool based on a hashable string -func hostByHashing(pool HostPool, s string) *UpstreamHost { - poolLen := uint32(len(pool)) - index := hash(s) % poolLen - for i := uint32(0); i < poolLen; i++ { - index += i - host := pool[index%poolLen] - if host.Available() { - return host - } - } - return nil -} - -// hash calculates a hash based on string s -func hash(s string) uint32 { - h := fnv.New32a() - h.Write([]byte(s)) - return h.Sum32() -} - -// IPHash is a policy that selects hosts based on hashing the request IP -type IPHash struct{} - -// Select selects an up host from the pool based on hashing the request IP -func (r *IPHash) Select(pool HostPool, request *http.Request) *UpstreamHost { - clientIP, _, err := net.SplitHostPort(request.RemoteAddr) - if err != nil { - clientIP = request.RemoteAddr - } - return hostByHashing(pool, clientIP) -} - -// URIHash is a policy that selects the host based on hashing the request URI -type URIHash struct{} - -// Select selects the host based on hashing the URI -func (r *URIHash) Select(pool HostPool, request *http.Request) *UpstreamHost { - return hostByHashing(pool, request.RequestURI) -} - -// First is a policy that selects the first available host -type First struct{} - -// Select selects the first available host from the pool -func (r *First) Select(pool HostPool, request *http.Request) *UpstreamHost { - for _, host := range pool { - if host.Available() { - return host - } - } - return nil -} - -// Header is a policy that selects based on a hash of the given header -type Header struct { - // The name of the request header, the value of which will determine - // how the request is routed - Name string -} - -// Select selects the host based on hashing the header value -func (r *Header) Select(pool HostPool, request *http.Request) *UpstreamHost { - if r.Name == "" { - return nil - } - val := request.Header.Get(r.Name) - if val == "" { - return nil - } - return hostByHashing(pool, val) -} diff --git a/caddyhttp/proxy/policy_test.go b/caddyhttp/proxy/policy_test.go deleted file mode 100644 index 6acf1e085dc..00000000000 --- a/caddyhttp/proxy/policy_test.go +++ /dev/null @@ -1,343 +0,0 @@ -package proxy - -import ( - "net/http" - "net/http/httptest" - "os" - "testing" -) - -var workableServer *httptest.Server - -func TestMain(m *testing.M) { - workableServer = httptest.NewServer(http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - // do nothing - })) - r := m.Run() - workableServer.Close() - os.Exit(r) -} - -type customPolicy struct{} - -func (r *customPolicy) Select(pool HostPool, request *http.Request) *UpstreamHost { - return pool[0] -} - -func testPool() HostPool { - pool := []*UpstreamHost{ - { - Name: workableServer.URL, // this should resolve (healthcheck test) - }, - { - Name: "http://localhost:99998", // this shouldn't - }, - { - Name: "http://C", - }, - } - return HostPool(pool) -} - -func TestRoundRobinPolicy(t *testing.T) { - pool := testPool() - rrPolicy := &RoundRobin{} - request, _ := http.NewRequest("GET", "/", nil) - - h := rrPolicy.Select(pool, request) - // First selected host is 1, because counter starts at 0 - // and increments before host is selected - if h != pool[1] { - t.Error("Expected first round robin host to be second host in the pool.") - } - h = rrPolicy.Select(pool, request) - if h != pool[2] { - t.Error("Expected second round robin host to be third host in the pool.") - } - h = rrPolicy.Select(pool, request) - if h != pool[0] { - t.Error("Expected third round robin host to be first host in the pool.") - } - // mark host as down - pool[1].Unhealthy = 1 - h = rrPolicy.Select(pool, request) - if h != pool[2] { - t.Error("Expected to skip down host.") - } - // mark host as up - pool[1].Unhealthy = 0 - - h = rrPolicy.Select(pool, request) - if h == pool[2] { - t.Error("Expected to balance evenly among healthy hosts") - } - // mark host as full - pool[1].Conns = 1 - pool[1].MaxConns = 1 - h = rrPolicy.Select(pool, request) - if h != pool[2] { - t.Error("Expected to skip full host.") - } -} - -func TestLeastConnPolicy(t *testing.T) { - pool := testPool() - lcPolicy := &LeastConn{} - request, _ := http.NewRequest("GET", "/", nil) - - pool[0].Conns = 10 - pool[1].Conns = 10 - h := lcPolicy.Select(pool, request) - if h != pool[2] { - t.Error("Expected least connection host to be third host.") - } - pool[2].Conns = 100 - h = lcPolicy.Select(pool, request) - if h != pool[0] && h != pool[1] { - t.Error("Expected least connection host to be first or second host.") - } -} - -func TestCustomPolicy(t *testing.T) { - pool := testPool() - customPolicy := &customPolicy{} - request, _ := http.NewRequest("GET", "/", nil) - - h := customPolicy.Select(pool, request) - if h != pool[0] { - t.Error("Expected custom policy host to be the first host.") - } -} - -func TestIPHashPolicy(t *testing.T) { - pool := testPool() - ipHash := &IPHash{} - request, _ := http.NewRequest("GET", "/", nil) - // We should be able to predict where every request is routed. - request.RemoteAddr = "172.0.0.1:80" - h := ipHash.Select(pool, request) - if h != pool[1] { - t.Error("Expected ip hash policy host to be the second host.") - } - request.RemoteAddr = "172.0.0.2:80" - h = ipHash.Select(pool, request) - if h != pool[1] { - t.Error("Expected ip hash policy host to be the second host.") - } - request.RemoteAddr = "172.0.0.3:80" - h = ipHash.Select(pool, request) - if h != pool[2] { - t.Error("Expected ip hash policy host to be the third host.") - } - request.RemoteAddr = "172.0.0.4:80" - h = ipHash.Select(pool, request) - if h != pool[1] { - t.Error("Expected ip hash policy host to be the second host.") - } - - // we should get the same results without a port - request.RemoteAddr = "172.0.0.1" - h = ipHash.Select(pool, request) - if h != pool[1] { - t.Error("Expected ip hash policy host to be the second host.") - } - request.RemoteAddr = "172.0.0.2" - h = ipHash.Select(pool, request) - if h != pool[1] { - t.Error("Expected ip hash policy host to be the second host.") - } - request.RemoteAddr = "172.0.0.3" - h = ipHash.Select(pool, request) - if h != pool[2] { - t.Error("Expected ip hash policy host to be the third host.") - } - request.RemoteAddr = "172.0.0.4" - h = ipHash.Select(pool, request) - if h != pool[1] { - t.Error("Expected ip hash policy host to be the second host.") - } - - // we should get a healthy host if the original host is unhealthy and a - // healthy host is available - request.RemoteAddr = "172.0.0.1" - pool[1].Unhealthy = 1 - h = ipHash.Select(pool, request) - if h != pool[2] { - t.Error("Expected ip hash policy host to be the third host.") - } - - request.RemoteAddr = "172.0.0.2" - h = ipHash.Select(pool, request) - if h != pool[2] { - t.Error("Expected ip hash policy host to be the third host.") - } - pool[1].Unhealthy = 0 - - request.RemoteAddr = "172.0.0.3" - pool[2].Unhealthy = 1 - h = ipHash.Select(pool, request) - if h != pool[0] { - t.Error("Expected ip hash policy host to be the first host.") - } - request.RemoteAddr = "172.0.0.4" - h = ipHash.Select(pool, request) - if h != pool[1] { - t.Error("Expected ip hash policy host to be the second host.") - } - - // We should be able to resize the host pool and still be able to predict - // where a request will be routed with the same IP's used above - pool = []*UpstreamHost{ - { - Name: workableServer.URL, // this should resolve (healthcheck test) - }, - { - Name: "http://localhost:99998", // this shouldn't - }, - } - pool = HostPool(pool) - request.RemoteAddr = "172.0.0.1:80" - h = ipHash.Select(pool, request) - if h != pool[0] { - t.Error("Expected ip hash policy host to be the first host.") - } - request.RemoteAddr = "172.0.0.2:80" - h = ipHash.Select(pool, request) - if h != pool[1] { - t.Error("Expected ip hash policy host to be the second host.") - } - request.RemoteAddr = "172.0.0.3:80" - h = ipHash.Select(pool, request) - if h != pool[0] { - t.Error("Expected ip hash policy host to be the first host.") - } - request.RemoteAddr = "172.0.0.4:80" - h = ipHash.Select(pool, request) - if h != pool[1] { - t.Error("Expected ip hash policy host to be the second host.") - } - - // We should get nil when there are no healthy hosts - pool[0].Unhealthy = 1 - pool[1].Unhealthy = 1 - h = ipHash.Select(pool, request) - if h != nil { - t.Error("Expected ip hash policy host to be nil.") - } -} - -func TestFirstPolicy(t *testing.T) { - pool := testPool() - firstPolicy := &First{} - req := httptest.NewRequest(http.MethodGet, "/", nil) - - h := firstPolicy.Select(pool, req) - if h != pool[0] { - t.Error("Expected first policy host to be the first host.") - } - - pool[0].Unhealthy = 1 - h = firstPolicy.Select(pool, req) - if h != pool[1] { - t.Error("Expected first policy host to be the second host.") - } -} - -func TestUriPolicy(t *testing.T) { - pool := testPool() - uriPolicy := &URIHash{} - - request := httptest.NewRequest(http.MethodGet, "/test", nil) - h := uriPolicy.Select(pool, request) - if h != pool[0] { - t.Error("Expected uri policy host to be the first host.") - } - - pool[0].Unhealthy = 1 - h = uriPolicy.Select(pool, request) - if h != pool[1] { - t.Error("Expected uri policy host to be the first host.") - } - - request = httptest.NewRequest(http.MethodGet, "/test_2", nil) - h = uriPolicy.Select(pool, request) - if h != pool[1] { - t.Error("Expected uri policy host to be the second host.") - } - - // We should be able to resize the host pool and still be able to predict - // where a request will be routed with the same URI's used above - pool = []*UpstreamHost{ - { - Name: workableServer.URL, // this should resolve (healthcheck test) - }, - { - Name: "http://localhost:99998", // this shouldn't - }, - } - - request = httptest.NewRequest(http.MethodGet, "/test", nil) - h = uriPolicy.Select(pool, request) - if h != pool[0] { - t.Error("Expected uri policy host to be the first host.") - } - - pool[0].Unhealthy = 1 - h = uriPolicy.Select(pool, request) - if h != pool[1] { - t.Error("Expected uri policy host to be the first host.") - } - - request = httptest.NewRequest(http.MethodGet, "/test_2", nil) - h = uriPolicy.Select(pool, request) - if h != pool[1] { - t.Error("Expected uri policy host to be the second host.") - } - - pool[0].Unhealthy = 1 - pool[1].Unhealthy = 1 - h = uriPolicy.Select(pool, request) - if h != nil { - t.Error("Expected uri policy policy host to be nil.") - } -} - -func TestHeaderPolicy(t *testing.T) { - pool := testPool() - tests := []struct { - Policy *Header - RequestHeaderName string - RequestHeaderValue string - NilHost bool - HostIndex int - }{ - {&Header{""}, "", "", true, 0}, - {&Header{""}, "Affinity", "somevalue", true, 0}, - {&Header{""}, "Affinity", "", true, 0}, - - {&Header{"Affinity"}, "", "", true, 0}, - {&Header{"Affinity"}, "Affinity", "somevalue", false, 1}, - {&Header{"Affinity"}, "Affinity", "somevalue2", false, 0}, - {&Header{"Affinity"}, "Affinity", "somevalue3", false, 2}, - {&Header{"Affinity"}, "Affinity", "", true, 0}, - } - - for idx, test := range tests { - request, _ := http.NewRequest("GET", "/", nil) - if test.RequestHeaderName != "" { - request.Header.Add(test.RequestHeaderName, test.RequestHeaderValue) - } - - host := test.Policy.Select(pool, request) - if test.NilHost && host != nil { - t.Errorf("%d: Expected host to be nil", idx) - } - if !test.NilHost && host == nil { - t.Errorf("%d: Did not expect host to be nil", idx) - } - if !test.NilHost && host != pool[test.HostIndex] { - t.Errorf("%d: Expected Header policy to be host %d", idx, test.HostIndex) - } - } -} diff --git a/caddyhttp/proxy/proxy.go b/caddyhttp/proxy/proxy.go deleted file mode 100644 index 56159f9fb19..00000000000 --- a/caddyhttp/proxy/proxy.go +++ /dev/null @@ -1,369 +0,0 @@ -// Package proxy is middleware that proxies HTTP requests. -package proxy - -import ( - "context" - "errors" - "net" - "net/http" - "net/url" - "strings" - "sync/atomic" - "time" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -// Proxy represents a middleware instance that can proxy requests. -type Proxy struct { - Next httpserver.Handler - Upstreams []Upstream -} - -// Upstream manages a pool of proxy upstream hosts. -type Upstream interface { - // The path this upstream host should be routed on - From() string - - // Selects an upstream host to be routed to. It - // should return a suitable upstream host, or nil - // if no such hosts are available. - Select(*http.Request) *UpstreamHost - - // Checks if subpath is not an ignored path - AllowedPath(string) bool - - // Gets how long to try selecting upstream hosts - // in the case of cascading failures. - GetTryDuration() time.Duration - - // Gets how long to wait between selecting upstream - // hosts in the case of cascading failures. - GetTryInterval() time.Duration - - // Gets the number of upstream hosts. - GetHostCount() int - - // Stops the upstream from proxying requests to shutdown goroutines cleanly. - Stop() error -} - -// UpstreamHostDownFunc can be used to customize how Down behaves. -type UpstreamHostDownFunc func(*UpstreamHost) bool - -// UpstreamHost represents a single proxy upstream -type UpstreamHost struct { - // This field is read & written to concurrently, so all access must use - // atomic operations. - Conns int64 // must be first field to be 64-bit aligned on 32-bit systems - MaxConns int64 - Name string // hostname of this upstream host - UpstreamHeaders http.Header - DownstreamHeaders http.Header - FailTimeout time.Duration - CheckDown UpstreamHostDownFunc - WithoutPathPrefix string - ReverseProxy *ReverseProxy - Fails int32 - // This is an int32 so that we can use atomic operations to do concurrent - // reads & writes to this value. The default value of 0 indicates that it - // is healthy and any non-zero value indicates unhealthy. - Unhealthy int32 -} - -// Down checks whether the upstream host is down or not. -// Down will try to use uh.CheckDown first, and will fall -// back to some default criteria if necessary. -func (uh *UpstreamHost) Down() bool { - if uh.CheckDown == nil { - // Default settings - return atomic.LoadInt32(&uh.Unhealthy) != 0 || atomic.LoadInt32(&uh.Fails) > 0 - } - return uh.CheckDown(uh) -} - -// Full checks whether the upstream host has reached its maximum connections -func (uh *UpstreamHost) Full() bool { - return uh.MaxConns > 0 && atomic.LoadInt64(&uh.Conns) >= uh.MaxConns -} - -// Available checks whether the upstream host is available for proxying to -func (uh *UpstreamHost) Available() bool { - return !uh.Down() && !uh.Full() -} - -// ServeHTTP satisfies the httpserver.Handler interface. -func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - // start by selecting most specific matching upstream config - upstream := p.match(r) - if upstream == nil { - return p.Next.ServeHTTP(w, r) - } - - // this replacer is used to fill in header field values - replacer := httpserver.NewReplacer(r, nil, "") - - // outreq is the request that makes a roundtrip to the backend - outreq, cancel := createUpstreamRequest(w, r) - defer cancel() - - // If we have more than one upstream host defined and if retrying is enabled - // by setting try_duration to a non-zero value, caddy will try to - // retry the request at a different host if the first one failed. - // - // This requires us to possibly rewind and replay the request body though, - // which in turn requires us to buffer the request body first. - // - // An unbuffered request is usually preferrable, because it reduces latency - // as well as memory usage. Furthermore it enables different kinds of - // HTTP streaming applications like gRPC for instance. - requiresBuffering := upstream.GetHostCount() > 1 && upstream.GetTryDuration() != 0 - - if requiresBuffering { - body, err := newBufferedBody(outreq.Body) - if err != nil { - return http.StatusBadRequest, errors.New("failed to read downstream request body") - } - if body != nil { - outreq.Body = body - } - } - - // The keepRetrying function will return true if we should - // loop and try to select another host, or false if we - // should break and stop retrying. - start := time.Now() - keepRetrying := func(backendErr error) bool { - // if downstream has canceled the request, break - if backendErr == context.Canceled { - return false - } - // if we've tried long enough, break - if time.Since(start) >= upstream.GetTryDuration() { - return false - } - // otherwise, wait and try the next available host - time.Sleep(upstream.GetTryInterval()) - return true - } - - var backendErr error - for { - // since Select() should give us "up" hosts, keep retrying - // hosts until timeout (or until we get a nil host). - host := upstream.Select(r) - if host == nil { - if backendErr == nil { - backendErr = errors.New("no hosts available upstream") - } - if !keepRetrying(backendErr) { - break - } - continue - } - if rr, ok := w.(*httpserver.ResponseRecorder); ok && rr.Replacer != nil { - rr.Replacer.Set("upstream", host.Name) - } - - proxy := host.ReverseProxy - - // a backend's name may contain more than just the host, - // so we parse it as a URL to try to isolate the host. - if nameURL, err := url.Parse(host.Name); err == nil { - outreq.Host = nameURL.Host - if proxy == nil { - proxy = NewSingleHostReverseProxy(nameURL, host.WithoutPathPrefix, http.DefaultMaxIdleConnsPerHost) - } - - // use upstream credentials by default - if outreq.Header.Get("Authorization") == "" && nameURL.User != nil { - pwd, _ := nameURL.User.Password() - outreq.SetBasicAuth(nameURL.User.Username(), pwd) - } - } else { - outreq.Host = host.Name - } - if proxy == nil { - return http.StatusInternalServerError, errors.New("proxy for host '" + host.Name + "' is nil") - } - - // set headers for request going upstream - if host.UpstreamHeaders != nil { - // modify headers for request that will be sent to the upstream host - mutateHeadersByRules(outreq.Header, host.UpstreamHeaders, replacer) - if hostHeaders, ok := outreq.Header["Host"]; ok && len(hostHeaders) > 0 { - outreq.Host = hostHeaders[len(hostHeaders)-1] - } - } - - // prepare a function that will update response - // headers coming back downstream - var downHeaderUpdateFn respUpdateFn - if host.DownstreamHeaders != nil { - downHeaderUpdateFn = createRespHeaderUpdateFn(host.DownstreamHeaders, replacer) - } - - // Before we retry the request we have to make sure - // that the body is rewound to it's beginning. - if bb, ok := outreq.Body.(*bufferedBody); ok { - if err := bb.rewind(); err != nil { - return http.StatusInternalServerError, errors.New("unable to rewind downstream request body") - } - } - - // tell the proxy to serve the request - // - // NOTE: - // The call to proxy.ServeHTTP can theoretically panic. - // To prevent host.Conns from getting out-of-sync we thus have to - // make sure that it's _always_ correctly decremented afterwards. - func() { - atomic.AddInt64(&host.Conns, 1) - defer atomic.AddInt64(&host.Conns, -1) - backendErr = proxy.ServeHTTP(w, outreq, downHeaderUpdateFn) - }() - - // if no errors, we're done here - if backendErr == nil { - return 0, nil - } - - if backendErr == httpserver.ErrMaxBytesExceeded { - return http.StatusRequestEntityTooLarge, backendErr - } - - // failover; remember this failure for some time if - // request failure counting is enabled - timeout := host.FailTimeout - if timeout > 0 { - atomic.AddInt32(&host.Fails, 1) - go func(host *UpstreamHost, timeout time.Duration) { - time.Sleep(timeout) - atomic.AddInt32(&host.Fails, -1) - }(host, timeout) - } - - // if we've tried long enough, break - if !keepRetrying(backendErr) { - break - } - } - - return http.StatusBadGateway, backendErr -} - -// match finds the best match for a proxy config based on r. -func (p Proxy) match(r *http.Request) Upstream { - var u Upstream - var longestMatch int - for _, upstream := range p.Upstreams { - basePath := upstream.From() - if !httpserver.Path(r.URL.Path).Matches(basePath) || !upstream.AllowedPath(r.URL.Path) { - continue - } - if len(basePath) > longestMatch { - longestMatch = len(basePath) - u = upstream - } - } - return u -} - -// createUpstremRequest shallow-copies r into a new request -// that can be sent upstream. -// -// Derived from reverseproxy.go in the standard Go httputil package. -func createUpstreamRequest(rw http.ResponseWriter, r *http.Request) (*http.Request, context.CancelFunc) { - // Original incoming server request may be canceled by the - // user or by std lib(e.g. too many idle connections). - ctx, cancel := context.WithCancel(r.Context()) - if cn, ok := rw.(http.CloseNotifier); ok { - notifyChan := cn.CloseNotify() - go func() { - select { - case <-notifyChan: - cancel() - case <-ctx.Done(): - } - }() - } - - outreq := r.WithContext(ctx) // includes shallow copies of maps, but okay - - // We should set body to nil explicitly if request body is empty. - // For server requests the Request Body is always non-nil. - if r.ContentLength == 0 { - outreq.Body = nil - } - - // We are modifying the same underlying map from req (shallow - // copied above) so we only copy it if necessary. - copiedHeaders := false - - // Remove hop-by-hop headers listed in the "Connection" header. - // See RFC 2616, section 14.10. - if c := outreq.Header.Get("Connection"); c != "" { - for _, f := range strings.Split(c, ",") { - if f = strings.TrimSpace(f); f != "" { - if !copiedHeaders { - outreq.Header = make(http.Header) - copyHeader(outreq.Header, r.Header) - copiedHeaders = true - } - outreq.Header.Del(f) - } - } - } - - // Remove hop-by-hop headers to the backend. Especially - // important is "Connection" because we want a persistent - // connection, regardless of what the client sent to us. - for _, h := range hopHeaders { - if outreq.Header.Get(h) != "" { - if !copiedHeaders { - outreq.Header = make(http.Header) - copyHeader(outreq.Header, r.Header) - copiedHeaders = true - } - outreq.Header.Del(h) - } - } - - if clientIP, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { - // If we aren't the first proxy, retain prior - // X-Forwarded-For information as a comma+space - // separated list and fold multiple headers into one. - if prior, ok := outreq.Header["X-Forwarded-For"]; ok { - clientIP = strings.Join(prior, ", ") + ", " + clientIP - } - outreq.Header.Set("X-Forwarded-For", clientIP) - } - - return outreq, cancel -} - -func createRespHeaderUpdateFn(rules http.Header, replacer httpserver.Replacer) respUpdateFn { - return func(resp *http.Response) { - mutateHeadersByRules(resp.Header, rules, replacer) - } -} - -func mutateHeadersByRules(headers, rules http.Header, repl httpserver.Replacer) { - for ruleField, ruleValues := range rules { - if strings.HasPrefix(ruleField, "+") { - for _, ruleValue := range ruleValues { - replacement := repl.Replace(ruleValue) - if len(replacement) > 0 { - headers.Add(strings.TrimPrefix(ruleField, "+"), replacement) - } - } - } else if strings.HasPrefix(ruleField, "-") { - headers.Del(strings.TrimPrefix(ruleField, "-")) - } else if len(ruleValues) > 0 { - replacement := repl.Replace(ruleValues[len(ruleValues)-1]) - if len(replacement) > 0 { - headers.Set(ruleField, replacement) - } - } - } -} diff --git a/caddyhttp/proxy/proxy_test.go b/caddyhttp/proxy/proxy_test.go deleted file mode 100644 index d7342560822..00000000000 --- a/caddyhttp/proxy/proxy_test.go +++ /dev/null @@ -1,1472 +0,0 @@ -package proxy - -import ( - "bufio" - "bytes" - "context" - "crypto/tls" - "fmt" - "io" - "io/ioutil" - "log" - "net" - "net/http" - "net/http/httptest" - "net/url" - "os" - "path/filepath" - "reflect" - "runtime" - "strings" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/mholt/caddy/caddyfile" - "github.com/mholt/caddy/caddyhttp/httpserver" - - "golang.org/x/net/websocket" -) - -// This is a simple wrapper around httptest.NewTLSServer() -// which forcefully enables (among others) HTTP/2 support. -// The httptest package only supports HTTP/1.1 by default. -func newTLSServer(handler http.Handler) *httptest.Server { - ts := httptest.NewUnstartedServer(handler) - ts.TLS = new(tls.Config) - ts.TLS.NextProtos = []string{"h2"} - ts.StartTLS() - return ts -} - -func TestReverseProxy(t *testing.T) { - log.SetOutput(ioutil.Discard) - defer log.SetOutput(os.Stderr) - - testHeaderValue := []string{"header-value"} - testHeaders := http.Header{ - "X-Header-1": testHeaderValue, - "X-Header-2": testHeaderValue, - "X-Header-3": testHeaderValue, - } - testTrailerValue := []string{"trailer-value"} - testTrailers := http.Header{ - "X-Trailer-1": testTrailerValue, - "X-Trailer-2": testTrailerValue, - "X-Trailer-3": testTrailerValue, - } - verifyHeaderValues := func(actual http.Header, expected http.Header) bool { - if actual == nil { - t.Error("Expected headers") - return true - } - - for k := range expected { - if expected.Get(k) != actual.Get(k) { - t.Errorf("Expected header '%s' to be proxied properly", k) - return true - } - } - - return false - } - verifyHeadersTrailers := func(headers http.Header, trailers http.Header) { - if verifyHeaderValues(headers, testHeaders) || verifyHeaderValues(trailers, testTrailers) { - t.FailNow() - } - } - - requestReceived := false - backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // read the body (even if it's empty) to make Go parse trailers - io.Copy(ioutil.Discard, r.Body) - - verifyHeadersTrailers(r.Header, r.Trailer) - requestReceived = true - - // Set headers. - copyHeader(w.Header(), testHeaders) - - // Only announce one of the trailers to test wether - // unannounced trailers are proxied correctly. - for k := range testTrailers { - w.Header().Set("Trailer", k) - break - } - - w.WriteHeader(http.StatusOK) - w.Write([]byte("Hello, client")) - - // Set trailers. - shallowCopyTrailers(w.Header(), testTrailers, true) - })) - defer backend.Close() - - // set up proxy - p := &Proxy{ - Next: httpserver.EmptyNext, // prevents panic in some cases when test fails - Upstreams: []Upstream{newFakeUpstream(backend.URL, false)}, - } - - // Create the fake request body. - // This will copy "trailersToSet" to r.Trailer right before it is closed and - // thus test for us wether unannounced client trailers are proxied correctly. - body := &trailerTestStringReader{ - Reader: *strings.NewReader("test"), - trailersToSet: testTrailers, - } - - // Create the fake request with the above body. - r := httptest.NewRequest("GET", "/", body) - r.Trailer = make(http.Header) - body.request = r - - copyHeader(r.Header, testHeaders) - - // Only announce one of the trailers to test wether - // unannounced trailers are proxied correctly. - for k, v := range testTrailers { - r.Trailer[k] = v - break - } - - w := httptest.NewRecorder() - p.ServeHTTP(w, r) - res := w.Result() - - if !requestReceived { - t.Error("Expected backend to receive request, but it didn't") - } - - verifyHeadersTrailers(res.Header, res.Trailer) - - // Make sure {upstream} placeholder is set - r.Body = ioutil.NopCloser(strings.NewReader("test")) - rr := httpserver.NewResponseRecorder(testResponseRecorder{ - ResponseWriterWrapper: &httpserver.ResponseWriterWrapper{ResponseWriter: httptest.NewRecorder()}, - }) - rr.Replacer = httpserver.NewReplacer(r, rr, "-") - - p.ServeHTTP(rr, r) - - if got, want := rr.Replacer.Replace("{upstream}"), backend.URL; got != want { - t.Errorf("Expected custom placeholder {upstream} to be set (%s), but it wasn't; got: %s", want, got) - } -} - -// trailerTestStringReader is used to test unannounced trailers coming -// from a client which should properly be proxied to the upstream. -type trailerTestStringReader struct { - strings.Reader - request *http.Request - trailersToSet http.Header -} - -var _ io.ReadCloser = &trailerTestStringReader{} - -func (r *trailerTestStringReader) Close() error { - copyHeader(r.request.Trailer, r.trailersToSet) - return nil -} - -func TestReverseProxyInsecureSkipVerify(t *testing.T) { - log.SetOutput(ioutil.Discard) - defer log.SetOutput(os.Stderr) - - var requestReceived bool - var requestWasHTTP2 bool - backend := newTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - requestReceived = true - requestWasHTTP2 = r.ProtoAtLeast(2, 0) - w.Write([]byte("Hello, client")) - })) - defer backend.Close() - - // set up proxy - p := &Proxy{ - Next: httpserver.EmptyNext, // prevents panic in some cases when test fails - Upstreams: []Upstream{newFakeUpstream(backend.URL, true)}, - } - - // create request and response recorder - r := httptest.NewRequest("GET", "/", nil) - w := httptest.NewRecorder() - - p.ServeHTTP(w, r) - - if !requestReceived { - t.Error("Even with insecure HTTPS, expected backend to receive request, but it didn't") - } - if !requestWasHTTP2 { - t.Error("Even with insecure HTTPS, expected proxy to use HTTP/2") - } -} - -// This test will fail when using the race detector without atomic reads & -// writes of UpstreamHost.Conns and UpstreamHost.Unhealthy. -func TestReverseProxyMaxConnLimit(t *testing.T) { - log.SetOutput(ioutil.Discard) - defer log.SetOutput(os.Stderr) - - const MaxTestConns = 2 - connReceived := make(chan bool, MaxTestConns) - connContinue := make(chan bool) - backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - connReceived <- true - <-connContinue - })) - defer backend.Close() - - su, err := NewStaticUpstreams(caddyfile.NewDispenser("Testfile", strings.NewReader(` - proxy / `+backend.URL+` { - max_conns `+fmt.Sprint(MaxTestConns)+` - } - `)), "") - if err != nil { - t.Fatal(err) - } - - // set up proxy - p := &Proxy{ - Next: httpserver.EmptyNext, // prevents panic in some cases when test fails - Upstreams: su, - } - - var jobs sync.WaitGroup - - for i := 0; i < MaxTestConns; i++ { - jobs.Add(1) - go func(i int) { - defer jobs.Done() - w := httptest.NewRecorder() - code, err := p.ServeHTTP(w, httptest.NewRequest("GET", "/", nil)) - if err != nil { - t.Errorf("Request %d failed: %v", i, err) - } else if code != 0 { - t.Errorf("Bad return code for request %d: %d", i, code) - } else if w.Code != 200 { - t.Errorf("Bad statuc code for request %d: %d", i, w.Code) - } - }(i) - } - // Wait for all the requests to hit the backend. - for i := 0; i < MaxTestConns; i++ { - <-connReceived - } - - // Now we should have MaxTestConns requests connected and sitting on the backend - // server. Verify that the next request is rejected. - w := httptest.NewRecorder() - code, err := p.ServeHTTP(w, httptest.NewRequest("GET", "/", nil)) - if code != http.StatusBadGateway { - t.Errorf("Expected request to be rejected, but got: %d [%v]\nStatus code: %d", - code, err, w.Code) - } - - // Now let all the requests complete and verify the status codes for those: - close(connContinue) - - // Wait for the initial requests to finish and check their results. - jobs.Wait() -} - -func TestWebSocketReverseProxyNonHijackerPanic(t *testing.T) { - // Capture the expected panic - defer func() { - r := recover() - if _, ok := r.(httpserver.NonHijackerError); !ok { - t.Error("not get the expected panic") - } - }() - - var connCount int32 - wsNop := httptest.NewServer(websocket.Handler(func(ws *websocket.Conn) { atomic.AddInt32(&connCount, 1) })) - defer wsNop.Close() - - // Get proxy to use for the test - p := newWebSocketTestProxy(wsNop.URL, false) - - // Create client request - r := httptest.NewRequest("GET", "/", nil) - - r.Header = http.Header{ - "Connection": {"Upgrade"}, - "Upgrade": {"websocket"}, - "Origin": {wsNop.URL}, - "Sec-WebSocket-Key": {"x3JJHMbDL1EzLkh9GBhXDw=="}, - "Sec-WebSocket-Version": {"13"}, - } - - nonHijacker := httptest.NewRecorder() - p.ServeHTTP(nonHijacker, r) -} - -func TestWebSocketReverseProxyServeHTTPHandler(t *testing.T) { - // No-op websocket backend simply allows the WS connection to be - // accepted then it will be immediately closed. Perfect for testing. - accepted := make(chan struct{}) - wsNop := httptest.NewServer(websocket.Handler(func(ws *websocket.Conn) { close(accepted) })) - defer wsNop.Close() - - // Get proxy to use for the test - p := newWebSocketTestProxy(wsNop.URL, false) - - // Create client request - r := httptest.NewRequest("GET", "/", nil) - - r.Header = http.Header{ - "Connection": {"Upgrade"}, - "Upgrade": {"websocket"}, - "Origin": {wsNop.URL}, - "Sec-WebSocket-Key": {"x3JJHMbDL1EzLkh9GBhXDw=="}, - "Sec-WebSocket-Version": {"13"}, - } - - // Capture the request - w := &recorderHijacker{httptest.NewRecorder(), new(fakeConn)} - - // Booya! Do the test. - p.ServeHTTP(w, r) - - // Make sure the backend accepted the WS connection. - // Mostly interested in the Upgrade and Connection response headers - // and the 101 status code. - expected := []byte("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=\r\n\r\n") - actual := w.fakeConn.writeBuf.Bytes() - if !bytes.Equal(actual, expected) { - t.Errorf("Expected backend to accept response:\n'%s'\nActually got:\n'%s'", expected, actual) - } - - // wait a minute for backend handling, see issue 1654. - time.Sleep(10 * time.Millisecond) - - select { - case <-accepted: - default: - t.Error("Expect a accepted websocket connection, but not") - } -} - -func TestWebSocketReverseProxyFromWSClient(t *testing.T) { - // Echo server allows us to test that socket bytes are properly - // being proxied. - wsEcho := httptest.NewServer(websocket.Handler(func(ws *websocket.Conn) { - io.Copy(ws, ws) - })) - defer wsEcho.Close() - - // Get proxy to use for the test - p := newWebSocketTestProxy(wsEcho.URL, false) - - // This is a full end-end test, so the proxy handler - // has to be part of a server listening on a port. Our - // WS client will connect to this test server, not - // the echo client directly. - echoProxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - p.ServeHTTP(w, r) - })) - defer echoProxy.Close() - - // Set up WebSocket client - url := strings.Replace(echoProxy.URL, "http://", "ws://", 1) - ws, err := websocket.Dial(url, "", echoProxy.URL) - - if err != nil { - t.Fatal(err) - } - defer ws.Close() - - // Send test message - trialMsg := "Is it working?" - - if sendErr := websocket.Message.Send(ws, trialMsg); sendErr != nil { - t.Fatal(sendErr) - } - - // It should be echoed back to us - var actualMsg string - - if rcvErr := websocket.Message.Receive(ws, &actualMsg); rcvErr != nil { - t.Fatal(rcvErr) - } - - if actualMsg != trialMsg { - t.Errorf("Expected '%s' but got '%s' instead", trialMsg, actualMsg) - } -} - -func TestWebSocketReverseProxyFromWSSClient(t *testing.T) { - wsEcho := newTLSServer(websocket.Handler(func(ws *websocket.Conn) { - io.Copy(ws, ws) - })) - defer wsEcho.Close() - - p := newWebSocketTestProxy(wsEcho.URL, true) - - echoProxy := newTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - p.ServeHTTP(w, r) - })) - defer echoProxy.Close() - - // Set up WebSocket client - url := strings.Replace(echoProxy.URL, "https://", "wss://", 1) - wsCfg, err := websocket.NewConfig(url, echoProxy.URL) - if err != nil { - t.Fatal(err) - } - wsCfg.TlsConfig = &tls.Config{InsecureSkipVerify: true} - ws, err := websocket.DialConfig(wsCfg) - - if err != nil { - t.Fatal(err) - } - defer ws.Close() - - // Send test message - trialMsg := "Is it working?" - - if sendErr := websocket.Message.Send(ws, trialMsg); sendErr != nil { - t.Fatal(sendErr) - } - - // It should be echoed back to us - var actualMsg string - - if rcvErr := websocket.Message.Receive(ws, &actualMsg); rcvErr != nil { - t.Fatal(rcvErr) - } - - if actualMsg != trialMsg { - t.Errorf("Expected '%s' but got '%s' instead", trialMsg, actualMsg) - } -} - -func TestUnixSocketProxy(t *testing.T) { - if runtime.GOOS == "windows" { - return - } - - trialMsg := "Is it working?" - - var proxySuccess bool - - // This is our fake "application" we want to proxy to - ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Request was proxied when this is called - proxySuccess = true - - fmt.Fprint(w, trialMsg) - })) - - // Get absolute path for unix: socket - dir, err := ioutil.TempDir("", "caddy_proxytest") - if err != nil { - t.Fatalf("Failed to make temp dir to contain unix socket. %v", err) - } - defer os.RemoveAll(dir) - socketPath := filepath.Join(dir, "test_socket") - - // Change httptest.Server listener to listen to unix: socket - ln, err := net.Listen("unix", socketPath) - if err != nil { - t.Fatalf("Unable to listen: %v", err) - } - ts.Listener = ln - - ts.Start() - defer ts.Close() - - url := strings.Replace(ts.URL, "http://", "unix:", 1) - p := newWebSocketTestProxy(url, false) - - echoProxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - p.ServeHTTP(w, r) - })) - defer echoProxy.Close() - - res, err := http.Get(echoProxy.URL) - if err != nil { - t.Fatalf("Unable to GET: %v", err) - } - - greeting, err := ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - t.Fatalf("Unable to GET: %v", err) - } - - actualMsg := fmt.Sprintf("%s", greeting) - - if !proxySuccess { - t.Errorf("Expected request to be proxied, but it wasn't") - } - - if actualMsg != trialMsg { - t.Errorf("Expected '%s' but got '%s' instead", trialMsg, actualMsg) - } -} - -func GetHTTPProxy(messageFormat string, prefix string) (*Proxy, *httptest.Server) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, messageFormat, r.URL.String()) - })) - - return newPrefixedWebSocketTestProxy(ts.URL, prefix), ts -} - -func GetSocketProxy(messageFormat string, prefix string) (*Proxy, *httptest.Server, string, error) { - ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, messageFormat, r.URL.String()) - })) - - dir, err := ioutil.TempDir("", "caddy_proxytest") - if err != nil { - return nil, nil, dir, fmt.Errorf("Failed to make temp dir to contain unix socket. %v", err) - } - socketPath := filepath.Join(dir, "test_socket") - - ln, err := net.Listen("unix", socketPath) - if err != nil { - os.RemoveAll(dir) - return nil, nil, dir, fmt.Errorf("Unable to listen: %v", err) - } - ts.Listener = ln - - ts.Start() - - tsURL := strings.Replace(ts.URL, "http://", "unix:", 1) - - return newPrefixedWebSocketTestProxy(tsURL, prefix), ts, dir, nil -} - -func GetTestServerMessage(p *Proxy, ts *httptest.Server, path string) (string, error) { - echoProxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - p.ServeHTTP(w, r) - })) - - // *httptest.Server is passed so it can be `defer`red properly - defer ts.Close() - defer echoProxy.Close() - - res, err := http.Get(echoProxy.URL + path) - if err != nil { - return "", fmt.Errorf("Unable to GET: %v", err) - } - - greeting, err := ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - return "", fmt.Errorf("Unable to read body: %v", err) - } - - return fmt.Sprintf("%s", greeting), nil -} - -func TestUnixSocketProxyPaths(t *testing.T) { - greeting := "Hello route %s" - - tests := []struct { - url string - prefix string - expected string - }{ - {"", "", fmt.Sprintf(greeting, "/")}, - {"/hello", "", fmt.Sprintf(greeting, "/hello")}, - {"/foo/bar", "", fmt.Sprintf(greeting, "/foo/bar")}, - {"/foo?bar", "", fmt.Sprintf(greeting, "/foo?bar")}, - {"/greet?name=john", "", fmt.Sprintf(greeting, "/greet?name=john")}, - {"/world?wonderful&colorful", "", fmt.Sprintf(greeting, "/world?wonderful&colorful")}, - {"/proxy/hello", "/proxy", fmt.Sprintf(greeting, "/hello")}, - {"/proxy/foo/bar", "/proxy", fmt.Sprintf(greeting, "/foo/bar")}, - {"/proxy/?foo=bar", "/proxy", fmt.Sprintf(greeting, "/?foo=bar")}, - {"/queues/%2F/fetchtasks", "", fmt.Sprintf(greeting, "/queues/%2F/fetchtasks")}, - {"/queues/%2F/fetchtasks?foo=bar", "", fmt.Sprintf(greeting, "/queues/%2F/fetchtasks?foo=bar")}, - } - - for _, test := range tests { - p, ts := GetHTTPProxy(greeting, test.prefix) - - actualMsg, err := GetTestServerMessage(p, ts, test.url) - - if err != nil { - t.Fatalf("Getting server message failed - %v", err) - } - - if actualMsg != test.expected { - t.Errorf("Expected '%s' but got '%s' instead", test.expected, actualMsg) - } - } - - if runtime.GOOS == "windows" { - return - } - - for _, test := range tests { - p, ts, tmpdir, err := GetSocketProxy(greeting, test.prefix) - if err != nil { - t.Fatalf("Getting socket proxy failed - %v", err) - } - - actualMsg, err := GetTestServerMessage(p, ts, test.url) - - if err != nil { - os.RemoveAll(tmpdir) - t.Fatalf("Getting server message failed - %v", err) - } - - if actualMsg != test.expected { - t.Errorf("Expected '%s' but got '%s' instead", test.expected, actualMsg) - } - - os.RemoveAll(tmpdir) - } -} - -func TestUpstreamHeadersUpdate(t *testing.T) { - log.SetOutput(ioutil.Discard) - defer log.SetOutput(os.Stderr) - - var actualHeaders http.Header - var actualHost string - backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("Hello, client")) - actualHeaders = r.Header - actualHost = r.Host - })) - defer backend.Close() - - upstream := newFakeUpstream(backend.URL, false) - upstream.host.UpstreamHeaders = http.Header{ - "Connection": {"{>Connection}"}, - "Upgrade": {"{>Upgrade}"}, - "+Merge-Me": {"Merge-Value"}, - "+Add-Me": {"Add-Value"}, - "+Add-Empty": {"{}"}, - "-Remove-Me": {""}, - "Replace-Me": {"{hostname}"}, - "Clear-Me": {""}, - "Host": {"{>Host}"}, - } - // set up proxy - p := &Proxy{ - Next: httpserver.EmptyNext, // prevents panic in some cases when test fails - Upstreams: []Upstream{upstream}, - } - - // create request and response recorder - r := httptest.NewRequest("GET", "/", nil) - w := httptest.NewRecorder() - - const expectHost = "example.com" - //add initial headers - r.Header.Add("Merge-Me", "Initial") - r.Header.Add("Remove-Me", "Remove-Value") - r.Header.Add("Replace-Me", "Replace-Value") - r.Header.Add("Host", expectHost) - - p.ServeHTTP(w, r) - - replacer := httpserver.NewReplacer(r, nil, "") - - for headerKey, expect := range map[string][]string{ - "Merge-Me": {"Initial", "Merge-Value"}, - "Add-Me": {"Add-Value"}, - "Add-Empty": nil, - "Remove-Me": nil, - "Replace-Me": {replacer.Replace("{hostname}")}, - "Clear-Me": nil, - } { - if got := actualHeaders[headerKey]; !reflect.DeepEqual(got, expect) { - t.Errorf("Upstream request does not contain expected %v header: expect %v, but got %v", - headerKey, expect, got) - } - } - - if actualHost != expectHost { - t.Errorf("Request sent to upstream backend should have value of Host with %s, but got %s", expectHost, actualHost) - } - -} - -func TestDownstreamHeadersUpdate(t *testing.T) { - log.SetOutput(ioutil.Discard) - defer log.SetOutput(os.Stderr) - - backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Add("Merge-Me", "Initial") - w.Header().Add("Remove-Me", "Remove-Value") - w.Header().Add("Replace-Me", "Replace-Value") - w.Header().Add("Content-Type", "text/html") - w.Header().Add("Overwrite-Me", "Overwrite-Value") - w.Write([]byte("Hello, client")) - })) - defer backend.Close() - - upstream := newFakeUpstream(backend.URL, false) - upstream.host.DownstreamHeaders = http.Header{ - "+Merge-Me": {"Merge-Value"}, - "+Add-Me": {"Add-Value"}, - "-Remove-Me": {""}, - "Replace-Me": {"{hostname}"}, - } - // set up proxy - p := &Proxy{ - Next: httpserver.EmptyNext, // prevents panic in some cases when test fails - Upstreams: []Upstream{upstream}, - } - - // create request and response recorder - r := httptest.NewRequest("GET", "/", nil) - w := httptest.NewRecorder() - // set a predefined skip header - w.Header().Set("Content-Type", "text/css") - // set a predefined overwritten header - w.Header().Set("Overwrite-Me", "Initial") - - p.ServeHTTP(w, r) - - replacer := httpserver.NewReplacer(r, nil, "") - actualHeaders := w.Header() - - for headerKey, expect := range map[string][]string{ - "Merge-Me": {"Initial", "Merge-Value"}, - "Add-Me": {"Add-Value"}, - "Remove-Me": nil, - "Replace-Me": {replacer.Replace("{hostname}")}, - "Content-Type": {"text/css"}, - "Overwrite-Me": {"Overwrite-Value"}, - } { - if got := actualHeaders[headerKey]; !reflect.DeepEqual(got, expect) { - t.Errorf("Downstream response does not contain expected %s header: expect %v, but got %v", - headerKey, expect, got) - } - } -} - -var ( - upstreamResp1 = []byte("Hello, /") - upstreamResp2 = []byte("Hello, /api/") -) - -func newMultiHostTestProxy() *Proxy { - // No-op backends. - upstreamServer1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "%s", upstreamResp1) - })) - upstreamServer2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "%s", upstreamResp2) - })) - p := &Proxy{ - Next: httpserver.EmptyNext, // prevents panic in some cases when test fails - Upstreams: []Upstream{ - // The order is important; the short path should go first to ensure - // we choose the most specific route, not the first one. - &fakeUpstream{ - name: upstreamServer1.URL, - from: "/", - }, - &fakeUpstream{ - name: upstreamServer2.URL, - from: "/api", - }, - }, - } - return p -} - -func TestMultiReverseProxyFromClient(t *testing.T) { - p := newMultiHostTestProxy() - - // This is a full end-end test, so the proxy handler. - proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - p.ServeHTTP(w, r) - })) - defer proxy.Close() - - // Table tests. - var multiProxy = []struct { - url string - body []byte - }{ - { - "/", - upstreamResp1, - }, - { - "/api/", - upstreamResp2, - }, - { - "/messages/", - upstreamResp1, - }, - { - "/api/messages/?text=cat", - upstreamResp2, - }, - } - - for _, tt := range multiProxy { - // Create client request - reqURL := proxy.URL + tt.url - req, err := http.NewRequest("GET", reqURL, nil) - - if err != nil { - t.Fatalf("Failed to make request: %v", err) - } - - resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("Failed to make request: %v", err) - } - body, err := ioutil.ReadAll(resp.Body) - resp.Body.Close() - if err != nil { - t.Fatalf("Failed to read response: %v", err) - } - - if !bytes.Equal(body, tt.body) { - t.Errorf("Expected '%s' but got '%s' instead", tt.body, body) - } - } -} - -func TestHostSimpleProxyNoHeaderForward(t *testing.T) { - var requestHost string - backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - requestHost = r.Host - w.Write([]byte("Hello, client")) - })) - defer backend.Close() - - // set up proxy - p := &Proxy{ - Next: httpserver.EmptyNext, // prevents panic in some cases when test fails - Upstreams: []Upstream{newFakeUpstream(backend.URL, false)}, - } - - r := httptest.NewRequest("GET", "/", nil) - r.Host = "test.com" - - w := httptest.NewRecorder() - - p.ServeHTTP(w, r) - - if !strings.Contains(backend.URL, "//") { - t.Fatalf("The URL of the backend server doesn't contains //: %s", backend.URL) - } - - expectedHost := strings.Split(backend.URL, "//") - if expectedHost[1] != requestHost { - t.Fatalf("Expected %s as a Host header got %s\n", expectedHost[1], requestHost) - } -} - -func TestHostHeaderReplacedUsingForward(t *testing.T) { - var requestHost string - backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - requestHost = r.Host - w.Write([]byte("Hello, client")) - })) - defer backend.Close() - - upstream := newFakeUpstream(backend.URL, false) - proxyHostHeader := "test2.com" - upstream.host.UpstreamHeaders = http.Header{"Host": []string{proxyHostHeader}} - // set up proxy - p := &Proxy{ - Next: httpserver.EmptyNext, // prevents panic in some cases when test fails - Upstreams: []Upstream{upstream}, - } - - r := httptest.NewRequest("GET", "/", nil) - r.Host = "test.com" - - w := httptest.NewRecorder() - - p.ServeHTTP(w, r) - - if proxyHostHeader != requestHost { - t.Fatalf("Expected %s as a Host header got %s\n", proxyHostHeader, requestHost) - } -} - -func TestBasicAuth(t *testing.T) { - basicAuthTestcase(t, nil, nil) - basicAuthTestcase(t, nil, url.UserPassword("username", "password")) - basicAuthTestcase(t, url.UserPassword("usename", "password"), nil) - basicAuthTestcase(t, url.UserPassword("unused", "unused"), - url.UserPassword("username", "password")) -} - -func basicAuthTestcase(t *testing.T, upstreamUser, clientUser *url.Userinfo) { - backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - u, p, ok := r.BasicAuth() - - if ok { - w.Write([]byte(u)) - } - if ok && p != "" { - w.Write([]byte(":")) - w.Write([]byte(p)) - } - })) - defer backend.Close() - - backURL, err := url.Parse(backend.URL) - if err != nil { - t.Fatalf("Failed to parse URL: %v", err) - } - backURL.User = upstreamUser - - p := &Proxy{ - Next: httpserver.EmptyNext, - Upstreams: []Upstream{newFakeUpstream(backURL.String(), false)}, - } - r, err := http.NewRequest("GET", "/foo", nil) - if err != nil { - t.Fatalf("Failed to create request: %v", err) - } - if clientUser != nil { - u := clientUser.Username() - p, _ := clientUser.Password() - r.SetBasicAuth(u, p) - } - w := httptest.NewRecorder() - - p.ServeHTTP(w, r) - - if w.Code != 200 { - t.Fatalf("Invalid response code: %d", w.Code) - } - body, _ := ioutil.ReadAll(w.Body) - - if clientUser != nil { - if string(body) != clientUser.String() { - t.Fatalf("Invalid auth info: %s", string(body)) - } - } else { - if upstreamUser != nil { - if string(body) != upstreamUser.String() { - t.Fatalf("Invalid auth info: %s", string(body)) - } - } else { - if string(body) != "" { - t.Fatalf("Invalid auth info: %s", string(body)) - } - } - } -} - -func TestProxyDirectorURL(t *testing.T) { - for i, c := range []struct { - requestURL string - targetURL string - without string - expectURL string - }{ - { - requestURL: `http://localhost:2020/test`, - targetURL: `https://localhost:2021`, - expectURL: `https://localhost:2021/test`, - }, - { - requestURL: `http://localhost:2020/test`, - targetURL: `https://localhost:2021/t`, - expectURL: `https://localhost:2021/t/test`, - }, - { - requestURL: `http://localhost:2020/test?t=w`, - targetURL: `https://localhost:2021/t`, - expectURL: `https://localhost:2021/t/test?t=w`, - }, - { - requestURL: `http://localhost:2020/test`, - targetURL: `https://localhost:2021/t?foo=bar`, - expectURL: `https://localhost:2021/t/test?foo=bar`, - }, - { - requestURL: `http://localhost:2020/test?t=w`, - targetURL: `https://localhost:2021/t?foo=bar`, - expectURL: `https://localhost:2021/t/test?foo=bar&t=w`, - }, - { - requestURL: `http://localhost:2020/test?t=w`, - targetURL: `https://localhost:2021/t?foo=bar`, - expectURL: `https://localhost:2021/t?foo=bar&t=w`, - without: "/test", - }, - { - requestURL: `http://localhost:2020/test?t%3dw`, - targetURL: `https://localhost:2021/t?foo%3dbar`, - expectURL: `https://localhost:2021/t?foo%3dbar&t%3dw`, - without: "/test", - }, - { - requestURL: `http://localhost:2020/test/`, - targetURL: `https://localhost:2021/t/`, - expectURL: `https://localhost:2021/t/test/`, - }, - { - requestURL: `http://localhost:2020/test/mypath`, - targetURL: `https://localhost:2021/t/`, - expectURL: `https://localhost:2021/t/mypath`, - without: "/test", - }, - { - requestURL: `http://localhost:2020/%2C`, - targetURL: `https://localhost:2021/t/`, - expectURL: `https://localhost:2021/t/%2C`, - }, - { - requestURL: `http://localhost:2020/%2C/`, - targetURL: `https://localhost:2021/t/`, - expectURL: `https://localhost:2021/t/%2C/`, - }, - { - requestURL: `http://localhost:2020/test`, - targetURL: `https://localhost:2021/%2C`, - expectURL: `https://localhost:2021/%2C/test`, - }, - { - requestURL: `http://localhost:2020/%2C`, - targetURL: `https://localhost:2021/%2C`, - expectURL: `https://localhost:2021/%2C/%2C`, - }, - { - requestURL: `http://localhost:2020/%2F/test`, - targetURL: `https://localhost:2021/`, - expectURL: `https://localhost:2021/%2F/test`, - }, - { - requestURL: `http://localhost:2020/test/%2F/mypath`, - targetURL: `https://localhost:2021/t/`, - expectURL: `https://localhost:2021/t/%2F/mypath`, - without: "/test", - }, - } { - targetURL, err := url.Parse(c.targetURL) - if err != nil { - t.Errorf("case %d failed to parse target URL: %s", i, err) - continue - } - req, err := http.NewRequest("GET", c.requestURL, nil) - if err != nil { - t.Errorf("case %d failed to create request: %s", i, err) - continue - } - - NewSingleHostReverseProxy(targetURL, c.without, 0).Director(req) - if expect, got := c.expectURL, req.URL.String(); expect != got { - t.Errorf("case %d url not equal: expect %q, but got %q", - i, expect, got) - } - } -} - -func TestReverseProxyRetry(t *testing.T) { - log.SetOutput(ioutil.Discard) - defer log.SetOutput(os.Stderr) - - // set up proxy - backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - io.Copy(w, r.Body) - r.Body.Close() - })) - defer backend.Close() - - su, err := NewStaticUpstreams(caddyfile.NewDispenser("Testfile", strings.NewReader(` - proxy / localhost:65535 localhost:65534 `+backend.URL+` { - policy round_robin - fail_timeout 5s - max_fails 1 - try_duration 5s - try_interval 250ms - } - `)), "") - if err != nil { - t.Fatal(err) - } - - p := &Proxy{ - Next: httpserver.EmptyNext, // prevents panic in some cases when test fails - Upstreams: su, - } - - // middle is required to simulate closable downstream request body - middle := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, err = p.ServeHTTP(w, r) - if err != nil { - t.Error(err) - } - })) - defer middle.Close() - - testcase := "test content" - r, err := http.NewRequest("POST", middle.URL, bytes.NewBufferString(testcase)) - if err != nil { - t.Fatal(err) - } - resp, err := http.DefaultTransport.RoundTrip(r) - if err != nil { - t.Fatal(err) - } - b, err := ioutil.ReadAll(resp.Body) - resp.Body.Close() - if err != nil { - t.Fatal(err) - } - if string(b) != testcase { - t.Fatalf("string(b) = %s, want %s", string(b), testcase) - } -} - -func TestReverseProxyLargeBody(t *testing.T) { - log.SetOutput(ioutil.Discard) - defer log.SetOutput(os.Stderr) - - // set up proxy - backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - io.Copy(ioutil.Discard, r.Body) - r.Body.Close() - })) - defer backend.Close() - - su, err := NewStaticUpstreams(caddyfile.NewDispenser("Testfile", strings.NewReader(`proxy / `+backend.URL)), "") - if err != nil { - t.Fatal(err) - } - - p := &Proxy{ - Next: httpserver.EmptyNext, // prevents panic in some cases when test fails - Upstreams: su, - } - - // middle is required to simulate closable downstream request body - middle := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, err = p.ServeHTTP(w, r) - if err != nil { - t.Error(err) - } - })) - defer middle.Close() - - // Our request body will be 100MB - bodySize := uint64(100 * 1000 * 1000) - - // We want to see how much memory the proxy module requires for this request. - // So lets record the mem stats before we start it. - begMemstats := &runtime.MemStats{} - runtime.ReadMemStats(begMemstats) - - r, err := http.NewRequest("POST", middle.URL, &noopReader{len: bodySize}) - if err != nil { - t.Fatal(err) - } - resp, err := http.DefaultTransport.RoundTrip(r) - if err != nil { - t.Fatal(err) - } - resp.Body.Close() - - // Finally we need the mem stats after the request is done... - endMemstats := &runtime.MemStats{} - runtime.ReadMemStats(endMemstats) - - // ...to calculate the total amount of allocated memory during the request. - totalAlloc := endMemstats.TotalAlloc - begMemstats.TotalAlloc - - // If that's as much as the size of the body itself it's a serious sign that the - // request was not "streamed" to the upstream without buffering it first. - if totalAlloc >= bodySize { - t.Fatalf("proxy allocated too much memory: %d bytes", totalAlloc) - } -} - -func TestCancelRequest(t *testing.T) { - reqInFlight := make(chan struct{}) - backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - close(reqInFlight) // cause the client to cancel its request - - select { - case <-time.After(10 * time.Second): - t.Error("Handler never saw CloseNotify") - return - case <-w.(http.CloseNotifier).CloseNotify(): - } - - w.WriteHeader(http.StatusOK) - w.Write([]byte("Hello, client")) - })) - defer backend.Close() - - // set up proxy - p := &Proxy{ - Next: httpserver.EmptyNext, // prevents panic in some cases when test fails - Upstreams: []Upstream{newFakeUpstream(backend.URL, false)}, - } - - // setup request with cancel ctx - req := httptest.NewRequest("GET", "/", nil) - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - req = req.WithContext(ctx) - - // wait for canceling the request - go func() { - <-reqInFlight - cancel() - }() - - rec := httptest.NewRecorder() - status, err := p.ServeHTTP(rec, req) - expectedStatus, expectErr := http.StatusBadGateway, context.Canceled - if status != expectedStatus || err != expectErr { - t.Errorf("expect proxy handle return status[%d] with error[%v], but got status[%d] with error[%v]", - expectedStatus, expectErr, status, err) - } - if body := rec.Body.String(); body != "" { - t.Errorf("expect a blank response, but got %q", body) - } -} - -type noopReader struct { - len uint64 - pos uint64 -} - -var _ io.Reader = &noopReader{} - -func (r *noopReader) Read(b []byte) (int, error) { - if r.pos >= r.len { - return 0, io.EOF - } - n := int(r.len - r.pos) - if n > len(b) { - n = len(b) - } - for i := range b[:n] { - b[i] = 0 - } - r.pos += uint64(n) - return n, nil -} - -func newFakeUpstream(name string, insecure bool) *fakeUpstream { - uri, _ := url.Parse(name) - u := &fakeUpstream{ - name: name, - from: "/", - host: &UpstreamHost{ - Name: name, - ReverseProxy: NewSingleHostReverseProxy(uri, "", http.DefaultMaxIdleConnsPerHost), - }, - } - if insecure { - u.host.ReverseProxy.UseInsecureTransport() - } - return u -} - -type fakeUpstream struct { - name string - host *UpstreamHost - from string - without string -} - -func (u *fakeUpstream) From() string { - return u.from -} - -func (u *fakeUpstream) Select(r *http.Request) *UpstreamHost { - if u.host == nil { - uri, err := url.Parse(u.name) - if err != nil { - log.Fatalf("Unable to url.Parse %s: %v", u.name, err) - } - u.host = &UpstreamHost{ - Name: u.name, - ReverseProxy: NewSingleHostReverseProxy(uri, u.without, http.DefaultMaxIdleConnsPerHost), - } - } - return u.host -} - -func (u *fakeUpstream) AllowedPath(requestPath string) bool { return true } -func (u *fakeUpstream) GetTryDuration() time.Duration { return 1 * time.Second } -func (u *fakeUpstream) GetTryInterval() time.Duration { return 250 * time.Millisecond } -func (u *fakeUpstream) GetHostCount() int { return 1 } -func (u *fakeUpstream) Stop() error { return nil } - -// newWebSocketTestProxy returns a test proxy that will -// redirect to the specified backendAddr. The function -// also sets up the rules/environment for testing WebSocket -// proxy. -func newWebSocketTestProxy(backendAddr string, insecure bool) *Proxy { - return &Proxy{ - Next: httpserver.EmptyNext, // prevents panic in some cases when test fails - Upstreams: []Upstream{&fakeWsUpstream{ - name: backendAddr, - without: "", - insecure: insecure, - }}, - } -} - -func newPrefixedWebSocketTestProxy(backendAddr string, prefix string) *Proxy { - return &Proxy{ - Next: httpserver.EmptyNext, // prevents panic in some cases when test fails - Upstreams: []Upstream{&fakeWsUpstream{name: backendAddr, without: prefix}}, - } -} - -type fakeWsUpstream struct { - name string - without string - insecure bool -} - -func (u *fakeWsUpstream) From() string { - return "/" -} - -func (u *fakeWsUpstream) Select(r *http.Request) *UpstreamHost { - uri, _ := url.Parse(u.name) - host := &UpstreamHost{ - Name: u.name, - ReverseProxy: NewSingleHostReverseProxy(uri, u.without, http.DefaultMaxIdleConnsPerHost), - UpstreamHeaders: http.Header{ - "Connection": {"{>Connection}"}, - "Upgrade": {"{>Upgrade}"}}, - } - if u.insecure { - host.ReverseProxy.UseInsecureTransport() - } - return host -} - -func (u *fakeWsUpstream) AllowedPath(requestPath string) bool { return true } -func (u *fakeWsUpstream) GetTryDuration() time.Duration { return 1 * time.Second } -func (u *fakeWsUpstream) GetTryInterval() time.Duration { return 250 * time.Millisecond } -func (u *fakeWsUpstream) GetHostCount() int { return 1 } -func (u *fakeWsUpstream) Stop() error { return nil } - -// recorderHijacker is a ResponseRecorder that can -// be hijacked. -type recorderHijacker struct { - *httptest.ResponseRecorder - fakeConn *fakeConn -} - -func (rh *recorderHijacker) Hijack() (net.Conn, *bufio.ReadWriter, error) { - return rh.fakeConn, nil, nil -} - -type fakeConn struct { - readBuf bytes.Buffer - writeBuf bytes.Buffer -} - -func (c *fakeConn) LocalAddr() net.Addr { return nil } -func (c *fakeConn) RemoteAddr() net.Addr { return nil } -func (c *fakeConn) SetDeadline(t time.Time) error { return nil } -func (c *fakeConn) SetReadDeadline(t time.Time) error { return nil } -func (c *fakeConn) SetWriteDeadline(t time.Time) error { return nil } -func (c *fakeConn) Close() error { return nil } -func (c *fakeConn) Read(b []byte) (int, error) { return c.readBuf.Read(b) } -func (c *fakeConn) Write(b []byte) (int, error) { return c.writeBuf.Write(b) } - -// testResponseRecorder wraps `httptest.ResponseRecorder`, -// also implements `http.CloseNotifier`, `http.Hijacker` and `http.Pusher`. -type testResponseRecorder struct { - *httpserver.ResponseWriterWrapper -} - -func (testResponseRecorder) CloseNotify() <-chan bool { return nil } - -// Interface guards -var _ httpserver.HTTPInterfaces = testResponseRecorder{} - -func BenchmarkProxy(b *testing.B) { - backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("Hello, client")) - })) - defer backend.Close() - - upstream := newFakeUpstream(backend.URL, false) - upstream.host.UpstreamHeaders = http.Header{ - "Hostname": {"{hostname}"}, - "Host": {"{host}"}, - "X-Real-IP": {"{remote}"}, - "X-Forwarded-Proto": {"{scheme}"}, - } - // set up proxy - p := &Proxy{ - Next: httpserver.EmptyNext, // prevents panic in some cases when test fails - Upstreams: []Upstream{upstream}, - } - - w := httptest.NewRecorder() - - b.ResetTimer() - for i := 0; i < b.N; i++ { - b.StopTimer() - // create request and response recorder - r, err := http.NewRequest("GET", "/", nil) - if err != nil { - b.Fatalf("Failed to create request: %v", err) - } - b.StartTimer() - p.ServeHTTP(w, r) - } -} - -func TestChunkedWebSocketReverseProxy(t *testing.T) { - s := websocket.Server{ - Handler: websocket.Handler(func(ws *websocket.Conn) { - for { - select {} - } - }), - } - s.Config.Header = http.Header(make(map[string][]string)) - s.Config.Header.Set("Transfer-Encoding", "chunked") - - wsNop := httptest.NewServer(s) - defer wsNop.Close() - - // Get proxy to use for the test - p := newWebSocketTestProxy(wsNop.URL, false) - - // Create client request - r := httptest.NewRequest("GET", "/", nil) - - r.Header = http.Header{ - "Connection": {"Upgrade"}, - "Upgrade": {"websocket"}, - "Origin": {wsNop.URL}, - "Sec-WebSocket-Key": {"x3JJHMbDL1EzLkh9GBhXDw=="}, - "Sec-WebSocket-Version": {"13"}, - } - - // Capture the request - w := &recorderHijacker{httptest.NewRecorder(), new(fakeConn)} - - // Booya! Do the test. - _, err := p.ServeHTTP(w, r) - - // Make sure the backend accepted the WS connection. - // Mostly interested in the Upgrade and Connection response headers - // and the 101 status code. - expected := []byte("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=\r\nTransfer-Encoding: chunked\r\n\r\n") - actual := w.fakeConn.writeBuf.Bytes() - if !bytes.Equal(actual, expected) { - t.Errorf("Expected backend to accept response:\n'%s'\nActually got:\n'%s'", expected, actual) - } - - if err != nil { - t.Error(err) - } -} diff --git a/caddyhttp/proxy/reverseproxy.go b/caddyhttp/proxy/reverseproxy.go deleted file mode 100644 index 41687cc189e..00000000000 --- a/caddyhttp/proxy/reverseproxy.go +++ /dev/null @@ -1,656 +0,0 @@ -// This file is adapted from code in the net/http/httputil -// package of the Go standard library, which is by the -// Go Authors, and bears this copyright and license info: -// -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. -// -// This file has been modified from the standard lib to -// meet the needs of the application. - -package proxy - -import ( - "crypto/tls" - "io" - "net" - "net/http" - "net/url" - "strings" - "sync" - "time" - - "golang.org/x/net/http2" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -var ( - defaultDialer = &net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - } - - bufferPool = sync.Pool{New: createBuffer} -) - -func createBuffer() interface{} { - return make([]byte, 0, 32*1024) -} - -func pooledIoCopy(dst io.Writer, src io.Reader) { - buf := bufferPool.Get().([]byte) - defer bufferPool.Put(buf) - - // CopyBuffer only uses buf up to its length and panics if it's 0. - // Due to that we extend buf's length to its capacity here and - // ensure it's always non-zero. - bufCap := cap(buf) - io.CopyBuffer(dst, src, buf[0:bufCap:bufCap]) -} - -// onExitFlushLoop is a callback set by tests to detect the state of the -// flushLoop() goroutine. -var onExitFlushLoop func() - -// ReverseProxy is an HTTP Handler that takes an incoming request and -// sends it to another server, proxying the response back to the -// client. -type ReverseProxy struct { - // Director must be a function which modifies - // the request into a new request to be sent - // using Transport. Its response is then copied - // back to the original client unmodified. - Director func(*http.Request) - - // The transport used to perform proxy requests. - // If nil, http.DefaultTransport is used. - Transport http.RoundTripper - - // FlushInterval specifies the flush interval - // to flush to the client while copying the - // response body. - // If zero, no periodic flushing is done. - FlushInterval time.Duration -} - -// Though the relevant directive prefix is just "unix:", url.Parse -// will - assuming the regular URL scheme - add additional slashes -// as if "unix" was a request protocol. -// What we need is just the path, so if "unix:/var/run/www.socket" -// was the proxy directive, the parsed hostName would be -// "unix:///var/run/www.socket", hence the ambiguous trimming. -func socketDial(hostName string) func(network, addr string) (conn net.Conn, err error) { - return func(network, addr string) (conn net.Conn, err error) { - return net.Dial("unix", hostName[len("unix://"):]) - } -} - -func singleJoiningSlash(a, b string) string { - aslash := strings.HasSuffix(a, "/") - bslash := strings.HasPrefix(b, "/") - switch { - case aslash && bslash: - return a + b[1:] - case !aslash && !bslash && b != "": - return a + "/" + b - } - return a + b -} - -// NewSingleHostReverseProxy returns a new ReverseProxy that rewrites -// URLs to the scheme, host, and base path provided in target. If the -// target's path is "/base" and the incoming request was for "/dir", -// the target request will be for /base/dir. -// Without logic: target's path is "/", incoming is "/api/messages", -// without is "/api", then the target request will be for /messages. -func NewSingleHostReverseProxy(target *url.URL, without string, keepalive int) *ReverseProxy { - targetQuery := target.RawQuery - director := func(req *http.Request) { - if target.Scheme == "unix" { - // to make Dial work with unix URL, - // scheme and host have to be faked - req.URL.Scheme = "http" - req.URL.Host = "socket" - } else { - req.URL.Scheme = target.Scheme - req.URL.Host = target.Host - } - - // remove the `without` prefix - if without != "" { - req.URL.Path = strings.TrimPrefix(req.URL.Path, without) - if req.URL.Opaque != "" { - req.URL.Opaque = strings.TrimPrefix(req.URL.Opaque, without) - } - if req.URL.RawPath != "" { - req.URL.RawPath = strings.TrimPrefix(req.URL.RawPath, without) - } - } - - // prefer returns val if it isn't empty, otherwise def - prefer := func(val, def string) string { - if val != "" { - return val - } - return def - } - - // Make up the final URL by concatenating the request and target URL. - // - // If there is encoded part in request or target URL, - // the final URL should also be in encoded format. - // Here, we concatenate their encoded parts which are stored - // in URL.Opaque and URL.RawPath, if it is empty use - // URL.Path instead. - if req.URL.Opaque != "" || target.Opaque != "" { - req.URL.Opaque = singleJoiningSlash( - prefer(target.Opaque, target.Path), - prefer(req.URL.Opaque, req.URL.Path)) - } - if req.URL.RawPath != "" || target.RawPath != "" { - req.URL.RawPath = singleJoiningSlash( - prefer(target.RawPath, target.Path), - prefer(req.URL.RawPath, req.URL.Path)) - } - req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path) - - // Trims the path of the socket from the URL path. - // This is done because req.URL passed to your proxied service - // will have the full path of the socket file prefixed to it. - // Calling /test on a server that proxies requests to - // unix:/var/run/www.socket will thus set the requested path - // to /var/run/www.socket/test, rendering paths useless. - if target.Scheme == "unix" { - // See comment on socketDial for the trim - socketPrefix := target.String()[len("unix://"):] - req.URL.Path = strings.TrimPrefix(req.URL.Path, socketPrefix) - if req.URL.Opaque != "" { - req.URL.Opaque = strings.TrimPrefix(req.URL.Opaque, socketPrefix) - } - if req.URL.RawPath != "" { - req.URL.RawPath = strings.TrimPrefix(req.URL.RawPath, socketPrefix) - } - } - - if targetQuery == "" || req.URL.RawQuery == "" { - req.URL.RawQuery = targetQuery + req.URL.RawQuery - } else { - req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery - } - } - rp := &ReverseProxy{Director: director, FlushInterval: 250 * time.Millisecond} // flushing good for streaming & server-sent events - if target.Scheme == "unix" { - rp.Transport = &http.Transport{ - Dial: socketDial(target.String()), - } - } else if keepalive != http.DefaultMaxIdleConnsPerHost { - // if keepalive is equal to the default, - // just use default transport, to avoid creating - // a brand new transport - transport := &http.Transport{ - Proxy: http.ProxyFromEnvironment, - Dial: defaultDialer.Dial, - TLSHandshakeTimeout: 10 * time.Second, - ExpectContinueTimeout: 1 * time.Second, - } - if keepalive == 0 { - transport.DisableKeepAlives = true - } else { - transport.MaxIdleConnsPerHost = keepalive - } - if httpserver.HTTP2 { - http2.ConfigureTransport(transport) - } - rp.Transport = transport - } - return rp -} - -// UseInsecureTransport is used to facilitate HTTPS proxying -// when it is OK for upstream to be using a bad certificate, -// since this transport skips verification. -func (rp *ReverseProxy) UseInsecureTransport() { - if rp.Transport == nil { - transport := &http.Transport{ - Proxy: http.ProxyFromEnvironment, - Dial: defaultDialer.Dial, - TLSHandshakeTimeout: 10 * time.Second, - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - if httpserver.HTTP2 { - http2.ConfigureTransport(transport) - } - rp.Transport = transport - } else if transport, ok := rp.Transport.(*http.Transport); ok { - if transport.TLSClientConfig == nil { - transport.TLSClientConfig = &tls.Config{} - } - transport.TLSClientConfig.InsecureSkipVerify = true - // No http2.ConfigureTransport() here. - // For now this is only added in places where - // an http.Transport is actually created. - } -} - -// ServeHTTP serves the proxied request to the upstream by performing a roundtrip. -// It is designed to handle websocket connection upgrades as well. -func (rp *ReverseProxy) ServeHTTP(rw http.ResponseWriter, outreq *http.Request, respUpdateFn respUpdateFn) error { - transport := rp.Transport - if requestIsWebsocket(outreq) { - transport = newConnHijackerTransport(transport) - } else if transport == nil { - transport = http.DefaultTransport - } - - rp.Director(outreq) - - res, err := transport.RoundTrip(outreq) - if err != nil { - return err - } - - isWebsocket := res.StatusCode == http.StatusSwitchingProtocols && strings.ToLower(res.Header.Get("Upgrade")) == "websocket" - - // Remove hop-by-hop headers listed in the - // "Connection" header of the response. - if c := res.Header.Get("Connection"); c != "" { - for _, f := range strings.Split(c, ",") { - if f = strings.TrimSpace(f); f != "" { - res.Header.Del(f) - } - } - } - - for _, h := range hopHeaders { - res.Header.Del(h) - } - - if respUpdateFn != nil { - respUpdateFn(res) - } - - if isWebsocket { - defer res.Body.Close() - hj, ok := rw.(http.Hijacker) - if !ok { - panic(httpserver.NonHijackerError{Underlying: rw}) - } - - conn, brw, err := hj.Hijack() - if err != nil { - return err - } - defer conn.Close() - - var backendConn net.Conn - if hj, ok := transport.(*connHijackerTransport); ok { - backendConn = hj.Conn - if _, err := conn.Write(hj.Replay); err != nil { - return err - } - bufferPool.Put(hj.Replay) - } else { - backendConn, err = net.Dial("tcp", outreq.URL.Host) - if err != nil { - return err - } - outreq.Write(backendConn) - } - defer backendConn.Close() - - // Proxy backend -> frontend. - go pooledIoCopy(conn, backendConn) - - // Proxy frontend -> backend. - // - // NOTE: Hijack() sometimes returns buffered up bytes in brw which - // would be lost if we didn't read them out manually below. - if brw != nil { - if n := brw.Reader.Buffered(); n > 0 { - rbuf, err := brw.Reader.Peek(n) - if err != nil { - return err - } - backendConn.Write(rbuf) - } - } - pooledIoCopy(backendConn, conn) - } else { - // NOTE: - // Closing the Body involves acquiring a mutex, which is a - // unnecessarily heavy operation, considering that this defer will - // pretty much never be executed with the Body still unclosed. - bodyOpen := true - closeBody := func() { - if bodyOpen { - res.Body.Close() - bodyOpen = false - } - } - defer closeBody() - - // Copy all headers over. - // res.Header does not include the "Trailer" header, - // which means we will have to do that manually below. - copyHeader(rw.Header(), res.Header) - - // The "Trailer" header isn't included in res' Header map, which - // is why we have to build one ourselves from res.Trailer. - // - // But res.Trailer does not necessarily contain all trailer keys at this - // point yet. The HTTP spec allows one to send "unannounced trailers" - // after a request and certain systems like gRPC make use of that. - announcedTrailerKeyCount := len(res.Trailer) - if announcedTrailerKeyCount > 0 { - vv := make([]string, 0, announcedTrailerKeyCount) - for k := range res.Trailer { - vv = append(vv, k) - } - rw.Header()["Trailer"] = vv - } - - // Now copy over the status code as well as the response body. - rw.WriteHeader(res.StatusCode) - if announcedTrailerKeyCount > 0 { - // Force chunking if we saw a response trailer. - // This prevents net/http from calculating the length - // for short bodies and adding a Content-Length. - if fl, ok := rw.(http.Flusher); ok { - fl.Flush() - } - } - rp.copyResponse(rw, res.Body) - - // Now close the body to fully populate res.Trailer. - closeBody() - - // Since Go does not remove keys from res.Trailer we - // can safely do a length comparison to check wether - // we received further, unannounced trailers. - // - // Most of the time forceSetTrailers should be false. - forceSetTrailers := len(res.Trailer) != announcedTrailerKeyCount - shallowCopyTrailers(rw.Header(), res.Trailer, forceSetTrailers) - } - - return nil -} - -func (rp *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) { - if rp.FlushInterval != 0 { - if wf, ok := dst.(writeFlusher); ok { - mlw := &maxLatencyWriter{ - dst: wf, - latency: rp.FlushInterval, - done: make(chan bool), - } - go mlw.flushLoop() - defer mlw.stop() - dst = mlw - } - } - pooledIoCopy(dst, src) -} - -// skip these headers if they already exist. -// see https://github.com/mholt/caddy/pull/1112#discussion_r80092582 -var skipHeaders = map[string]struct{}{ - "Content-Type": {}, - "Content-Disposition": {}, - "Accept-Ranges": {}, - "Set-Cookie": {}, - "Cache-Control": {}, - "Expires": {}, -} - -func copyHeader(dst, src http.Header) { - for k, vv := range src { - if _, ok := dst[k]; ok { - // skip some predefined headers - // see https://github.com/mholt/caddy/issues/1086 - if _, shouldSkip := skipHeaders[k]; shouldSkip { - continue - } - // otherwise, overwrite to avoid duplicated fields that can be - // problematic (see issue #1086) -- however, allow duplicate - // Server fields so we can see the reality of the proxying. - if k != "Server" { - dst.Del(k) - } - } - for _, v := range vv { - dst.Add(k, v) - } - } -} - -// shallowCopyTrailers copies all headers from srcTrailer to dstHeader. -// -// If forceSetTrailers is set to true, the http.TrailerPrefix will be added to -// all srcTrailer key names. Otherwise the Go stdlib will ignore all keys -// which weren't listed in the Trailer map before submitting the Response. -// -// WARNING: Only a shallow copy will be created! -func shallowCopyTrailers(dstHeader, srcTrailer http.Header, forceSetTrailers bool) { - for k, vv := range srcTrailer { - if forceSetTrailers { - k = http.TrailerPrefix + k - } - dstHeader[k] = vv - } -} - -// Hop-by-hop headers. These are removed when sent to the backend. -// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html -var hopHeaders = []string{ - "Alt-Svc", - "Alternate-Protocol", - "Connection", - "Keep-Alive", - "Proxy-Authenticate", - "Proxy-Authorization", - "Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google - "Te", // canonicalized version of "TE" - "Trailer", // not Trailers per URL above; http://www.rfc-editor.org/errata_search.php?eid=4522 - "Transfer-Encoding", - "Upgrade", -} - -type respUpdateFn func(resp *http.Response) - -type hijackedConn struct { - net.Conn - hj *connHijackerTransport -} - -func (c *hijackedConn) Read(b []byte) (n int, err error) { - n, err = c.Conn.Read(b) - c.hj.Replay = append(c.hj.Replay, b[:n]...) - return -} - -func (c *hijackedConn) Close() error { - return nil -} - -type connHijackerTransport struct { - *http.Transport - Conn net.Conn - Replay []byte -} - -func newConnHijackerTransport(base http.RoundTripper) *connHijackerTransport { - t := &http.Transport{ - MaxIdleConnsPerHost: -1, - } - if b, _ := base.(*http.Transport); b != nil { - tlsClientConfig := b.TLSClientConfig - if tlsClientConfig != nil && tlsClientConfig.NextProtos != nil { - tlsClientConfig = tlsClientConfig.Clone() - tlsClientConfig.NextProtos = nil - } - - t.Proxy = b.Proxy - t.TLSClientConfig = tlsClientConfig - t.TLSHandshakeTimeout = b.TLSHandshakeTimeout - t.Dial = b.Dial - t.DialTLS = b.DialTLS - } else { - t.Proxy = http.ProxyFromEnvironment - t.TLSHandshakeTimeout = 10 * time.Second - } - hj := &connHijackerTransport{t, nil, bufferPool.Get().([]byte)[:0]} - - dial := getTransportDial(t) - dialTLS := getTransportDialTLS(t) - t.Dial = func(network, addr string) (net.Conn, error) { - c, err := dial(network, addr) - hj.Conn = c - return &hijackedConn{c, hj}, err - } - t.DialTLS = func(network, addr string) (net.Conn, error) { - c, err := dialTLS(network, addr) - hj.Conn = c - return &hijackedConn{c, hj}, err - } - - return hj -} - -// getTransportDial always returns a plain Dialer -// and defaults to the existing t.Dial. -func getTransportDial(t *http.Transport) func(network, addr string) (net.Conn, error) { - if t.Dial != nil { - return t.Dial - } - return defaultDialer.Dial -} - -// getTransportDial always returns a TLS Dialer -// and defaults to the existing t.DialTLS. -func getTransportDialTLS(t *http.Transport) func(network, addr string) (net.Conn, error) { - if t.DialTLS != nil { - return t.DialTLS - } - - // newConnHijackerTransport will modify t.Dial after calling this method - // => Create a backup reference. - plainDial := getTransportDial(t) - - // The following DialTLS implementation stems from the Go stdlib and - // is identical to what happens if DialTLS is not provided. - // Source: https://github.com/golang/go/blob/230a376b5a67f0e9341e1fa47e670ff762213c83/src/net/http/transport.go#L1018-L1051 - return func(network, addr string) (net.Conn, error) { - plainConn, err := plainDial(network, addr) - if err != nil { - return nil, err - } - - tlsClientConfig := t.TLSClientConfig - if tlsClientConfig == nil { - tlsClientConfig = &tls.Config{} - } - if !tlsClientConfig.InsecureSkipVerify && tlsClientConfig.ServerName == "" { - tlsClientConfig.ServerName = stripPort(addr) - } - - tlsConn := tls.Client(plainConn, tlsClientConfig) - errc := make(chan error, 2) - var timer *time.Timer - if d := t.TLSHandshakeTimeout; d != 0 { - timer = time.AfterFunc(d, func() { - errc <- tlsHandshakeTimeoutError{} - }) - } - go func() { - err := tlsConn.Handshake() - if timer != nil { - timer.Stop() - } - errc <- err - }() - if err := <-errc; err != nil { - plainConn.Close() - return nil, err - } - if !tlsClientConfig.InsecureSkipVerify { - hostname := tlsClientConfig.ServerName - if hostname == "" { - hostname = stripPort(addr) - } - if err := tlsConn.VerifyHostname(hostname); err != nil { - plainConn.Close() - return nil, err - } - } - - return tlsConn, nil - } -} - -// stripPort returns address without its port if it has one and -// works with IP addresses as well as hostnames formatted as host:port. -// -// IPv6 addresses (excluding the port) must be enclosed in -// square brackets similar to the requirements of Go's stdlib. -func stripPort(address string) string { - // Keep in mind that the address might be a IPv6 address - // and thus contain a colon, but not have a port. - portIdx := strings.LastIndex(address, ":") - ipv6Idx := strings.LastIndex(address, "]") - if portIdx > ipv6Idx { - address = address[:portIdx] - } - return address -} - -type tlsHandshakeTimeoutError struct{} - -func (tlsHandshakeTimeoutError) Timeout() bool { return true } -func (tlsHandshakeTimeoutError) Temporary() bool { return true } -func (tlsHandshakeTimeoutError) Error() string { return "net/http: TLS handshake timeout" } - -func requestIsWebsocket(req *http.Request) bool { - return strings.ToLower(req.Header.Get("Upgrade")) == "websocket" && strings.Contains(strings.ToLower(req.Header.Get("Connection")), "upgrade") -} - -type writeFlusher interface { - io.Writer - http.Flusher -} - -type maxLatencyWriter struct { - dst writeFlusher - latency time.Duration - - lk sync.Mutex // protects Write + Flush - done chan bool -} - -func (m *maxLatencyWriter) Write(p []byte) (int, error) { - m.lk.Lock() - defer m.lk.Unlock() - return m.dst.Write(p) -} - -func (m *maxLatencyWriter) flushLoop() { - t := time.NewTicker(m.latency) - defer t.Stop() - for { - select { - case <-m.done: - if onExitFlushLoop != nil { - onExitFlushLoop() - } - return - case <-t.C: - m.lk.Lock() - m.dst.Flush() - m.lk.Unlock() - } - } -} - -func (m *maxLatencyWriter) stop() { m.done <- true } diff --git a/caddyhttp/proxy/setup.go b/caddyhttp/proxy/setup.go deleted file mode 100644 index e96b3f595fc..00000000000 --- a/caddyhttp/proxy/setup.go +++ /dev/null @@ -1,31 +0,0 @@ -package proxy - -import ( - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func init() { - caddy.RegisterPlugin("proxy", caddy.Plugin{ - ServerType: "http", - Action: setup, - }) -} - -// setup configures a new Proxy middleware instance. -func setup(c *caddy.Controller) error { - upstreams, err := NewStaticUpstreams(c.Dispenser, httpserver.GetConfig(c).Host()) - if err != nil { - return err - } - httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler { - return Proxy{Next: next, Upstreams: upstreams} - }) - - // Register shutdown handlers. - for _, upstream := range upstreams { - c.OnShutdown(upstream.Stop) - } - - return nil -} diff --git a/caddyhttp/proxy/setup_test.go b/caddyhttp/proxy/setup_test.go deleted file mode 100644 index 02809058fd8..00000000000 --- a/caddyhttp/proxy/setup_test.go +++ /dev/null @@ -1,177 +0,0 @@ -package proxy - -import ( - "reflect" - "testing" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestSetup(t *testing.T) { - for i, test := range []struct { - input string - shouldErr bool - expectedHosts map[string]struct{} - }{ - // test #0 test usual to destination still works normally - { - "proxy / localhost:80", - false, - map[string]struct{}{ - "http://localhost:80": {}, - }, - }, - - // test #1 test usual to destination with port range - { - "proxy / localhost:8080-8082", - false, - map[string]struct{}{ - "http://localhost:8080": {}, - "http://localhost:8081": {}, - "http://localhost:8082": {}, - }, - }, - - // test #2 test upstream directive - { - "proxy / {\n upstream localhost:8080\n}", - false, - map[string]struct{}{ - "http://localhost:8080": {}, - }, - }, - - // test #3 test upstream directive with port range - { - "proxy / {\n upstream localhost:8080-8081\n}", - false, - map[string]struct{}{ - "http://localhost:8080": {}, - "http://localhost:8081": {}, - }, - }, - - // test #4 test to destination with upstream directive - { - "proxy / localhost:8080 {\n upstream localhost:8081-8082\n}", - false, - map[string]struct{}{ - "http://localhost:8080": {}, - "http://localhost:8081": {}, - "http://localhost:8082": {}, - }, - }, - - // test #5 test with unix sockets - { - "proxy / localhost:8080 {\n upstream unix:/var/foo\n}", - false, - map[string]struct{}{ - "http://localhost:8080": {}, - "unix:/var/foo": {}, - }, - }, - - // test #6 test fail on malformed port range - { - "proxy / localhost:8090-8080", - true, - nil, - }, - - // test #7 test fail on malformed port range 2 - { - "proxy / {\n upstream localhost:80-A\n}", - true, - nil, - }, - - // test #8 test upstreams without ports work correctly - { - "proxy / http://localhost {\n upstream testendpoint\n}", - false, - map[string]struct{}{ - "http://localhost": {}, - "http://testendpoint": {}, - }, - }, - - // test #9 test several upstream directives - { - "proxy / localhost:8080 {\n upstream localhost:8081-8082\n upstream localhost:8083-8085\n}", - false, - map[string]struct{}{ - "http://localhost:8080": {}, - "http://localhost:8081": {}, - "http://localhost:8082": {}, - "http://localhost:8083": {}, - "http://localhost:8084": {}, - "http://localhost:8085": {}, - }, - }, - // test #10 test hyphen without port range - { - "proxy / http://localhost:8001/a--b", - false, - map[string]struct{}{ - "http://localhost:8001/a--b": {}, - }, - }, - // test #11 test hyphen with port range - { - "proxy / http://localhost:8001-8005/a--b", - false, - map[string]struct{}{ - "http://localhost:8001/a--b": {}, - "http://localhost:8002/a--b": {}, - "http://localhost:8003/a--b": {}, - "http://localhost:8004/a--b": {}, - "http://localhost:8005/a--b": {}, - }, - }, - // test #12 test value is optional when remove upstream header - { - "proxy / localhost:1984 {\n header_upstream -server \n}", - false, - map[string]struct{}{ - "http://localhost:1984": {}, - }, - }, - // test #13 test value is optional when remove downstream header - { - "proxy / localhost:1984 {\n header_downstream -server \n}", - false, - map[string]struct{}{ - "http://localhost:1984": {}, - }, - }, - } { - 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) - } else if test.shouldErr { - continue - } - - mids := httpserver.GetConfig(c).Middleware() - mid := mids[len(mids)-1] - - upstreams := mid(nil).(Proxy).Upstreams - for _, upstream := range upstreams { - val := reflect.ValueOf(upstream).Elem() - hosts := val.FieldByName("Hosts").Interface().(HostPool) - if len(hosts) != len(test.expectedHosts) { - t.Errorf("Test case #%d expected %d hosts but received %d", i, len(test.expectedHosts), len(hosts)) - } else { - for _, host := range hosts { - if _, found := test.expectedHosts[host.Name]; !found { - t.Errorf("Test case #%d has an unexpected host %s", i, host.Name) - } - } - } - } - } -} diff --git a/caddyhttp/proxy/upstream.go b/caddyhttp/proxy/upstream.go deleted file mode 100644 index e7cc392b111..00000000000 --- a/caddyhttp/proxy/upstream.go +++ /dev/null @@ -1,548 +0,0 @@ -package proxy - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "net" - "net/http" - "net/url" - "path" - "strconv" - "strings" - "sync" - "sync/atomic" - "time" - - "crypto/tls" - - "github.com/mholt/caddy/caddyfile" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -var ( - supportedPolicies = make(map[string]func(string) Policy) -) - -type staticUpstream struct { - from string - upstreamHeaders http.Header - downstreamHeaders http.Header - stop chan struct{} // Signals running goroutines to stop. - wg sync.WaitGroup // Used to wait for running goroutines to stop. - Hosts HostPool - Policy Policy - KeepAlive int - FailTimeout time.Duration - TryDuration time.Duration - TryInterval time.Duration - MaxConns int64 - HealthCheck struct { - Client http.Client - Path string - Interval time.Duration - Timeout time.Duration - Host string - Port string - ContentString string - } - WithoutPathPrefix string - IgnoredSubPaths []string - insecureSkipVerify bool - MaxFails int32 -} - -// NewStaticUpstreams parses the configuration input and sets up -// static upstreams for the proxy middleware. The host string parameter, -// if not empty, is used for setting the upstream Host header for the -// health checks if the upstream header config requires it. -func NewStaticUpstreams(c caddyfile.Dispenser, host string) ([]Upstream, error) { - var upstreams []Upstream - for c.Next() { - - upstream := &staticUpstream{ - from: "", - stop: make(chan struct{}), - upstreamHeaders: make(http.Header), - downstreamHeaders: make(http.Header), - Hosts: nil, - Policy: &Random{}, - MaxFails: 1, - TryInterval: 250 * time.Millisecond, - MaxConns: 0, - KeepAlive: http.DefaultMaxIdleConnsPerHost, - } - - if !c.Args(&upstream.from) { - return upstreams, c.ArgErr() - } - - var to []string - for _, t := range c.RemainingArgs() { - parsed, err := parseUpstream(t) - if err != nil { - return upstreams, err - } - to = append(to, parsed...) - } - - for c.NextBlock() { - switch c.Val() { - case "upstream": - if !c.NextArg() { - return upstreams, c.ArgErr() - } - parsed, err := parseUpstream(c.Val()) - if err != nil { - return upstreams, err - } - to = append(to, parsed...) - default: - if err := parseBlock(&c, upstream); err != nil { - return upstreams, err - } - } - } - - if len(to) == 0 { - return upstreams, c.ArgErr() - } - - upstream.Hosts = make([]*UpstreamHost, len(to)) - for i, host := range to { - uh, err := upstream.NewHost(host) - if err != nil { - return upstreams, err - } - upstream.Hosts[i] = uh - } - - if upstream.HealthCheck.Path != "" { - upstream.HealthCheck.Client = http.Client{ - Timeout: upstream.HealthCheck.Timeout, - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: upstream.insecureSkipVerify}, - }, - } - - // set up health check upstream host if we have one - if host != "" { - hostHeader := upstream.upstreamHeaders.Get("Host") - if strings.Contains(hostHeader, "{host}") { - upstream.HealthCheck.Host = strings.Replace(hostHeader, "{host}", host, -1) - } - } - upstream.wg.Add(1) - go func() { - defer upstream.wg.Done() - upstream.HealthCheckWorker(upstream.stop) - }() - } - upstreams = append(upstreams, upstream) - } - return upstreams, nil -} - -func (u *staticUpstream) From() string { - return u.from -} - -func (u *staticUpstream) NewHost(host string) (*UpstreamHost, error) { - if !strings.HasPrefix(host, "http") && - !strings.HasPrefix(host, "unix:") { - host = "http://" + host - } - uh := &UpstreamHost{ - Name: host, - Conns: 0, - Fails: 0, - FailTimeout: u.FailTimeout, - Unhealthy: 0, - UpstreamHeaders: u.upstreamHeaders, - DownstreamHeaders: u.downstreamHeaders, - CheckDown: func(u *staticUpstream) UpstreamHostDownFunc { - return func(uh *UpstreamHost) bool { - if atomic.LoadInt32(&uh.Unhealthy) != 0 { - return true - } - if atomic.LoadInt32(&uh.Fails) >= u.MaxFails { - return true - } - return false - } - }(u), - WithoutPathPrefix: u.WithoutPathPrefix, - MaxConns: u.MaxConns, - } - - baseURL, err := url.Parse(uh.Name) - if err != nil { - return nil, err - } - - uh.ReverseProxy = NewSingleHostReverseProxy(baseURL, uh.WithoutPathPrefix, u.KeepAlive) - if u.insecureSkipVerify { - uh.ReverseProxy.UseInsecureTransport() - } - - return uh, nil -} - -func parseUpstream(u string) ([]string, error) { - if !strings.HasPrefix(u, "unix:") { - colonIdx := strings.LastIndex(u, ":") - protoIdx := strings.Index(u, "://") - - if colonIdx != -1 && colonIdx != protoIdx { - us := u[:colonIdx] - ue := "" - portsEnd := len(u) - if nextSlash := strings.Index(u[colonIdx:], "/"); nextSlash != -1 { - portsEnd = colonIdx + nextSlash - ue = u[portsEnd:] - } - ports := u[len(us)+1 : portsEnd] - - if separators := strings.Count(ports, "-"); separators == 1 { - portsStr := strings.Split(ports, "-") - pIni, err := strconv.Atoi(portsStr[0]) - if err != nil { - return nil, err - } - - pEnd, err := strconv.Atoi(portsStr[1]) - if err != nil { - return nil, err - } - - if pEnd <= pIni { - return nil, fmt.Errorf("port range [%s] is invalid", ports) - } - - hosts := []string{} - for p := pIni; p <= pEnd; p++ { - hosts = append(hosts, fmt.Sprintf("%s:%d%s", us, p, ue)) - } - return hosts, nil - } - } - } - - return []string{u}, nil - -} - -func parseBlock(c *caddyfile.Dispenser, u *staticUpstream) error { - switch c.Val() { - case "policy": - if !c.NextArg() { - return c.ArgErr() - } - policyCreateFunc, ok := supportedPolicies[c.Val()] - if !ok { - return c.ArgErr() - } - arg := "" - if c.NextArg() { - arg = c.Val() - } - u.Policy = policyCreateFunc(arg) - case "fail_timeout": - if !c.NextArg() { - return c.ArgErr() - } - dur, err := time.ParseDuration(c.Val()) - if err != nil { - return err - } - u.FailTimeout = dur - case "max_fails": - if !c.NextArg() { - return c.ArgErr() - } - n, err := strconv.Atoi(c.Val()) - if err != nil { - return err - } - if n < 1 { - return c.Err("max_fails must be at least 1") - } - u.MaxFails = int32(n) - case "try_duration": - if !c.NextArg() { - return c.ArgErr() - } - dur, err := time.ParseDuration(c.Val()) - if err != nil { - return err - } - u.TryDuration = dur - case "try_interval": - if !c.NextArg() { - return c.ArgErr() - } - interval, err := time.ParseDuration(c.Val()) - if err != nil { - return err - } - u.TryInterval = interval - case "max_conns": - if !c.NextArg() { - return c.ArgErr() - } - n, err := strconv.ParseInt(c.Val(), 10, 64) - if err != nil { - return err - } - u.MaxConns = n - case "health_check": - if !c.NextArg() { - return c.ArgErr() - } - u.HealthCheck.Path = c.Val() - - // Set defaults - if u.HealthCheck.Interval == 0 { - u.HealthCheck.Interval = 30 * time.Second - } - if u.HealthCheck.Timeout == 0 { - u.HealthCheck.Timeout = 60 * time.Second - } - case "health_check_interval": - var interval string - if !c.Args(&interval) { - return c.ArgErr() - } - dur, err := time.ParseDuration(interval) - if err != nil { - return err - } - u.HealthCheck.Interval = dur - case "health_check_timeout": - var interval string - if !c.Args(&interval) { - return c.ArgErr() - } - dur, err := time.ParseDuration(interval) - if err != nil { - return err - } - u.HealthCheck.Timeout = dur - case "health_check_port": - if !c.NextArg() { - return c.ArgErr() - } - port := c.Val() - n, err := strconv.Atoi(port) - if err != nil { - return err - } - - if n < 0 { - return c.Errf("invalid health_check_port '%s'", port) - } - u.HealthCheck.Port = port - case "health_check_contains": - if !c.NextArg() { - return c.ArgErr() - } - u.HealthCheck.ContentString = c.Val() - case "header_upstream": - var header, value string - if !c.Args(&header, &value) { - // When removing a header, the value can be optional. - if !strings.HasPrefix(header, "-") { - return c.ArgErr() - } - } - u.upstreamHeaders.Add(header, value) - case "header_downstream": - var header, value string - if !c.Args(&header, &value) { - // When removing a header, the value can be optional. - if !strings.HasPrefix(header, "-") { - return c.ArgErr() - } - } - u.downstreamHeaders.Add(header, value) - case "transparent": - u.upstreamHeaders.Add("Host", "{host}") - u.upstreamHeaders.Add("X-Real-IP", "{remote}") - u.upstreamHeaders.Add("X-Forwarded-For", "{remote}") - u.upstreamHeaders.Add("X-Forwarded-Proto", "{scheme}") - case "websocket": - u.upstreamHeaders.Add("Connection", "{>Connection}") - u.upstreamHeaders.Add("Upgrade", "{>Upgrade}") - case "without": - if !c.NextArg() { - return c.ArgErr() - } - u.WithoutPathPrefix = c.Val() - case "except": - ignoredPaths := c.RemainingArgs() - if len(ignoredPaths) == 0 { - return c.ArgErr() - } - u.IgnoredSubPaths = ignoredPaths - case "insecure_skip_verify": - u.insecureSkipVerify = true - case "keepalive": - if !c.NextArg() { - return c.ArgErr() - } - n, err := strconv.Atoi(c.Val()) - if err != nil { - return err - } - if n < 0 { - return c.ArgErr() - } - u.KeepAlive = n - default: - return c.Errf("unknown property '%s'", c.Val()) - } - return nil -} - -func (u *staticUpstream) healthCheck() { - for _, host := range u.Hosts { - hostURL := host.Name - if u.HealthCheck.Port != "" { - hostURL = replacePort(host.Name, u.HealthCheck.Port) - } - hostURL += u.HealthCheck.Path - - unhealthy := func() bool { - // set up request, needed to be able to modify headers - // possible errors are bad HTTP methods or un-parsable urls - req, err := http.NewRequest("GET", hostURL, nil) - if err != nil { - return true - } - // set host for request going upstream - if u.HealthCheck.Host != "" { - req.Host = u.HealthCheck.Host - } - r, err := u.HealthCheck.Client.Do(req) - if err != nil { - return true - } - defer func() { - io.Copy(ioutil.Discard, r.Body) - r.Body.Close() - }() - if r.StatusCode < 200 || r.StatusCode >= 400 { - return true - } - if u.HealthCheck.ContentString == "" { // don't check for content string - return false - } - // TODO ReadAll will be replaced if deemed necessary - // See https://github.com/mholt/caddy/pull/1691 - buf, err := ioutil.ReadAll(r.Body) - if err != nil { - return true - } - if bytes.Contains(buf, []byte(u.HealthCheck.ContentString)) { - return false - } - return true - }() - if unhealthy { - atomic.StoreInt32(&host.Unhealthy, 1) - } else { - atomic.StoreInt32(&host.Unhealthy, 0) - } - } -} - -func (u *staticUpstream) HealthCheckWorker(stop chan struct{}) { - ticker := time.NewTicker(u.HealthCheck.Interval) - u.healthCheck() - for { - select { - case <-ticker.C: - u.healthCheck() - case <-stop: - ticker.Stop() - return - } - } -} - -func (u *staticUpstream) Select(r *http.Request) *UpstreamHost { - pool := u.Hosts - if len(pool) == 1 { - if !pool[0].Available() { - return nil - } - return pool[0] - } - allUnavailable := true - for _, host := range pool { - if host.Available() { - allUnavailable = false - break - } - } - if allUnavailable { - return nil - } - if u.Policy == nil { - return (&Random{}).Select(pool, r) - } - return u.Policy.Select(pool, r) -} - -func (u *staticUpstream) AllowedPath(requestPath string) bool { - for _, ignoredSubPath := range u.IgnoredSubPaths { - if httpserver.Path(path.Clean(requestPath)).Matches(path.Join(u.From(), ignoredSubPath)) { - return false - } - } - return true -} - -// GetTryDuration returns u.TryDuration. -func (u *staticUpstream) GetTryDuration() time.Duration { - return u.TryDuration -} - -// GetTryInterval returns u.TryInterval. -func (u *staticUpstream) GetTryInterval() time.Duration { - return u.TryInterval -} - -func (u *staticUpstream) GetHostCount() int { - return len(u.Hosts) -} - -// Stop sends a signal to all goroutines started by this staticUpstream to exit -// and waits for them to finish before returning. -func (u *staticUpstream) Stop() error { - close(u.stop) - u.wg.Wait() - return nil -} - -// RegisterPolicy adds a custom policy to the proxy. -func RegisterPolicy(name string, policy func(string) Policy) { - supportedPolicies[name] = policy -} - -func replacePort(originalURL string, newPort string) string { - parsedURL, err := url.Parse(originalURL) - if err != nil { - return originalURL - } - - // handles 'localhost' and 'localhost:8080' - parsedHost, _, err := net.SplitHostPort(parsedURL.Host) - if err != nil { - parsedHost = parsedURL.Host - } - - parsedURL.Host = net.JoinHostPort(parsedHost, newPort) - return parsedURL.String() -} diff --git a/caddyhttp/proxy/upstream_test.go b/caddyhttp/proxy/upstream_test.go deleted file mode 100644 index 8d1ef7198f0..00000000000 --- a/caddyhttp/proxy/upstream_test.go +++ /dev/null @@ -1,503 +0,0 @@ -package proxy - -import ( - "fmt" - "net" - "net/http" - "net/http/httptest" - "strings" - "sync/atomic" - "testing" - "time" - - "github.com/mholt/caddy/caddyfile" -) - -func TestNewHost(t *testing.T) { - upstream := &staticUpstream{ - FailTimeout: 10 * time.Second, - MaxConns: 1, - MaxFails: 1, - } - - uh, err := upstream.NewHost("example.com") - if err != nil { - t.Error("Expected no error") - } - if uh.Name != "http://example.com" { - t.Error("Expected default schema to be added to Name.") - } - if uh.FailTimeout != upstream.FailTimeout { - t.Error("Expected default FailTimeout to be set.") - } - if uh.MaxConns != upstream.MaxConns { - t.Error("Expected default MaxConns to be set.") - } - if uh.CheckDown == nil { - t.Error("Expected default CheckDown to be set.") - } - if uh.CheckDown(uh) { - t.Error("Expected new host not to be down.") - } - // mark Unhealthy - uh.Unhealthy = 1 - if !uh.CheckDown(uh) { - t.Error("Expected unhealthy host to be down.") - } - // mark with Fails - uh.Unhealthy = 0 - uh.Fails = 1 - if !uh.CheckDown(uh) { - t.Error("Expected failed host to be down.") - } -} - -func TestHealthCheck(t *testing.T) { - upstream := &staticUpstream{ - from: "", - Hosts: testPool(), - Policy: &Random{}, - FailTimeout: 10 * time.Second, - MaxFails: 1, - } - upstream.healthCheck() - if upstream.Hosts[0].Down() { - t.Error("Expected first host in testpool to not fail healthcheck.") - } - if !upstream.Hosts[1].Down() { - t.Error("Expected second host in testpool to fail healthcheck.") - } -} - -func TestSelect(t *testing.T) { - upstream := &staticUpstream{ - from: "", - Hosts: testPool()[:3], - Policy: &Random{}, - FailTimeout: 10 * time.Second, - MaxFails: 1, - } - r, _ := http.NewRequest("GET", "/", nil) - upstream.Hosts[0].Unhealthy = 1 - upstream.Hosts[1].Unhealthy = 1 - upstream.Hosts[2].Unhealthy = 1 - if h := upstream.Select(r); h != nil { - t.Error("Expected select to return nil as all host are down") - } - upstream.Hosts[2].Unhealthy = 0 - if h := upstream.Select(r); h == nil { - t.Error("Expected select to not return nil") - } - upstream.Hosts[0].Conns = 1 - upstream.Hosts[0].MaxConns = 1 - upstream.Hosts[1].Conns = 1 - upstream.Hosts[1].MaxConns = 1 - upstream.Hosts[2].Conns = 1 - upstream.Hosts[2].MaxConns = 1 - if h := upstream.Select(r); h != nil { - t.Error("Expected select to return nil as all hosts are full") - } - upstream.Hosts[2].Conns = 0 - if h := upstream.Select(r); h == nil { - t.Error("Expected select to not return nil") - } -} - -func TestRegisterPolicy(t *testing.T) { - name := "custom" - customPolicy := &customPolicy{} - RegisterPolicy(name, func(string) Policy { return customPolicy }) - if _, ok := supportedPolicies[name]; !ok { - t.Error("Expected supportedPolicies to have a custom policy.") - } - -} - -func TestAllowedPaths(t *testing.T) { - upstream := &staticUpstream{ - from: "/proxy", - IgnoredSubPaths: []string{"/download", "/static"}, - } - tests := []struct { - url string - expected bool - }{ - {"/proxy", true}, - {"/proxy/dl", true}, - {"/proxy/download", false}, - {"/proxy/download/static", false}, - {"/proxy/static", false}, - {"/proxy/static/download", false}, - {"/proxy/something/download", true}, - {"/proxy/something/static", true}, - {"/proxy//static", false}, - {"/proxy//static//download", false}, - {"/proxy//download", false}, - } - - for i, test := range tests { - allowed := upstream.AllowedPath(test.url) - if test.expected != allowed { - t.Errorf("Test %d: expected %v found %v", i+1, test.expected, allowed) - } - } -} - -func TestParseBlockHealthCheck(t *testing.T) { - tests := []struct { - config string - interval string - timeout string - }{ - // Test #1: Both options set correct time - {"health_check /health\n health_check_interval 10s\n health_check_timeout 20s", "10s", "20s"}, - - // Test #2: Health check options flipped around. Making sure health_check doesn't overwrite it - {"health_check_interval 10s\n health_check_timeout 20s\n health_check /health", "10s", "20s"}, - - // Test #3: No health_check options. So default. - {"health_check /health", "30s", "1m0s"}, - - // Test #4: Interval sets it to 15s and timeout defaults - {"health_check /health\n health_check_interval 15s", "15s", "1m0s"}, - - // Test #5: Timeout sets it to 15s and interval defaults - {"health_check /health\n health_check_timeout 15s", "30s", "15s"}, - - // Test #6: Some funky spelling to make sure it still defaults - {"health_check /health health_check_time 15s", "30s", "1m0s"}, - } - - for i, test := range tests { - u := staticUpstream{} - c := caddyfile.NewDispenser("Testfile", strings.NewReader(test.config)) - for c.Next() { - parseBlock(&c, &u) - } - if u.HealthCheck.Interval.String() != test.interval { - t.Errorf( - "Test %d: HealthCheck interval not the same from config. Got %v. Expected: %v", - i+1, - u.HealthCheck.Interval, - test.interval, - ) - } - if u.HealthCheck.Timeout.String() != test.timeout { - t.Errorf( - "Test %d: HealthCheck timeout not the same from config. Got %v. Expected: %v", - i+1, - u.HealthCheck.Timeout, - test.timeout, - ) - } - } -} - -func TestStop(t *testing.T) { - config := "proxy / %s {\n health_check /healthcheck \nhealth_check_interval %dms \n}" - tests := []struct { - name string - intervalInMilliseconds int - numHealthcheckIntervals int - }{ - { - "No Healthchecks After Stop - 5ms, 1 intervals", - 5, - 1, - }, - { - "No Healthchecks After Stop - 5ms, 2 intervals", - 5, - 2, - }, - { - "No Healthchecks After Stop - 5ms, 3 intervals", - 5, - 3, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - - // Set up proxy. - var counter int64 - backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - r.Body.Close() - atomic.AddInt64(&counter, 1) - })) - - defer backend.Close() - - upstreams, err := NewStaticUpstreams(caddyfile.NewDispenser("Testfile", strings.NewReader(fmt.Sprintf(config, backend.URL, test.intervalInMilliseconds))), "") - if err != nil { - t.Error("Expected no error. Got:", err.Error()) - } - - // Give some time for healthchecks to hit the server. - time.Sleep(time.Duration(test.intervalInMilliseconds*test.numHealthcheckIntervals) * time.Millisecond) - - for _, upstream := range upstreams { - if err := upstream.Stop(); err != nil { - t.Error("Expected no error stopping upstream. Got: ", err.Error()) - } - } - - counterValueAfterShutdown := atomic.LoadInt64(&counter) - - // Give some time to see if healthchecks are still hitting the server. - time.Sleep(time.Duration(test.intervalInMilliseconds*test.numHealthcheckIntervals) * time.Millisecond) - - if counterValueAfterShutdown == 0 { - t.Error("Expected healthchecks to hit test server. Got no healthchecks.") - } - - counterValueAfterWaiting := atomic.LoadInt64(&counter) - if counterValueAfterWaiting != counterValueAfterShutdown { - t.Errorf("Expected no more healthchecks after shutdown. Got: %d healthchecks after shutdown", counterValueAfterWaiting-counterValueAfterShutdown) - } - - }) - - } -} - -func TestParseBlock(t *testing.T) { - r, _ := http.NewRequest("GET", "/", nil) - tests := []struct { - config string - }{ - // Test #1: transparent preset - {"proxy / localhost:8080 {\n transparent \n}"}, - - // Test #2: transparent preset with another param - {"proxy / localhost:8080 {\n transparent \nheader_upstream X-Test Tester \n}"}, - - // Test #3: transparent preset on multiple sites - {"proxy / localhost:8080 {\n transparent \n} \nproxy /api localhost:8081 { \ntransparent \n}"}, - } - - for i, test := range tests { - upstreams, err := NewStaticUpstreams(caddyfile.NewDispenser("Testfile", strings.NewReader(test.config)), "") - if err != nil { - t.Errorf("Expected no error. Got: %s", err.Error()) - } - for _, upstream := range upstreams { - headers := upstream.Select(r).UpstreamHeaders - - if _, ok := headers["Host"]; !ok { - t.Errorf("Test %d: Could not find the Host header", i+1) - } - - if _, ok := headers["X-Real-Ip"]; !ok { - t.Errorf("Test %d: Could not find the X-Real-Ip header", i+1) - } - - if _, ok := headers["X-Forwarded-Proto"]; !ok { - t.Errorf("Test %d: Could not find the X-Forwarded-Proto header", i+1) - } - } - } -} - -func TestHealthSetUp(t *testing.T) { - // tests for insecure skip verify - tests := []struct { - config string - flag bool - }{ - // Test #1: without flag - {"proxy / localhost:8080 {\n health_check / \n}", false}, - - // Test #2: with flag - {"proxy / localhost:8080 {\n health_check / \n insecure_skip_verify \n}", true}, - } - - for i, test := range tests { - upstreams, err := NewStaticUpstreams(caddyfile.NewDispenser("Testfile", strings.NewReader(test.config)), "") - if err != nil { - t.Errorf("Expected no error. Got: %s", err.Error()) - } - for _, upstream := range upstreams { - staticUpstream, ok := upstream.(*staticUpstream) - if !ok { - t.Errorf("Type mismatch: %#v", upstream) - continue - } - transport, ok := staticUpstream.HealthCheck.Client.Transport.(*http.Transport) - if !ok { - t.Errorf("Type mismatch: %#v", staticUpstream.HealthCheck.Client.Transport) - continue - } - if test.flag != transport.TLSClientConfig.InsecureSkipVerify { - t.Errorf("Test %d: expected transport.TLSClientCnfig.InsecureSkipVerify=%v, got %v", i, test.flag, transport.TLSClientConfig.InsecureSkipVerify) - } - } - } -} - -func TestHealthCheckHost(t *testing.T) { - // tests for upstream host on health checks - tests := []struct { - config string - flag bool - host string - }{ - // Test #1: without upstream header - {"proxy / localhost:8080 {\n health_check / \n}", false, "example.com"}, - - // Test #2: without upstream header, missing host - {"proxy / localhost:8080 {\n health_check / \n}", true, ""}, - - // Test #3: with upstream header (via transparent preset) - {"proxy / localhost:8080 {\n health_check / \n transparent \n}", true, "foo.example.com"}, - - // Test #4: with upstream header (explicit header) - {"proxy / localhost:8080 {\n health_check / \n header_upstream Host {host} \n}", true, "example.com"}, - - // Test #5: with upstream header, missing host - {"proxy / localhost:8080 {\n health_check / \n transparent \n}", true, ""}, - } - - for i, test := range tests { - upstreams, err := NewStaticUpstreams(caddyfile.NewDispenser("Testfile", strings.NewReader(test.config)), test.host) - if err != nil { - t.Errorf("Expected no error. Got: %s", err.Error()) - } - for _, upstream := range upstreams { - staticUpstream, ok := upstream.(*staticUpstream) - if !ok { - t.Errorf("Type mismatch: %#v", upstream) - continue - } - if test.flag != (staticUpstream.HealthCheck.Host == test.host) { - t.Errorf("Test %d: expected staticUpstream.HealthCheck.Host=%v, got %v", i, test.host, staticUpstream.HealthCheck.Host) - } - } - } -} - -func TestHealthCheckPort(t *testing.T) { - var counter int64 - - healthCounter := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - r.Body.Close() - atomic.AddInt64(&counter, 1) - })) - - _, healthPort, err := net.SplitHostPort(healthCounter.Listener.Addr().String()) - if err != nil { - t.Fatal(err) - } - - defer healthCounter.Close() - - tests := []struct { - config string - }{ - // Test #1: upstream with port - {"proxy / localhost:8080 {\n health_check / health_check_port " + healthPort + "\n}"}, - - // Test #2: upstream without port (default to 80) - {"proxy / localhost {\n health_check / health_check_port " + healthPort + "\n}"}, - } - - for i, test := range tests { - counterValueAtStart := atomic.LoadInt64(&counter) - upstreams, err := NewStaticUpstreams(caddyfile.NewDispenser("Testfile", strings.NewReader(test.config)), "") - if err != nil { - t.Error("Expected no error. Got:", err.Error()) - } - - // Give some time for healthchecks to hit the server. - time.Sleep(500 * time.Millisecond) - - for _, upstream := range upstreams { - if err := upstream.Stop(); err != nil { - t.Errorf("Test %d: Expected no error stopping upstream. Got: %v", i, err.Error()) - } - } - - counterValueAfterShutdown := atomic.LoadInt64(&counter) - - if counterValueAfterShutdown == counterValueAtStart { - t.Errorf("Test %d: Expected healthchecks to hit test server. Got no healthchecks.", i) - } - } - - t.Run("valid_port", func(t *testing.T) { - tests := []struct { - config string - }{ - // Test #1: invalid port (nil) - {"proxy / localhost {\n health_check / health_check_port\n}"}, - - // Test #2: invalid port (string) - {"proxy / localhost {\n health_check / health_check_port abc\n}"}, - - // Test #3: invalid port (negative) - {"proxy / localhost {\n health_check / health_check_port -1\n}"}, - } - - for i, test := range tests { - _, err := NewStaticUpstreams(caddyfile.NewDispenser("Testfile", strings.NewReader(test.config)), "") - if err == nil { - t.Errorf("Test %d accepted invalid config", i) - } - } - }) - -} - -func TestHealthCheckContentString(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "blablabla good blablabla") - r.Body.Close() - })) - _, port, err := net.SplitHostPort(server.Listener.Addr().String()) - if err != nil { - t.Fatal(err) - } - defer server.Close() - - tests := []struct { - config string - shouldContain bool - }{ - {"proxy / localhost:" + port + - " { health_check /testhealth " + - " health_check_contains good\n}", - true, - }, - {"proxy / localhost:" + port + " {\n health_check /testhealth health_check_port " + port + - " \n health_check_contains bad\n}", - false, - }, - } - for i, test := range tests { - u, err := NewStaticUpstreams(caddyfile.NewDispenser("Testfile", strings.NewReader(test.config)), "") - if err != nil { - t.Errorf("Expected no error. Test %d Got: %s", i, err.Error()) - } - for _, upstream := range u { - staticUpstream, ok := upstream.(*staticUpstream) - if !ok { - t.Errorf("Type mismatch: %#v", upstream) - continue - } - staticUpstream.healthCheck() - for _, host := range staticUpstream.Hosts { - if test.shouldContain && atomic.LoadInt32(&host.Unhealthy) == 0 { - // healthcheck url was hit and the required test string was found - continue - } - if !test.shouldContain && atomic.LoadInt32(&host.Unhealthy) != 0 { - // healthcheck url was hit and the required string was not found - continue - } - t.Errorf("Health check bad response") - } - upstream.Stop() - } - } -} diff --git a/caddyhttp/push/handler.go b/caddyhttp/push/handler.go deleted file mode 100644 index fcc6ab0b08e..00000000000 --- a/caddyhttp/push/handler.go +++ /dev/null @@ -1,115 +0,0 @@ -package push - -import ( - "net/http" - "strings" - - "github.com/mholt/caddy/caddyhttp/httpserver" - "github.com/mholt/caddy/caddyhttp/staticfiles" -) - -func (h Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - pusher, hasPusher := w.(http.Pusher) - - // no push possible, carry on - if !hasPusher { - return h.Next.ServeHTTP(w, r) - } - - // check if this is a request for the pushed resource (avoid recursion) - if _, exists := r.Header[pushHeader]; exists { - return h.Next.ServeHTTP(w, r) - } - - headers := h.filterProxiedHeaders(r.Header) - - // push first -outer: - for _, rule := range h.Rules { - urlPath := r.URL.Path - matches := httpserver.Path(urlPath).Matches(rule.Path) - // Also check IndexPages when requesting a directory - if !matches { - _, matches = httpserver.IndexFile(h.Root, urlPath, staticfiles.IndexPages) - } - if matches { - for _, resource := range rule.Resources { - pushErr := pusher.Push(resource.Path, &http.PushOptions{ - Method: resource.Method, - Header: h.mergeHeaders(headers, resource.Header), - }) - if pushErr != nil { - // if we cannot push (either not supported or concurrent streams are full - break) - break outer - } - } - } - } - - // serve later - code, err := h.Next.ServeHTTP(w, r) - - // push resources returned in Link headers from upstream middlewares or proxied apps - if links, exists := w.Header()["Link"]; exists { - h.servePreloadLinks(pusher, headers, links) - } - - return code, err -} - -func (h Middleware) servePreloadLinks(pusher http.Pusher, headers http.Header, links []string) { - for _, link := range links { - parts := strings.Split(link, ";") - - if link == "" || strings.HasSuffix(link, "nopush") { - continue - } - - target := strings.TrimSuffix(strings.TrimPrefix(parts[0], "<"), ">") - - err := pusher.Push(target, &http.PushOptions{ - Method: http.MethodGet, - Header: headers, - }) - - if err != nil { - break - } - } -} - -func (h Middleware) mergeHeaders(l, r http.Header) http.Header { - out := http.Header{} - - for k, v := range l { - out[k] = v - } - - for k, vv := range r { - for _, v := range vv { - out.Add(k, v) - } - } - - return out -} - -func (h Middleware) filterProxiedHeaders(headers http.Header) http.Header { - filter := http.Header{} - - for _, header := range proxiedHeaders { - if val, ok := headers[header]; ok { - filter[header] = val - } - } - - return filter -} - -var proxiedHeaders = []string{ - "Accept-Encoding", - "Accept-Language", - "Cache-Control", - "Host", - "User-Agent", -} diff --git a/caddyhttp/push/handler_test.go b/caddyhttp/push/handler_test.go deleted file mode 100644 index 40903b6fa8b..00000000000 --- a/caddyhttp/push/handler_test.go +++ /dev/null @@ -1,389 +0,0 @@ -package push - -import ( - "errors" - "io/ioutil" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "reflect" - "testing" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -type MockedPusher struct { - http.ResponseWriter - pushed map[string]*http.PushOptions - returnedError error -} - -func (w *MockedPusher) Push(target string, options *http.PushOptions) error { - if w.pushed == nil { - w.pushed = make(map[string]*http.PushOptions) - } - - w.pushed[target] = options - return w.returnedError -} - -func TestMiddlewareWillPushResources(t *testing.T) { - - // given - request, err := http.NewRequest(http.MethodGet, "/index.html", nil) - writer := httptest.NewRecorder() - - if err != nil { - t.Fatalf("Could not create HTTP request: %v", err) - } - - middleware := Middleware{ - Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - return 0, nil - }), - Rules: []Rule{ - {Path: "/index.html", Resources: []Resource{ - {Path: "/index.css", Method: http.MethodHead, Header: http.Header{"Test": []string{"Value"}}}, - {Path: "/index2.css", Method: http.MethodGet}, - }}, - }, - } - - pushingWriter := &MockedPusher{ResponseWriter: writer} - - // when - middleware.ServeHTTP(pushingWriter, request) - - // then - expectedPushedResources := map[string]*http.PushOptions{ - "/index.css": { - Method: http.MethodHead, - Header: http.Header{"Test": []string{"Value"}}, - }, - - "/index2.css": { - Method: http.MethodGet, - Header: http.Header{}, - }, - } - - comparePushedResources(t, expectedPushedResources, pushingWriter.pushed) -} - -func TestMiddlewareWillPushResourcesWithMergedHeaders(t *testing.T) { - - // given - request, err := http.NewRequest(http.MethodGet, "/index.html", nil) - request.Header = http.Header{"Accept-Encoding": []string{"br"}, "Invalid-Header": []string{"Should be filter out"}} - writer := httptest.NewRecorder() - - if err != nil { - t.Fatalf("Could not create HTTP request: %v", err) - } - - middleware := Middleware{ - Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - return 0, nil - }), - Rules: []Rule{ - {Path: "/index.html", Resources: []Resource{ - {Path: "/index.css", Method: http.MethodHead, Header: http.Header{"Test": []string{"Value"}}}, - {Path: "/index2.css", Method: http.MethodGet}, - }}, - }, - } - - pushingWriter := &MockedPusher{ResponseWriter: writer} - - // when - middleware.ServeHTTP(pushingWriter, request) - - // then - expectedPushedResources := map[string]*http.PushOptions{ - "/index.css": { - Method: http.MethodHead, - Header: http.Header{"Test": []string{"Value"}, "Accept-Encoding": []string{"br"}}, - }, - - "/index2.css": { - Method: http.MethodGet, - Header: http.Header{"Accept-Encoding": []string{"br"}}, - }, - } - - comparePushedResources(t, expectedPushedResources, pushingWriter.pushed) -} - -func TestMiddlewareShouldntDoRecursivePush(t *testing.T) { - - // given - request, err := http.NewRequest(http.MethodGet, "/index.css", nil) - request.Header.Add(pushHeader, "") - - writer := httptest.NewRecorder() - - if err != nil { - t.Fatalf("Could not create HTTP request: %v", err) - } - - middleware := Middleware{ - Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - return 0, nil - }), - Rules: []Rule{ - {Path: "/", Resources: []Resource{ - {Path: "/index.css", Method: http.MethodHead, Header: http.Header{"Test": []string{"Value"}}}, - {Path: "/index2.css", Method: http.MethodGet}, - }}, - }, - } - - pushingWriter := &MockedPusher{ResponseWriter: writer} - - // when - middleware.ServeHTTP(pushingWriter, request) - - // then - if len(pushingWriter.pushed) > 0 { - t.Errorf("Expected 0 pushed resources, actual %d", len(pushingWriter.pushed)) - } -} - -func TestMiddlewareShouldStopPushingOnError(t *testing.T) { - - // given - request, err := http.NewRequest(http.MethodGet, "/index.html", nil) - writer := httptest.NewRecorder() - - if err != nil { - t.Fatalf("Could not create HTTP request: %v", err) - } - - middleware := Middleware{ - Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - return 0, nil - }), - Rules: []Rule{ - {Path: "/index.html", Resources: []Resource{ - {Path: "/only.css", Method: http.MethodHead, Header: http.Header{"Test": []string{"Value"}}}, - {Path: "/index2.css", Method: http.MethodGet}, - {Path: "/index3.css", Method: http.MethodGet}, - }}, - }, - } - - pushingWriter := &MockedPusher{ResponseWriter: writer, returnedError: errors.New("Cannot push right now")} - - // when - middleware.ServeHTTP(pushingWriter, request) - - // then - expectedPushedResources := map[string]*http.PushOptions{ - "/only.css": { - Method: http.MethodHead, - Header: http.Header{"Test": []string{"Value"}}, - }, - } - - comparePushedResources(t, expectedPushedResources, pushingWriter.pushed) -} - -func TestMiddlewareWillNotPushResources(t *testing.T) { - // given - request, err := http.NewRequest(http.MethodGet, "/index.html", nil) - - if err != nil { - t.Fatalf("Could not create HTTP request: %v", err) - } - - middleware := Middleware{ - Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - return 0, nil - }), - Rules: []Rule{ - {Path: "/index.html", Resources: []Resource{ - {Path: "/index.css", Method: http.MethodHead, Header: http.Header{"Test": []string{"Value"}}}, - {Path: "/index2.css", Method: http.MethodGet}, - }}, - }, - } - - writer := httptest.NewRecorder() - - // when - _, err2 := middleware.ServeHTTP(writer, request) - - // then - if err2 != nil { - t.Error("Should not return error") - } -} - -func TestMiddlewareShouldInterceptLinkHeader(t *testing.T) { - // given - request, err := http.NewRequest(http.MethodGet, "/index.html", nil) - writer := httptest.NewRecorder() - - if err != nil { - t.Fatalf("Could not create HTTP request: %v", err) - } - - middleware := Middleware{ - Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - w.Header().Add("Link", "; rel=preload; as=stylesheet;") - w.Header().Add("Link", "; rel=preload; as=stylesheet;") - w.Header().Add("Link", "") - w.Header().Add("Link", "") - w.Header().Add("Link", "; rel=preload; nopush") - return 0, nil - }), - Rules: []Rule{}, - } - - pushingWriter := &MockedPusher{ResponseWriter: writer} - - // when - _, err2 := middleware.ServeHTTP(pushingWriter, request) - - // then - if err2 != nil { - t.Error("Should not return error") - } - - expectedPushedResources := map[string]*http.PushOptions{ - "/index.css": { - Method: http.MethodGet, - Header: http.Header{}, - }, - "/index2.css": { - Method: http.MethodGet, - Header: http.Header{}, - }, - "/index3.css": { - Method: http.MethodGet, - Header: http.Header{}, - }, - } - - comparePushedResources(t, expectedPushedResources, pushingWriter.pushed) -} - -func TestMiddlewareShouldInterceptLinkHeaderPusherError(t *testing.T) { - // given - expectedHeaders := http.Header{"Accept-Encoding": []string{"br"}} - request, err := http.NewRequest(http.MethodGet, "/index.html", nil) - request.Header = http.Header{"Accept-Encoding": []string{"br"}, "Invalid-Header": []string{"Should be filter out"}} - - writer := httptest.NewRecorder() - - if err != nil { - t.Fatalf("Could not create HTTP request: %v", err) - } - - middleware := Middleware{ - Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - w.Header().Add("Link", "; rel=preload; as=stylesheet;") - w.Header().Add("Link", "; rel=preload; as=stylesheet;") - return 0, nil - }), - Rules: []Rule{}, - } - - pushingWriter := &MockedPusher{ResponseWriter: writer, returnedError: errors.New("Cannot push right now")} - - // when - _, err2 := middleware.ServeHTTP(pushingWriter, request) - - // then - if err2 != nil { - t.Error("Should not return error") - } - - expectedPushedResources := map[string]*http.PushOptions{ - "/index.css": { - Method: http.MethodGet, - Header: expectedHeaders, - }, - } - - comparePushedResources(t, expectedPushedResources, pushingWriter.pushed) -} - -func TestMiddlewareShouldPushIndexFile(t *testing.T) { - // given - indexFile := "/index.html" - request, err := http.NewRequest(http.MethodGet, "/", nil) // Request root directory, not indexfile itself - if err != nil { - t.Fatalf("Could not create HTTP request: %v", err) - } - - root, err := ioutil.TempDir("", "caddy") - if err != nil { - t.Fatalf("Could not create temporary directory: %v", err) - } - defer os.Remove(root) - - middleware := Middleware{ - Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - return 0, nil - }), - Rules: []Rule{ - {Path: indexFile, Resources: []Resource{ - {Path: "/index.css", Method: http.MethodGet}, - }}, - }, - Root: http.Dir(root), - } - - indexFilePath := filepath.Join(root, indexFile) - _, err = os.Create(indexFilePath) - if err != nil { - t.Fatalf("Could not create index file: %s: %v", indexFile, err) - } - defer os.Remove(indexFilePath) - - pushingWriter := &MockedPusher{ - ResponseWriter: httptest.NewRecorder(), - returnedError: errors.New("Cannot push right now"), - } - - // when - _, err2 := middleware.ServeHTTP(pushingWriter, request) - - // then - if err2 != nil { - t.Error("Should not return error") - } - - expectedPushedResources := map[string]*http.PushOptions{ - "/index.css": { - Method: http.MethodGet, - Header: http.Header{}, - }, - } - - comparePushedResources(t, expectedPushedResources, pushingWriter.pushed) - -} - -func comparePushedResources(t *testing.T, expected, actual map[string]*http.PushOptions) { - if len(expected) != len(actual) { - t.Errorf("Expected %d pushed resources, actual: %d", len(expected), len(actual)) - } - - for target, expectedTarget := range expected { - if actualTarget, exists := actual[target]; exists { - - if expectedTarget.Method != actualTarget.Method { - t.Errorf("Expected %s resource method to be %s, actual: %s", target, expectedTarget.Method, actualTarget.Method) - } - - if !reflect.DeepEqual(expectedTarget.Header, actualTarget.Header) { - t.Errorf("Expected %s resource push headers to be %+v, actual: %+v", target, expectedTarget.Header, actualTarget.Header) - } - } else { - t.Errorf("Expected %s to be pushed", target) - } - } -} diff --git a/caddyhttp/push/push.go b/caddyhttp/push/push.go deleted file mode 100644 index 2c5821a5b95..00000000000 --- a/caddyhttp/push/push.go +++ /dev/null @@ -1,31 +0,0 @@ -package push - -import ( - "net/http" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -type ( - // Rule describes conditions on which resources will be pushed - Rule struct { - Path string - Resources []Resource - } - - // Resource describes resource to be pushed - Resource struct { - Path string - Method string - Header http.Header - } - - // Middleware supports pushing resources to clients - Middleware struct { - Next httpserver.Handler - Rules []Rule - Root http.FileSystem - } - - ruleOp func([]Resource) -) diff --git a/caddyhttp/push/setup.go b/caddyhttp/push/setup.go deleted file mode 100644 index 28e8dbc532e..00000000000 --- a/caddyhttp/push/setup.go +++ /dev/null @@ -1,179 +0,0 @@ -package push - -import ( - "errors" - "fmt" - "net/http" - "strings" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func init() { - caddy.RegisterPlugin("push", caddy.Plugin{ - ServerType: "http", - Action: setup, - }) -} - -var errInvalidHeader = errors.New("header directive requires [name] [value]") - -var errHeaderStartsWithColon = errors.New("header cannot start with colon") -var errMethodNotSupported = errors.New("push supports only GET and HEAD methods") - -const pushHeader = "X-Push" - -var emptyRules = []Rule{} - -// setup configures a new Push middleware -func setup(c *caddy.Controller) error { - rules, err := parsePushRules(c) - - if err != nil { - return err - } - - cfg := httpserver.GetConfig(c) - cfg.AddMiddleware(func(next httpserver.Handler) httpserver.Handler { - return Middleware{Next: next, Rules: rules, Root: http.Dir(cfg.Root)} - }) - - return nil -} - -func parsePushRules(c *caddy.Controller) ([]Rule, error) { - var rules = make(map[string]*Rule) - - for c.NextLine() { - var rule *Rule - var resources []Resource - var ops []ruleOp - - parseBlock := func() error { - for c.NextBlock() { - val := c.Val() - - switch val { - case "method": - if !c.NextArg() { - return c.ArgErr() - } - - method := c.Val() - - if err := validateMethod(method); err != nil { - return errMethodNotSupported - } - - ops = append(ops, setMethodOp(method)) - - case "header": - args := c.RemainingArgs() - - if len(args) != 2 { - return errInvalidHeader - } - - if err := validateHeader(args[0]); err != nil { - return err - } - - ops = append(ops, setHeaderOp(args[0], args[1])) - default: - resources = append(resources, Resource{ - Path: val, - Method: http.MethodGet, - Header: http.Header{pushHeader: []string{}}, - }) - } - } - return nil - } - - args := c.RemainingArgs() - - if len(args) == 0 { - rule = new(Rule) - rule.Path = "/" - rules["/"] = rule - err := parseBlock() - if err != nil { - return emptyRules, err - } - } else { - path := args[0] - - if existingRule, ok := rules[path]; ok { - rule = existingRule - } else { - rule = new(Rule) - rule.Path = path - rules[rule.Path] = rule - } - - for i := 1; i < len(args); i++ { - resources = append(resources, Resource{ - Path: args[i], - Method: http.MethodGet, - Header: http.Header{pushHeader: []string{}}, - }) - } - - err := parseBlock() - if err != nil { - return emptyRules, err - } - } - - for _, op := range ops { - op(resources) - } - rule.Resources = append(rule.Resources, resources...) - } - - var returnRules []Rule - for _, rule := range rules { - returnRules = append(returnRules, *rule) - } - - return returnRules, nil -} - -func setHeaderOp(key, value string) func(resources []Resource) { - return func(resources []Resource) { - for index := range resources { - resources[index].Header.Set(key, value) - } - } -} - -func setMethodOp(method string) func(resources []Resource) { - return func(resources []Resource) { - for index := range resources { - resources[index].Method = method - } - } -} - -func validateHeader(header string) error { - if strings.HasPrefix(header, ":") { - return errHeaderStartsWithColon - } - - switch strings.ToLower(header) { - case "content-length", "content-encoding", "trailer", "te", "expect", "host": - return fmt.Errorf("push headers cannot include %s", header) - } - - return nil -} - -// rules based on https://go-review.googlesource.com/#/c/29439/4/http2/go18.go#94 -func validateMethod(method string) error { - if method != http.MethodGet && method != http.MethodHead { - return errMethodNotSupported - } - - return nil -} diff --git a/caddyhttp/push/setup_test.go b/caddyhttp/push/setup_test.go deleted file mode 100644 index e1d21ff17a1..00000000000 --- a/caddyhttp/push/setup_test.go +++ /dev/null @@ -1,267 +0,0 @@ -package push - -import ( - "net/http" - "reflect" - "testing" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestPushAvailable(t *testing.T) { - err := setup(caddy.NewTestController("http", "push /index.html /available.css")) - - if err != nil { - t.Fatalf("Error %s occurred, expected none", err) - } -} - -func TestConfigParse(t *testing.T) { - tests := []struct { - name string - input string - shouldErr bool - expected []Rule - }{ - { - "ParseInvalidEmptyConfig", `push`, false, []Rule{{Path: "/"}}, - }, - { - "ParseInvalidConfig", `push /index.html`, false, []Rule{{Path: "/index.html"}}, - }, - { - "ParseInvalidConfigBlock", `push /index.html /index.css { - method - }`, true, []Rule{}, - }, - { - "ParseInvalidHeaderFormat", `push /index.html /index.css { - header :invalid value - }`, true, []Rule{}, - }, - { - "ParseForbiddenHeader", `push /index.html /index.css { - header Content-Length 1000 - }`, true, []Rule{}, - }, - { - "ParseInvalidMethod", `push /index.html /index.css { - method POST - }`, true, []Rule{}, - }, - { - "ParseInvalidHeaderBlock", `push /index.html /index.css { - header - }`, true, []Rule{}, - }, - { - "ParseInvalidHeaderBlock2", `push /index.html /index.css { - header name - }`, true, []Rule{}, - }, - { - "ParseProperConfig", `push /index.html /style.css /style2.css`, false, []Rule{ - { - Path: "/index.html", - Resources: []Resource{ - { - Path: "/style.css", - Method: http.MethodGet, - Header: http.Header{pushHeader: []string{}}, - }, - { - Path: "/style2.css", - Method: http.MethodGet, - Header: http.Header{pushHeader: []string{}}, - }, - }, - }, - }, - }, - { - "ParseSimpleInlinePush", `push /index.html { - /style.css - /style2.css - }`, false, []Rule{ - { - Path: "/index.html", - Resources: []Resource{ - { - Path: "/style.css", - Method: http.MethodGet, - Header: http.Header{pushHeader: []string{}}, - }, - { - Path: "/style2.css", - Method: http.MethodGet, - Header: http.Header{pushHeader: []string{}}, - }, - }, - }, - }, - }, - { - "ParseSimpleInlinePushWithOps", `push /index.html { - /style.css - /style2.css - header Test Value - }`, false, []Rule{ - { - Path: "/index.html", - Resources: []Resource{ - { - Path: "/style.css", - Method: http.MethodGet, - Header: http.Header{pushHeader: []string{}, "Test": []string{"Value"}}, - }, - { - Path: "/style2.css", - Method: http.MethodGet, - Header: http.Header{pushHeader: []string{}, "Test": []string{"Value"}}, - }, - }, - }, - }, - }, - { - "ParseProperConfigWithBlock", `push /index.html /style.css /style2.css { - method HEAD - header Own-Header Value - header Own-Header2 Value2 - }`, false, []Rule{ - { - Path: "/index.html", - Resources: []Resource{ - { - Path: "/style.css", - Method: http.MethodHead, - Header: http.Header{ - "Own-Header": []string{"Value"}, - "Own-Header2": []string{"Value2"}, - "X-Push": []string{}, - }, - }, - { - Path: "/style2.css", - Method: http.MethodHead, - Header: http.Header{ - "Own-Header": []string{"Value"}, - "Own-Header2": []string{"Value2"}, - "X-Push": []string{}, - }, - }, - }, - }, - }, - }, - { - "ParseMergesRules", `push /index.html /index.css { - header name value - } - - push /index.html /index2.css { - header name2 value2 - method HEAD - } - `, false, []Rule{ - { - Path: "/index.html", - Resources: []Resource{ - { - Path: "/index.css", - Method: http.MethodGet, - Header: http.Header{ - "Name": []string{"value"}, - "X-Push": []string{}, - }, - }, - { - Path: "/index2.css", - Method: http.MethodHead, - Header: http.Header{ - "Name2": []string{"value2"}, - "X-Push": []string{}, - }, - }, - }, - }, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t2 *testing.T) { - actual, err := parsePushRules(caddy.NewTestController("http", test.input)) - - if err == nil && test.shouldErr { - t2.Errorf("Test %s didn't error, but it should have", test.name) - } else if err != nil && !test.shouldErr { - t2.Errorf("Test %s errored, but it shouldn't have; got '%v'", test.name, err) - } - - if len(actual) != len(test.expected) { - t2.Fatalf("Test %s expected %d rules, but got %d", - test.name, len(test.expected), len(actual)) - } - - for j, expectedRule := range test.expected { - actualRule := actual[j] - - if actualRule.Path != expectedRule.Path { - t.Errorf("Test %s, rule %d: Expected path %s, but got %s", - test.name, j, expectedRule.Path, actualRule.Path) - } - - if !reflect.DeepEqual(actualRule.Resources, expectedRule.Resources) { - t.Errorf("Test %s, rule %d: Expected resources %v, but got %v", - test.name, j, expectedRule.Resources, actualRule.Resources) - } - } - }) - } -} - -func TestSetupInstalledMiddleware(t *testing.T) { - - // given - c := caddy.NewTestController("http", `push /index.html /test.js`) - - // when - err := setup(c) - - // then - if err != nil { - t.Errorf("Expected no errors, but got: %v", err) - } - - middlewares := httpserver.GetConfig(c).Middleware() - - if len(middlewares) != 1 { - t.Fatalf("Expected 1 middleware, had %d instead", len(middlewares)) - } - - handler := middlewares[0](httpserver.EmptyNext) - pushHandler, ok := handler.(Middleware) - - if !ok { - t.Fatalf("Expected handler to be type Middleware, got: %#v", handler) - } - - if !httpserver.SameNext(pushHandler.Next, httpserver.EmptyNext) { - t.Error("'Next' field of handler Middleware was not set properly") - } -} - -func TestSetupWithError(t *testing.T) { - // given - c := caddy.NewTestController("http", "push {\nmethod\n}") - - // when - err := setup(c) - - // then - if err == nil { - t.Error("Expected error but none occurred") - } -} diff --git a/caddyhttp/redirect/redirect.go b/caddyhttp/redirect/redirect.go deleted file mode 100644 index 711313a8cad..00000000000 --- a/caddyhttp/redirect/redirect.go +++ /dev/null @@ -1,60 +0,0 @@ -// Package redirect is middleware for redirecting certain requests -// to other locations. -package redirect - -import ( - "fmt" - "html" - "net/http" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -// Redirect is middleware to respond with HTTP redirects -type Redirect struct { - Next httpserver.Handler - Rules []Rule -} - -// ServeHTTP implements the httpserver.Handler interface. -func (rd Redirect) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - for _, rule := range rd.Rules { - if (rule.FromPath == "/" || r.URL.Path == rule.FromPath) && schemeMatches(rule, r) && rule.Match(r) { - to := httpserver.NewReplacer(r, nil, "").Replace(rule.To) - if rule.Meta { - safeTo := html.EscapeString(to) - fmt.Fprintf(w, metaRedir, safeTo, safeTo) - } else { - http.Redirect(w, r, to, rule.Code) - } - return 0, nil - } - } - return rd.Next.ServeHTTP(w, r) -} - -func schemeMatches(rule Rule, req *http.Request) bool { - return (rule.FromScheme() == "https" && req.TLS != nil) || - (rule.FromScheme() != "https" && req.TLS == nil) -} - -// Rule describes an HTTP redirect rule. -type Rule struct { - FromScheme func() string - FromPath, To string - Code int - Meta bool - httpserver.RequestMatcher -} - -// Script tag comes first since that will better imitate a redirect in the browser's -// history, but the meta tag is a fallback for most non-JS clients. -const metaRedir = ` - - - - - - Redirecting... - -` diff --git a/caddyhttp/redirect/redirect_test.go b/caddyhttp/redirect/redirect_test.go deleted file mode 100644 index faf3846ee6a..00000000000 --- a/caddyhttp/redirect/redirect_test.go +++ /dev/null @@ -1,159 +0,0 @@ -package redirect - -import ( - "bytes" - "context" - "crypto/tls" - "io/ioutil" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestRedirect(t *testing.T) { - for i, test := range []struct { - from string - expectedLocation string - expectedCode int - }{ - {"http://localhost/from", "/to", http.StatusMovedPermanently}, - {"http://localhost/a", "/b", http.StatusTemporaryRedirect}, - {"http://localhost/aa", "", http.StatusOK}, - {"http://localhost/", "", http.StatusOK}, - {"http://localhost/a?foo=bar", "/b", http.StatusTemporaryRedirect}, - {"http://localhost/asdf?foo=bar", "", http.StatusOK}, - {"http://localhost/foo#bar", "", http.StatusOK}, - {"http://localhost/a#foo", "/b", http.StatusTemporaryRedirect}, - - // The scheme checks that were added to this package don't actually - // help with redirects because of Caddy's design: a redirect middleware - // for http will always be different than the redirect middleware for - // https because they have to be on different listeners. These tests - // just go to show extra bulletproofing, I guess. - {"http://localhost/scheme", "https://localhost/scheme", http.StatusMovedPermanently}, - {"https://localhost/scheme", "", http.StatusOK}, - {"https://localhost/scheme2", "http://localhost/scheme2", http.StatusMovedPermanently}, - {"http://localhost/scheme2", "", http.StatusOK}, - {"http://localhost/scheme3", "https://localhost/scheme3", http.StatusMovedPermanently}, - {"https://localhost/scheme3", "", http.StatusOK}, - } { - var nextCalled bool - - re := Redirect{ - Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - nextCalled = true - return 0, nil - }), - Rules: []Rule{ - {FromScheme: func() string { return "http" }, FromPath: "/from", To: "/to", Code: http.StatusMovedPermanently, RequestMatcher: httpserver.IfMatcher{}}, - {FromScheme: func() string { return "http" }, FromPath: "/a", To: "/b", Code: http.StatusTemporaryRedirect, RequestMatcher: httpserver.IfMatcher{}}, - - // These http and https schemes would never actually be mixed in the same - // redirect rule with Caddy because http and https schemes have different listeners, - // so they don't share a redirect rule. So although these tests prove something - // impossible with Caddy, it's extra bulletproofing at very little cost. - {FromScheme: func() string { return "http" }, FromPath: "/scheme", To: "https://localhost/scheme", Code: http.StatusMovedPermanently, RequestMatcher: httpserver.IfMatcher{}}, - {FromScheme: func() string { return "https" }, FromPath: "/scheme2", To: "http://localhost/scheme2", Code: http.StatusMovedPermanently, RequestMatcher: httpserver.IfMatcher{}}, - {FromScheme: func() string { return "" }, FromPath: "/scheme3", To: "https://localhost/scheme3", Code: http.StatusMovedPermanently, RequestMatcher: httpserver.IfMatcher{}}, - }, - } - - req, err := http.NewRequest("GET", test.from, nil) - if err != nil { - t.Fatalf("Test %d: Could not create HTTP request: %v", i, err) - } - if strings.HasPrefix(test.from, "https://") { - req.TLS = new(tls.ConnectionState) // faux HTTPS - } - - rec := httptest.NewRecorder() - re.ServeHTTP(rec, req) - - if rec.Header().Get("Location") != test.expectedLocation { - t.Errorf("Test %d: Expected Location header to be %q but was %q", - i, test.expectedLocation, rec.Header().Get("Location")) - } - - if rec.Code != test.expectedCode { - t.Errorf("Test %d: Expected status code to be %d but was %d", - i, test.expectedCode, rec.Code) - } - - if nextCalled && test.expectedLocation != "" { - t.Errorf("Test %d: Next handler was unexpectedly called", i) - } - } -} - -func TestParametersRedirect(t *testing.T) { - re := Redirect{ - Rules: []Rule{ - {FromScheme: func() string { return "http" }, FromPath: "/", Meta: false, To: "http://example.com{uri}", RequestMatcher: httpserver.IfMatcher{}}, - }, - } - - req, err := http.NewRequest("GET", "/a?b=c", nil) - if err != nil { - t.Fatalf("Test 1: Could not create HTTP request: %v", err) - } - ctx := context.WithValue(req.Context(), httpserver.OriginalURLCtxKey, *req.URL) - req = req.WithContext(ctx) - - rec := httptest.NewRecorder() - re.ServeHTTP(rec, req) - - if got, want := rec.Header().Get("Location"), "http://example.com/a?b=c"; got != want { - t.Fatalf("Test 1: expected location header %s but was %s", want, got) - } - - re = Redirect{ - Rules: []Rule{ - {FromScheme: func() string { return "http" }, FromPath: "/", Meta: false, To: "http://example.com/a{path}?b=c&{query}", RequestMatcher: httpserver.IfMatcher{}}, - }, - } - - req, err = http.NewRequest("GET", "/d?e=f", nil) - if err != nil { - t.Fatalf("Test 2: Could not create HTTP request: %v", err) - } - ctx = context.WithValue(req.Context(), httpserver.OriginalURLCtxKey, *req.URL) - req = req.WithContext(ctx) - - re.ServeHTTP(rec, req) - - if got, want := rec.Header().Get("Location"), "http://example.com/a/d?b=c&e=f"; got != want { - t.Fatalf("Test 2: expected location header %s but was %s", want, got) - } -} - -func TestMetaRedirect(t *testing.T) { - re := Redirect{ - Rules: []Rule{ - {FromScheme: func() string { return "http" }, FromPath: "/whatever", Meta: true, To: "/something", RequestMatcher: httpserver.IfMatcher{}}, - {FromScheme: func() string { return "http" }, FromPath: "/", Meta: true, To: "https://example.com/", RequestMatcher: httpserver.IfMatcher{}}, - }, - } - - for i, test := range re.Rules { - req, err := http.NewRequest("GET", test.FromPath, nil) - if err != nil { - t.Fatalf("Test %d: Could not create HTTP request: %v", i, err) - } - - rec := httptest.NewRecorder() - re.ServeHTTP(rec, req) - - body, err := ioutil.ReadAll(rec.Body) - if err != nil { - t.Fatalf("Test %d: Could not read HTTP response body: %v", i, err) - } - expectedSnippet := `` - if !bytes.Contains(body, []byte(expectedSnippet)) { - t.Errorf("Test %d: Expected Response Body to contain %q but was %q", - i, expectedSnippet, body) - } - } -} diff --git a/caddyhttp/redirect/setup.go b/caddyhttp/redirect/setup.go deleted file mode 100644 index 2e238dd1363..00000000000 --- a/caddyhttp/redirect/setup.go +++ /dev/null @@ -1,170 +0,0 @@ -package redirect - -import ( - "net/http" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func init() { - caddy.RegisterPlugin("redir", caddy.Plugin{ - ServerType: "http", - Action: setup, - }) -} - -// setup configures a new Redirect middleware instance. -func setup(c *caddy.Controller) error { - rules, err := redirParse(c) - if err != nil { - return err - } - - httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler { - return Redirect{Next: next, Rules: rules} - }) - - return nil -} - -func redirParse(c *caddy.Controller) ([]Rule, error) { - var redirects []Rule - - cfg := httpserver.GetConfig(c) - - initRule := func(rule *Rule, defaultCode string, args []string) error { - rule.FromScheme = func() string { - if cfg.TLS.Enabled { - return "https" - } - return "http" - } - - var ( - from = "/" - to string - code = defaultCode - ) - switch len(args) { - case 1: - // To specified (catch-all redirect) - // Not sure why user is doing this in a table, as it causes all other redirects to be ignored. - // As such, this feature remains undocumented. - to = args[0] - case 2: - // From and To specified - from = args[0] - to = args[1] - case 3: - // From, To, and Code specified - from = args[0] - to = args[1] - code = args[2] - default: - return c.ArgErr() - } - - rule.FromPath = from - rule.To = to - if code == "meta" { - rule.Meta = true - code = defaultCode - } - if codeNumber, ok := httpRedirs[code]; ok { - rule.Code = codeNumber - } else { - return c.Errf("Invalid redirect code '%v'", code) - } - - return nil - } - - // checkAndSaveRule checks the rule for validity (except the redir code) - // and saves it if it's valid, or returns an error. - checkAndSaveRule := func(rule Rule) error { - if rule.FromPath == rule.To { - return c.Err("'from' and 'to' values of redirect rule cannot be the same") - } - - for _, otherRule := range redirects { - if otherRule.FromPath == rule.FromPath { - return c.Errf("rule with duplicate 'from' value: %s -> %s", otherRule.FromPath, otherRule.To) - } - } - - redirects = append(redirects, rule) - return nil - } - - const initDefaultCode = "301" - - for c.Next() { - args := c.RemainingArgs() - matcher, err := httpserver.SetupIfMatcher(c) - if err != nil { - return nil, err - } - - var hadOptionalBlock bool - for c.NextBlock() { - if httpserver.IfMatcherKeyword(c) { - continue - } - - hadOptionalBlock = true - - rule := Rule{ - RequestMatcher: matcher, - } - - defaultCode := initDefaultCode - // Set initial redirect code - if len(args) == 1 { - defaultCode = args[0] - } - - // RemainingArgs only gets the values after the current token, but in our - // case we want to include the current token to get an accurate count. - insideArgs := append([]string{c.Val()}, c.RemainingArgs()...) - err := initRule(&rule, defaultCode, insideArgs) - if err != nil { - return redirects, err - } - - err = checkAndSaveRule(rule) - if err != nil { - return redirects, err - } - } - - if !hadOptionalBlock { - rule := Rule{ - RequestMatcher: matcher, - } - err := initRule(&rule, initDefaultCode, args) - if err != nil { - return redirects, err - } - - err = checkAndSaveRule(rule) - if err != nil { - return redirects, err - } - } - } - - return redirects, nil -} - -// httpRedirs is a list of supported HTTP redirect codes. -var httpRedirs = map[string]int{ - "300": http.StatusMultipleChoices, - "301": http.StatusMovedPermanently, - "302": http.StatusFound, // (NOT CORRECT for "Temporary Redirect", see 307) - "303": http.StatusSeeOther, - "304": http.StatusNotModified, - "305": http.StatusUseProxy, - "307": http.StatusTemporaryRedirect, - "308": http.StatusPermanentRedirect, // Permanent Redirect (RFC 7238) -} diff --git a/caddyhttp/redirect/setup_test.go b/caddyhttp/redirect/setup_test.go deleted file mode 100644 index bb082fd84b1..00000000000 --- a/caddyhttp/redirect/setup_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package redirect - -import ( - "fmt" - "testing" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestSetup(t *testing.T) { - - for j, test := range []struct { - input string - shouldErr bool - expectedRules []Rule - }{ - // test case #0 tests the recognition of a valid HTTP status code defined outside of block statement - {"redir 300 {\n/ /foo\n}", false, []Rule{{FromPath: "/", To: "/foo", Code: 300, RequestMatcher: httpserver.IfMatcher{}}}}, - - // test case #1 tests the recognition of an invalid HTTP status code defined outside of block statement - {"redir 9000 {\n/ /foo\n}", true, []Rule{{}}}, - - // test case #2 tests the detection of a valid HTTP status code outside of a block statement being overridden by an invalid HTTP status code inside statement of a block statement - {"redir 300 {\n/ /foo 9000\n}", true, []Rule{{}}}, - - // test case #3 tests the detection of an invalid HTTP status code outside of a block statement being overridden by a valid HTTP status code inside statement of a block statement - {"redir 9000 {\n/ /foo 300\n}", true, []Rule{{}}}, - - // test case #4 tests the recognition of a TO redirection in a block statement.The HTTP status code is set to the default of 301 - MovedPermanently - {"redir 302 {\n/foo\n}", false, []Rule{{FromPath: "/", To: "/foo", Code: 302, RequestMatcher: httpserver.IfMatcher{}}}}, - - // test case #5 tests the recognition of a TO and From redirection in a block statement - {"redir {\n/bar /foo 303\n}", false, []Rule{{FromPath: "/bar", To: "/foo", Code: 303, RequestMatcher: httpserver.IfMatcher{}}}}, - - // test case #6 tests the recognition of a TO redirection in a non-block statement. The HTTP status code is set to the default of 301 - MovedPermanently - {"redir /foo", false, []Rule{{FromPath: "/", To: "/foo", Code: 301, RequestMatcher: httpserver.IfMatcher{}}}}, - - // test case #7 tests the recognition of a TO and From redirection in a non-block statement - {"redir /bar /foo 303", false, []Rule{{FromPath: "/bar", To: "/foo", Code: 303, RequestMatcher: httpserver.IfMatcher{}}}}, - - // test case #8 tests the recognition of multiple redirections - {"redir {\n / /foo 304 \n} \n redir {\n /bar /foobar 305 \n}", false, - []Rule{{FromPath: "/", To: "/foo", Code: 304, RequestMatcher: httpserver.IfMatcher{}}, - {FromPath: "/bar", To: "/foobar", Code: 305, RequestMatcher: httpserver.IfMatcher{}}}}, - - // test case #9 tests the detection of duplicate redirections - {"redir {\n /bar /foo 304 \n} redir {\n /bar /foo 304 \n}", true, []Rule{{}}}, - - // test case #10 tests the detection of a valid HTTP status code outside of a block statement being overridden by an valid HTTP status code inside statement of a block statement - {"redir 300 {\n/ /foo 301\n}", false, []Rule{{FromPath: "/", To: "/foo", Code: 301, RequestMatcher: httpserver.IfMatcher{}}}}, - - // test case #11 tests the recognition of a matcher - {"redir {\n if {port} is 80\n/ /foo\n}", false, []Rule{{FromPath: "/", To: "/foo", Code: 301, - RequestMatcher: func() httpserver.IfMatcher { - c := caddy.NewTestController("http", "{\n if {port} is 80\n}") - matcher, _ := httpserver.SetupIfMatcher(c) - return matcher.(httpserver.IfMatcher) - }()}}}, - - // test case #12 tests the detection of a valid HTTP status code outside of a block statement with a matcher - {"redir 300 {\n if {port} is 80\n/ /foo\n}", false, []Rule{{FromPath: "/", To: "/foo", Code: 300, - RequestMatcher: func() httpserver.IfMatcher { - c := caddy.NewTestController("http", "{\n if {port} is 80\n}") - matcher, _ := httpserver.SetupIfMatcher(c) - return matcher.(httpserver.IfMatcher) - }()}}}, - } { - c := caddy.NewTestController("http", test.input) - err := setup(c) - if err != nil && !test.shouldErr { - t.Errorf("Test case #%d received an error of %v", j, err) - } else if test.shouldErr { - continue - } - mids := httpserver.GetConfig(c).Middleware() - receivedRules := mids[len(mids)-1](nil).(Redirect).Rules - - for i, receivedRule := range receivedRules { - if receivedRule.FromPath != test.expectedRules[i].FromPath { - t.Errorf("Test case #%d.%d expected a from path of %s, but received a from path of %s", j, i, test.expectedRules[i].FromPath, receivedRule.FromPath) - } - if receivedRule.To != test.expectedRules[i].To { - t.Errorf("Test case #%d.%d expected a TO path of %s, but received a TO path of %s", j, i, test.expectedRules[i].To, receivedRule.To) - } - if receivedRule.Code != test.expectedRules[i].Code { - t.Errorf("Test case #%d.%d expected a HTTP status code of %d, but received a code of %d", j, i, test.expectedRules[i].Code, receivedRule.Code) - } - if gotMatcher, expectMatcher := fmt.Sprint(receivedRule.RequestMatcher), fmt.Sprint(test.expectedRules[i].RequestMatcher); gotMatcher != expectMatcher { - t.Errorf("Test case #%d.%d expected a Matcher %s, but received a Matcher %s", j, i, expectMatcher, gotMatcher) - } - } - } - -} diff --git a/caddyhttp/requestid/requestid.go b/caddyhttp/requestid/requestid.go deleted file mode 100644 index 40b258d4200..00000000000 --- a/caddyhttp/requestid/requestid.go +++ /dev/null @@ -1,34 +0,0 @@ -package requestid - -import ( - "context" - "log" - "net/http" - - "github.com/mholt/caddy/caddyhttp/httpserver" - uuid "github.com/nu7hatch/gouuid" -) - -// Handler is a middleware handler -type Handler struct { - Next httpserver.Handler -} - -func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - reqid := UUID() - c := context.WithValue(r.Context(), httpserver.RequestIDCtxKey, reqid) - r = r.WithContext(c) - - return h.Next.ServeHTTP(w, r) -} - -// UUID returns U4 UUID -func UUID() string { - u4, err := uuid.NewV4() - if err != nil { - log.Printf("[ERROR] generating request ID: %v", err) - return "" - } - - return u4.String() -} diff --git a/caddyhttp/requestid/requestid_test.go b/caddyhttp/requestid/requestid_test.go deleted file mode 100644 index 77014dfd433..00000000000 --- a/caddyhttp/requestid/requestid_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package requestid - -import ( - "context" - "net/http" - "testing" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestRequestID(t *testing.T) { - request, err := http.NewRequest("GET", "http://localhost/", nil) - if err != nil { - t.Fatal("Could not create HTTP request:", err) - } - - reqid := UUID() - - c := context.WithValue(request.Context(), httpserver.RequestIDCtxKey, reqid) - - request = request.WithContext(c) - - // See caddyhttp/replacer.go - value, _ := request.Context().Value(httpserver.RequestIDCtxKey).(string) - - if value == "" { - t.Fatal("Request ID should not be empty") - } - - if value != reqid { - t.Fatal("Request ID does not match") - } -} diff --git a/caddyhttp/requestid/setup.go b/caddyhttp/requestid/setup.go deleted file mode 100644 index 8c06af9e938..00000000000 --- a/caddyhttp/requestid/setup.go +++ /dev/null @@ -1,27 +0,0 @@ -package requestid - -import ( - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func init() { - caddy.RegisterPlugin("request_id", caddy.Plugin{ - ServerType: "http", - Action: setup, - }) -} - -func setup(c *caddy.Controller) error { - for c.Next() { - if c.NextArg() { - return c.ArgErr() //no arg expected. - } - } - - httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler { - return Handler{Next: next} - }) - - return nil -} diff --git a/caddyhttp/requestid/setup_test.go b/caddyhttp/requestid/setup_test.go deleted file mode 100644 index 17cbceee39f..00000000000 --- a/caddyhttp/requestid/setup_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package requestid - -import ( - "testing" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestSetup(t *testing.T) { - c := caddy.NewTestController("http", `requestid`) - err := setup(c) - if err != nil { - t.Errorf("Expected no errors, 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.(Handler) - - if !ok { - t.Fatalf("Expected handler to be type Handler, got: %#v", handler) - } - - if !httpserver.SameNext(myHandler.Next, httpserver.EmptyNext) { - t.Error("'Next' field of handler was not set properly") - } -} - -func TestSetupWithArg(t *testing.T) { - c := caddy.NewTestController("http", `requestid abc`) - err := setup(c) - if err == nil { - t.Errorf("Expected an error, got: %v", err) - } - mids := httpserver.GetConfig(c).Middleware() - if len(mids) != 0 { - t.Fatal("Expected no middleware") - } -} diff --git a/caddyhttp/rewrite/rewrite.go b/caddyhttp/rewrite/rewrite.go deleted file mode 100644 index 14d25ca585e..00000000000 --- a/caddyhttp/rewrite/rewrite.go +++ /dev/null @@ -1,233 +0,0 @@ -// Package rewrite is middleware for rewriting requests internally to -// a different path. -package rewrite - -import ( - "fmt" - "net/http" - "net/url" - "path" - "path/filepath" - "regexp" - "strings" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -// Result is the result of a rewrite -type Result int - -const ( - // RewriteIgnored is returned when rewrite is not done on request. - RewriteIgnored Result = iota - // RewriteDone is returned when rewrite is done on request. - RewriteDone -) - -// Rewrite is middleware to rewrite request locations internally before being handled. -type Rewrite struct { - Next httpserver.Handler - FileSys http.FileSystem - Rules []httpserver.HandlerConfig -} - -// ServeHTTP implements the httpserver.Handler interface. -func (rw Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - if rule := httpserver.ConfigSelector(rw.Rules).Select(r); rule != nil { - rule.(Rule).Rewrite(rw.FileSys, r) - } - - return rw.Next.ServeHTTP(w, r) -} - -// Rule describes an internal location rewrite rule. -type Rule interface { - httpserver.HandlerConfig - // Rewrite rewrites the internal location of the current request. - Rewrite(http.FileSystem, *http.Request) Result -} - -// SimpleRule is a simple rewrite rule. -type SimpleRule struct { - From, To string -} - -// NewSimpleRule creates a new Simple Rule -func NewSimpleRule(from, to string) SimpleRule { - return SimpleRule{from, to} -} - -// BasePath satisfies httpserver.Config -func (s SimpleRule) BasePath() string { return s.From } - -// Match satisfies httpserver.Config -func (s SimpleRule) Match(r *http.Request) bool { return s.From == r.URL.Path } - -// Rewrite rewrites the internal location of the current request. -func (s SimpleRule) Rewrite(fs http.FileSystem, r *http.Request) Result { - - // attempt rewrite - return To(fs, r, s.To, newReplacer(r)) -} - -// ComplexRule is a rewrite rule based on a regular expression -type ComplexRule struct { - // Path base. Request to this path and subpaths will be rewritten - Base string - - // Path to rewrite to - To string - - // Extensions to filter by - Exts []string - - // Request matcher - httpserver.RequestMatcher - - Regexp *regexp.Regexp -} - -// NewComplexRule creates a new RegexpRule. It returns an error if regexp -// pattern (pattern) or extensions (ext) are invalid. -func NewComplexRule(base, pattern, to string, ext []string, matcher httpserver.RequestMatcher) (ComplexRule, error) { - // validate regexp if present - var r *regexp.Regexp - if pattern != "" { - var err error - r, err = regexp.Compile(pattern) - if err != nil { - return ComplexRule{}, err - } - } - - // validate extensions if present - for _, v := range ext { - if len(v) < 2 || (len(v) < 3 && v[0] == '!') { - // check if no extension is specified - if v != "/" && v != "!/" { - return ComplexRule{}, fmt.Errorf("invalid extension %v", v) - } - } - } - - // use both IfMatcher and PathMatcher - matcher = httpserver.MergeRequestMatchers( - // If condition matcher - matcher, - // Base path matcher - httpserver.PathMatcher(base), - ) - - return ComplexRule{ - Base: base, - To: to, - Exts: ext, - RequestMatcher: matcher, - Regexp: r, - }, nil -} - -// BasePath satisfies httpserver.Config -func (r ComplexRule) BasePath() string { return r.Base } - -// Match satisfies httpserver.Config. -// -// Though ComplexRule embeds a RequestMatcher, additional -// checks are needed which requires a custom implementation. -func (r ComplexRule) Match(req *http.Request) bool { - // validate RequestMatcher - // includes if and path - if !r.RequestMatcher.Match(req) { - return false - } - - // validate extensions - if !r.matchExt(req.URL.Path) { - return false - } - - // if regex is nil, ignore - if r.Regexp == nil { - return true - } - // otherwise validate regex - return r.regexpMatches(req.URL.Path) != nil -} - -// Rewrite rewrites the internal location of the current request. -func (r ComplexRule) Rewrite(fs http.FileSystem, req *http.Request) (re Result) { - replacer := newReplacer(req) - - // validate regexp if present - if r.Regexp != nil { - matches := r.regexpMatches(req.URL.Path) - switch len(matches) { - case 0: - // no match - return - default: - // set regexp match variables {1}, {2} ... - - // url escaped values of ? and #. - q, f := url.QueryEscape("?"), url.QueryEscape("#") - - for i := 1; i < len(matches); i++ { - // Special case of unescaped # and ? by stdlib regexp. - // Reverse the unescape. - if strings.ContainsAny(matches[i], "?#") { - matches[i] = strings.NewReplacer("?", q, "#", f).Replace(matches[i]) - } - - replacer.Set(fmt.Sprint(i), matches[i]) - } - } - } - - // attempt rewrite - return To(fs, req, r.To, replacer) -} - -// matchExt matches rPath against registered file extensions. -// Returns true if a match is found and false otherwise. -func (r ComplexRule) matchExt(rPath string) bool { - f := filepath.Base(rPath) - ext := path.Ext(f) - if ext == "" { - ext = "/" - } - - mustUse := false - for _, v := range r.Exts { - use := true - if v[0] == '!' { - use = false - v = v[1:] - } - - if use { - mustUse = true - } - - if ext == v { - return use - } - } - - return !mustUse -} - -func (r ComplexRule) regexpMatches(rPath string) []string { - if r.Regexp != nil { - // include trailing slash in regexp if present - start := len(r.Base) - if strings.HasSuffix(r.Base, "/") { - start-- - } - return r.Regexp.FindStringSubmatch(rPath[start:]) - } - return nil -} - -func newReplacer(r *http.Request) httpserver.Replacer { - return httpserver.NewReplacer(r, nil, "") -} diff --git a/caddyhttp/rewrite/rewrite_test.go b/caddyhttp/rewrite/rewrite_test.go deleted file mode 100644 index 6be2f221183..00000000000 --- a/caddyhttp/rewrite/rewrite_test.go +++ /dev/null @@ -1,123 +0,0 @@ -package rewrite - -import ( - "context" - "fmt" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestRewrite(t *testing.T) { - rw := Rewrite{ - Next: httpserver.HandlerFunc(urlPrinter), - Rules: []httpserver.HandlerConfig{ - NewSimpleRule("/from", "/to"), - NewSimpleRule("/a", "/b"), - NewSimpleRule("/b", "/b{uri}"), - }, - FileSys: http.Dir("."), - } - - regexps := [][]string{ - {"/reg/", ".*", "/to", ""}, - {"/r/", "[a-z]+", "/toaz", "!.html|"}, - {"/path/", "[a-z0-9]", "/to/{path}", ""}, - {"/url/", "a([a-z0-9]*)s([A-Z]{2})", "/to/{rewrite_path}", ""}, - {"/ab/", "ab", "/ab?{query}", ".txt|"}, - {"/ab/", "ab", "/ab?type=html&{query}", ".html|"}, - {"/abc/", "ab", "/abc/{file}", ".html|"}, - {"/abcd/", "ab", "/a/{dir}/{file}", ".html|"}, - {"/abcde/", "ab", "/a#{fragment}", ".html|"}, - {"/ab/", `.*\.jpg`, "/ajpg", ""}, - {"/reggrp", `/ad/([0-9]+)([a-z]*)`, "/a{1}/{2}", ""}, - {"/reg2grp", `(.*)`, "/{1}", ""}, - {"/reg3grp", `(.*)/(.*)/(.*)`, "/{1}{2}{3}", ""}, - {"/hashtest", "(.*)", "/{1}", ""}, - } - - for _, regexpRule := range regexps { - var ext []string - if s := strings.Split(regexpRule[3], "|"); len(s) > 1 { - ext = s[:len(s)-1] - } - rule, err := NewComplexRule(regexpRule[0], regexpRule[1], regexpRule[2], ext, httpserver.IfMatcher{}) - if err != nil { - t.Fatal(err) - } - rw.Rules = append(rw.Rules, rule) - } - - tests := []struct { - from string - expectedTo string - }{ - {"/from", "/to"}, - {"/a", "/b"}, - {"/b", "/b/b"}, - {"/aa", "/aa"}, - {"/", "/"}, - {"/a?foo=bar", "/b?foo=bar"}, - {"/asdf?foo=bar", "/asdf?foo=bar"}, - {"/foo#bar", "/foo#bar"}, - {"/a#foo", "/b#foo"}, - {"/reg/foo", "/to"}, - {"/re", "/re"}, - {"/r/", "/r/"}, - {"/r/123", "/r/123"}, - {"/r/a123", "/toaz"}, - {"/r/abcz", "/toaz"}, - {"/r/z", "/toaz"}, - {"/r/z.html", "/r/z.html"}, - {"/r/z.js", "/toaz"}, - {"/path/a1b2c", "/to/path/a1b2c"}, - {"/path/d3e4f", "/to/path/d3e4f"}, - {"/url/asAB", "/to/url/asAB"}, - {"/url/aBsAB", "/url/aBsAB"}, - {"/url/a00sAB", "/to/url/a00sAB"}, - {"/url/a0z0sAB", "/to/url/a0z0sAB"}, - {"/ab/aa", "/ab/aa"}, - {"/ab/ab", "/ab/ab"}, - {"/ab/ab.txt", "/ab"}, - {"/ab/ab.txt?name=name", "/ab?name=name"}, - {"/ab/ab.html?name=name", "/ab?type=html&name=name"}, - {"/abc/ab.html", "/abc/ab.html"}, - {"/abcd/abcd.html", "/a/abcd/abcd.html"}, - {"/abcde/abcde.html", "/a"}, - {"/abcde/abcde.html#1234", "/a#1234"}, - {"/ab/ab.jpg", "/ajpg"}, - {"/reggrp/ad/12", "/a12/"}, - {"/reggrp/ad/124a", "/a124/a"}, - {"/reggrp/ad/124abc", "/a124/abc"}, - {"/reg2grp/ad/124abc", "/ad/124abc"}, - {"/reg3grp/ad/aa/66", "/adaa66"}, - {"/reg3grp/ad612/n1n/ab", "/ad612n1nab"}, - {"/hashtest/a%20%23%20test", "/a%20%23%20test"}, - {"/hashtest/a%20%3F%20test", "/a%20%3F%20test"}, - {"/hashtest/a%20%3F%23test", "/a%20%3F%23test"}, - } - - 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) - } - ctx := context.WithValue(req.Context(), httpserver.OriginalURLCtxKey, *req.URL) - req = req.WithContext(ctx) - - rec := httptest.NewRecorder() - rw.ServeHTTP(rec, req) - - if got, want := rec.Body.String(), test.expectedTo; got != want { - t.Errorf("Test %d: Expected URL to be '%s' but was '%s'", i, want, got) - } - } -} - -func urlPrinter(w http.ResponseWriter, r *http.Request) (int, error) { - fmt.Fprint(w, r.URL.String()) - return 0, nil -} diff --git a/caddyhttp/rewrite/setup.go b/caddyhttp/rewrite/setup.go deleted file mode 100644 index 71c498e1d68..00000000000 --- a/caddyhttp/rewrite/setup.go +++ /dev/null @@ -1,107 +0,0 @@ -package rewrite - -import ( - "net/http" - "strings" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func init() { - caddy.RegisterPlugin("rewrite", caddy.Plugin{ - ServerType: "http", - Action: setup, - }) -} - -// setup configures a new Rewrite middleware instance. -func setup(c *caddy.Controller) error { - rewrites, err := rewriteParse(c) - if err != nil { - return err - } - - cfg := httpserver.GetConfig(c) - - cfg.AddMiddleware(func(next httpserver.Handler) httpserver.Handler { - return Rewrite{ - Next: next, - FileSys: http.Dir(cfg.Root), - Rules: rewrites, - } - }) - - return nil -} - -func rewriteParse(c *caddy.Controller) ([]httpserver.HandlerConfig, error) { - var rules []httpserver.HandlerConfig - - for c.Next() { - var rule Rule - var err error - var base = "/" - var pattern, to string - var ext []string - - args := c.RemainingArgs() - - var matcher httpserver.RequestMatcher - - switch len(args) { - case 1: - base = args[0] - fallthrough - case 0: - // Integrate request matcher for 'if' conditions. - matcher, err = httpserver.SetupIfMatcher(c) - if err != nil { - return nil, err - } - - for c.NextBlock() { - if httpserver.IfMatcherKeyword(c) { - continue - } - switch c.Val() { - case "r", "regexp": - if !c.NextArg() { - return nil, c.ArgErr() - } - pattern = c.Val() - case "to": - args1 := c.RemainingArgs() - if len(args1) == 0 { - return nil, c.ArgErr() - } - to = strings.Join(args1, " ") - case "ext": - args1 := c.RemainingArgs() - if len(args1) == 0 { - return nil, c.ArgErr() - } - ext = args1 - default: - return nil, c.ArgErr() - } - } - // ensure to is specified - if to == "" { - return nil, c.ArgErr() - } - if rule, err = NewComplexRule(base, pattern, to, ext, matcher); err != nil { - return nil, err - } - rules = append(rules, rule) - - // the only unhandled case is 2 and above - default: - rule = NewSimpleRule(args[0], strings.Join(args[1:], " ")) - rules = append(rules, rule) - } - - } - - return rules, nil -} diff --git a/caddyhttp/rewrite/setup_test.go b/caddyhttp/rewrite/setup_test.go deleted file mode 100644 index 4e32c2eed64..00000000000 --- a/caddyhttp/rewrite/setup_test.go +++ /dev/null @@ -1,190 +0,0 @@ -package rewrite - -import ( - "fmt" - "regexp" - "strings" - "testing" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestSetup(t *testing.T) { - c := caddy.NewTestController("http", `rewrite /from /to`) - 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, had 0 instead") - } - - handler := mids[0](httpserver.EmptyNext) - myHandler, ok := handler.(Rewrite) - if !ok { - t.Fatalf("Expected handler to be type Rewrite, got: %#v", handler) - } - - if !httpserver.SameNext(myHandler.Next, httpserver.EmptyNext) { - t.Error("'Next' field of handler was not set properly") - } - - if len(myHandler.Rules) != 1 { - t.Errorf("Expected handler to have %d rule, has %d instead", 1, len(myHandler.Rules)) - } -} - -func TestRewriteParse(t *testing.T) { - simpleTests := []struct { - input string - shouldErr bool - expected []Rule - }{ - {`rewrite /from /to`, false, []Rule{ - SimpleRule{From: "/from", To: "/to"}, - }}, - {`rewrite /from /to - rewrite a b`, false, []Rule{ - SimpleRule{From: "/from", To: "/to"}, - SimpleRule{From: "a", To: "b"}, - }}, - {`rewrite a`, true, []Rule{}}, - {`rewrite`, true, []Rule{}}, - {`rewrite a b c`, false, []Rule{ - SimpleRule{From: "a", To: "b c"}, - }}, - } - - for i, test := range simpleTests { - actual, err := rewriteParse(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) - } else if err != nil && test.shouldErr { - continue - } - - if len(actual) != len(test.expected) { - t.Fatalf("Test %d expected %d rules, but got %d", - i, len(test.expected), len(actual)) - } - - for j, e := range test.expected { - actualRule := actual[j].(SimpleRule) - expectedRule := e.(SimpleRule) - - if actualRule.From != expectedRule.From { - t.Errorf("Test %d, rule %d: Expected From=%s, got %s", - i, j, expectedRule.From, actualRule.From) - } - - if actualRule.To != expectedRule.To { - t.Errorf("Test %d, rule %d: Expected To=%s, got %s", - i, j, expectedRule.To, actualRule.To) - } - } - } - - regexpTests := []struct { - input string - shouldErr bool - expected []Rule - }{ - {`rewrite { - r .* - to /to /index.php? - }`, false, []Rule{ - ComplexRule{Base: "/", To: "/to /index.php?", Regexp: regexp.MustCompile(".*")}, - }}, - {`rewrite { - regexp .* - to /to - ext / html txt - }`, false, []Rule{ - ComplexRule{Base: "/", To: "/to", Exts: []string{"/", "html", "txt"}, Regexp: regexp.MustCompile(".*")}, - }}, - {`rewrite /path { - r rr - to /dest - } - rewrite / { - regexp [a-z]+ - to /to /to2 - } - `, false, []Rule{ - ComplexRule{Base: "/path", To: "/dest", Regexp: regexp.MustCompile("rr")}, - ComplexRule{Base: "/", To: "/to /to2", Regexp: regexp.MustCompile("[a-z]+")}, - }}, - {`rewrite { - r .* - }`, true, []Rule{ - ComplexRule{}, - }}, - {`rewrite { - - }`, true, []Rule{ - ComplexRule{}, - }}, - {`rewrite /`, true, []Rule{ - ComplexRule{}, - }}, - {`rewrite { - if {path} match / - to /to - }`, false, []Rule{ - ComplexRule{Base: "/", To: "/to"}, - }}, - } - - for i, test := range regexpTests { - actual, err := rewriteParse(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) - } else if err != nil && test.shouldErr { - continue - } - - if len(actual) != len(test.expected) { - t.Fatalf("Test %d expected %d rules, but got %d", - i, len(test.expected), len(actual)) - } - - for j, e := range test.expected { - actualRule := actual[j].(ComplexRule) - expectedRule := e.(ComplexRule) - - if actualRule.Base != expectedRule.Base { - t.Errorf("Test %d, rule %d: Expected Base=%s, got %s", - i, j, expectedRule.Base, actualRule.Base) - } - - if actualRule.To != expectedRule.To { - t.Errorf("Test %d, rule %d: Expected To=%s, got %s", - i, j, expectedRule.To, actualRule.To) - } - - if fmt.Sprint(actualRule.Exts) != fmt.Sprint(expectedRule.Exts) { - t.Errorf("Test %d, rule %d: Expected Ext=%v, got %v", - i, j, expectedRule.To, actualRule.To) - } - - if actualRule.Regexp != nil { - if actualRule.Regexp.String() != expectedRule.Regexp.String() { - t.Errorf("Test %d, rule %d: Expected Pattern=%s, got %s", - i, j, actualRule.Regexp.String(), expectedRule.Regexp.String()) - } - } - } - - if rules_fmt := fmt.Sprintf("%v", actual); strings.HasPrefix(rules_fmt, "%!") { - t.Errorf("Test %d: Failed to string encode: %#v", i, rules_fmt) - } - } -} diff --git a/caddyhttp/rewrite/testdata/testfile b/caddyhttp/rewrite/testdata/testfile deleted file mode 100644 index 7b4d68d70fc..00000000000 --- a/caddyhttp/rewrite/testdata/testfile +++ /dev/null @@ -1 +0,0 @@ -empty \ No newline at end of file diff --git a/caddyhttp/rewrite/to.go b/caddyhttp/rewrite/to.go deleted file mode 100644 index 6e296dc9c03..00000000000 --- a/caddyhttp/rewrite/to.go +++ /dev/null @@ -1,89 +0,0 @@ -package rewrite - -import ( - "log" - "net/http" - "net/url" - "path" - "strings" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -// To attempts rewrite. It attempts to rewrite to first valid path -// or the last path if none of the paths are valid. -func To(fs http.FileSystem, r *http.Request, to string, replacer httpserver.Replacer) Result { - tos := strings.Fields(to) - - // try each rewrite paths - t := "" - query := "" - for _, v := range tos { - t = replacer.Replace(v) - tparts := strings.SplitN(t, "?", 2) - t = path.Clean(tparts[0]) - - if len(tparts) > 1 { - query = tparts[1] - } - - // add trailing slash for directories, if present - if strings.HasSuffix(tparts[0], "/") && !strings.HasSuffix(t, "/") { - t += "/" - } - - // validate file - if validFile(fs, t) { - break - } - } - - // validate resulting path - u, err := url.Parse(t) - if err != nil { - // Let the user know we got here. Rewrite is expected but - // the resulting url is invalid. - log.Printf("[ERROR] rewrite: resulting path '%v' is invalid. error: %v", t, err) - return RewriteIgnored - } - - // perform rewrite - r.URL.Path = u.Path - if query != "" { - // overwrite query string if present - r.URL.RawQuery = query - } - if u.Fragment != "" { - // overwrite fragment if present - r.URL.Fragment = u.Fragment - } - - return RewriteDone -} - -// validFile checks if file exists on the filesystem. -// if file ends with `/`, it is validated as a directory. -func validFile(fs http.FileSystem, file string) bool { - if fs == nil { - return false - } - - f, err := fs.Open(file) - if err != nil { - return false - } - defer f.Close() - - stat, err := f.Stat() - if err != nil { - return false - } - - // directory - if strings.HasSuffix(file, "/") { - return stat.IsDir() - } - - // file - return !stat.IsDir() -} diff --git a/caddyhttp/rewrite/to_test.go b/caddyhttp/rewrite/to_test.go deleted file mode 100644 index e809eec9d81..00000000000 --- a/caddyhttp/rewrite/to_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package rewrite - -import ( - "context" - "net/http" - "net/url" - "testing" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestTo(t *testing.T) { - fs := http.Dir("testdata") - tests := []struct { - url string - to string - expected string - }{ - {"/", "/somefiles", "/somefiles"}, - {"/somefiles", "/somefiles /index.php{uri}", "/index.php/somefiles"}, - {"/somefiles", "/testfile /index.php{uri}", "/testfile"}, - {"/somefiles", "/testfile/ /index.php{uri}", "/index.php/somefiles"}, - {"/somefiles", "/somefiles /index.php{uri}", "/index.php/somefiles"}, - {"/?a=b", "/somefiles /index.php?{query}", "/index.php?a=b"}, - {"/?a=b", "/testfile /index.php?{query}", "/testfile?a=b"}, - {"/?a=b", "/testdir /index.php?{query}", "/index.php?a=b"}, - {"/?a=b", "/testdir/ /index.php?{query}", "/testdir/?a=b"}, - {"/test?url=http://", " /p/{path}?{query}", "/p/test?url=http://"}, - {"/test?url=http://", " /p/{rewrite_path}?{query}", "/p/test?url=http://"}, - {"/test/?url=http://", " /{uri}", "/test/?url=http://"}, - } - - uri := func(r *url.URL) string { - uri := r.Path - if r.RawQuery != "" { - uri += "?" + r.RawQuery - } - return uri - } - for i, test := range tests { - r, err := http.NewRequest("GET", test.url, nil) - if err != nil { - t.Error(err) - } - ctx := context.WithValue(r.Context(), httpserver.OriginalURLCtxKey, *r.URL) - r = r.WithContext(ctx) - To(fs, r, test.to, newReplacer(r)) - if uri(r.URL) != test.expected { - t.Errorf("Test %v: expected %v found %v", i, test.expected, uri(r.URL)) - } - } -} diff --git a/caddyhttp/root/root.go b/caddyhttp/root/root.go deleted file mode 100644 index f9f11bfa2dd..00000000000 --- a/caddyhttp/root/root.go +++ /dev/null @@ -1,52 +0,0 @@ -package root - -import ( - "log" - "os" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func init() { - caddy.RegisterPlugin("root", caddy.Plugin{ - ServerType: "http", - Action: setupRoot, - }) -} - -func setupRoot(c *caddy.Controller) error { - config := httpserver.GetConfig(c) - - for c.Next() { - if !c.NextArg() { - return c.ArgErr() - } - config.Root = c.Val() - if c.NextArg() { - // only one argument allowed - return c.ArgErr() - } - } - //first check that the path is not a symlink, os.Stat panics when this is true - info, _ := os.Lstat(config.Root) - if info != nil && info.Mode()&os.ModeSymlink == os.ModeSymlink { - //just print out info, delegate responsibility for symlink validity to - //underlying Go framework, no need to test / verify twice - log.Printf("[INFO] Root path is symlink: %s", config.Root) - } else { - // Check if root path exists - _, err := os.Stat(config.Root) - if err != nil { - if os.IsNotExist(err) { - // Allow this, because the folder might appear later. - // But make sure the user knows! - log.Printf("[WARNING] Root path does not exist: %s", config.Root) - } else { - return c.Errf("Unable to access root path '%s': %v", config.Root, err) - } - } - } - - return nil -} diff --git a/caddyhttp/root/root_test.go b/caddyhttp/root/root_test.go deleted file mode 100644 index e4ad8841bf8..00000000000 --- a/caddyhttp/root/root_test.go +++ /dev/null @@ -1,142 +0,0 @@ -package root - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestRoot(t *testing.T) { - // Predefined error substrings - parseErrContent := "Parse error:" - unableToAccessErrContent := "Unable to access root path" - - existingDirPath, err := getTempDirPath() - if err != nil { - t.Fatalf("BeforeTest: Failed to find an existing directory for testing! Error was: %v", err) - } - - nonExistingDir := filepath.Join(existingDirPath, "highly_unlikely_to_exist_dir") - - existingFile, err := ioutil.TempFile("", "root_test") - if err != nil { - t.Fatalf("BeforeTest: Failed to create temp file for testing! Error was: %v", err) - } - defer func() { - existingFile.Close() - os.Remove(existingFile.Name()) - }() - - inaccessiblePath := getInaccessiblePath(existingFile.Name()) - - tests := []struct { - input string - shouldErr bool - expectedRoot string // expected root, set to the controller. Empty for negative cases. - expectedErrContent string // substring from the expected error. Empty for positive cases. - }{ - // positive - { - fmt.Sprintf(`root %s`, nonExistingDir), false, nonExistingDir, "", - }, - { - fmt.Sprintf(`root %s`, existingDirPath), false, existingDirPath, "", - }, - // negative - { - `root `, true, "", parseErrContent, - }, - { - `root /a /b`, true, "", parseErrContent, - }, - { - fmt.Sprintf(`root %s`, inaccessiblePath), true, "", unableToAccessErrContent, - }, - { - fmt.Sprintf(`root { - %s - }`, existingDirPath), true, "", parseErrContent, - }, - } - - for i, test := range tests { - c := caddy.NewTestController("http", test.input) - err := setupRoot(c) - cfg := httpserver.GetConfig(c) - - if test.shouldErr && err == nil { - t.Errorf("Test %d: Expected error but got nil for input '%s'", i, test.input) - } - - if err != nil { - if !test.shouldErr { - t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err) - } - - if !strings.Contains(err.Error(), test.expectedErrContent) { - t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input) - } - } - - // check root only if we are in a positive test. - if !test.shouldErr && test.expectedRoot != cfg.Root { - t.Errorf("Root not correctly set for input %s. Expected: %s, actual: %s", test.input, test.expectedRoot, cfg.Root) - } - } -} - -// getTempDirPath returns the path to the system temp directory. If it does not exists - an error is returned. -func getTempDirPath() (string, error) { - tempDir := os.TempDir() - _, err := os.Stat(tempDir) - if err != nil { - return "", err - } - return tempDir, nil -} - -func getInaccessiblePath(file string) string { - return filepath.Join("C:", "file\x00name") // null byte in filename is not allowed on Windows AND unix -} - -func TestSymlinkRoot(t *testing.T) { - origDir, err := ioutil.TempDir("", "root_test") - if err != nil { - t.Fatalf("BeforeTest: Failed to create temp dir for testing! Error was: %v", err) - } - defer func() { - os.Remove(origDir) - }() - - tempDir, err := getTempDirPath() - if err != nil { - t.Fatalf("BeforeTest: Failed to find an existing directory for testing! Error was: %v", err) - } - symlinkDir := filepath.Join(tempDir, "symlink") - - err = os.Symlink(origDir, symlinkDir) - if err != nil { - if strings.Contains(err.Error(), "A required privilege is not held by the client") { - t.Skip("BeforeTest: A required privilege is not held by the client and is required to create a symlink to run this test.") - } - t.Fatalf("BeforeTest: Cannot create symlink! Error was: %v", err) - } - defer func() { - os.Remove(symlinkDir) - }() - - input := fmt.Sprintf(`root %s`, symlinkDir) - c := caddy.NewTestController("http", input) - err = setupRoot(c) - _ = httpserver.GetConfig(c) - - if err != nil { - t.Errorf("Test Symlink Root: Expected no error but found one for input %s. Error was: %v", input, err) - } -} diff --git a/caddyhttp/staticfiles/fileserver.go b/caddyhttp/staticfiles/fileserver.go deleted file mode 100644 index a66e1b13605..00000000000 --- a/caddyhttp/staticfiles/fileserver.go +++ /dev/null @@ -1,300 +0,0 @@ -// Package staticfiles provides middleware for serving static files from disk. -// Its handler is the default HTTP handler for the HTTP server. -// -// TODO: Should this package be rolled into the httpserver package? -package staticfiles - -import ( - "math/rand" - "net/http" - "os" - "path" - "path/filepath" - "runtime" - "strconv" - "strings" - - "github.com/mholt/caddy" -) - -// FileServer implements a production-ready file server -// and is the 'default' handler for all requests to Caddy. -// It simply loads and serves the URI requested. FileServer -// is adapted from the one in net/http by the Go authors. -// Significant modifications have been made. -// -// Original license: -// -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. -type FileServer struct { - Root http.FileSystem // jailed access to the file system - Hide []string // list of files for which to respond with "Not Found" -} - -// ServeHTTP serves static files for r according to fs's configuration. -func (fs FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - return fs.serveFile(w, r) -} - -// serveFile writes the specified file to the HTTP response. -// name is '/'-separated, not filepath.Separator. -func (fs FileServer) serveFile(w http.ResponseWriter, r *http.Request) (int, error) { - reqPath := r.URL.Path - - // Prevent absolute path access on Windows. - // TODO remove when stdlib http.Dir fixes this. - if runtime.GOOS == "windows" && len(reqPath) > 0 && filepath.IsAbs(reqPath[1:]) { - return http.StatusNotFound, nil - } - - // open the requested file - f, err := fs.Root.Open(reqPath) - if err != nil { - // TODO: remove when http.Dir handles this (Go 1.9?) - // Go issue #18984 - err = mapFSRootOpenErr(err) - if os.IsNotExist(err) { - return http.StatusNotFound, nil - } else if os.IsPermission(err) { - return http.StatusForbidden, err - } - // otherwise, maybe the server is under load and ran out of file descriptors? - backoff := int(3 + rand.Int31()%3) // 3–5 seconds to prevent a stampede - w.Header().Set("Retry-After", strconv.Itoa(backoff)) - return http.StatusServiceUnavailable, err - } - defer f.Close() - - // get information about the file - d, err := f.Stat() - if err != nil { - if os.IsNotExist(err) { - return http.StatusNotFound, nil - } else if os.IsPermission(err) { - return http.StatusForbidden, err - } - // return a different status code than above to distinguish these cases - return http.StatusInternalServerError, err - } - - // redirect to canonical path (being careful to preserve other parts of URL and - // considering cases where a site is defined with a path prefix that gets stripped) - urlCopy := *r.URL - pathPrefix, _ := r.Context().Value(caddy.CtxKey("path_prefix")).(string) - if pathPrefix != "/" { - urlCopy.Path = pathPrefix + urlCopy.Path - } - if urlCopy.Path == "" { - urlCopy.Path = "/" - } - if d.IsDir() { - // ensure there is a trailing slash - if urlCopy.Path[len(urlCopy.Path)-1] != '/' { - urlCopy.Path += "/" - http.Redirect(w, r, urlCopy.String(), http.StatusMovedPermanently) - return http.StatusMovedPermanently, nil - } - } else { - // ensure no trailing slash - redir := false - if urlCopy.Path[len(urlCopy.Path)-1] == '/' { - urlCopy.Path = urlCopy.Path[:len(urlCopy.Path)-1] - redir = true - } - - // if an index file was explicitly requested, strip file name from the request - // ("/foo/index.html" -> "/foo/") - var requestPage = path.Base(urlCopy.Path) - for _, indexPage := range IndexPages { - if requestPage == indexPage { - urlCopy.Path = urlCopy.Path[:len(urlCopy.Path)-len(indexPage)] - redir = true - break - } - } - - if redir { - http.Redirect(w, r, urlCopy.String(), http.StatusMovedPermanently) - return http.StatusMovedPermanently, nil - } - } - - // use contents of an index file, if present, for directory requests - if d.IsDir() { - for _, indexPage := range IndexPages { - indexPath := path.Join(reqPath, indexPage) - indexFile, err := fs.Root.Open(indexPath) - if err != nil { - continue - } - - indexInfo, err := indexFile.Stat() - if err != nil { - indexFile.Close() - continue - } - - // this defer does not leak fds even though we are in a loop, - // because previous iterations of the loop must have had an - // err, so there's nothing to close from earlier iterations. - defer indexFile.Close() - - // close previously-opened file immediately to release fd - f.Close() - - // switch to using the index file, and we're done here - d = indexInfo - f = indexFile - reqPath = indexPath - break - } - } - - // return Not Found if we either did not find an index file (and thus are - // still a directory) or if this file is supposed to be hidden - if d.IsDir() || fs.IsHidden(d) { - return http.StatusNotFound, nil - } - - etag := calculateEtag(d) - - // look for compressed versions of the file on disk, if the client supports that encoding - for _, encoding := range staticEncodingPriority { - // see if the client accepts a compressed encoding we offer - acceptEncoding := strings.Split(r.Header.Get("Accept-Encoding"), ",") - accepted := false - for _, acc := range acceptEncoding { - if strings.TrimSpace(acc) == encoding { - accepted = true - break - } - } - - // if client doesn't support this encoding, don't even bother; try next one - if !accepted { - continue - } - - // see if the compressed version of this file exists - encodedFile, err := fs.Root.Open(reqPath + staticEncoding[encoding]) - if err != nil { - continue - } - - encodedFileInfo, err := encodedFile.Stat() - if err != nil { - encodedFile.Close() - continue - } - - // close the encoded file when we're done, and close the - // previously-opened file immediately to release the fd - defer encodedFile.Close() - f.Close() - - // the encoded file is now what we're serving - f = encodedFile - etag = calculateEtag(encodedFileInfo) - w.Header().Add("Vary", "Accept-Encoding") - w.Header().Set("Content-Encoding", encoding) - w.Header().Set("Content-Length", strconv.FormatInt(encodedFileInfo.Size(), 10)) - break - } - - // Set the ETag returned to the user-agent. Note that a conditional If-None-Match - // request is handled in http.ServeContent below, which checks against this ETag value. - w.Header().Set("ETag", etag) - - // Note: Errors generated by ServeContent are written immediately - // to the response. This usually only happens if seeking fails (rare). - // Its signature does not bubble the error up to us, so we cannot - // return it for any logging middleware to record. Oh well. - http.ServeContent(w, r, d.Name(), d.ModTime(), f) - - return http.StatusOK, nil -} - -// IsHidden checks if file with FileInfo d is on hide list. -func (fs FileServer) IsHidden(d os.FileInfo) bool { - for _, hiddenPath := range fs.Hide { - // TODO: Could these FileInfos be stored instead of their paths, to avoid opening them all the time? - if hFile, err := fs.Root.Open(hiddenPath); err == nil { - fs, _ := hFile.Stat() - hFile.Close() - if os.SameFile(d, fs) { - return true - } - } - } - return false -} - -// calculateEtag produces a strong etag by default, although, for -// efficiency reasons, it does not actually consume the contents -// of the file to make a hash of all the bytes. ¯\_(ツ)_/¯ -// Prefix the etag with "W/" to convert it into a weak etag. -// See: https://tools.ietf.org/html/rfc7232#section-2.3 -func calculateEtag(d os.FileInfo) string { - t := strconv.FormatInt(d.ModTime().Unix(), 36) - s := strconv.FormatInt(d.Size(), 36) - return `"` + t + s + `"` -} - -// IndexPages is a list of pages that may be understood as -// the "index" files to directories. -var IndexPages = []string{ - "index.html", - "index.htm", - "index.txt", - "default.html", - "default.htm", - "default.txt", -} - -// staticEncoding is a map of content-encoding to a file extension. -// If client accepts given encoding (via Accept-Encoding header) and compressed file with given extensions exists -// it will be served to the client instead of original one. -var staticEncoding = map[string]string{ - "gzip": ".gz", - "br": ".br", -} - -// staticEncodingPriority is a list of preferred static encodings (most efficient compression to least one). -var staticEncodingPriority = []string{ - "br", - "gzip", -} - -// mapFSRootOpenErr maps the provided non-nil error -// to a possibly better non-nil error. In particular, it turns OS-specific errors -// about opening files in non-directories into os.ErrNotExist. -// -// TODO: remove when http.Dir handles this (slated for Go 1.9) -// Go issue #18984 -func mapFSRootOpenErr(originalErr error) error { - if os.IsNotExist(originalErr) || os.IsPermission(originalErr) { - return originalErr - } - - perr, ok := originalErr.(*os.PathError) - if !ok { - return originalErr - } - name := perr.Path - parts := strings.Split(name, string(filepath.Separator)) - for i := range parts { - if parts[i] == "" { - continue - } - fi, err := os.Stat(strings.Join(parts[:i+1], string(filepath.Separator))) - if err != nil { - return originalErr - } - if !fi.IsDir() { - return os.ErrNotExist - } - } - return originalErr -} diff --git a/caddyhttp/staticfiles/fileserver_test.go b/caddyhttp/staticfiles/fileserver_test.go deleted file mode 100644 index 715915b3c29..00000000000 --- a/caddyhttp/staticfiles/fileserver_test.go +++ /dev/null @@ -1,596 +0,0 @@ -package staticfiles - -import ( - "context" - "errors" - "io/ioutil" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "strconv" - "strings" - "testing" - "time" - - "github.com/mholt/caddy" -) - -// TestServeHTTP covers positive scenarios when serving files. -func TestServeHTTP(t *testing.T) { - tmpWebRootDir := beforeServeHTTPTest(t) - defer afterServeHTTPTest(t, tmpWebRootDir) - - fileserver := FileServer{ - Root: http.Dir(filepath.Join(tmpWebRootDir, webrootName)), - Hide: []string{"dir/hidden.html"}, - } - - movedPermanently := "Moved Permanently" - - tests := []struct { - url string - stripPathPrefix string // for when sites are defined with a path (e.g. "example.com/foo/") - acceptEncoding string - expectedLocation string - expectedStatus int - expectedBodyContent string - expectedEtag string - expectedVary string - expectedEncoding string - expectedContentLength string - }{ - // Test 0 - access without any path - { - url: "https://foo", - expectedStatus: http.StatusNotFound, - }, - // Test 1 - access root (without index.html) - { - url: "https://foo/", - expectedStatus: http.StatusNotFound, - }, - // Test 2 - access existing file - { - url: "https://foo/file1.html", - expectedStatus: http.StatusOK, - expectedBodyContent: testFiles[webrootFile1HTML], - expectedEtag: `"2n9cj"`, - expectedContentLength: strconv.Itoa(len(testFiles[webrootFile1HTML])), - }, - // Test 3 - access folder with index file with trailing slash - { - url: "https://foo/dirwithindex/", - expectedStatus: http.StatusOK, - expectedBodyContent: testFiles[webrootDirwithindexIndeHTML], - expectedEtag: `"2n9cw"`, - expectedContentLength: strconv.Itoa(len(testFiles[webrootDirwithindexIndeHTML])), - }, - // Test 4 - access folder with index file without trailing slash - { - url: "https://foo/dirwithindex", - expectedStatus: http.StatusMovedPermanently, - expectedLocation: "https://foo/dirwithindex/", - expectedBodyContent: movedPermanently, - }, - // Test 5 - access folder without index file - { - url: "https://foo/dir/", - expectedStatus: http.StatusNotFound, - }, - // Test 6 - access folder without trailing slash - { - url: "https://foo/dir", - expectedStatus: http.StatusMovedPermanently, - expectedLocation: "https://foo/dir/", - expectedBodyContent: movedPermanently, - }, - // Test 7 - access file with trailing slash - { - url: "https://foo/file1.html/", - expectedStatus: http.StatusMovedPermanently, - expectedLocation: "https://foo/file1.html", - expectedBodyContent: movedPermanently, - }, - // Test 8 - access not existing path - { - url: "https://foo/not_existing", - expectedStatus: http.StatusNotFound, - }, - // Test 9 - access a file, marked as hidden - { - url: "https://foo/dir/hidden.html", - expectedStatus: http.StatusNotFound, - }, - // Test 10 - access an index file directly - { - url: "https://foo/dirwithindex/index.html", - expectedStatus: http.StatusMovedPermanently, - expectedLocation: "https://foo/dirwithindex/", - }, - // Test 11 - access an index file with a trailing slash - { - url: "https://foo/dirwithindex/index.html/", - expectedStatus: http.StatusMovedPermanently, - expectedLocation: "https://foo/dirwithindex/", - }, - // Test 12 - send a request with query params - { - url: "https://foo/dir?param1=val", - expectedStatus: http.StatusMovedPermanently, - expectedLocation: "https://foo/dir/?param1=val", - expectedBodyContent: movedPermanently, - }, - // Test 13 - attempt to bypass hidden file - { - url: "https://foo/dir/hidden.html%20", - expectedStatus: http.StatusNotFound, - }, - // Test 14 - attempt to bypass hidden file - { - url: "https://foo/dir/hidden.html.", - expectedStatus: http.StatusNotFound, - }, - // Test 15 - attempt to bypass hidden file - { - url: "https://foo/dir/hidden.html.%20", - expectedStatus: http.StatusNotFound, - }, - // Test 16 - attempt to bypass hidden file - { - url: "https://foo/dir/hidden.html%20.", - acceptEncoding: "br, gzip", - expectedStatus: http.StatusNotFound, - }, - // Test 17 - serve another file with same name as hidden file. - { - url: "https://foo/hidden.html", - expectedStatus: http.StatusNotFound, - }, - // Test 18 - try to get below the root directory. - { - url: "https://foo/../unreachable.html", - expectedStatus: http.StatusNotFound, - }, - // Test 19 - try to get below the root directory (encoded slashes). - { - url: "https://foo/..%2funreachable.html", - expectedStatus: http.StatusNotFound, - }, - // Test 20 - try to get pre-gzipped file. - { - url: "https://foo/sub/gzipped.html", - acceptEncoding: "gzip", - expectedStatus: http.StatusOK, - expectedBodyContent: testFiles[webrootSubGzippedHTMLGz], - expectedEtag: `"2n9ch"`, - expectedVary: "Accept-Encoding", - expectedEncoding: "gzip", - expectedContentLength: strconv.Itoa(len(testFiles[webrootSubGzippedHTMLGz])), - }, - // Test 21 - try to get pre-brotli encoded file. - { - url: "https://foo/sub/brotli.html", - acceptEncoding: "br,gzip", - expectedStatus: http.StatusOK, - expectedBodyContent: testFiles[webrootSubBrotliHTMLBr], - expectedEtag: `"2n9cg"`, - expectedVary: "Accept-Encoding", - expectedEncoding: "br", - expectedContentLength: strconv.Itoa(len(testFiles[webrootSubBrotliHTMLBr])), - }, - // Test 22 - not allowed to get pre-brotli encoded file. - { - url: "https://foo/sub/brotli.html", - acceptEncoding: "nicebrew", // contains "br" substring but not "br" - expectedStatus: http.StatusOK, - expectedBodyContent: testFiles[webrootSubBrotliHTML], - expectedEtag: `"2n9cd"`, - expectedVary: "", - expectedEncoding: "", - expectedContentLength: strconv.Itoa(len(testFiles[webrootSubBrotliHTML])), - }, - // Test 23 - treat existing file as a directory. - { - url: "https://foo/file1.html/other", - expectedStatus: http.StatusNotFound, - }, - // Test 24 - access folder with index file without trailing slash, with stripped path - { - url: "https://foo/bar/dirwithindex", - stripPathPrefix: "/bar/", - expectedStatus: http.StatusMovedPermanently, - expectedLocation: "https://foo/bar/dirwithindex/", - expectedBodyContent: movedPermanently, - }, - // Test 25 - access folder with index file without trailing slash, with stripped path and query params - { - url: "https://foo/bar/dirwithindex?param1=val", - stripPathPrefix: "/bar/", - expectedStatus: http.StatusMovedPermanently, - expectedLocation: "https://foo/bar/dirwithindex/?param1=val", - expectedBodyContent: movedPermanently, - }, - // Test 26 - site defined with path ("bar"), which has that prefix stripped - { - url: "https://foo/bar/file1.html/", - stripPathPrefix: "/bar/", - expectedStatus: http.StatusMovedPermanently, - expectedLocation: "https://foo/bar/file1.html", - expectedBodyContent: movedPermanently, - }, - { - url: "https://foo/notindex.html", - expectedStatus: http.StatusOK, - expectedBodyContent: testFiles[webrootNotIndexHTML], - expectedEtag: `"2n9cm"`, - expectedContentLength: strconv.Itoa(len(testFiles[webrootNotIndexHTML])), - }, - } - - for i, test := range tests { - // set up response writer and rewuest - responseRecorder := httptest.NewRecorder() - request, err := http.NewRequest("GET", test.url, nil) - if err != nil { - t.Errorf("Test %d: Error making request: %v", i, err) - continue - } - - // set the original URL and path prefix on the context - ctx := context.WithValue(request.Context(), caddy.CtxKey("original_url"), *request.URL) - request = request.WithContext(ctx) - ctx = context.WithValue(request.Context(), caddy.CtxKey("path_prefix"), test.stripPathPrefix) - request = request.WithContext(ctx) - - request.Header.Add("Accept-Encoding", test.acceptEncoding) - - // simulate cases where a site is defined with a path prefix (e.g. "localhost/foo/") - if test.stripPathPrefix != "" { - request.URL.Path = strings.TrimPrefix(request.URL.Path, test.stripPathPrefix) - } - - // perform the test - status, err := fileserver.ServeHTTP(responseRecorder, request) - etag := responseRecorder.Header().Get("Etag") - body := responseRecorder.Body.String() - vary := responseRecorder.Header().Get("Vary") - encoding := responseRecorder.Header().Get("Content-Encoding") - length := responseRecorder.Header().Get("Content-Length") - - // check if error matches expectations - if err != nil { - t.Errorf("Test %d: Serving file at %s failed. Error was: %v", i, test.url, err) - } - - // check status code - if test.expectedStatus != status { - t.Errorf("Test %d: Expected status %d, found %d", i, test.expectedStatus, status) - } - - // check etag - if test.expectedEtag != etag { - t.Errorf("Test %d: Expected Etag header %s, found %s", i, test.expectedEtag, etag) - } - - // check vary - if test.expectedVary != vary { - t.Errorf("Test %d: Expected Vary header %s, found %s", i, test.expectedVary, vary) - } - - // check content-encoding - if test.expectedEncoding != encoding { - t.Errorf("Test %d: Expected Content-Encoding header %s, found %s", i, test.expectedEncoding, encoding) - } - - // check body content - if !strings.Contains(body, test.expectedBodyContent) { - t.Errorf("Test %d: Expected body to contain %q, found %q", i, test.expectedBodyContent, body) - } - - // check Location header - if test.expectedLocation != "" { - l := responseRecorder.Header().Get("Location") - if test.expectedLocation != l { - t.Errorf("Test %d: Expected Location header %q, found %q", i, test.expectedLocation, l) - } - } - - // check content length - if test.expectedContentLength != length { - t.Errorf("Test %d: Expected Content-Length header %s, found %s", i, test.expectedContentLength, length) - } - } - -} - -// beforeServeHTTPTest creates a test directory with the structure, defined in the variable testFiles -func beforeServeHTTPTest(t *testing.T) string { - tmpdir, err := ioutil.TempDir("", testDirPrefix) - if err != nil { - t.Fatalf("failed to create test directory: %v", err) - } - - fixedTime := time.Unix(123456, 0) - - for relFile, fileContent := range testFiles { - absFile := filepath.Join(tmpdir, relFile) - - // make sure the parent directories exist - parentDir := filepath.Dir(absFile) - _, err = os.Stat(parentDir) - if err != nil { - os.MkdirAll(parentDir, os.ModePerm) - } - - // now create the test files - f, err := os.Create(absFile) - if err != nil { - t.Fatalf("Failed to create test file %s. Error was: %v", absFile, err) - } - - // and fill them with content - _, err = f.WriteString(fileContent) - if err != nil { - t.Fatalf("Failed to write to %s. Error was: %v", absFile, err) - } - f.Close() - - // and set the last modified time - err = os.Chtimes(absFile, fixedTime, fixedTime) - if err != nil { - t.Fatalf("Failed to set file time to %s. Error was: %v", fixedTime, err) - } - } - - return tmpdir -} - -// afterServeHTTPTest removes the test dir and all its content -func afterServeHTTPTest(t *testing.T, webroot string) { - if !strings.Contains(webroot, testDirPrefix) { - t.Fatalf("Cannot clean up after test because webroot is: %s", webroot) - } - // cleans up everything under the test dir. No need to clean the individual files. - err := os.RemoveAll(webroot) - if err != nil { - t.Fatalf("Failed to clean up test dir %s. Error was: %v", webroot, err) - } -} - -// failingFS implements the http.FileSystem interface. The Open method always returns the error, assigned to err -type failingFS struct { - err error // the error to return when Open is called - fileImpl http.File // inject the file implementation -} - -// Open returns the assigned failingFile and error -func (f failingFS) Open(path string) (http.File, error) { - return f.fileImpl, f.err -} - -// failingFile implements http.File but returns a predefined error on every Stat() method call. -type failingFile struct { - http.File - err error -} - -// Stat returns nil FileInfo and the provided error on every call -func (ff failingFile) Stat() (os.FileInfo, error) { - return nil, ff.err -} - -// Close is noop and returns no error -func (ff failingFile) Close() error { - return nil -} - -// TestServeHTTPFailingFS tests error cases where the Open -// function fails with various errors. -func TestServeHTTPFailingFS(t *testing.T) { - tests := []struct { - fsErr error - expectedStatus int - expectedErr error - expectedHeaders map[string]string - }{ - { - fsErr: os.ErrNotExist, - expectedStatus: http.StatusNotFound, - expectedErr: nil, - }, - { - fsErr: os.ErrPermission, - expectedStatus: http.StatusForbidden, - expectedErr: os.ErrPermission, - }, - { - fsErr: errCustom, - expectedStatus: http.StatusServiceUnavailable, - expectedErr: errCustom, - expectedHeaders: map[string]string{"Retry-After": "5"}, - }, - } - - for i, test := range tests { - // initialize a file server with the failing FileSystem - fileserver := FileServer{Root: failingFS{err: test.fsErr}} - - // prepare the request and response - request, err := http.NewRequest("GET", "https://foo/", nil) - if err != nil { - t.Fatalf("Failed to build request. Error was: %v", err) - } - responseRecorder := httptest.NewRecorder() - - status, actualErr := fileserver.ServeHTTP(responseRecorder, request) - - // check the status - if status != test.expectedStatus { - t.Errorf("Test %d: Expected status %d, found %d", i, test.expectedStatus, status) - } - - // check the error - if actualErr != test.expectedErr { - t.Errorf("Test %d: Expected err %v, found %v", i, test.expectedErr, actualErr) - } - - // check the headers - a special case for server under load - if test.expectedHeaders != nil && len(test.expectedHeaders) > 0 { - for expectedKey, expectedVal := range test.expectedHeaders { - actualVal := responseRecorder.Header().Get(expectedKey) - if expectedVal != actualVal { - t.Errorf("Test %d: Expected header %s: %s, found %s", i, expectedKey, expectedVal, actualVal) - } - } - } - } -} - -// TestServeHTTPFailingStat tests error cases where the initial Open function succeeds, -// but the Stat method on the opened file fails. -func TestServeHTTPFailingStat(t *testing.T) { - tests := []struct { - statErr error - expectedStatus int - expectedErr error - }{ - { - statErr: os.ErrNotExist, - expectedStatus: http.StatusNotFound, - expectedErr: nil, - }, - { - statErr: os.ErrPermission, - expectedStatus: http.StatusForbidden, - expectedErr: os.ErrPermission, - }, - { - statErr: errCustom, - expectedStatus: http.StatusInternalServerError, - expectedErr: errCustom, - }, - } - - for i, test := range tests { - // initialize a file server. The FileSystem will not fail, but calls to the Stat method of the returned File object will - fileserver := FileServer{Root: failingFS{err: nil, fileImpl: failingFile{err: test.statErr}}} - - // prepare the request and response - request, err := http.NewRequest("GET", "https://foo/", nil) - if err != nil { - t.Fatalf("Failed to build request. Error was: %v", err) - } - responseRecorder := httptest.NewRecorder() - - status, actualErr := fileserver.ServeHTTP(responseRecorder, request) - - // check the status - if status != test.expectedStatus { - t.Errorf("Test %d: Expected status %d, found %d", i, test.expectedStatus, status) - } - - // check the error - if actualErr != test.expectedErr { - t.Errorf("Test %d: Expected err %v, found %v", i, test.expectedErr, actualErr) - } - } -} - -// Paths for the fake site used temporarily during testing. -var ( - webrootFile1HTML = filepath.Join(webrootName, "file1.html") - webrootNotIndexHTML = filepath.Join(webrootName, "notindex.html") - webrootDirFile2HTML = filepath.Join(webrootName, "dir", "file2.html") - webrootDirHiddenHTML = filepath.Join(webrootName, "dir", "hidden.html") - webrootDirwithindexIndeHTML = filepath.Join(webrootName, "dirwithindex", "index.html") - webrootSubGzippedHTML = filepath.Join(webrootName, "sub", "gzipped.html") - webrootSubGzippedHTMLGz = filepath.Join(webrootName, "sub", "gzipped.html.gz") - webrootSubGzippedHTMLBr = filepath.Join(webrootName, "sub", "gzipped.html.br") - webrootSubBrotliHTML = filepath.Join(webrootName, "sub", "brotli.html") - webrootSubBrotliHTMLGz = filepath.Join(webrootName, "sub", "brotli.html.gz") - webrootSubBrotliHTMLBr = filepath.Join(webrootName, "sub", "brotli.html.br") - webrootSubBarDirWithIndexIndexHTML = filepath.Join(webrootName, "bar", "dirwithindex", "index.html") -) - -// testFiles is a map with relative paths to test files as keys and file content as values. -// The map represents the following structure: -// - $TEMP/caddy_testdir/ -// '-- unreachable.html -// '-- webroot/ -// '---- file1.html -// '---- dirwithindex/ -// '------ index.html -// '---- dir/ -// '------ file2.html -// '------ hidden.html -var testFiles = map[string]string{ - "unreachable.html": "

    must not leak

    ", - webrootFile1HTML: "

    file1.html

    ", - webrootNotIndexHTML: "

    notindex.html

    ", - webrootDirFile2HTML: "

    dir/file2.html

    ", - webrootDirwithindexIndeHTML: "

    dirwithindex/index.html

    ", - webrootDirHiddenHTML: "

    dir/hidden.html

    ", - webrootSubGzippedHTML: "

    gzipped.html

    ", - webrootSubGzippedHTMLGz: "1.gzipped.html.gz", - webrootSubGzippedHTMLBr: "2.gzipped.html.br", - webrootSubBrotliHTML: "3.brotli.html", - webrootSubBrotliHTMLGz: "4.brotli.html.gz", - webrootSubBrotliHTMLBr: "5.brotli.html.br", - webrootSubBarDirWithIndexIndexHTML: "

    bar/dirwithindex/index.html

    ", -} - -var errCustom = errors.New("custom error") - -const ( - testDirPrefix = "caddy_fileserver_test" - webrootName = "webroot" // name of the folder inside the tmp dir that has the site -) - -//------------------------------------------------------------------------------------------------- - -type fileInfo struct { - name string - size int64 - mode os.FileMode - modTime time.Time - isDir bool -} - -func (fi fileInfo) Name() string { - return fi.name -} - -func (fi fileInfo) Size() int64 { - return fi.size -} - -func (fi fileInfo) Mode() os.FileMode { - return fi.mode -} - -func (fi fileInfo) ModTime() time.Time { - return fi.modTime -} - -func (fi fileInfo) IsDir() bool { - return fi.isDir -} - -func (fi fileInfo) Sys() interface{} { - return nil -} - -var _ os.FileInfo = fileInfo{} - -func BenchmarkEtag(b *testing.B) { - d := fileInfo{ - size: 1234567890, - modTime: time.Now(), - } - - b.ResetTimer() - for i := 0; i < b.N; i++ { - calculateEtag(d) - } -} diff --git a/caddyhttp/status/setup.go b/caddyhttp/status/setup.go deleted file mode 100644 index 805ba601b16..00000000000 --- a/caddyhttp/status/setup.go +++ /dev/null @@ -1,93 +0,0 @@ -package status - -import ( - "strconv" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -// init registers Status plugin -func init() { - caddy.RegisterPlugin("status", caddy.Plugin{ - ServerType: "http", - Action: setup, - }) -} - -// setup configures new Status middleware instance. -func setup(c *caddy.Controller) error { - rules, err := statusParse(c) - if err != nil { - return err - } - - cfg := httpserver.GetConfig(c) - mid := func(next httpserver.Handler) httpserver.Handler { - return Status{Rules: rules, Next: next} - } - cfg.AddMiddleware(mid) - - return nil -} - -// statusParse parses status directive -func statusParse(c *caddy.Controller) ([]httpserver.HandlerConfig, error) { - var rules []httpserver.HandlerConfig - - for c.Next() { - hadBlock := false - args := c.RemainingArgs() - - switch len(args) { - case 1: - status, err := strconv.Atoi(args[0]) - if err != nil { - return rules, c.Errf("Expecting a numeric status code, got '%s'", args[0]) - } - - for c.NextBlock() { - hadBlock = true - basePath := c.Val() - - for _, cfg := range rules { - rule := cfg.(*Rule) - if rule.Base == basePath { - return rules, c.Errf("Duplicate path: '%s'", basePath) - } - } - - rule := NewRule(basePath, status) - rules = append(rules, rule) - - if c.NextArg() { - return rules, c.ArgErr() - } - } - - if !hadBlock { - return rules, c.ArgErr() - } - case 2: - status, err := strconv.Atoi(args[0]) - if err != nil { - return rules, c.Errf("Expecting a numeric status code, got '%s'", args[0]) - } - - basePath := args[1] - for _, cfg := range rules { - rule := cfg.(*Rule) - if rule.Base == basePath { - return rules, c.Errf("Duplicate path: '%s'", basePath) - } - } - - rule := NewRule(basePath, status) - rules = append(rules, rule) - default: - return rules, c.ArgErr() - } - } - - return rules, nil -} diff --git a/caddyhttp/status/setup_test.go b/caddyhttp/status/setup_test.go deleted file mode 100644 index 493d4b906b4..00000000000 --- a/caddyhttp/status/setup_test.go +++ /dev/null @@ -1,131 +0,0 @@ -package status - -import ( - "testing" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestSetup(t *testing.T) { - c := caddy.NewTestController("http", `status 404 /foo`) - 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, had 0 instead") - } - - handler := mids[0](httpserver.EmptyNext) - myHandler, ok := handler.(Status) - if !ok { - t.Fatalf("Expected handler to be type Status, got: %#v", - handler) - } - - if !httpserver.SameNext(myHandler.Next, httpserver.EmptyNext) { - t.Error("'Next' field of handler was not set properly") - } - - if len(myHandler.Rules) != 1 { - t.Errorf("Expected handler to have %d rule, has %d instead", 1, len(myHandler.Rules)) - } -} - -func TestStatusParse(t *testing.T) { - tests := []struct { - input string - shouldErr bool - expected []*Rule - }{ - {`status`, true, []*Rule{}}, - {`status /foo`, true, []*Rule{}}, - {`status bar /foo`, true, []*Rule{}}, - {`status 404 /foo bar`, true, []*Rule{}}, - {`status 404 /foo`, false, []*Rule{ - {Base: "/foo", StatusCode: 404}, - }, - }, - {`status { - }`, - true, - []*Rule{}, - }, - {`status 404 { - }`, - true, - []*Rule{}, - }, - {`status 404 { - /foo - /foo - }`, - true, - []*Rule{}, - }, - {`status 404 { - 404 /foo - }`, - true, - []*Rule{}, - }, - {`status 404 { - /foo - /bar - }`, - false, - []*Rule{ - {Base: "/foo", StatusCode: 404}, - {Base: "/bar", StatusCode: 404}, - }, - }, - } - - for i, test := range tests { - actual, err := statusParse(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) - } else if err != nil && test.shouldErr { - continue - } - - if len(actual) != len(test.expected) { - t.Fatalf("Test %d expected %d rules, but got %d", - i, len(test.expected), len(actual)) - } - - findRule := func(basePath string) (bool, *Rule) { - for _, cfg := range actual { - actualRule := cfg.(*Rule) - - if actualRule.Base == basePath { - return true, actualRule - } - } - - return false, nil - } - - for _, expectedRule := range test.expected { - found, actualRule := findRule(expectedRule.Base) - - if !found { - t.Errorf("Test %d: Missing rule for path '%s'", - i, expectedRule.Base) - } - - if actualRule.StatusCode != expectedRule.StatusCode { - t.Errorf("Test %d: Expected status code %d for path '%s'. Got %d", - i, expectedRule.StatusCode, expectedRule.Base, actualRule.StatusCode) - } - } - } -} diff --git a/caddyhttp/status/status.go b/caddyhttp/status/status.go deleted file mode 100644 index e10a7eef44b..00000000000 --- a/caddyhttp/status/status.go +++ /dev/null @@ -1,59 +0,0 @@ -// Package status is middleware for returning status code for requests -package status - -import ( - "net/http" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -// Rule describes status rewriting rule -type Rule struct { - // Base path. Request to this path and sub-paths will be answered with StatusCode - Base string - - // Status code to return - StatusCode int - - // Request matcher - httpserver.RequestMatcher -} - -// NewRule creates new Rule. -func NewRule(basePath string, status int) *Rule { - return &Rule{ - Base: basePath, - StatusCode: status, - RequestMatcher: httpserver.PathMatcher(basePath), - } -} - -// BasePath implements httpserver.HandlerConfig interface -func (rule *Rule) BasePath() string { - return rule.Base -} - -// Status is a middleware to return status code for request -type Status struct { - Rules []httpserver.HandlerConfig - Next httpserver.Handler -} - -// ServeHTTP implements the httpserver.Handler interface -func (status Status) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - if cfg := httpserver.ConfigSelector(status.Rules).Select(r); cfg != nil { - rule := cfg.(*Rule) - - if rule.StatusCode < 400 { - // There's no ability to return response body -- - // write the response status code in header and signal - // to other handlers that response is already handled - w.WriteHeader(rule.StatusCode) - return 0, nil - } - - return rule.StatusCode, nil - } - - return status.Next.ServeHTTP(w, r) -} diff --git a/caddyhttp/status/status_test.go b/caddyhttp/status/status_test.go deleted file mode 100644 index 56048ea08e0..00000000000 --- a/caddyhttp/status/status_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package status - -import ( - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestStatus(t *testing.T) { - status := Status{ - Rules: []httpserver.HandlerConfig{ - NewRule("/foo", http.StatusNotFound), - NewRule("/teapot", http.StatusTeapot), - NewRule("/foo/bar1", http.StatusInternalServerError), - NewRule("/temporary-redirected", http.StatusTemporaryRedirect), - }, - Next: httpserver.HandlerFunc(urlPrinter), - } - - tests := []struct { - path string - statusExpected bool - status int - }{ - {"/foo", true, http.StatusNotFound}, - {"/teapot", true, http.StatusTeapot}, - {"/foo/bar", true, http.StatusNotFound}, - {"/foo/bar1", true, http.StatusInternalServerError}, - {"/someotherpath", false, 0}, - {"/temporary-redirected", false, http.StatusTemporaryRedirect}, - } - - for i, test := range tests { - req, err := http.NewRequest("GET", test.path, nil) - if err != nil { - t.Fatalf("Test %d: Could not create HTTP request: %v", - i, err) - } - - rec := httptest.NewRecorder() - actualStatus, err := status.ServeHTTP(rec, req) - if err != nil { - t.Fatalf("Test %d: Serving request failed with error %v", - i, err) - } - - if test.statusExpected { - if test.status != actualStatus { - t.Errorf("Test %d: Expected status code %d, got %d", - i, test.status, actualStatus) - } - if rec.Body.String() != "" { - t.Errorf("Test %d: Expected empty body, got '%s'", - i, rec.Body.String()) - } - } else { - if test.status != 0 { // Expecting status in response - if test.status != rec.Code { - t.Errorf("Test %d: Expected status code %d, got %d", - i, test.status, rec.Code) - } - } else if rec.Body.String() != test.path { - t.Errorf("Test %d: Expected body '%s', got '%s'", - i, test.path, rec.Body.String()) - } - } - } -} - -func urlPrinter(w http.ResponseWriter, r *http.Request) (int, error) { - fmt.Fprint(w, r.URL.String()) - return 0, nil -} diff --git a/caddyhttp/templates/setup.go b/caddyhttp/templates/setup.go deleted file mode 100644 index 4622afe9561..00000000000 --- a/caddyhttp/templates/setup.go +++ /dev/null @@ -1,108 +0,0 @@ -package templates - -import ( - "bytes" - "net/http" - "sync" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func init() { - caddy.RegisterPlugin("templates", caddy.Plugin{ - ServerType: "http", - Action: setup, - }) -} - -// setup configures a new Templates middleware instance. -func setup(c *caddy.Controller) error { - rules, err := templatesParse(c) - if err != nil { - return err - } - - cfg := httpserver.GetConfig(c) - - tmpls := Templates{ - Rules: rules, - Root: cfg.Root, - FileSys: http.Dir(cfg.Root), - BufPool: &sync.Pool{ - New: func() interface{} { - return new(bytes.Buffer) - }, - }, - } - - cfg.AddMiddleware(func(next httpserver.Handler) httpserver.Handler { - tmpls.Next = next - return tmpls - }) - - return nil -} - -func templatesParse(c *caddy.Controller) ([]Rule, error) { - var rules []Rule - - for c.Next() { - var rule Rule - - rule.Path = defaultTemplatePath - rule.Extensions = defaultTemplateExtensions - - args := c.RemainingArgs() - - switch len(args) { - case 0: - // Optional block - for c.NextBlock() { - switch c.Val() { - case "path": - args := c.RemainingArgs() - if len(args) != 1 { - return nil, c.ArgErr() - } - rule.Path = args[0] - - case "ext": - args := c.RemainingArgs() - if len(args) == 0 { - return nil, c.ArgErr() - } - rule.Extensions = args - - case "between": - args := c.RemainingArgs() - if len(args) != 2 { - return nil, c.ArgErr() - } - rule.Delims[0] = args[0] - rule.Delims[1] = args[1] - } - } - default: - // First argument would be the path - rule.Path = args[0] - - // Any remaining arguments are extensions - rule.Extensions = args[1:] - if len(rule.Extensions) == 0 { - rule.Extensions = defaultTemplateExtensions - } - } - - for _, ext := range rule.Extensions { - rule.IndexFiles = append(rule.IndexFiles, "index"+ext) - } - - rules = append(rules, rule) - } - return rules, nil -} - -const defaultTemplatePath = "/" - -var defaultTemplateExtensions = []string{".html", ".htm", ".tmpl", ".tpl", ".txt"} diff --git a/caddyhttp/templates/setup_test.go b/caddyhttp/templates/setup_test.go deleted file mode 100644 index c3287dffdac..00000000000 --- a/caddyhttp/templates/setup_test.go +++ /dev/null @@ -1,108 +0,0 @@ -package templates - -import ( - "fmt" - "testing" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestSetup(t *testing.T) { - c := caddy.NewTestController("http", `templates`) - err := setup(c) - if err != nil { - t.Errorf("Expected no errors, 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.(Templates) - - if !ok { - t.Fatalf("Expected handler to be type Templates, got: %#v", handler) - } - - if myHandler.Rules[0].Path != defaultTemplatePath { - t.Errorf("Expected / as the default Path") - } - if fmt.Sprint(myHandler.Rules[0].Extensions) != fmt.Sprint(defaultTemplateExtensions) { - t.Errorf("Expected %v to be the Default Extensions", defaultTemplateExtensions) - } - var indexFiles []string - for _, extension := range defaultTemplateExtensions { - indexFiles = append(indexFiles, "index"+extension) - } - if fmt.Sprint(myHandler.Rules[0].IndexFiles) != fmt.Sprint(indexFiles) { - t.Errorf("Expected %v to be the Default Index files", indexFiles) - } - if myHandler.Rules[0].Delims != [2]string{} { - t.Errorf("Expected %v to be the Default Delims", [2]string{}) - } -} - -func TestTemplatesParse(t *testing.T) { - tests := []struct { - inputTemplateConfig string - shouldErr bool - expectedTemplateConfig []Rule - }{ - {`templates /api1`, false, []Rule{{ - Path: "/api1", - Extensions: defaultTemplateExtensions, - Delims: [2]string{}, - }}}, - {`templates /api2 .txt .htm`, false, []Rule{{ - Path: "/api2", - Extensions: []string{".txt", ".htm"}, - Delims: [2]string{}, - }}}, - - {`templates /api3 .htm .html - templates /api4 .txt .tpl `, false, []Rule{{ - Path: "/api3", - Extensions: []string{".htm", ".html"}, - Delims: [2]string{}, - }, { - Path: "/api4", - Extensions: []string{".txt", ".tpl"}, - Delims: [2]string{}, - }}}, - {`templates { - path /api5 - ext .html - between {% %} - }`, false, []Rule{{ - Path: "/api5", - Extensions: []string{".html"}, - Delims: [2]string{"{%", "%}"}, - }}}, - } - for i, test := range tests { - c := caddy.NewTestController("http", test.inputTemplateConfig) - actualTemplateConfigs, err := templatesParse(c) - - if err == nil && test.shouldErr { - t.Errorf("Test %d didn't error, but it should have", i) - } else if err != nil && !test.shouldErr { - t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err) - } - if len(actualTemplateConfigs) != len(test.expectedTemplateConfig) { - t.Fatalf("Test %d expected %d no of Template configs, but got %d ", - i, len(test.expectedTemplateConfig), len(actualTemplateConfigs)) - } - for j, actualTemplateConfig := range actualTemplateConfigs { - if actualTemplateConfig.Path != test.expectedTemplateConfig[j].Path { - t.Errorf("Test %d expected %dth Template Config Path to be %s , but got %s", - i, j, test.expectedTemplateConfig[j].Path, actualTemplateConfig.Path) - } - if fmt.Sprint(actualTemplateConfig.Extensions) != fmt.Sprint(test.expectedTemplateConfig[j].Extensions) { - t.Errorf("Expected %v to be the Extensions , but got %v instead", test.expectedTemplateConfig[j].Extensions, actualTemplateConfig.Extensions) - } - } - } - -} diff --git a/caddyhttp/templates/templates.go b/caddyhttp/templates/templates.go deleted file mode 100644 index 0b547f4b616..00000000000 --- a/caddyhttp/templates/templates.go +++ /dev/null @@ -1,117 +0,0 @@ -// Package templates implements template execution for files to be -// dynamically rendered for the client. -package templates - -import ( - "bytes" - "mime" - "net/http" - "os" - "path" - "path/filepath" - "sync" - "text/template" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -// ServeHTTP implements the httpserver.Handler interface. -func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - for _, rule := range t.Rules { - if !httpserver.Path(r.URL.Path).Matches(rule.Path) { - continue - } - - // Check for index files - fpath := r.URL.Path - if idx, ok := httpserver.IndexFile(t.FileSys, fpath, rule.IndexFiles); ok { - fpath = idx - } - - // Check the extension - reqExt := path.Ext(fpath) - - for _, ext := range rule.Extensions { - if reqExt == ext { - // Create execution context - ctx := httpserver.NewContextWithHeader(w.Header()) - ctx.Root = t.FileSys - ctx.Req = r - ctx.URL = r.URL - - // New template - templateName := filepath.Base(fpath) - tpl := template.New(templateName) - - // Set delims - if rule.Delims != [2]string{} { - tpl.Delims(rule.Delims[0], rule.Delims[1]) - } - - // Add custom functions - tpl.Funcs(httpserver.TemplateFuncs) - - // Build the template - templatePath := filepath.Join(t.Root, fpath) - tpl, err := tpl.ParseFiles(templatePath) - if err != nil { - if os.IsNotExist(err) { - return http.StatusNotFound, nil - } else if os.IsPermission(err) { - return http.StatusForbidden, nil - } - return http.StatusInternalServerError, err - } - - // Execute it - buf := t.BufPool.Get().(*bytes.Buffer) - buf.Reset() - defer t.BufPool.Put(buf) - err = tpl.Execute(buf, ctx) - if err != nil { - return http.StatusInternalServerError, err - } - - // If Content-Type isn't set here, http.ResponseWriter.Write - // will set it according to response body. But other middleware - // such as gzip can modify response body, then Content-Type - // detected by http.ResponseWriter.Write is wrong. - ctype := mime.TypeByExtension(ext) - if ctype == "" { - ctype = http.DetectContentType(buf.Bytes()) - } - w.Header().Set("Content-Type", ctype) - - templateInfo, err := os.Stat(templatePath) - if err == nil { - // add the Last-Modified header if we were able to read the stamp - httpserver.SetLastModifiedHeader(w, templateInfo.ModTime()) - } - buf.WriteTo(w) - - return http.StatusOK, nil - } - } - } - - return t.Next.ServeHTTP(w, r) -} - -// Templates is middleware to render templated files as the HTTP response. -type Templates struct { - Next httpserver.Handler - Rules []Rule - Root string - FileSys http.FileSystem - BufPool *sync.Pool // docs: "A Pool must not be copied after first use." -} - -// Rule represents a template rule. A template will only execute -// with this rule if the request path matches the Path specified -// and requests a resource with one of the extensions specified. -type Rule struct { - Path string - Extensions []string - IndexFiles []string - Delims [2]string -} diff --git a/caddyhttp/templates/templates_test.go b/caddyhttp/templates/templates_test.go deleted file mode 100644 index c1d53a2de5e..00000000000 --- a/caddyhttp/templates/templates_test.go +++ /dev/null @@ -1,144 +0,0 @@ -package templates - -import ( - "bytes" - "net/http" - "net/http/httptest" - "sync" - "testing" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestTemplates(t *testing.T) { - tmpl := Templates{ - Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - return 0, nil - }), - Rules: []Rule{ - { - Extensions: []string{".html"}, - IndexFiles: []string{"index.html"}, - Path: "/photos", - }, - { - Extensions: []string{".html", ".htm"}, - IndexFiles: []string{"index.html", "index.htm"}, - Path: "/images", - Delims: [2]string{"{%", "%}"}, - }, - }, - Root: "./testdata", - FileSys: http.Dir("./testdata"), - BufPool: &sync.Pool{New: func() interface{} { return new(bytes.Buffer) }}, - } - - tmplroot := Templates{ - Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { - return 0, nil - }), - Rules: []Rule{ - { - Extensions: []string{".html"}, - IndexFiles: []string{"index.html"}, - Path: "/", - }, - }, - Root: "./testdata", - FileSys: http.Dir("./testdata"), - BufPool: &sync.Pool{New: func() interface{} { return new(bytes.Buffer) }}, - } - - // Test tmpl on /photos/test.html - req, err := http.NewRequest("GET", "/photos/test.html", nil) - if err != nil { - t.Fatalf("Test: Could not create HTTP request: %v", err) - } - - rec := httptest.NewRecorder() - - tmpl.ServeHTTP(rec, req) - - if rec.Code != http.StatusOK { - t.Fatalf("Test: Wrong response code: %d, should be %d", rec.Code, http.StatusOK) - } - - respBody := rec.Body.String() - expectedBody := `test page

    Header title

    - -` - - if respBody != expectedBody { - t.Fatalf("Test: the expected body %v is different from the response one: %v", expectedBody, respBody) - } - - // Test tmpl on /images/img.htm - req, err = http.NewRequest("GET", "/images/img.htm", nil) - if err != nil { - t.Fatalf("Could not create HTTP request: %v", err) - } - - rec = httptest.NewRecorder() - - tmpl.ServeHTTP(rec, req) - - if rec.Code != http.StatusOK { - t.Fatalf("Test: Wrong response code: %d, should be %d", rec.Code, http.StatusOK) - } - - respBody = rec.Body.String() - expectedBody = `img

    Header title

    - -` - - if respBody != expectedBody { - t.Fatalf("Test: the expected body %v is different from the response one: %v", expectedBody, respBody) - } - - // Test tmpl on /images/img2.htm - req, err = http.NewRequest("GET", "/images/img2.htm", nil) - if err != nil { - t.Fatalf("Could not create HTTP request: %v", err) - } - - rec = httptest.NewRecorder() - - tmpl.ServeHTTP(rec, req) - - if rec.Code != http.StatusOK { - t.Fatalf("Test: Wrong response code: %d, should be %d", rec.Code, http.StatusOK) - } - - respBody = rec.Body.String() - expectedBody = `img{{.Include "header.html"}} -` - - if respBody != expectedBody { - t.Fatalf("Test: the expected body %v is different from the response one: %v", expectedBody, respBody) - } - - // Test tmplroot on /root.html - req, err = http.NewRequest("GET", "/root.html", nil) - if err != nil { - t.Fatalf("Could not create HTTP request: %v", err) - } - - rec = httptest.NewRecorder() - - // register custom function which is used in template - httpserver.TemplateFuncs["root"] = func() string { return "root" } - tmplroot.ServeHTTP(rec, req) - - if rec.Code != http.StatusOK { - t.Fatalf("Test: Wrong response code: %d, should be %d", rec.Code, http.StatusOK) - } - - respBody = rec.Body.String() - expectedBody = `root

    Header title

    - -` - - if respBody != expectedBody { - t.Fatalf("Test: the expected body %v is different from the response one: %v", expectedBody, respBody) - } -} diff --git a/caddyhttp/templates/testdata/header.html b/caddyhttp/templates/testdata/header.html deleted file mode 100644 index 9c96e0e3717..00000000000 --- a/caddyhttp/templates/testdata/header.html +++ /dev/null @@ -1 +0,0 @@ -

    Header title

    diff --git a/caddyhttp/templates/testdata/images/header.html b/caddyhttp/templates/testdata/images/header.html deleted file mode 100644 index 9c96e0e3717..00000000000 --- a/caddyhttp/templates/testdata/images/header.html +++ /dev/null @@ -1 +0,0 @@ -

    Header title

    diff --git a/caddyhttp/templates/testdata/images/img.htm b/caddyhttp/templates/testdata/images/img.htm deleted file mode 100644 index c9060204458..00000000000 --- a/caddyhttp/templates/testdata/images/img.htm +++ /dev/null @@ -1 +0,0 @@ -img{%.Include "header.html"%} diff --git a/caddyhttp/templates/testdata/images/img2.htm b/caddyhttp/templates/testdata/images/img2.htm deleted file mode 100644 index 865a73809df..00000000000 --- a/caddyhttp/templates/testdata/images/img2.htm +++ /dev/null @@ -1 +0,0 @@ -img{{.Include "header.html"}} diff --git a/caddyhttp/templates/testdata/photos/test.html b/caddyhttp/templates/testdata/photos/test.html deleted file mode 100644 index e2e95e13303..00000000000 --- a/caddyhttp/templates/testdata/photos/test.html +++ /dev/null @@ -1 +0,0 @@ -test page{{.Include "../header.html"}} diff --git a/caddyhttp/templates/testdata/root.html b/caddyhttp/templates/testdata/root.html deleted file mode 100644 index ccc1f6349c5..00000000000 --- a/caddyhttp/templates/testdata/root.html +++ /dev/null @@ -1 +0,0 @@ -{{ root }}{{.Include "header.html"}} diff --git a/caddyhttp/timeouts/timeouts.go b/caddyhttp/timeouts/timeouts.go deleted file mode 100644 index 2f1630388cf..00000000000 --- a/caddyhttp/timeouts/timeouts.go +++ /dev/null @@ -1,106 +0,0 @@ -package timeouts - -import ( - "time" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func init() { - caddy.RegisterPlugin("timeouts", caddy.Plugin{ - ServerType: "http", - Action: setupTimeouts, - }) -} - -func setupTimeouts(c *caddy.Controller) error { - config := httpserver.GetConfig(c) - - for c.Next() { - var hasOptionalBlock bool - for c.NextBlock() { - hasOptionalBlock = true - - // ensure the kind of timeout is recognized - kind := c.Val() - if kind != "read" && kind != "header" && kind != "write" && kind != "idle" { - return c.Errf("unknown timeout '%s': must be read, header, write, or idle", kind) - } - - // parse the timeout duration - if !c.NextArg() { - return c.ArgErr() - } - if c.NextArg() { - // only one value permitted - return c.ArgErr() - } - var dur time.Duration - if c.Val() != "none" { - var err error - dur, err = time.ParseDuration(c.Val()) - if err != nil { - return c.Errf("%v", err) - } - if dur < 0 { - return c.Err("non-negative duration required for timeout value") - } - } - - // set this timeout's duration - switch kind { - case "read": - config.Timeouts.ReadTimeout = dur - config.Timeouts.ReadTimeoutSet = true - case "header": - config.Timeouts.ReadHeaderTimeout = dur - config.Timeouts.ReadHeaderTimeoutSet = true - case "write": - config.Timeouts.WriteTimeout = dur - config.Timeouts.WriteTimeoutSet = true - case "idle": - config.Timeouts.IdleTimeout = dur - config.Timeouts.IdleTimeoutSet = true - } - } - if !hasOptionalBlock { - // set all timeouts to the same value - - if !c.NextArg() { - return c.ArgErr() - } - if c.NextArg() { - // only one value permitted - return c.ArgErr() - } - val := c.Val() - - config.Timeouts.ReadTimeoutSet = true - config.Timeouts.ReadHeaderTimeoutSet = true - config.Timeouts.WriteTimeoutSet = true - config.Timeouts.IdleTimeoutSet = true - - if val == "none" { - config.Timeouts.ReadTimeout = 0 - config.Timeouts.ReadHeaderTimeout = 0 - config.Timeouts.WriteTimeout = 0 - config.Timeouts.IdleTimeout = 0 - } else { - dur, err := time.ParseDuration(val) - if err != nil { - return c.Errf("unknown timeout duration: %v", err) - } - if dur < 0 { - return c.Err("non-negative duration required for timeout value") - } - config.Timeouts.ReadTimeout = dur - config.Timeouts.ReadHeaderTimeout = dur - config.Timeouts.WriteTimeout = dur - config.Timeouts.IdleTimeout = dur - } - } - } - - return nil -} diff --git a/caddyhttp/timeouts/timeouts_test.go b/caddyhttp/timeouts/timeouts_test.go deleted file mode 100644 index 239f4a68c81..00000000000 --- a/caddyhttp/timeouts/timeouts_test.go +++ /dev/null @@ -1,147 +0,0 @@ -package timeouts - -import ( - "testing" - "time" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestSetupTimeouts(t *testing.T) { - testCases := []struct { - input string - shouldErr bool - }{ - {input: "timeouts none", shouldErr: false}, - {input: "timeouts 5s", shouldErr: false}, - {input: "timeouts 0", shouldErr: false}, - {input: "timeouts { \n read 15s \n }", shouldErr: false}, - {input: "timeouts { \n read 15s \n idle 10s \n }", shouldErr: false}, - {input: "timeouts", shouldErr: true}, - {input: "timeouts 5s 10s", shouldErr: true}, - {input: "timeouts 12", shouldErr: true}, - {input: "timeouts -2s", shouldErr: true}, - {input: "timeouts { \n foo 1s \n }", shouldErr: true}, - {input: "timeouts { \n read \n }", shouldErr: true}, - {input: "timeouts { \n read 1s 2s \n }", shouldErr: true}, - {input: "timeouts { \n foo \n }", shouldErr: true}, - } - for i, tc := range testCases { - controller := caddy.NewTestController("", tc.input) - err := setupTimeouts(controller) - if tc.shouldErr && err == nil { - t.Errorf("Test %d: Expected an error, but did not have one", i) - } - if !tc.shouldErr && err != nil { - t.Errorf("Test %d: Did not expect error, but got: %v", i, err) - } - } -} - -func TestTimeoutsSetProperly(t *testing.T) { - testCases := []struct { - input string - expected httpserver.Timeouts - }{ - { - input: "timeouts none", - expected: httpserver.Timeouts{ - ReadTimeout: 0, ReadTimeoutSet: true, - ReadHeaderTimeout: 0, ReadHeaderTimeoutSet: true, - WriteTimeout: 0, WriteTimeoutSet: true, - IdleTimeout: 0, IdleTimeoutSet: true, - }, - }, - { - input: "timeouts {\n read 15s \n}", - expected: httpserver.Timeouts{ - ReadTimeout: 15 * time.Second, ReadTimeoutSet: true, - }, - }, - { - input: "timeouts {\n header 15s \n}", - expected: httpserver.Timeouts{ - ReadHeaderTimeout: 15 * time.Second, ReadHeaderTimeoutSet: true, - }, - }, - { - input: "timeouts {\n write 15s \n}", - expected: httpserver.Timeouts{ - WriteTimeout: 15 * time.Second, WriteTimeoutSet: true, - }, - }, - { - input: "timeouts {\n idle 15s \n}", - expected: httpserver.Timeouts{ - IdleTimeout: 15 * time.Second, IdleTimeoutSet: true, - }, - }, - { - input: "timeouts {\n idle 15s \n read 1m \n }", - expected: httpserver.Timeouts{ - IdleTimeout: 15 * time.Second, IdleTimeoutSet: true, - ReadTimeout: 1 * time.Minute, ReadTimeoutSet: true, - }, - }, - { - input: "timeouts {\n read none \n }", - expected: httpserver.Timeouts{ - ReadTimeout: 0, ReadTimeoutSet: true, - }, - }, - { - input: "timeouts {\n write 0 \n }", - expected: httpserver.Timeouts{ - WriteTimeout: 0, WriteTimeoutSet: true, - }, - }, - { - input: "timeouts {\n write 1s \n write 2s \n }", - expected: httpserver.Timeouts{ - WriteTimeout: 2 * time.Second, WriteTimeoutSet: true, - }, - }, - { - input: "timeouts 1s\ntimeouts 2s", - expected: httpserver.Timeouts{ - ReadTimeout: 2 * time.Second, ReadTimeoutSet: true, - ReadHeaderTimeout: 2 * time.Second, ReadHeaderTimeoutSet: true, - WriteTimeout: 2 * time.Second, WriteTimeoutSet: true, - IdleTimeout: 2 * time.Second, IdleTimeoutSet: true, - }, - }, - } - for i, tc := range testCases { - controller := caddy.NewTestController("", tc.input) - err := setupTimeouts(controller) - if err != nil { - t.Fatalf("Test %d: Did not expect error, but got: %v", i, err) - } - cfg := httpserver.GetConfig(controller) - if got, want := cfg.Timeouts.ReadTimeout, tc.expected.ReadTimeout; got != want { - t.Errorf("Test %d: Expected ReadTimeout=%v, got %v", i, want, got) - } - if got, want := cfg.Timeouts.ReadTimeoutSet, tc.expected.ReadTimeoutSet; got != want { - t.Errorf("Test %d: Expected ReadTimeoutSet=%v, got %v", i, want, got) - } - if got, want := cfg.Timeouts.ReadHeaderTimeout, tc.expected.ReadHeaderTimeout; got != want { - t.Errorf("Test %d: Expected ReadHeaderTimeout=%v, got %v", i, want, got) - } - if got, want := cfg.Timeouts.ReadHeaderTimeoutSet, tc.expected.ReadHeaderTimeoutSet; got != want { - t.Errorf("Test %d: Expected ReadHeaderTimeoutSet=%v, got %v", i, want, got) - } - if got, want := cfg.Timeouts.WriteTimeout, tc.expected.WriteTimeout; got != want { - t.Errorf("Test %d: Expected WriteTimeout=%v, got %v", i, want, got) - } - if got, want := cfg.Timeouts.WriteTimeoutSet, tc.expected.WriteTimeoutSet; got != want { - t.Errorf("Test %d: Expected WriteTimeoutSet=%v, got %v", i, want, got) - } - if got, want := cfg.Timeouts.IdleTimeout, tc.expected.IdleTimeout; got != want { - t.Errorf("Test %d: Expected IdleTimeout=%v, got %v", i, want, got) - } - if got, want := cfg.Timeouts.IdleTimeoutSet, tc.expected.IdleTimeoutSet; got != want { - t.Errorf("Test %d: Expected IdleTimeoutSet=%v, got %v", i, want, got) - } - } -} diff --git a/caddyhttp/websocket/setup.go b/caddyhttp/websocket/setup.go deleted file mode 100644 index 12a0e541af0..00000000000 --- a/caddyhttp/websocket/setup.go +++ /dev/null @@ -1,96 +0,0 @@ -package websocket - -import ( - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func init() { - caddy.RegisterPlugin("websocket", caddy.Plugin{ - ServerType: "http", - Action: setup, - }) -} - -// setup configures a new WebSocket middleware instance. -func setup(c *caddy.Controller) error { - websocks, err := webSocketParse(c) - if err != nil { - return err - } - - GatewayInterface = caddy.AppName + "-CGI/1.1" - ServerSoftware = caddy.AppName + "/" + caddy.AppVersion - - httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler { - return WebSocket{Next: next, Sockets: websocks} - }) - - return nil -} - -func webSocketParse(c *caddy.Controller) ([]Config, error) { - var websocks []Config - var respawn bool - - optionalBlock := func() (hadBlock bool, err error) { - for c.NextBlock() { - hadBlock = true - if c.Val() == "respawn" { - respawn = true - } else { - return true, c.Err("Expected websocket configuration parameter in block") - } - } - return - } - - for c.Next() { - var val, path, command string - - // Path or command; not sure which yet - if !c.NextArg() { - return nil, c.ArgErr() - } - val = c.Val() - - // Extra configuration may be in a block - hadBlock, err := optionalBlock() - if err != nil { - return nil, err - } - - if !hadBlock { - // The next argument on this line will be the command or an open curly brace - if c.NextArg() { - path = val - command = c.Val() - } else { - path = "/" - command = val - } - - // Okay, check again for optional block - _, err = optionalBlock() - if err != nil { - return nil, err - } - } - - // Split command into the actual command and its arguments - cmd, args, err := caddy.SplitCommandAndArgs(command) - if err != nil { - return nil, err - } - - websocks = append(websocks, Config{ - Path: path, - Command: cmd, - Arguments: args, - Respawn: respawn, // TODO: This isn't used currently - }) - } - - return websocks, nil - -} diff --git a/caddyhttp/websocket/setup_test.go b/caddyhttp/websocket/setup_test.go deleted file mode 100644 index e81f67d99b2..00000000000 --- a/caddyhttp/websocket/setup_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package websocket - -import ( - "testing" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -func TestWebSocket(t *testing.T) { - c := caddy.NewTestController("http", `websocket cat`) - err := setup(c) - if err != nil { - t.Errorf("Expected no errors, 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.(WebSocket) - - if !ok { - t.Fatalf("Expected handler to be type WebSocket, got: %#v", handler) - } - - if myHandler.Sockets[0].Path != "/" { - t.Errorf("Expected / as the default Path") - } - if myHandler.Sockets[0].Command != "cat" { - t.Errorf("Expected %s as the command", "cat") - } - -} -func TestWebSocketParse(t *testing.T) { - tests := []struct { - inputWebSocketConfig string - shouldErr bool - expectedWebSocketConfig []Config - }{ - {`websocket /api1 cat`, false, []Config{{ - Path: "/api1", - Command: "cat", - }}}, - - {`websocket /api3 cat - websocket /api4 cat `, false, []Config{{ - Path: "/api3", - Command: "cat", - }, { - Path: "/api4", - Command: "cat", - }}}, - - {`websocket /api5 "cmd arg1 arg2 arg3"`, false, []Config{{ - Path: "/api5", - Command: "cmd", - Arguments: []string{"arg1", "arg2", "arg3"}, - }}}, - - // accept respawn - {`websocket /api6 cat { - respawn - }`, false, []Config{{ - Path: "/api6", - Command: "cat", - }}}, - - // invalid configuration - {`websocket /api7 cat { - invalid - }`, true, []Config{}}, - } - for i, test := range tests { - c := caddy.NewTestController("http", test.inputWebSocketConfig) - actualWebSocketConfigs, err := webSocketParse(c) - - if err == nil && test.shouldErr { - t.Errorf("Test %d didn't error, but it should have", i) - } else if err != nil && !test.shouldErr { - t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err) - } - if len(actualWebSocketConfigs) != len(test.expectedWebSocketConfig) { - t.Fatalf("Test %d expected %d no of WebSocket configs, but got %d ", - i, len(test.expectedWebSocketConfig), len(actualWebSocketConfigs)) - } - for j, actualWebSocketConfig := range actualWebSocketConfigs { - - if actualWebSocketConfig.Path != test.expectedWebSocketConfig[j].Path { - t.Errorf("Test %d expected %dth WebSocket Config Path to be %s , but got %s", - i, j, test.expectedWebSocketConfig[j].Path, actualWebSocketConfig.Path) - } - - if actualWebSocketConfig.Command != test.expectedWebSocketConfig[j].Command { - t.Errorf("Test %d expected %dth WebSocket Config Command to be %s , but got %s", - i, j, test.expectedWebSocketConfig[j].Command, actualWebSocketConfig.Command) - } - - } - } - -} diff --git a/caddyhttp/websocket/websocket.go b/caddyhttp/websocket/websocket.go deleted file mode 100644 index 3bead97b043..00000000000 --- a/caddyhttp/websocket/websocket.go +++ /dev/null @@ -1,259 +0,0 @@ -// Package websocket implements a WebSocket server by executing -// a command and piping its input and output through the WebSocket -// connection. -package websocket - -import ( - "bufio" - "bytes" - "io" - "net" - "net/http" - "os" - "os/exec" - "strings" - "time" - - "github.com/gorilla/websocket" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -const ( - // Time allowed to write a message to the peer. - writeWait = 10 * time.Second - - // Time allowed to read the next pong message from the peer. - pongWait = 60 * time.Second - - // Send pings to peer with this period. Must be less than pongWait. - pingPeriod = (pongWait * 9) / 10 - - // Maximum message size allowed from peer. - maxMessageSize = 1024 * 1024 * 10 // 10 MB default. -) - -var ( - // GatewayInterface is the dialect of CGI being used by the server - // to communicate with the script. See CGI spec, 4.1.4 - GatewayInterface string - - // ServerSoftware is the name and version of the information server - // software making the CGI request. See CGI spec, 4.1.17 - ServerSoftware string -) - -type ( - // WebSocket is a type that holds configuration for the - // websocket middleware generally, like a list of all the - // websocket endpoints. - WebSocket struct { - // Next is the next HTTP handler in the chain for when the path doesn't match - Next httpserver.Handler - - // Sockets holds all the web socket endpoint configurations - Sockets []Config - } - - // Config holds the configuration for a single websocket - // endpoint which may serve multiple websocket connections. - Config struct { - Path string - Command string - Arguments []string - Respawn bool // TODO: Not used, but parser supports it until we decide on it - } -) - -// ServeHTTP converts the HTTP request to a WebSocket connection and serves it up. -func (ws WebSocket) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - for _, sockconfig := range ws.Sockets { - if httpserver.Path(r.URL.Path).Matches(sockconfig.Path) { - return serveWS(w, r, &sockconfig) - } - } - - // Didn't match a websocket path, so pass-through - return ws.Next.ServeHTTP(w, r) -} - -// serveWS is used for setting and upgrading the HTTP connection to a websocket connection. -// It also spawns the child process that is associated with matched HTTP path/url. -func serveWS(w http.ResponseWriter, r *http.Request, config *Config) (int, error) { - upgrader := websocket.Upgrader{ - ReadBufferSize: 1024, - WriteBufferSize: 1024, - CheckOrigin: func(r *http.Request) bool { return true }, - } - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - return http.StatusBadRequest, err - } - defer conn.Close() - - cmd := exec.Command(config.Command, config.Arguments...) - - stdout, err := cmd.StdoutPipe() - if err != nil { - return http.StatusBadGateway, err - } - defer stdout.Close() - - stdin, err := cmd.StdinPipe() - if err != nil { - return http.StatusBadGateway, err - } - defer stdin.Close() - - metavars, err := buildEnv(cmd.Path, r) - if err != nil { - return http.StatusBadGateway, err - } - - cmd.Env = metavars - - if err := cmd.Start(); err != nil { - return http.StatusBadGateway, err - } - - done := make(chan struct{}) - go pumpStdout(conn, stdout, done) - pumpStdin(conn, stdin) - - stdin.Close() // close stdin to end the process - - if err := cmd.Process.Signal(os.Interrupt); err != nil { // signal an interrupt to kill the process - return http.StatusInternalServerError, err - } - - select { - case <-done: - case <-time.After(time.Second): - // terminate with extreme prejudice. - if err := cmd.Process.Signal(os.Kill); err != nil { - return http.StatusInternalServerError, err - } - <-done - } - - // not sure what we want to do here. - // status for an "exited" process is greater - // than 0, but isn't really an error per se. - // just going to ignore it for now. - cmd.Wait() - - return 0, nil -} - -// buildEnv creates the meta-variables for the child process according -// to the CGI 1.1 specification: http://tools.ietf.org/html/rfc3875#section-4.1 -// cmdPath should be the path of the command being run. -// The returned string slice can be set to the command's Env property. -func buildEnv(cmdPath string, r *http.Request) (metavars []string, err error) { - if !strings.Contains(r.RemoteAddr, ":") { - r.RemoteAddr += ":" - } - remoteHost, remotePort, err := net.SplitHostPort(r.RemoteAddr) - if err != nil { - return - } - - if !strings.Contains(r.Host, ":") { - r.Host += ":" - } - serverHost, serverPort, err := net.SplitHostPort(r.Host) - if err != nil { - return - } - - metavars = []string{ - `AUTH_TYPE=`, // Not used - `CONTENT_LENGTH=`, // Not used - `CONTENT_TYPE=`, // Not used - `GATEWAY_INTERFACE=` + GatewayInterface, - `PATH_INFO=`, // TODO - `PATH_TRANSLATED=`, // TODO - `QUERY_STRING=` + r.URL.RawQuery, - `REMOTE_ADDR=` + remoteHost, - `REMOTE_HOST=` + remoteHost, // Host lookups are slow - don't do them - `REMOTE_IDENT=`, // Not used - `REMOTE_PORT=` + remotePort, - `REMOTE_USER=`, // Not used, - `REQUEST_METHOD=` + r.Method, - `REQUEST_URI=` + r.RequestURI, - `SCRIPT_NAME=` + cmdPath, // path of the program being executed - `SERVER_NAME=` + serverHost, - `SERVER_PORT=` + serverPort, - `SERVER_PROTOCOL=` + r.Proto, - `SERVER_SOFTWARE=` + ServerSoftware, - } - - // Add each HTTP header to the environment as well - for header, values := range r.Header { - value := strings.Join(values, ", ") - header = strings.ToUpper(header) - header = strings.Replace(header, "-", "_", -1) - value = strings.Replace(value, "\n", " ", -1) - metavars = append(metavars, "HTTP_"+header+"="+value) - } - - return -} - -// pumpStdin handles reading data from the websocket connection and writing -// it to stdin of the process. -func pumpStdin(conn *websocket.Conn, stdin io.WriteCloser) { - // Setup our connection's websocket ping/pong handlers from our const values. - defer conn.Close() - conn.SetReadLimit(maxMessageSize) - conn.SetReadDeadline(time.Now().Add(pongWait)) - conn.SetPongHandler(func(string) error { conn.SetReadDeadline(time.Now().Add(pongWait)); return nil }) - for { - _, message, err := conn.ReadMessage() - if err != nil { - break - } - message = append(message, '\n') - if _, err := stdin.Write(message); err != nil { - break - } - } -} - -// pumpStdout handles reading data from stdout of the process and writing -// it to websocket connection. -func pumpStdout(conn *websocket.Conn, stdout io.Reader, done chan struct{}) { - go pinger(conn, done) - defer func() { - conn.Close() - close(done) // make sure to close the pinger when we are done. - }() - - s := bufio.NewScanner(stdout) - for s.Scan() { - conn.SetWriteDeadline(time.Now().Add(writeWait)) - if err := conn.WriteMessage(websocket.TextMessage, bytes.TrimSpace(s.Bytes())); err != nil { - break - } - } - if s.Err() != nil { - conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseGoingAway, s.Err().Error()), time.Time{}) - } -} - -// pinger simulates the websocket to keep it alive with ping messages. -func pinger(conn *websocket.Conn, done chan struct{}) { - ticker := time.NewTicker(pingPeriod) - defer ticker.Stop() - - for { // blocking loop with select to wait for stimulation. - select { - case <-ticker.C: - if err := conn.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait)); err != nil { - conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseGoingAway, err.Error()), time.Time{}) - return - } - case <-done: - return // clean up this routine. - } - } -} diff --git a/caddyhttp/websocket/websocket_test.go b/caddyhttp/websocket/websocket_test.go deleted file mode 100644 index 61d7d382bcd..00000000000 --- a/caddyhttp/websocket/websocket_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package websocket - -import ( - "net/http" - "testing" -) - -func TestBuildEnv(t *testing.T) { - req, err := http.NewRequest("GET", "http://localhost", nil) - if err != nil { - t.Fatal("Error setting up request:", err) - } - req.RemoteAddr = "localhost:50302" - - env, err := buildEnv("/bin/command", req) - if err != nil { - t.Fatal("Didn't expect an error:", err) - } - if len(env) == 0 { - t.Fatalf("Expected non-empty environment; got %#v", env) - } -} diff --git a/caddytest/a.caddy.localhost.crt b/caddytest/a.caddy.localhost.crt new file mode 100644 index 00000000000..79880609c36 --- /dev/null +++ b/caddytest/a.caddy.localhost.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID5zCCAs8CFG4+w/pqR5AZQ+aVB330uRRRKMF0MA0GCSqGSIb3DQEBCwUAMIGv +MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTlkxGzAZBgNVBAoMEkxvY2FsIERldmVs +b3BlbWVudDEbMBkGA1UEBwwSTG9jYWwgRGV2ZWxvcGVtZW50MRowGAYDVQQDDBFh +LmNhZGR5LmxvY2FsaG9zdDEbMBkGA1UECwwSTG9jYWwgRGV2ZWxvcGVtZW50MSAw +HgYJKoZIhvcNAQkBFhFhZG1pbkBjYWRkeS5sb2NhbDAeFw0yMDAzMTMxODUwMTda +Fw0zMDAzMTExODUwMTdaMIGvMQswCQYDVQQGEwJVUzELMAkGA1UECAwCTlkxGzAZ +BgNVBAoMEkxvY2FsIERldmVsb3BlbWVudDEbMBkGA1UEBwwSTG9jYWwgRGV2ZWxv +cGVtZW50MRowGAYDVQQDDBFhLmNhZGR5LmxvY2FsaG9zdDEbMBkGA1UECwwSTG9j +YWwgRGV2ZWxvcGVtZW50MSAwHgYJKoZIhvcNAQkBFhFhZG1pbkBjYWRkeS5sb2Nh +bDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMd9pC9wF7j0459FndPs +Deud/rq41jEZFsVOVtjQgjS1A5ct6NfeMmSlq8i1F7uaTMPZjbOHzY6y6hzLc9+y +/VWNgyUC543HjXnNTnp9Xug6tBBxOxvRMw5mv2nAyzjBGDePPgN84xKhOXG2Wj3u +fOZ+VPVISefRNvjKfN87WLJ0B0HI9wplG5ASVdPQsWDY1cndrZgt2sxQ/3fjIno4 +VvrgRWC9Penizgps/a0ZcFZMD/6HJoX/mSZVa1LjopwbMTXvyHCpXkth21E+rBt6 +I9DMHerdioVQcX25CqPmAwePxPZSNGEQo/Qu32kzcmscmYxTtYBhDa+yLuHgGggI +j7ECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAP/94KPtkpYtkWADnhtzDmgQ6Q1pH +SubTUZdCwQtm6/LrvpT+uFNsOj4L3Mv3TVUnIQDmKd5VvR42W2MRBiTN2LQptgEn +C7g9BB+UA9kjL3DPk1pJMjzxLHohh0uNLi7eh4mAj8eNvjz9Z4qMWPQoVS0y7/ZK +cCBRKh2GkIqKm34ih6pX7xmMpPEQsFoTVPRHYJfYD1SZ8Iui+EN+7WqLuJWPsPXw +JM1HuZKn7pZmJU2MZZBsrupHGUvNMbBg2mFJcxt4D1VvU+p+a67PSjpFQ6dJG2re +pZoF+N1vMGAFkxe6UqhcC/bXDX+ILVQHJ+RNhzDO6DcWf8dRrC2LaJk3WA== +-----END CERTIFICATE----- diff --git a/caddytest/a.caddy.localhost.key b/caddytest/a.caddy.localhost.key new file mode 100644 index 00000000000..332ea747cf0 --- /dev/null +++ b/caddytest/a.caddy.localhost.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAx32kL3AXuPTjn0Wd0+wN653+urjWMRkWxU5W2NCCNLUDly3o +194yZKWryLUXu5pMw9mNs4fNjrLqHMtz37L9VY2DJQLnjceNec1Oen1e6Dq0EHE7 +G9EzDma/acDLOMEYN48+A3zjEqE5cbZaPe585n5U9UhJ59E2+Mp83ztYsnQHQcj3 +CmUbkBJV09CxYNjVyd2tmC3azFD/d+MiejhW+uBFYL096eLOCmz9rRlwVkwP/ocm +hf+ZJlVrUuOinBsxNe/IcKleS2HbUT6sG3oj0Mwd6t2KhVBxfbkKo+YDB4/E9lI0 +YRCj9C7faTNyaxyZjFO1gGENr7Iu4eAaCAiPsQIDAQABAoIBAQDD/YFIBeWYlifn +e9risQDAIrp3sk7lb9O6Rwv1+Wxi4hBEABvJsYhq74VFK/3EF4UhyWR5JIvkjYyK +e6w887oGyoA05ZSe65XoO7fFidSrbbkoikZbPv3dQT7/ZCWEfdkQBNAVVyY0UGeC +e3hPbjYRsb5AOSQ694X9idqC6uhqcOrBDjITFrctUoP4S6l9A6a+mLSUIwiICcuh +mrNl+j0lzy7DMXRp/Z5Hyo5kuUlrC0dCLa1UHqtrrK7MR55AVEOihSNp1w+OC+vw +f0VjE4JUtO7RQEQUmD1tDfLXwNfMFeWaobB2W0WMvRg0IqoitiqPxsPHRm56OxfM +SRo/Q7QBAoGBAP8DapzBMuaIcJ7cE8Yl07ZGndWWf8buIKIItGF8rkEO3BXhrIke +EmpOi+ELtpbMOG0APhORZyQ58f4ZOVrqZfneNKtDiEZV4mJZaYUESm1pU+2Y6+y5 +g4bpQSVKN0ow0xR+MH7qDYtSlsmBU7qAOz775L7BmMA1Bnu72aN/H1JBAoGBAMhD +OzqCSakHOjUbEd22rPwqWmcIyVyo04gaSmcVVT2dHbqR4/t0gX5a9D9U2qwyO6xi +/R+PXyMd32xIeVR2D/7SQ0x6dK68HXICLV8ofHZ5UQcHbxy5og4v/YxSZVTkN374 +cEsUeyB0s/UPOHLktFU5hpIlON72/Rp7b+pNIwFxAoGAczpq+Qu/YTWzlcSh1r4O +7OT5uqI3eH7vFehTAV3iKxl4zxZa7NY+wfRd9kFhrr/2myIp6pOgBFl+hC+HoBIc +JAyIxf5M3GNAWOpH6MfojYmzV7/qktu8l8BcJGplk0t+hVsDtMUze4nFAqZCXBpH +Kw2M7bjyuZ78H/rgu6TcVUECgYEAo1M5ldE2U/VCApeuLX1TfWDpU8i1uK0zv3d5 +oLKkT1i5KzTak3SEO9HgC1qf8PoS8tfUio26UICHe99rnHehOfivzEq+qNdgyF+A +M3BoeZMdgzcL5oh640k+Zte4LtDlddcWdhUhCepD7iPYrNNbQ3pkBwL2a9lRuOxc +7OC2IPECgYBH8f3OrwXjDltIG1dDvuDPNljxLZbFEFbQyVzMePYNftgZknAyGEdh +NW/LuWeTzstnmz/s6RE3jN5ZrrMa4sW77VA9+yU9QW2dkHqFyukQ4sfuNg6kDDNZ ++lqZYMCLw0M5P9fIbmnIYwey7tXkHfmzoCpnYHGQDN6hL0Bh0zGwmg== +-----END RSA PRIVATE KEY----- diff --git a/caddytest/caddy.ca.cer b/caddytest/caddy.ca.cer new file mode 100644 index 00000000000..00a9a1c119e --- /dev/null +++ b/caddytest/caddy.ca.cer @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQEL +BQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkw +ODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU +7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl0 +3WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45t +wOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNx +tdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTU +ApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAd +BgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS +2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5u +NY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkq +hkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfK +D66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEO +fG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnk +oNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZ +ks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdle +Ih6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/caddytest/caddy.localhost.crt b/caddytest/caddy.localhost.crt new file mode 100644 index 00000000000..a3a2f4c5ab7 --- /dev/null +++ b/caddytest/caddy.localhost.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID5zCCAs8CFFmAAFKV79uhzxc5qXbUw3oBNsYXMA0GCSqGSIb3DQEBCwUAMIGv +MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTlkxGzAZBgNVBAoMEkxvY2FsIERldmVs +b3BlbWVudDEbMBkGA1UEBwwSTG9jYWwgRGV2ZWxvcGVtZW50MRowGAYDVQQDDBEq +LmNhZGR5LmxvY2FsaG9zdDEbMBkGA1UECwwSTG9jYWwgRGV2ZWxvcGVtZW50MSAw +HgYJKoZIhvcNAQkBFhFhZG1pbkBjYWRkeS5sb2NhbDAeFw0yMDAzMDIwODAxMTZa +Fw0zMDAyMjgwODAxMTZaMIGvMQswCQYDVQQGEwJVUzELMAkGA1UECAwCTlkxGzAZ +BgNVBAoMEkxvY2FsIERldmVsb3BlbWVudDEbMBkGA1UEBwwSTG9jYWwgRGV2ZWxv +cGVtZW50MRowGAYDVQQDDBEqLmNhZGR5LmxvY2FsaG9zdDEbMBkGA1UECwwSTG9j +YWwgRGV2ZWxvcGVtZW50MSAwHgYJKoZIhvcNAQkBFhFhZG1pbkBjYWRkeS5sb2Nh +bDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJngfeirQkWaU8ihgIC5 +SKpRQX/3koRjljDK/oCbhLs+wg592kIwVv06l7+mn7NSaNBloabjuA1GqyLRsNLL +ptrv0HvXa5qLx28+icsb2Ny3dJnQaj9w9PwjxQ1qZqEJfWRH1D8Vz9AmB+QSV/Gu +8e8alGFewlYZVfH1kbxoTT6QorF37TeA3bh1fgKFtzsGYKswcaZNdDBBHzLunCKZ +HU6U6L45hm+yLADj3mmDLafUeiVOt6MRLLoSD1eLRVSXGrNo+brJ87zkZntI9+W1 +JxOBoXtZCwka7k2DlAtLihsrmBZA2ZC9yVeu/SQy3qb3iCNnTFTCyAnWeTCr6Tcq +6w8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAOWfXqpAmD4C3wGiMeZAeaaS4hDAR ++JmN+avPDA6F6Bq7DB4NJuIwVUlaDL2s07w5VJJtW52aZVKoBlgHR5yG/XUli6J7 +YUJRmdQJvHUSu26cmKvyoOaTrEYbmvtGICWtZc8uTlMf9wQZbJA4KyxTgEQJDXsZ +B2XFe+wVdhAgEpobYDROi+l/p8TL5z3U24LpwVTcJy5sEZVv7Wfs886IyxU8ORt8 +VZNcDiH6V53OIGeiufIhia/mPe6jbLntfGZfIFxtCcow4IA/lTy1ned7K5fmvNNb +ZilxOQUk+wVK8genjdrZVAnAxsYLHJIb5yf9O7rr6fWciVMF3a0k5uNK1w== +-----END CERTIFICATE----- diff --git a/caddytest/caddy.localhost.key b/caddytest/caddy.localhost.key new file mode 100644 index 00000000000..d85cd40439e --- /dev/null +++ b/caddytest/caddy.localhost.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAmeB96KtCRZpTyKGAgLlIqlFBf/eShGOWMMr+gJuEuz7CDn3a +QjBW/TqXv6afs1Jo0GWhpuO4DUarItGw0sum2u/Qe9drmovHbz6JyxvY3Ld0mdBq +P3D0/CPFDWpmoQl9ZEfUPxXP0CYH5BJX8a7x7xqUYV7CVhlV8fWRvGhNPpCisXft +N4DduHV+AoW3OwZgqzBxpk10MEEfMu6cIpkdTpTovjmGb7IsAOPeaYMtp9R6JU63 +oxEsuhIPV4tFVJcas2j5usnzvORme0j35bUnE4Ghe1kLCRruTYOUC0uKGyuYFkDZ +kL3JV679JDLepveII2dMVMLICdZ5MKvpNyrrDwIDAQABAoIBAFcPK01zb6hfm12c ++k5aBiHOnUdgc/YRPg1XHEz5MEycQkDetZjTLrRQ7UBSbnKPgpu9lIsOtbhVLkgh +6XAqJroiCou2oruqr+hhsqZGmBiwdvj7cNF6ADGTr05az7v22YneFdinZ481pStF +sZocx+bm2+KHMV5zMSwXKyA0xtdJLxs2yklniDBxSZRppgppq1pDPprP5DkgKPfe +3ekUmbQd5bHmivhW8ItbJLuf82XSsMBZ9ZhKiKIlWlbKAgiSV3SqnUQb5fi7l8hG +yYZxbuCUIGFwKmEpUBBt/nyxrOlMiNtDh9JhrPmijTV3slq70pCLwLL/Ai2aeear +EVA5VhkCgYEAyAmxfPqc2P7BsDAp67/sA7OEPso9qM4WyuWiVdlX2gb9TLNLYbPX +Kk/UmpAIVzpoTAGY5Zp3wkvdD/ou8uUQsE8ioNn4S1a4G9XURH1wVhcEbUiAKI1S +QVBH9B/Pj3eIp5OTKwob0Wj7DNdxoH7ed/Eok0EaTWzOA8pCWADKv/MCgYEAxOzY +YsX7Nl+eyZr2+9unKyeAK/D1DCT/o99UUAHx72/xaBVP/06cfzpvKBNcF9iYc+fq +R1yIUIrDRoSmYKBq+Kb3+nOg1nrqih/NBTokbTiI4Q+/30OQt0Al1e7y9iNKqV8H +jYZItzluGNrWKedZbATwBwbVCY2jnNl6RMDnS3UCgYBxj3cwQUHLuoyQjjcuO80r +qLzZvIxWiXDNDKIk5HcIMlGYOmz/8U2kGp/SgxQJGQJeq8V2C0QTjGfaCyieAcaA +oNxCvptDgd6RBsoze5bLeNOtiqwe2WOp6n5+q5R0mOJ+Z7vzghCayGNFPgWmnH+F +TeW/+wSIkc0+v5L8TK7NWwKBgBrlWlyLO9deUfqpHqihhICBYaEexOlGuF+yZfqT +eW7BdFBJ8OYm33sFCR+JHV/oZlIWT8o1Wizd9vPPtEWoQ1P4wg/D8Si6GwSIeWEI +YudD/HX4x7T/rmlI6qIAg9CYW18sqoRq3c2gm2fro6qPfYgiWIItLbWjUcBfd7Ki +QjTtAoGARKdRv3jMWL84rlEx1nBRgL3pe9Dt+Uxzde2xT3ZeF+5Hp9NfU01qE6M6 +1I6H64smqpetlsXmCEVKwBemP3pJa6avLKgIYiQvHAD/v4rs9mqgy1RTqtYyGNhR +1A/6dKkbiZ6wzePLLPasXVZxSKEviXf5gJooqumQVSVhCswyCZ0= +-----END RSA PRIVATE KEY----- diff --git a/caddytest/caddytest.go b/caddytest/caddytest.go new file mode 100644 index 00000000000..05aa1e3f52f --- /dev/null +++ b/caddytest/caddytest.go @@ -0,0 +1,568 @@ +package caddytest + +import ( + "bytes" + "context" + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "log" + "net" + "net/http" + "net/http/cookiejar" + "os" + "path" + "reflect" + "regexp" + "runtime" + "strings" + "testing" + "time" + + "github.com/aryann/difflib" + + caddycmd "github.com/caddyserver/caddy/v2/cmd" + + "github.com/caddyserver/caddy/v2/caddyconfig" + // plug in Caddy modules here + _ "github.com/caddyserver/caddy/v2/modules/standard" +) + +// Defaults store any configuration required to make the tests run +type Defaults struct { + // Port we expect caddy to listening on + AdminPort int + // Certificates we expect to be loaded before attempting to run the tests + Certificates []string + // TestRequestTimeout is the time to wait for a http request to + TestRequestTimeout time.Duration + // LoadRequestTimeout is the time to wait for the config to be loaded against the caddy server + LoadRequestTimeout time.Duration +} + +// Default testing values +var Default = Defaults{ + AdminPort: 2999, // different from what a real server also running on a developer's machine might be + Certificates: []string{"/caddy.localhost.crt", "/caddy.localhost.key"}, + TestRequestTimeout: 5 * time.Second, + LoadRequestTimeout: 5 * time.Second, +} + +var ( + matchKey = regexp.MustCompile(`(/[\w\d\.]+\.key)`) + matchCert = regexp.MustCompile(`(/[\w\d\.]+\.crt)`) +) + +// Tester represents an instance of a test client. +type Tester struct { + Client *http.Client + configLoaded bool + t testing.TB +} + +// NewTester will create a new testing client with an attached cookie jar +func NewTester(t testing.TB) *Tester { + jar, err := cookiejar.New(nil) + if err != nil { + t.Fatalf("failed to create cookiejar: %s", err) + } + + return &Tester{ + Client: &http.Client{ + Transport: CreateTestingTransport(), + Jar: jar, + Timeout: Default.TestRequestTimeout, + }, + configLoaded: false, + t: t, + } +} + +type configLoadError struct { + Response string +} + +func (e configLoadError) Error() string { return e.Response } + +func timeElapsed(start time.Time, name string) { + elapsed := time.Since(start) + log.Printf("%s took %s", name, elapsed) +} + +// InitServer this will configure the server with a configurion of a specific +// type. The configType must be either "json" or the adapter type. +func (tc *Tester) InitServer(rawConfig string, configType string) { + if err := tc.initServer(rawConfig, configType); err != nil { + tc.t.Logf("failed to load config: %s", err) + tc.t.Fail() + } + if err := tc.ensureConfigRunning(rawConfig, configType); err != nil { + tc.t.Logf("failed ensuring config is running: %s", err) + tc.t.Fail() + } +} + +// InitServer this will configure the server with a configurion of a specific +// type. The configType must be either "json" or the adapter type. +func (tc *Tester) initServer(rawConfig string, configType string) error { + if testing.Short() { + tc.t.SkipNow() + return nil + } + + err := validateTestPrerequisites(tc.t) + if err != nil { + tc.t.Skipf("skipping tests as failed integration prerequisites. %s", err) + return nil + } + + tc.t.Cleanup(func() { + if tc.t.Failed() && tc.configLoaded { + res, err := http.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort)) + if err != nil { + tc.t.Log("unable to read the current config") + return + } + defer res.Body.Close() + body, _ := io.ReadAll(res.Body) + + var out bytes.Buffer + _ = json.Indent(&out, body, "", " ") + tc.t.Logf("----------- failed with config -----------\n%s", out.String()) + } + }) + + rawConfig = prependCaddyFilePath(rawConfig) + // normalize JSON config + if configType == "json" { + tc.t.Logf("Before: %s", rawConfig) + var conf any + if err := json.Unmarshal([]byte(rawConfig), &conf); err != nil { + return err + } + c, err := json.Marshal(conf) + if err != nil { + return err + } + rawConfig = string(c) + tc.t.Logf("After: %s", rawConfig) + } + client := &http.Client{ + Timeout: Default.LoadRequestTimeout, + } + start := time.Now() + req, err := http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/load", Default.AdminPort), strings.NewReader(rawConfig)) + if err != nil { + tc.t.Errorf("failed to create request. %s", err) + return err + } + + if configType == "json" { + req.Header.Add("Content-Type", "application/json") + } else { + req.Header.Add("Content-Type", "text/"+configType) + } + + res, err := client.Do(req) + if err != nil { + tc.t.Errorf("unable to contact caddy server. %s", err) + return err + } + timeElapsed(start, "caddytest: config load time") + + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + tc.t.Errorf("unable to read response. %s", err) + return err + } + + if res.StatusCode != 200 { + return configLoadError{Response: string(body)} + } + + tc.configLoaded = true + return nil +} + +func (tc *Tester) ensureConfigRunning(rawConfig string, configType string) error { + expectedBytes := []byte(prependCaddyFilePath(rawConfig)) + if configType != "json" { + adapter := caddyconfig.GetAdapter(configType) + if adapter == nil { + return fmt.Errorf("adapter of config type is missing: %s", configType) + } + expectedBytes, _, _ = adapter.Adapt([]byte(rawConfig), nil) + } + + var expected any + err := json.Unmarshal(expectedBytes, &expected) + if err != nil { + return err + } + + client := &http.Client{ + Timeout: Default.LoadRequestTimeout, + } + + fetchConfig := func(client *http.Client) any { + resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort)) + if err != nil { + return nil + } + defer resp.Body.Close() + actualBytes, err := io.ReadAll(resp.Body) + if err != nil { + return nil + } + var actual any + err = json.Unmarshal(actualBytes, &actual) + if err != nil { + return nil + } + return actual + } + + for retries := 10; retries > 0; retries-- { + if reflect.DeepEqual(expected, fetchConfig(client)) { + return nil + } + time.Sleep(1 * time.Second) + } + tc.t.Errorf("POSTed configuration isn't active") + return errors.New("EnsureConfigRunning: POSTed configuration isn't active") +} + +const initConfig = `{ + admin localhost:2999 +} +` + +// validateTestPrerequisites ensures the certificates are available in the +// designated path and Caddy sub-process is running. +func validateTestPrerequisites(t testing.TB) error { + // check certificates are found + for _, certName := range Default.Certificates { + if _, err := os.Stat(getIntegrationDir() + certName); errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("caddy integration test certificates (%s) not found", certName) + } + } + + if isCaddyAdminRunning() != nil { + // setup the init config file, and set the cleanup afterwards + f, err := os.CreateTemp("", "") + if err != nil { + return err + } + t.Cleanup(func() { + os.Remove(f.Name()) + }) + if _, err := f.WriteString(initConfig); err != nil { + return err + } + + // start inprocess caddy server + os.Args = []string{"caddy", "run", "--config", f.Name(), "--adapter", "caddyfile"} + go func() { + caddycmd.Main() + }() + + // wait for caddy to start serving the initial config + for retries := 10; retries > 0 && isCaddyAdminRunning() != nil; retries-- { + time.Sleep(1 * time.Second) + } + } + + // one more time to return the error + return isCaddyAdminRunning() +} + +func isCaddyAdminRunning() error { + // assert that caddy is running + client := &http.Client{ + Timeout: Default.LoadRequestTimeout, + } + resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort)) + if err != nil { + return fmt.Errorf("caddy integration test caddy server not running. Expected to be listening on localhost:%d", Default.AdminPort) + } + resp.Body.Close() + + return nil +} + +func getIntegrationDir() string { + _, filename, _, ok := runtime.Caller(1) + if !ok { + panic("unable to determine the current file path") + } + + return path.Dir(filename) +} + +// use the convention to replace /[certificatename].[crt|key] with the full path +// this helps reduce the noise in test configurations and also allow this +// to run in any path +func prependCaddyFilePath(rawConfig string) string { + r := matchKey.ReplaceAllString(rawConfig, getIntegrationDir()+"$1") + r = matchCert.ReplaceAllString(r, getIntegrationDir()+"$1") + return r +} + +// CreateTestingTransport creates a testing transport that forces call dialing connections to happen locally +func CreateTestingTransport() *http.Transport { + dialer := net.Dialer{ + Timeout: 5 * time.Second, + KeepAlive: 5 * time.Second, + DualStack: true, + } + + dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) { + parts := strings.Split(addr, ":") + destAddr := fmt.Sprintf("127.0.0.1:%s", parts[1]) + log.Printf("caddytest: redirecting the dialer from %s to %s", addr, destAddr) + return dialer.DialContext(ctx, network, destAddr) + } + + return &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: dialContext, + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 5 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec + } +} + +// AssertLoadError will load a config and expect an error +func AssertLoadError(t *testing.T, rawConfig string, configType string, expectedError string) { + tc := NewTester(t) + + err := tc.initServer(rawConfig, configType) + if !strings.Contains(err.Error(), expectedError) { + t.Errorf("expected error \"%s\" but got \"%s\"", expectedError, err.Error()) + } +} + +// AssertRedirect makes a request and asserts the redirection happens +func (tc *Tester) AssertRedirect(requestURI string, expectedToLocation string, expectedStatusCode int) *http.Response { + redirectPolicyFunc := func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + } + + // using the existing client, we override the check redirect policy for this test + old := tc.Client.CheckRedirect + tc.Client.CheckRedirect = redirectPolicyFunc + defer func() { tc.Client.CheckRedirect = old }() + + resp, err := tc.Client.Get(requestURI) + if err != nil { + tc.t.Errorf("failed to call server %s", err) + return nil + } + + if expectedStatusCode != resp.StatusCode { + tc.t.Errorf("requesting \"%s\" expected status code: %d but got %d", requestURI, expectedStatusCode, resp.StatusCode) + } + + loc, err := resp.Location() + if err != nil { + tc.t.Errorf("requesting \"%s\" expected location: \"%s\" but got error: %s", requestURI, expectedToLocation, err) + } + if loc == nil && expectedToLocation != "" { + tc.t.Errorf("requesting \"%s\" expected a Location header, but didn't get one", requestURI) + } + if loc != nil { + if expectedToLocation != loc.String() { + tc.t.Errorf("requesting \"%s\" expected location: \"%s\" but got \"%s\"", requestURI, expectedToLocation, loc.String()) + } + } + + return resp +} + +// CompareAdapt adapts a config and then compares it against an expected result +func CompareAdapt(t testing.TB, filename, rawConfig string, adapterName string, expectedResponse string) bool { + cfgAdapter := caddyconfig.GetAdapter(adapterName) + if cfgAdapter == nil { + t.Logf("unrecognized config adapter '%s'", adapterName) + return false + } + + options := make(map[string]any) + + result, warnings, err := cfgAdapter.Adapt([]byte(rawConfig), options) + if err != nil { + t.Logf("adapting config using %s adapter: %v", adapterName, err) + return false + } + + // prettify results to keep tests human-manageable + var prettyBuf bytes.Buffer + err = json.Indent(&prettyBuf, result, "", "\t") + if err != nil { + return false + } + result = prettyBuf.Bytes() + + if len(warnings) > 0 { + for _, w := range warnings { + t.Logf("warning: %s:%d: %s: %s", filename, w.Line, w.Directive, w.Message) + } + } + + diff := difflib.Diff( + strings.Split(expectedResponse, "\n"), + strings.Split(string(result), "\n")) + + // scan for failure + failed := false + for _, d := range diff { + if d.Delta != difflib.Common { + failed = true + break + } + } + + if failed { + for _, d := range diff { + switch d.Delta { + case difflib.Common: + fmt.Printf(" %s\n", d.Payload) + case difflib.LeftOnly: + fmt.Printf(" - %s\n", d.Payload) + case difflib.RightOnly: + fmt.Printf(" + %s\n", d.Payload) + } + } + return false + } + return true +} + +// AssertAdapt adapts a config and then tests it against an expected result +func AssertAdapt(t testing.TB, rawConfig string, adapterName string, expectedResponse string) { + ok := CompareAdapt(t, "Caddyfile", rawConfig, adapterName, expectedResponse) + if !ok { + t.Fail() + } +} + +// Generic request functions + +func applyHeaders(t testing.TB, req *http.Request, requestHeaders []string) { + requestContentType := "" + for _, requestHeader := range requestHeaders { + arr := strings.SplitAfterN(requestHeader, ":", 2) + k := strings.TrimRight(arr[0], ":") + v := strings.TrimSpace(arr[1]) + if k == "Content-Type" { + requestContentType = v + } + t.Logf("Request header: %s => %s", k, v) + req.Header.Set(k, v) + } + + if requestContentType == "" { + t.Logf("Content-Type header not provided") + } +} + +// AssertResponseCode will execute the request and verify the status code, returns a response for additional assertions +func (tc *Tester) AssertResponseCode(req *http.Request, expectedStatusCode int) *http.Response { + resp, err := tc.Client.Do(req) + if err != nil { + tc.t.Fatalf("failed to call server %s", err) + } + + if expectedStatusCode != resp.StatusCode { + tc.t.Errorf("requesting \"%s\" expected status code: %d but got %d", req.URL.RequestURI(), expectedStatusCode, resp.StatusCode) + } + + return resp +} + +// AssertResponse request a URI and assert the status code and the body contains a string +func (tc *Tester) AssertResponse(req *http.Request, expectedStatusCode int, expectedBody string) (*http.Response, string) { + resp := tc.AssertResponseCode(req, expectedStatusCode) + + defer resp.Body.Close() + bytes, err := io.ReadAll(resp.Body) + if err != nil { + tc.t.Fatalf("unable to read the response body %s", err) + } + + body := string(bytes) + + if body != expectedBody { + tc.t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", req.RequestURI, expectedBody, body) + } + + return resp, body +} + +// Verb specific test functions + +// AssertGetResponse GET a URI and expect a statusCode and body text +func (tc *Tester) AssertGetResponse(requestURI string, expectedStatusCode int, expectedBody string) (*http.Response, string) { + req, err := http.NewRequest("GET", requestURI, nil) + if err != nil { + tc.t.Fatalf("unable to create request %s", err) + } + + return tc.AssertResponse(req, expectedStatusCode, expectedBody) +} + +// AssertDeleteResponse request a URI and expect a statusCode and body text +func (tc *Tester) AssertDeleteResponse(requestURI string, expectedStatusCode int, expectedBody string) (*http.Response, string) { + req, err := http.NewRequest("DELETE", requestURI, nil) + if err != nil { + tc.t.Fatalf("unable to create request %s", err) + } + + return tc.AssertResponse(req, expectedStatusCode, expectedBody) +} + +// AssertPostResponseBody POST to a URI and assert the response code and body +func (tc *Tester) AssertPostResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) { + req, err := http.NewRequest("POST", requestURI, requestBody) + if err != nil { + tc.t.Errorf("failed to create request %s", err) + return nil, "" + } + + applyHeaders(tc.t, req, requestHeaders) + + return tc.AssertResponse(req, expectedStatusCode, expectedBody) +} + +// AssertPutResponseBody PUT to a URI and assert the response code and body +func (tc *Tester) AssertPutResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) { + req, err := http.NewRequest("PUT", requestURI, requestBody) + if err != nil { + tc.t.Errorf("failed to create request %s", err) + return nil, "" + } + + applyHeaders(tc.t, req, requestHeaders) + + return tc.AssertResponse(req, expectedStatusCode, expectedBody) +} + +// AssertPatchResponseBody PATCH to a URI and assert the response code and body +func (tc *Tester) AssertPatchResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) { + req, err := http.NewRequest("PATCH", requestURI, requestBody) + if err != nil { + tc.t.Errorf("failed to create request %s", err) + return nil, "" + } + + applyHeaders(tc.t, req, requestHeaders) + + return tc.AssertResponse(req, expectedStatusCode, expectedBody) +} diff --git a/caddytest/caddytest_test.go b/caddytest/caddytest_test.go new file mode 100644 index 00000000000..a9d5da9365b --- /dev/null +++ b/caddytest/caddytest_test.go @@ -0,0 +1,128 @@ +package caddytest + +import ( + "net/http" + "strings" + "testing" +) + +func TestReplaceCertificatePaths(t *testing.T) { + rawConfig := `a.caddy.localhost:9443 { + tls /caddy.localhost.crt /caddy.localhost.key { + } + + redir / https://b.caddy.localhost:9443/version 301 + + respond /version 200 { + body "hello from a.caddy.localhost" + } + }` + + r := prependCaddyFilePath(rawConfig) + + if !strings.Contains(r, getIntegrationDir()+"/caddy.localhost.crt") { + t.Error("expected the /caddy.localhost.crt to be expanded to include the full path") + } + + if !strings.Contains(r, getIntegrationDir()+"/caddy.localhost.key") { + t.Error("expected the /caddy.localhost.crt to be expanded to include the full path") + } + + if !strings.Contains(r, "https://b.caddy.localhost:9443/version") { + t.Error("expected redirect uri to be unchanged") + } +} + +func TestLoadUnorderedJSON(t *testing.T) { + tester := NewTester(t) + tester.InitServer(` + { + "logging": { + "logs": { + "default": { + "level": "DEBUG", + "writer": { + "output": "stdout" + } + }, + "sStdOutLogs": { + "level": "DEBUG", + "writer": { + "output": "stdout" + }, + "include": [ + "http.*", + "admin.*" + ] + }, + "sFileLogs": { + "level": "DEBUG", + "writer": { + "output": "stdout" + }, + "include": [ + "http.*", + "admin.*" + ] + } + } + }, + "admin": { + "listen": "localhost:2999" + }, + "apps": { + "pki": { + "certificate_authorities" : { + "local" : { + "install_trust": false + } + } + }, + "http": { + "http_port": 9080, + "https_port": 9443, + "servers": { + "s_server": { + "listen": [ + ":9080" + ], + "routes": [ + { + "handle": [ + { + "handler": "static_response", + "body": "Hello" + } + ] + }, + { + "match": [ + { + "host": [ + "localhost", + "127.0.0.1" + ] + } + ] + } + ], + "logs": { + "default_logger_name": "sStdOutLogs", + "logger_names": { + "localhost": "sStdOutLogs", + "127.0.0.1": "sFileLogs" + } + } + } + } + } + } + } + `, "json") + req, err := http.NewRequest(http.MethodGet, "http://localhost:9080/", nil) + if err != nil { + t.Fail() + return + } + tester.AssertResponseCode(req, 200) +} diff --git a/caddytest/integration/acme_test.go b/caddytest/integration/acme_test.go new file mode 100644 index 00000000000..d7e4c296d50 --- /dev/null +++ b/caddytest/integration/acme_test.go @@ -0,0 +1,208 @@ +package integration + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "fmt" + "log/slog" + "net" + "net/http" + "strings" + "testing" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddytest" + "github.com/mholt/acmez/v3" + "github.com/mholt/acmez/v3/acme" + smallstepacme "github.com/smallstep/certificates/acme" + "go.uber.org/zap" + "go.uber.org/zap/exp/zapslog" +) + +const acmeChallengePort = 9081 + +// Test the basic functionality of Caddy's ACME server +func TestACMEServerWithDefaults(t *testing.T) { + ctx := context.Background() + logger, err := zap.NewDevelopment() + if err != nil { + t.Error(err) + return + } + + tester := caddytest.NewTester(t) + tester.InitServer(` + { + skip_install_trust + admin localhost:2999 + http_port 9080 + https_port 9443 + local_certs + } + acme.localhost { + acme_server + } + `, "caddyfile") + + client := acmez.Client{ + Client: &acme.Client{ + Directory: "https://acme.localhost:9443/acme/local/directory", + HTTPClient: tester.Client, + Logger: slog.New(zapslog.NewHandler(logger.Core())), + }, + ChallengeSolvers: map[string]acmez.Solver{ + acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger}, + }, + } + + accountPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Errorf("generating account key: %v", err) + } + account := acme.Account{ + Contact: []string{"mailto:you@example.com"}, + TermsOfServiceAgreed: true, + PrivateKey: accountPrivateKey, + } + account, err = client.NewAccount(ctx, account) + if err != nil { + t.Errorf("new account: %v", err) + return + } + + // Every certificate needs a key. + certPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Errorf("generating certificate key: %v", err) + return + } + + certs, err := client.ObtainCertificateForSANs(ctx, account, certPrivateKey, []string{"localhost"}) + if err != nil { + t.Errorf("obtaining certificate: %v", err) + return + } + + // ACME servers should usually give you the entire certificate chain + // in PEM format, and sometimes even alternate chains! It's up to you + // which one(s) to store and use, but whatever you do, be sure to + // store the certificate and key somewhere safe and secure, i.e. don't + // lose them! + for _, cert := range certs { + t.Logf("Certificate %q:\n%s\n\n", cert.URL, cert.ChainPEM) + } +} + +func TestACMEServerWithMismatchedChallenges(t *testing.T) { + ctx := context.Background() + logger := caddy.Log().Named("acmez") + + tester := caddytest.NewTester(t) + tester.InitServer(` + { + skip_install_trust + admin localhost:2999 + http_port 9080 + https_port 9443 + local_certs + } + acme.localhost { + acme_server { + challenges tls-alpn-01 + } + } + `, "caddyfile") + + client := acmez.Client{ + Client: &acme.Client{ + Directory: "https://acme.localhost:9443/acme/local/directory", + HTTPClient: tester.Client, + Logger: slog.New(zapslog.NewHandler(logger.Core())), + }, + ChallengeSolvers: map[string]acmez.Solver{ + acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger}, + }, + } + + accountPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Errorf("generating account key: %v", err) + } + account := acme.Account{ + Contact: []string{"mailto:you@example.com"}, + TermsOfServiceAgreed: true, + PrivateKey: accountPrivateKey, + } + account, err = client.NewAccount(ctx, account) + if err != nil { + t.Errorf("new account: %v", err) + return + } + + // Every certificate needs a key. + certPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Errorf("generating certificate key: %v", err) + return + } + + certs, err := client.ObtainCertificateForSANs(ctx, account, certPrivateKey, []string{"localhost"}) + if len(certs) > 0 { + t.Errorf("expected '0' certificates, but received '%d'", len(certs)) + } + if err == nil { + t.Error("expected errors, but received none") + } + const expectedErrMsg = "no solvers available for remaining challenges (configured=[http-01] offered=[tls-alpn-01] remaining=[tls-alpn-01])" + if !strings.Contains(err.Error(), expectedErrMsg) { + t.Errorf(`received error message does not match expectation: expected="%s" received="%s"`, expectedErrMsg, err.Error()) + } +} + +// naiveHTTPSolver is a no-op acmez.Solver for example purposes only. +type naiveHTTPSolver struct { + srv *http.Server + logger *zap.Logger +} + +func (s *naiveHTTPSolver) Present(ctx context.Context, challenge acme.Challenge) error { + smallstepacme.InsecurePortHTTP01 = acmeChallengePort + s.srv = &http.Server{ + Addr: fmt.Sprintf(":%d", acmeChallengePort), + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + host, _, err := net.SplitHostPort(r.Host) + if err != nil { + host = r.Host + } + s.logger.Info("received request on challenge server", zap.String("path", r.URL.Path)) + if r.Method == "GET" && r.URL.Path == challenge.HTTP01ResourcePath() && strings.EqualFold(host, challenge.Identifier.Value) { + w.Header().Add("Content-Type", "text/plain") + w.Write([]byte(challenge.KeyAuthorization)) + r.Close = true + s.logger.Info("served key authentication", + zap.String("identifier", challenge.Identifier.Value), + zap.String("challenge", "http-01"), + zap.String("remote", r.RemoteAddr), + ) + } + }), + } + l, err := net.Listen("tcp", fmt.Sprintf(":%d", acmeChallengePort)) + if err != nil { + return err + } + s.logger.Info("present challenge", zap.Any("challenge", challenge)) + go s.srv.Serve(l) + return nil +} + +func (s naiveHTTPSolver) CleanUp(ctx context.Context, challenge acme.Challenge) error { + smallstepacme.InsecurePortHTTP01 = 0 + s.logger.Info("cleanup", zap.Any("challenge", challenge)) + if s.srv != nil { + s.srv.Close() + } + return nil +} diff --git a/caddytest/integration/acmeserver_test.go b/caddytest/integration/acmeserver_test.go new file mode 100644 index 00000000000..ca5845f8711 --- /dev/null +++ b/caddytest/integration/acmeserver_test.go @@ -0,0 +1,206 @@ +package integration + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "log/slog" + "strings" + "testing" + + "github.com/caddyserver/caddy/v2/caddytest" + "github.com/mholt/acmez/v3" + "github.com/mholt/acmez/v3/acme" + "go.uber.org/zap" + "go.uber.org/zap/exp/zapslog" +) + +func TestACMEServerDirectory(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + skip_install_trust + local_certs + admin localhost:2999 + http_port 9080 + https_port 9443 + pki { + ca local { + name "Caddy Local Authority" + } + } + } + acme.localhost:9443 { + acme_server + } + `, "caddyfile") + tester.AssertGetResponse( + "https://acme.localhost:9443/acme/local/directory", + 200, + `{"newNonce":"https://acme.localhost:9443/acme/local/new-nonce","newAccount":"https://acme.localhost:9443/acme/local/new-account","newOrder":"https://acme.localhost:9443/acme/local/new-order","revokeCert":"https://acme.localhost:9443/acme/local/revoke-cert","keyChange":"https://acme.localhost:9443/acme/local/key-change"} +`) +} + +func TestACMEServerAllowPolicy(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + skip_install_trust + local_certs + admin localhost:2999 + http_port 9080 + https_port 9443 + pki { + ca local { + name "Caddy Local Authority" + } + } + } + acme.localhost { + acme_server { + challenges http-01 + allow { + domains localhost + } + } + } + `, "caddyfile") + + ctx := context.Background() + logger, err := zap.NewDevelopment() + if err != nil { + t.Error(err) + return + } + + client := acmez.Client{ + Client: &acme.Client{ + Directory: "https://acme.localhost:9443/acme/local/directory", + HTTPClient: tester.Client, + Logger: slog.New(zapslog.NewHandler(logger.Core())), + }, + ChallengeSolvers: map[string]acmez.Solver{ + acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger}, + }, + } + + accountPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Errorf("generating account key: %v", err) + } + account := acme.Account{ + Contact: []string{"mailto:you@example.com"}, + TermsOfServiceAgreed: true, + PrivateKey: accountPrivateKey, + } + account, err = client.NewAccount(ctx, account) + if err != nil { + t.Errorf("new account: %v", err) + return + } + + // Every certificate needs a key. + certPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Errorf("generating certificate key: %v", err) + return + } + { + certs, err := client.ObtainCertificateForSANs(ctx, account, certPrivateKey, []string{"localhost"}) + if err != nil { + t.Errorf("obtaining certificate for allowed domain: %v", err) + return + } + + // ACME servers should usually give you the entire certificate chain + // in PEM format, and sometimes even alternate chains! It's up to you + // which one(s) to store and use, but whatever you do, be sure to + // store the certificate and key somewhere safe and secure, i.e. don't + // lose them! + for _, cert := range certs { + t.Logf("Certificate %q:\n%s\n\n", cert.URL, cert.ChainPEM) + } + } + { + _, err := client.ObtainCertificateForSANs(ctx, account, certPrivateKey, []string{"not-matching.localhost"}) + if err == nil { + t.Errorf("obtaining certificate for 'not-matching.localhost' domain") + } else if err != nil && !strings.Contains(err.Error(), "urn:ietf:params:acme:error:rejectedIdentifier") { + t.Logf("unexpected error: %v", err) + } + } +} + +func TestACMEServerDenyPolicy(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + skip_install_trust + local_certs + admin localhost:2999 + http_port 9080 + https_port 9443 + pki { + ca local { + name "Caddy Local Authority" + } + } + } + acme.localhost { + acme_server { + deny { + domains deny.localhost + } + } + } + `, "caddyfile") + + ctx := context.Background() + logger, err := zap.NewDevelopment() + if err != nil { + t.Error(err) + return + } + + client := acmez.Client{ + Client: &acme.Client{ + Directory: "https://acme.localhost:9443/acme/local/directory", + HTTPClient: tester.Client, + Logger: slog.New(zapslog.NewHandler(logger.Core())), + }, + ChallengeSolvers: map[string]acmez.Solver{ + acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger}, + }, + } + + accountPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Errorf("generating account key: %v", err) + } + account := acme.Account{ + Contact: []string{"mailto:you@example.com"}, + TermsOfServiceAgreed: true, + PrivateKey: accountPrivateKey, + } + account, err = client.NewAccount(ctx, account) + if err != nil { + t.Errorf("new account: %v", err) + return + } + + // Every certificate needs a key. + certPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Errorf("generating certificate key: %v", err) + return + } + { + _, err := client.ObtainCertificateForSANs(ctx, account, certPrivateKey, []string{"deny.localhost"}) + if err == nil { + t.Errorf("obtaining certificate for 'deny.localhost' domain") + } else if err != nil && !strings.Contains(err.Error(), "urn:ietf:params:acme:error:rejectedIdentifier") { + t.Logf("unexpected error: %v", err) + } + } +} diff --git a/caddytest/integration/autohttps_test.go b/caddytest/integration/autohttps_test.go new file mode 100644 index 00000000000..1dbdbcee209 --- /dev/null +++ b/caddytest/integration/autohttps_test.go @@ -0,0 +1,145 @@ +package integration + +import ( + "net/http" + "testing" + + "github.com/caddyserver/caddy/v2/caddytest" +) + +func TestAutoHTTPtoHTTPSRedirectsImplicitPort(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + admin localhost:2999 + skip_install_trust + http_port 9080 + https_port 9443 + } + localhost + respond "Yahaha! You found me!" + `, "caddyfile") + + tester.AssertRedirect("http://localhost:9080/", "https://localhost/", http.StatusPermanentRedirect) +} + +func TestAutoHTTPtoHTTPSRedirectsExplicitPortSameAsHTTPSPort(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + skip_install_trust + admin localhost:2999 + http_port 9080 + https_port 9443 + } + localhost:9443 + respond "Yahaha! You found me!" + `, "caddyfile") + + tester.AssertRedirect("http://localhost:9080/", "https://localhost/", http.StatusPermanentRedirect) +} + +func TestAutoHTTPtoHTTPSRedirectsExplicitPortDifferentFromHTTPSPort(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + skip_install_trust + admin localhost:2999 + http_port 9080 + https_port 9443 + } + localhost:1234 + respond "Yahaha! You found me!" + `, "caddyfile") + + tester.AssertRedirect("http://localhost:9080/", "https://localhost:1234/", http.StatusPermanentRedirect) +} + +func TestAutoHTTPRedirectsWithHTTPListenerFirstInAddresses(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` +{ + "admin": { + "listen": "localhost:2999" + }, + "apps": { + "http": { + "http_port": 9080, + "https_port": 9443, + "servers": { + "ingress_server": { + "listen": [ + ":9080", + ":9443" + ], + "routes": [ + { + "match": [ + { + "host": ["localhost"] + } + ] + } + ] + } + } + }, + "pki": { + "certificate_authorities": { + "local": { + "install_trust": false + } + } + } + } +} +`, "json") + tester.AssertRedirect("http://localhost:9080/", "https://localhost/", http.StatusPermanentRedirect) +} + +func TestAutoHTTPRedirectsInsertedBeforeUserDefinedCatchAll(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + skip_install_trust + admin localhost:2999 + http_port 9080 + https_port 9443 + local_certs + } + http://:9080 { + respond "Foo" + } + http://baz.localhost:9080 { + respond "Baz" + } + bar.localhost { + respond "Bar" + } + `, "caddyfile") + tester.AssertRedirect("http://bar.localhost:9080/", "https://bar.localhost/", http.StatusPermanentRedirect) + tester.AssertGetResponse("http://foo.localhost:9080/", 200, "Foo") + tester.AssertGetResponse("http://baz.localhost:9080/", 200, "Baz") +} + +func TestAutoHTTPRedirectsInsertedBeforeUserDefinedCatchAllWithNoExplicitHTTPSite(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + skip_install_trust + admin localhost:2999 + http_port 9080 + https_port 9443 + local_certs + } + http://:9080 { + respond "Foo" + } + bar.localhost { + respond "Bar" + } + `, "caddyfile") + tester.AssertRedirect("http://bar.localhost:9080/", "https://bar.localhost/", http.StatusPermanentRedirect) + tester.AssertGetResponse("http://foo.localhost:9080/", 200, "Foo") + tester.AssertGetResponse("http://baz.localhost:9080/", 200, "Foo") +} diff --git a/caddytest/integration/caddyfile_adapt/acme_server_custom_challenges.caddyfiletest b/caddytest/integration/caddyfile_adapt/acme_server_custom_challenges.caddyfiletest new file mode 100644 index 00000000000..2a7a5149243 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/acme_server_custom_challenges.caddyfiletest @@ -0,0 +1,65 @@ +{ + pki { + ca custom-ca { + name "Custom CA" + } + } +} + +acme.example.com { + acme_server { + ca custom-ca + challenges dns-01 + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "acme.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "ca": "custom-ca", + "challenges": [ + "dns-01" + ], + "handler": "acme_server" + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + }, + "pki": { + "certificate_authorities": { + "custom-ca": { + "name": "Custom CA" + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/acme_server_default_challenges.caddyfiletest b/caddytest/integration/caddyfile_adapt/acme_server_default_challenges.caddyfiletest new file mode 100644 index 00000000000..26d345047c9 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/acme_server_default_challenges.caddyfiletest @@ -0,0 +1,62 @@ +{ + pki { + ca custom-ca { + name "Custom CA" + } + } +} + +acme.example.com { + acme_server { + ca custom-ca + challenges + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "acme.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "ca": "custom-ca", + "handler": "acme_server" + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + }, + "pki": { + "certificate_authorities": { + "custom-ca": { + "name": "Custom CA" + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/acme_server_lifetime.caddyfiletest b/caddytest/integration/caddyfile_adapt/acme_server_lifetime.caddyfiletest new file mode 100644 index 00000000000..6099440a434 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/acme_server_lifetime.caddyfiletest @@ -0,0 +1,108 @@ +{ + pki { + ca internal { + name "Internal" + root_cn "Internal Root Cert" + intermediate_cn "Internal Intermediate Cert" + } + ca internal-long-lived { + name "Long-lived" + root_cn "Internal Root Cert 2" + intermediate_cn "Internal Intermediate Cert 2" + } + } +} + +acme-internal.example.com { + acme_server { + ca internal + } +} + +acme-long-lived.example.com { + acme_server { + ca internal-long-lived + lifetime 7d + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "acme-long-lived.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "ca": "internal-long-lived", + "handler": "acme_server", + "lifetime": 604800000000000 + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "acme-internal.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "ca": "internal", + "handler": "acme_server" + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + }, + "pki": { + "certificate_authorities": { + "internal": { + "name": "Internal", + "root_common_name": "Internal Root Cert", + "intermediate_common_name": "Internal Intermediate Cert" + }, + "internal-long-lived": { + "name": "Long-lived", + "root_common_name": "Internal Root Cert 2", + "intermediate_common_name": "Internal Intermediate Cert 2" + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/acme_server_multi_custom_challenges.caddyfiletest b/caddytest/integration/caddyfile_adapt/acme_server_multi_custom_challenges.caddyfiletest new file mode 100644 index 00000000000..7fe3ca663db --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/acme_server_multi_custom_challenges.caddyfiletest @@ -0,0 +1,66 @@ +{ + pki { + ca custom-ca { + name "Custom CA" + } + } +} + +acme.example.com { + acme_server { + ca custom-ca + challenges dns-01 http-01 + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "acme.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "ca": "custom-ca", + "challenges": [ + "dns-01", + "http-01" + ], + "handler": "acme_server" + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + }, + "pki": { + "certificate_authorities": { + "custom-ca": { + "name": "Custom CA" + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/acme_server_sign_with_root.caddyfiletest b/caddytest/integration/caddyfile_adapt/acme_server_sign_with_root.caddyfiletest new file mode 100644 index 00000000000..5b50401077c --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/acme_server_sign_with_root.caddyfiletest @@ -0,0 +1,67 @@ +{ + pki { + ca internal { + name "Internal" + root_cn "Internal Root Cert" + intermediate_cn "Internal Intermediate Cert" + } + } +} +acme.example.com { + acme_server { + ca internal + sign_with_root + } +} + +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "acme.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "ca": "internal", + "handler": "acme_server", + "sign_with_root": true + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + }, + "pki": { + "certificate_authorities": { + "internal": { + "name": "Internal", + "root_common_name": "Internal Root Cert", + "intermediate_common_name": "Internal Intermediate Cert" + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/auto_https_disable_redirects.caddyfiletest b/caddytest/integration/caddyfile_adapt/auto_https_disable_redirects.caddyfiletest new file mode 100644 index 00000000000..61637bbac0f --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/auto_https_disable_redirects.caddyfiletest @@ -0,0 +1,34 @@ +{ + auto_https disable_redirects +} + +localhost +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "terminal": true + } + ], + "automatic_https": { + "disable_redirects": true + } + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/auto_https_ignore_loaded_certs.caddyfiletest b/caddytest/integration/caddyfile_adapt/auto_https_ignore_loaded_certs.caddyfiletest new file mode 100644 index 00000000000..1c6543851ae --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/auto_https_ignore_loaded_certs.caddyfiletest @@ -0,0 +1,34 @@ +{ + auto_https ignore_loaded_certs +} + +localhost +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "terminal": true + } + ], + "automatic_https": { + "ignore_loaded_certificates": true + } + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/auto_https_off.caddyfiletest b/caddytest/integration/caddyfile_adapt/auto_https_off.caddyfiletest new file mode 100644 index 00000000000..d4014d2ad8a --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/auto_https_off.caddyfiletest @@ -0,0 +1,37 @@ +{ + auto_https off +} + +localhost +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "terminal": true + } + ], + "tls_connection_policies": [ + {} + ], + "automatic_https": { + "disable": true + } + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/auto_https_prefer_wildcard.caddyfiletest b/caddytest/integration/caddyfile_adapt/auto_https_prefer_wildcard.caddyfiletest new file mode 100644 index 00000000000..04f2c46655b --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/auto_https_prefer_wildcard.caddyfiletest @@ -0,0 +1,109 @@ +{ + auto_https prefer_wildcard +} + +*.example.com { + tls { + dns mock + } + respond "fallback" +} + +foo.example.com { + respond "foo" +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "foo.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "foo", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "*.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "fallback", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ], + "automatic_https": { + "skip_certificates": [ + "foo.example.com" + ], + "prefer_wildcard": true + } + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "*.example.com" + ], + "issuers": [ + { + "challenges": { + "dns": { + "provider": { + "name": "mock" + } + } + }, + "module": "acme" + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/auto_https_prefer_wildcard_multi.caddyfiletest b/caddytest/integration/caddyfile_adapt/auto_https_prefer_wildcard_multi.caddyfiletest new file mode 100644 index 00000000000..4f8c26a5d98 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/auto_https_prefer_wildcard_multi.caddyfiletest @@ -0,0 +1,268 @@ +{ + auto_https prefer_wildcard +} + +# Covers two domains +*.one.example.com { + tls { + dns mock + } + respond "one fallback" +} + +# Is covered, should not get its own AP +foo.one.example.com { + respond "foo one" +} + +# This one has its own tls config so it doesn't get covered (escape hatch) +bar.one.example.com { + respond "bar one" + tls bar@bar.com +} + +# Covers nothing but AP gets consolidated with the first +*.two.example.com { + tls { + dns mock + } + respond "two fallback" +} + +# Is HTTP so it should not cover +http://*.three.example.com { + respond "three fallback" +} + +# Has no wildcard coverage so it gets an AP +foo.three.example.com { + respond "foo three" +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "foo.three.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "foo three", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "foo.one.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "foo one", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "bar.one.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "bar one", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "*.one.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "one fallback", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "*.two.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "two fallback", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ], + "automatic_https": { + "skip_certificates": [ + "foo.one.example.com", + "bar.one.example.com" + ], + "prefer_wildcard": true + } + }, + "srv1": { + "listen": [ + ":80" + ], + "routes": [ + { + "match": [ + { + "host": [ + "*.three.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "three fallback", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ], + "automatic_https": { + "prefer_wildcard": true + } + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "foo.three.example.com" + ] + }, + { + "subjects": [ + "bar.one.example.com" + ], + "issuers": [ + { + "email": "bar@bar.com", + "module": "acme" + }, + { + "ca": "https://acme.zerossl.com/v2/DV90", + "email": "bar@bar.com", + "module": "acme" + } + ] + }, + { + "subjects": [ + "*.one.example.com", + "*.two.example.com" + ], + "issuers": [ + { + "challenges": { + "dns": { + "provider": { + "name": "mock" + } + } + }, + "module": "acme" + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/bind_fd_fdgram_h123.caddyfiletest b/caddytest/integration/caddyfile_adapt/bind_fd_fdgram_h123.caddyfiletest new file mode 100644 index 00000000000..08f30d18a5f --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/bind_fd_fdgram_h123.caddyfiletest @@ -0,0 +1,142 @@ +{ + auto_https disable_redirects + admin off +} + +http://localhost { + bind fd/{env.CADDY_HTTP_FD} { + protocols h1 + } + log + respond "Hello, HTTP!" +} + +https://localhost { + bind fd/{env.CADDY_HTTPS_FD} { + protocols h1 h2 + } + bind fdgram/{env.CADDY_HTTP3_FD} { + protocols h3 + } + log + respond "Hello, HTTPS!" +} +---------- +{ + "admin": { + "disabled": true + }, + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + "fd/{env.CADDY_HTTPS_FD}", + "fdgram/{env.CADDY_HTTP3_FD}" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Hello, HTTPS!", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ], + "automatic_https": { + "disable_redirects": true + }, + "logs": { + "logger_names": { + "localhost": [ + "" + ] + } + }, + "listen_protocols": [ + [ + "h1", + "h2" + ], + [ + "h3" + ] + ] + }, + "srv1": { + "automatic_https": { + "disable_redirects": true + } + }, + "srv2": { + "listen": [ + "fd/{env.CADDY_HTTP_FD}" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Hello, HTTP!", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ], + "automatic_https": { + "disable_redirects": true, + "skip": [ + "localhost" + ] + }, + "logs": { + "logger_names": { + "localhost": [ + "" + ] + } + }, + "listen_protocols": [ + [ + "h1" + ] + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/bind_ipv6.caddyfiletest b/caddytest/integration/caddyfile_adapt/bind_ipv6.caddyfiletest new file mode 100644 index 00000000000..d9d9bec6b25 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/bind_ipv6.caddyfiletest @@ -0,0 +1,29 @@ +example.com { + bind tcp6/[::] +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + "tcp6/[::]:443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/enable_tls_for_catch_all_site.caddyfiletest b/caddytest/integration/caddyfile_adapt/enable_tls_for_catch_all_site.caddyfiletest new file mode 100644 index 00000000000..b37b40c00a6 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/enable_tls_for_catch_all_site.caddyfiletest @@ -0,0 +1,37 @@ +:8443 { + tls internal { + on_demand + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8443" + ], + "tls_connection_policies": [ + {} + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "issuers": [ + { + "module": "internal" + } + ], + "on_demand": true + } + ] + } + } + } +} + diff --git a/caddytest/integration/caddyfile_adapt/encode_options.caddyfiletest b/caddytest/integration/caddyfile_adapt/encode_options.caddyfiletest new file mode 100644 index 00000000000..ea9038ef8e5 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/encode_options.caddyfiletest @@ -0,0 +1,100 @@ +:80 + +# All the options +encode gzip zstd { + minimum_length 256 + match { + status 2xx 4xx 500 + header Content-Type text/* + header Content-Type application/json* + header Content-Type application/javascript* + header Content-Type application/xhtml+xml* + header Content-Type application/atom+xml* + header Content-Type application/rss+xml* + header Content-Type application/wasm* + header Content-Type image/svg+xml* + } +} + +# Long way with a block for each encoding +encode { + zstd + gzip 5 +} + +encode +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "handle": [ + { + "encodings": { + "gzip": {}, + "zstd": {} + }, + "handler": "encode", + "match": { + "headers": { + "Content-Type": [ + "text/*", + "application/json*", + "application/javascript*", + "application/xhtml+xml*", + "application/atom+xml*", + "application/rss+xml*", + "application/wasm*", + "image/svg+xml*" + ] + }, + "status_code": [ + 2, + 4, + 500 + ] + }, + "minimum_length": 256, + "prefer": [ + "gzip", + "zstd" + ] + }, + { + "encodings": { + "gzip": { + "level": 5 + }, + "zstd": {} + }, + "handler": "encode", + "prefer": [ + "zstd", + "gzip" + ] + }, + { + "encodings": { + "gzip": {}, + "zstd": {} + }, + "handler": "encode", + "prefer": [ + "zstd", + "gzip" + ] + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/error_example.caddyfiletest b/caddytest/integration/caddyfile_adapt/error_example.caddyfiletest new file mode 100644 index 00000000000..bd42aee5556 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/error_example.caddyfiletest @@ -0,0 +1,138 @@ +example.com { + root * /srv + + # Trigger errors for certain paths + error /private* "Unauthorized" 403 + error /hidden* "Not found" 404 + + # Handle the error by serving an HTML page + handle_errors { + rewrite * /{http.error.status_code}.html + file_server + } + + file_server +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "vars", + "root": "/srv" + } + ] + }, + { + "handle": [ + { + "error": "Unauthorized", + "handler": "error", + "status_code": 403 + } + ], + "match": [ + { + "path": [ + "/private*" + ] + } + ] + }, + { + "handle": [ + { + "error": "Not found", + "handler": "error", + "status_code": 404 + } + ], + "match": [ + { + "path": [ + "/hidden*" + ] + } + ] + }, + { + "handle": [ + { + "handler": "file_server", + "hide": [ + "./Caddyfile" + ] + } + ] + } + ] + } + ], + "terminal": true + } + ], + "errors": { + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "group": "group0", + "handle": [ + { + "handler": "rewrite", + "uri": "/{http.error.status_code}.html" + } + ] + }, + { + "handle": [ + { + "handler": "file_server", + "hide": [ + "./Caddyfile" + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/error_multi_site_blocks.caddyfiletest b/caddytest/integration/caddyfile_adapt/error_multi_site_blocks.caddyfiletest new file mode 100644 index 00000000000..0e84a13c2d3 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/error_multi_site_blocks.caddyfiletest @@ -0,0 +1,245 @@ +foo.localhost { + root * /srv + error /private* "Unauthorized" 410 + error /fivehundred* "Internal Server Error" 500 + + handle_errors 5xx { + respond "Error In range [500 .. 599]" + } + handle_errors 410 { + respond "404 or 410 error" + } +} + +bar.localhost { + root * /srv + error /private* "Unauthorized" 410 + error /fivehundred* "Internal Server Error" 500 + + handle_errors 5xx { + respond "Error In range [500 .. 599] from second site" + } + handle_errors 410 { + respond "404 or 410 error from second site" + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "foo.localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "vars", + "root": "/srv" + } + ] + }, + { + "handle": [ + { + "error": "Internal Server Error", + "handler": "error", + "status_code": 500 + } + ], + "match": [ + { + "path": [ + "/fivehundred*" + ] + } + ] + }, + { + "handle": [ + { + "error": "Unauthorized", + "handler": "error", + "status_code": 410 + } + ], + "match": [ + { + "path": [ + "/private*" + ] + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "bar.localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "vars", + "root": "/srv" + } + ] + }, + { + "handle": [ + { + "error": "Internal Server Error", + "handler": "error", + "status_code": 500 + } + ], + "match": [ + { + "path": [ + "/fivehundred*" + ] + } + ] + }, + { + "handle": [ + { + "error": "Unauthorized", + "handler": "error", + "status_code": 410 + } + ], + "match": [ + { + "path": [ + "/private*" + ] + } + ] + } + ] + } + ], + "terminal": true + } + ], + "errors": { + "routes": [ + { + "match": [ + { + "host": [ + "foo.localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "404 or 410 error", + "handler": "static_response" + } + ], + "match": [ + { + "expression": "{http.error.status_code} in [410]" + } + ] + }, + { + "handle": [ + { + "body": "Error In range [500 .. 599]", + "handler": "static_response" + } + ], + "match": [ + { + "expression": "{http.error.status_code} \u003e= 500 \u0026\u0026 {http.error.status_code} \u003c= 599" + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "bar.localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "404 or 410 error from second site", + "handler": "static_response" + } + ], + "match": [ + { + "expression": "{http.error.status_code} in [410]" + } + ] + }, + { + "handle": [ + { + "body": "Error In range [500 .. 599] from second site", + "handler": "static_response" + } + ], + "match": [ + { + "expression": "{http.error.status_code} \u003e= 500 \u0026\u0026 {http.error.status_code} \u003c= 599" + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/error_range_codes.caddyfiletest b/caddytest/integration/caddyfile_adapt/error_range_codes.caddyfiletest new file mode 100644 index 00000000000..46b70c8e384 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/error_range_codes.caddyfiletest @@ -0,0 +1,120 @@ +{ + http_port 3010 +} +localhost:3010 { + root * /srv + error /private* "Unauthorized" 410 + error /hidden* "Not found" 404 + + handle_errors 4xx { + respond "Error in the [400 .. 499] range" + } +} +---------- +{ + "apps": { + "http": { + "http_port": 3010, + "servers": { + "srv0": { + "listen": [ + ":3010" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "vars", + "root": "/srv" + } + ] + }, + { + "handle": [ + { + "error": "Unauthorized", + "handler": "error", + "status_code": 410 + } + ], + "match": [ + { + "path": [ + "/private*" + ] + } + ] + }, + { + "handle": [ + { + "error": "Not found", + "handler": "error", + "status_code": 404 + } + ], + "match": [ + { + "path": [ + "/hidden*" + ] + } + ] + } + ] + } + ], + "terminal": true + } + ], + "errors": { + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Error in the [400 .. 499] range", + "handler": "static_response" + } + ], + "match": [ + { + "expression": "{http.error.status_code} \u003e= 400 \u0026\u0026 {http.error.status_code} \u003c= 499" + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/error_range_simple_codes.caddyfiletest b/caddytest/integration/caddyfile_adapt/error_range_simple_codes.caddyfiletest new file mode 100644 index 00000000000..70158830c26 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/error_range_simple_codes.caddyfiletest @@ -0,0 +1,153 @@ +{ + http_port 2099 +} +localhost:2099 { + root * /srv + error /private* "Unauthorized" 410 + error /threehundred* "Moved Permanently" 301 + error /internalerr* "Internal Server Error" 500 + + handle_errors 500 3xx { + respond "Error code is equal to 500 or in the [300..399] range" + } + handle_errors 4xx { + respond "Error in the [400 .. 499] range" + } +} +---------- +{ + "apps": { + "http": { + "http_port": 2099, + "servers": { + "srv0": { + "listen": [ + ":2099" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "vars", + "root": "/srv" + } + ] + }, + { + "handle": [ + { + "error": "Moved Permanently", + "handler": "error", + "status_code": 301 + } + ], + "match": [ + { + "path": [ + "/threehundred*" + ] + } + ] + }, + { + "handle": [ + { + "error": "Internal Server Error", + "handler": "error", + "status_code": 500 + } + ], + "match": [ + { + "path": [ + "/internalerr*" + ] + } + ] + }, + { + "handle": [ + { + "error": "Unauthorized", + "handler": "error", + "status_code": 410 + } + ], + "match": [ + { + "path": [ + "/private*" + ] + } + ] + } + ] + } + ], + "terminal": true + } + ], + "errors": { + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Error in the [400 .. 499] range", + "handler": "static_response" + } + ], + "match": [ + { + "expression": "{http.error.status_code} \u003e= 400 \u0026\u0026 {http.error.status_code} \u003c= 499" + } + ] + }, + { + "handle": [ + { + "body": "Error code is equal to 500 or in the [300..399] range", + "handler": "static_response" + } + ], + "match": [ + { + "expression": "{http.error.status_code} \u003e= 300 \u0026\u0026 {http.error.status_code} \u003c= 399 || {http.error.status_code} in [500]" + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/error_simple_codes.caddyfiletest b/caddytest/integration/caddyfile_adapt/error_simple_codes.caddyfiletest new file mode 100644 index 00000000000..5ac5863e32d --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/error_simple_codes.caddyfiletest @@ -0,0 +1,120 @@ +{ + http_port 3010 +} +localhost:3010 { + root * /srv + error /private* "Unauthorized" 410 + error /hidden* "Not found" 404 + + handle_errors 404 410 { + respond "404 or 410 error" + } +} +---------- +{ + "apps": { + "http": { + "http_port": 3010, + "servers": { + "srv0": { + "listen": [ + ":3010" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "vars", + "root": "/srv" + } + ] + }, + { + "handle": [ + { + "error": "Unauthorized", + "handler": "error", + "status_code": 410 + } + ], + "match": [ + { + "path": [ + "/private*" + ] + } + ] + }, + { + "handle": [ + { + "error": "Not found", + "handler": "error", + "status_code": 404 + } + ], + "match": [ + { + "path": [ + "/hidden*" + ] + } + ] + } + ] + } + ], + "terminal": true + } + ], + "errors": { + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "404 or 410 error", + "handler": "static_response" + } + ], + "match": [ + { + "expression": "{http.error.status_code} in [404, 410]" + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/error_sort.caddyfiletest b/caddytest/integration/caddyfile_adapt/error_sort.caddyfiletest new file mode 100644 index 00000000000..63701cccba9 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/error_sort.caddyfiletest @@ -0,0 +1,148 @@ +{ + http_port 2099 +} +localhost:2099 { + root * /srv + error /private* "Unauthorized" 410 + error /hidden* "Not found" 404 + error /internalerr* "Internal Server Error" 500 + + handle_errors { + respond "Fallback route: code outside the [400..499] range" + } + handle_errors 4xx { + respond "Error in the [400 .. 499] range" + } +} +---------- +{ + "apps": { + "http": { + "http_port": 2099, + "servers": { + "srv0": { + "listen": [ + ":2099" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "vars", + "root": "/srv" + } + ] + }, + { + "handle": [ + { + "error": "Internal Server Error", + "handler": "error", + "status_code": 500 + } + ], + "match": [ + { + "path": [ + "/internalerr*" + ] + } + ] + }, + { + "handle": [ + { + "error": "Unauthorized", + "handler": "error", + "status_code": 410 + } + ], + "match": [ + { + "path": [ + "/private*" + ] + } + ] + }, + { + "handle": [ + { + "error": "Not found", + "handler": "error", + "status_code": 404 + } + ], + "match": [ + { + "path": [ + "/hidden*" + ] + } + ] + } + ] + } + ], + "terminal": true + } + ], + "errors": { + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Error in the [400 .. 499] range", + "handler": "static_response" + } + ], + "match": [ + { + "expression": "{http.error.status_code} \u003e= 400 \u0026\u0026 {http.error.status_code} \u003c= 499" + } + ] + }, + { + "handle": [ + { + "body": "Fallback route: code outside the [400..499] range", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/expression_quotes.caddyfiletest b/caddytest/integration/caddyfile_adapt/expression_quotes.caddyfiletest new file mode 100644 index 00000000000..4bc47a3dc8e --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/expression_quotes.caddyfiletest @@ -0,0 +1,162 @@ +(snippet) { + @g `{http.error.status_code} == 404` +} + +example.com + +@a expression {http.error.status_code} == 400 +abort @a + +@b expression {http.error.status_code} == "401" +abort @b + +@c expression {http.error.status_code} == `402` +abort @c + +@d expression "{http.error.status_code} == 403" +abort @d + +@e expression `{http.error.status_code} == 404` +abort @e + +@f `{http.error.status_code} == 404` +abort @f + +import snippet +abort @g +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "abort": true, + "handler": "static_response" + } + ], + "match": [ + { + "expression": "{http.error.status_code} == 400" + } + ] + }, + { + "handle": [ + { + "abort": true, + "handler": "static_response" + } + ], + "match": [ + { + "expression": "{http.error.status_code} == \"401\"" + } + ] + }, + { + "handle": [ + { + "abort": true, + "handler": "static_response" + } + ], + "match": [ + { + "expression": "{http.error.status_code} == `402`" + } + ] + }, + { + "handle": [ + { + "abort": true, + "handler": "static_response" + } + ], + "match": [ + { + "expression": { + "expr": "{http.error.status_code} == 403", + "name": "d" + } + } + ] + }, + { + "handle": [ + { + "abort": true, + "handler": "static_response" + } + ], + "match": [ + { + "expression": { + "expr": "{http.error.status_code} == 404", + "name": "e" + } + } + ] + }, + { + "handle": [ + { + "abort": true, + "handler": "static_response" + } + ], + "match": [ + { + "expression": { + "expr": "{http.error.status_code} == 404", + "name": "f" + } + } + ] + }, + { + "handle": [ + { + "abort": true, + "handler": "static_response" + } + ], + "match": [ + { + "expression": { + "expr": "{http.error.status_code} == 404", + "name": "g" + } + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/file_server_disable_canonical_uris.caddyfiletest b/caddytest/integration/caddyfile_adapt/file_server_disable_canonical_uris.caddyfiletest new file mode 100644 index 00000000000..c30b9e95fbf --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/file_server_disable_canonical_uris.caddyfiletest @@ -0,0 +1,32 @@ +:80 + +file_server { + disable_canonical_uris +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "handle": [ + { + "canonical_uris": false, + "handler": "file_server", + "hide": [ + "./Caddyfile" + ] + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/file_server_etag_file_extensions.caddyfiletest b/caddytest/integration/caddyfile_adapt/file_server_etag_file_extensions.caddyfiletest new file mode 100644 index 00000000000..d0dc79216c8 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/file_server_etag_file_extensions.caddyfiletest @@ -0,0 +1,40 @@ +:8080 { + root * ./ + file_server { + etag_file_extensions .b3sum .sha256 + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8080" + ], + "routes": [ + { + "handle": [ + { + "handler": "vars", + "root": "./" + }, + { + "etag_file_extensions": [ + ".b3sum", + ".sha256" + ], + "handler": "file_server", + "hide": [ + "./Caddyfile" + ] + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/file_server_file_limit.caddyfiletest b/caddytest/integration/caddyfile_adapt/file_server_file_limit.caddyfiletest new file mode 100644 index 00000000000..cd73fbff684 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/file_server_file_limit.caddyfiletest @@ -0,0 +1,36 @@ +:80 + +file_server { + browse { + file_limit 4000 + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "handle": [ + { + "browse": { + "file_limit": 4000 + }, + "handler": "file_server", + "hide": [ + "./Caddyfile" + ] + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/file_server_pass_thru.caddyfiletest b/caddytest/integration/caddyfile_adapt/file_server_pass_thru.caddyfiletest new file mode 100644 index 00000000000..cc7051b2aec --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/file_server_pass_thru.caddyfiletest @@ -0,0 +1,32 @@ +:80 + +file_server { + pass_thru +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "handle": [ + { + "handler": "file_server", + "hide": [ + "./Caddyfile" + ], + "pass_thru": true + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/file_server_precompressed.caddyfiletest b/caddytest/integration/caddyfile_adapt/file_server_precompressed.caddyfiletest new file mode 100644 index 00000000000..3154de96edc --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/file_server_precompressed.caddyfiletest @@ -0,0 +1,61 @@ +:80 + +file_server { + precompressed zstd br gzip +} + +file_server { + precompressed +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "handle": [ + { + "handler": "file_server", + "hide": [ + "./Caddyfile" + ], + "precompressed": { + "br": {}, + "gzip": {}, + "zstd": {} + }, + "precompressed_order": [ + "zstd", + "br", + "gzip" + ] + }, + { + "handler": "file_server", + "hide": [ + "./Caddyfile" + ], + "precompressed": { + "br": {}, + "gzip": {}, + "zstd": {} + }, + "precompressed_order": [ + "br", + "zstd", + "gzip" + ] + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/file_server_sort.caddyfiletest b/caddytest/integration/caddyfile_adapt/file_server_sort.caddyfiletest new file mode 100644 index 00000000000..7f07cba8051 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/file_server_sort.caddyfiletest @@ -0,0 +1,39 @@ +:80 + +file_server { + browse { + sort size desc + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "handle": [ + { + "browse": { + "sort": [ + "size", + "desc" + ] + }, + "handler": "file_server", + "hide": [ + "./Caddyfile" + ] + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/file_server_status.caddyfiletest b/caddytest/integration/caddyfile_adapt/file_server_status.caddyfiletest new file mode 100644 index 00000000000..ede1f4adde3 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/file_server_status.caddyfiletest @@ -0,0 +1,112 @@ +localhost + +root * /srv + +handle /nope* { + file_server { + status 403 + } +} + +handle /custom-status* { + file_server { + status {env.CUSTOM_STATUS} + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "vars", + "root": "/srv" + } + ] + }, + { + "group": "group2", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "file_server", + "hide": [ + "./Caddyfile" + ], + "status_code": "{env.CUSTOM_STATUS}" + } + ] + } + ] + } + ], + "match": [ + { + "path": [ + "/custom-status*" + ] + } + ] + }, + { + "group": "group2", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "file_server", + "hide": [ + "./Caddyfile" + ], + "status_code": 403 + } + ] + } + ] + } + ], + "match": [ + { + "path": [ + "/nope*" + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/forward_auth_authelia.caddyfiletest b/caddytest/integration/caddyfile_adapt/forward_auth_authelia.caddyfiletest new file mode 100644 index 00000000000..240bdc62f4a --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/forward_auth_authelia.caddyfiletest @@ -0,0 +1,203 @@ +app.example.com { + forward_auth authelia:9091 { + uri /api/authz/forward-auth + copy_headers Remote-User Remote-Groups Remote-Name Remote-Email + } + + reverse_proxy backend:8080 +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "app.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handle_response": [ + { + "match": { + "status_code": [ + 2 + ] + }, + "routes": [ + { + "handle": [ + { + "handler": "vars" + } + ] + }, + { + "handle": [ + { + "handler": "headers", + "request": { + "set": { + "Remote-Email": [ + "{http.reverse_proxy.header.Remote-Email}" + ] + } + } + } + ], + "match": [ + { + "not": [ + { + "vars": { + "{http.reverse_proxy.header.Remote-Email}": [ + "" + ] + } + } + ] + } + ] + }, + { + "handle": [ + { + "handler": "headers", + "request": { + "set": { + "Remote-Groups": [ + "{http.reverse_proxy.header.Remote-Groups}" + ] + } + } + } + ], + "match": [ + { + "not": [ + { + "vars": { + "{http.reverse_proxy.header.Remote-Groups}": [ + "" + ] + } + } + ] + } + ] + }, + { + "handle": [ + { + "handler": "headers", + "request": { + "set": { + "Remote-Name": [ + "{http.reverse_proxy.header.Remote-Name}" + ] + } + } + } + ], + "match": [ + { + "not": [ + { + "vars": { + "{http.reverse_proxy.header.Remote-Name}": [ + "" + ] + } + } + ] + } + ] + }, + { + "handle": [ + { + "handler": "headers", + "request": { + "set": { + "Remote-User": [ + "{http.reverse_proxy.header.Remote-User}" + ] + } + } + } + ], + "match": [ + { + "not": [ + { + "vars": { + "{http.reverse_proxy.header.Remote-User}": [ + "" + ] + } + } + ] + } + ] + } + ] + } + ], + "handler": "reverse_proxy", + "headers": { + "request": { + "set": { + "X-Forwarded-Method": [ + "{http.request.method}" + ], + "X-Forwarded-Uri": [ + "{http.request.uri}" + ] + } + } + }, + "rewrite": { + "method": "GET", + "uri": "/api/authz/forward-auth" + }, + "upstreams": [ + { + "dial": "authelia:9091" + } + ] + }, + { + "handler": "reverse_proxy", + "upstreams": [ + { + "dial": "backend:8080" + } + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/forward_auth_rename_headers.caddyfiletest b/caddytest/integration/caddyfile_adapt/forward_auth_rename_headers.caddyfiletest new file mode 100644 index 00000000000..c2be2ed438f --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/forward_auth_rename_headers.caddyfiletest @@ -0,0 +1,206 @@ +:8881 + +forward_auth localhost:9000 { + uri /auth + copy_headers A>1 B C>3 { + D + E>5 + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8881" + ], + "routes": [ + { + "handle": [ + { + "handle_response": [ + { + "match": { + "status_code": [ + 2 + ] + }, + "routes": [ + { + "handle": [ + { + "handler": "vars" + } + ] + }, + { + "handle": [ + { + "handler": "headers", + "request": { + "set": { + "1": [ + "{http.reverse_proxy.header.A}" + ] + } + } + } + ], + "match": [ + { + "not": [ + { + "vars": { + "{http.reverse_proxy.header.A}": [ + "" + ] + } + } + ] + } + ] + }, + { + "handle": [ + { + "handler": "headers", + "request": { + "set": { + "B": [ + "{http.reverse_proxy.header.B}" + ] + } + } + } + ], + "match": [ + { + "not": [ + { + "vars": { + "{http.reverse_proxy.header.B}": [ + "" + ] + } + } + ] + } + ] + }, + { + "handle": [ + { + "handler": "headers", + "request": { + "set": { + "3": [ + "{http.reverse_proxy.header.C}" + ] + } + } + } + ], + "match": [ + { + "not": [ + { + "vars": { + "{http.reverse_proxy.header.C}": [ + "" + ] + } + } + ] + } + ] + }, + { + "handle": [ + { + "handler": "headers", + "request": { + "set": { + "D": [ + "{http.reverse_proxy.header.D}" + ] + } + } + } + ], + "match": [ + { + "not": [ + { + "vars": { + "{http.reverse_proxy.header.D}": [ + "" + ] + } + } + ] + } + ] + }, + { + "handle": [ + { + "handler": "headers", + "request": { + "set": { + "5": [ + "{http.reverse_proxy.header.E}" + ] + } + } + } + ], + "match": [ + { + "not": [ + { + "vars": { + "{http.reverse_proxy.header.E}": [ + "" + ] + } + } + ] + } + ] + } + ] + } + ], + "handler": "reverse_proxy", + "headers": { + "request": { + "set": { + "X-Forwarded-Method": [ + "{http.request.method}" + ], + "X-Forwarded-Uri": [ + "{http.request.uri}" + ] + } + } + }, + "rewrite": { + "method": "GET", + "uri": "/auth" + }, + "upstreams": [ + { + "dial": "localhost:9000" + } + ] + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/global_options.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options.caddyfiletest new file mode 100644 index 00000000000..99f45cdd5e0 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/global_options.caddyfiletest @@ -0,0 +1,83 @@ +{ + debug + http_port 8080 + https_port 8443 + grace_period 5s + shutdown_delay 10s + default_sni localhost + order root first + storage file_system { + root /data + } + storage_check off + storage_clean_interval off + acme_ca https://example.com + acme_ca_root /path/to/ca.crt + ocsp_stapling off + + email test@example.com + admin off + on_demand_tls { + ask https://example.com + } + local_certs + key_type ed25519 +} + +:80 +---------- +{ + "admin": { + "disabled": true + }, + "logging": { + "logs": { + "default": { + "level": "DEBUG" + } + } + }, + "storage": { + "module": "file_system", + "root": "/data" + }, + "apps": { + "http": { + "http_port": 8080, + "https_port": 8443, + "grace_period": 5000000000, + "shutdown_delay": 10000000000, + "servers": { + "srv0": { + "listen": [ + ":80" + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "issuers": [ + { + "module": "internal" + } + ], + "key_type": "ed25519", + "disable_ocsp_stapling": true + } + ], + "on_demand": { + "permission": { + "endpoint": "https://example.com", + "module": "http" + } + } + }, + "disable_ocsp_stapling": true, + "disable_storage_check": true, + "disable_storage_clean": true + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/global_options_acme.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_acme.caddyfiletest new file mode 100644 index 00000000000..004a3a32ece --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/global_options_acme.caddyfiletest @@ -0,0 +1,98 @@ +{ + debug + http_port 8080 + https_port 8443 + default_sni localhost + order root first + storage file_system { + root /data + } + acme_ca https://example.com + acme_eab { + key_id 4K2scIVbBpNd-78scadB2g + mac_key abcdefghijklmnopqrstuvwx-abcdefghijklnopqrstuvwxyz12ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh + } + acme_ca_root /path/to/ca.crt + email test@example.com + admin off + on_demand_tls { + ask https://example.com + } + storage_clean_interval 7d + renew_interval 1d + ocsp_interval 2d + + key_type ed25519 +} + +:80 +---------- +{ + "admin": { + "disabled": true + }, + "logging": { + "logs": { + "default": { + "level": "DEBUG" + } + } + }, + "storage": { + "module": "file_system", + "root": "/data" + }, + "apps": { + "http": { + "http_port": 8080, + "https_port": 8443, + "servers": { + "srv0": { + "listen": [ + ":80" + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "issuers": [ + { + "ca": "https://example.com", + "challenges": { + "http": { + "alternate_port": 8080 + }, + "tls-alpn": { + "alternate_port": 8443 + } + }, + "email": "test@example.com", + "external_account": { + "key_id": "4K2scIVbBpNd-78scadB2g", + "mac_key": "abcdefghijklmnopqrstuvwx-abcdefghijklnopqrstuvwxyz12ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh" + }, + "module": "acme", + "trusted_roots_pem_files": [ + "/path/to/ca.crt" + ] + } + ], + "key_type": "ed25519" + } + ], + "on_demand": { + "permission": { + "endpoint": "https://example.com", + "module": "http" + } + }, + "ocsp_interval": 172800000000000, + "renew_interval": 86400000000000, + "storage_clean_interval": 604800000000000 + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/global_options_admin.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_admin.caddyfiletest new file mode 100644 index 00000000000..be309eaa304 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/global_options_admin.caddyfiletest @@ -0,0 +1,80 @@ +{ + debug + http_port 8080 + https_port 8443 + default_sni localhost + order root first + storage file_system { + root /data + } + acme_ca https://example.com + acme_ca_root /path/to/ca.crt + + email test@example.com + admin { + origins localhost:2019 [::1]:2019 127.0.0.1:2019 192.168.10.128 + } + on_demand_tls { + ask https://example.com + } + local_certs + key_type ed25519 +} + +:80 +---------- +{ + "admin": { + "listen": "localhost:2019", + "origins": [ + "localhost:2019", + "[::1]:2019", + "127.0.0.1:2019", + "192.168.10.128" + ] + }, + "logging": { + "logs": { + "default": { + "level": "DEBUG" + } + } + }, + "storage": { + "module": "file_system", + "root": "/data" + }, + "apps": { + "http": { + "http_port": 8080, + "https_port": 8443, + "servers": { + "srv0": { + "listen": [ + ":80" + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "issuers": [ + { + "module": "internal" + } + ], + "key_type": "ed25519" + } + ], + "on_demand": { + "permission": { + "endpoint": "https://example.com", + "module": "http" + } + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/global_options_admin_with_persist_config_off.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_admin_with_persist_config_off.caddyfiletest new file mode 100644 index 00000000000..998fe2234bf --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/global_options_admin_with_persist_config_off.caddyfiletest @@ -0,0 +1,36 @@ +{ + http_port 8080 + persist_config off + admin { + origins localhost:2019 [::1]:2019 127.0.0.1:2019 192.168.10.128 + } +} + +:80 +---------- +{ + "admin": { + "listen": "localhost:2019", + "origins": [ + "localhost:2019", + "[::1]:2019", + "127.0.0.1:2019", + "192.168.10.128" + ], + "config": { + "persist": false + } + }, + "apps": { + "http": { + "http_port": 8080, + "servers": { + "srv0": { + "listen": [ + ":80" + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/global_options_debug_with_access_log.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_debug_with_access_log.caddyfiletest new file mode 100644 index 00000000000..772cd089b70 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/global_options_debug_with_access_log.caddyfiletest @@ -0,0 +1,45 @@ +{ + debug +} + +:8881 { + log { + format console + } +} +---------- +{ + "logging": { + "logs": { + "default": { + "level": "DEBUG", + "exclude": [ + "http.log.access.log0" + ] + }, + "log0": { + "encoder": { + "format": "console" + }, + "level": "DEBUG", + "include": [ + "http.log.access.log0" + ] + } + } + }, + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8881" + ], + "logs": { + "default_logger_name": "log0" + } + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/global_options_default_bind.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_default_bind.caddyfiletest new file mode 100644 index 00000000000..d0b4e269389 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/global_options_default_bind.caddyfiletest @@ -0,0 +1,54 @@ +{ + default_bind tcp4/0.0.0.0 tcp6/[::] +} + +example.com { +} + +example.org:12345 { +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + "tcp4/0.0.0.0:12345", + "tcp6/[::]:12345" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.org" + ] + } + ], + "terminal": true + } + ] + }, + "srv1": { + "listen": [ + "tcp4/0.0.0.0:443", + "tcp6/[::]:443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/global_options_log_and_site.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_log_and_site.caddyfiletest new file mode 100644 index 00000000000..037c8b653a2 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/global_options_log_and_site.caddyfiletest @@ -0,0 +1,77 @@ +{ + log { + output file caddy.log + include some-log-source + exclude admin.api admin2.api + } + log custom-logger { + output file caddy.log + level WARN + include custom-log-source + } +} + +:8884 { + log { + format json + output file access.log + } +} +---------- +{ + "logging": { + "logs": { + "custom-logger": { + "writer": { + "filename": "caddy.log", + "output": "file" + }, + "level": "WARN", + "include": [ + "custom-log-source" + ] + }, + "default": { + "writer": { + "filename": "caddy.log", + "output": "file" + }, + "include": [ + "some-log-source" + ], + "exclude": [ + "admin.api", + "admin2.api", + "custom-log-source", + "http.log.access.log0" + ] + }, + "log0": { + "writer": { + "filename": "access.log", + "output": "file" + }, + "encoder": { + "format": "json" + }, + "include": [ + "http.log.access.log0" + ] + } + } + }, + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "logs": { + "default_logger_name": "log0" + } + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/global_options_log_basic.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_log_basic.caddyfiletest new file mode 100644 index 00000000000..b8d32dc317c --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/global_options_log_basic.caddyfiletest @@ -0,0 +1,18 @@ +{ + log { + output file foo.log + } +} +---------- +{ + "logging": { + "logs": { + "default": { + "writer": { + "filename": "foo.log", + "output": "file" + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/global_options_log_custom.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_log_custom.caddyfiletest new file mode 100644 index 00000000000..b39cce9e377 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/global_options_log_custom.caddyfiletest @@ -0,0 +1,35 @@ +{ + log custom-logger { + format filter { + wrap console + fields { + request>remote_ip ip_mask { + ipv4 24 + ipv6 32 + } + } + } + } +} +---------- +{ + "logging": { + "logs": { + "custom-logger": { + "encoder": { + "fields": { + "request\u003eremote_ip": { + "filter": "ip_mask", + "ipv4_cidr": 24, + "ipv6_cidr": 32 + } + }, + "format": "filter", + "wrap": { + "format": "console" + } + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/global_options_log_multi.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_log_multi.caddyfiletest new file mode 100644 index 00000000000..c20251ab8d1 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/global_options_log_multi.caddyfiletest @@ -0,0 +1,26 @@ +{ + log first { + output file foo.log + } + log second { + format json + } +} +---------- +{ + "logging": { + "logs": { + "first": { + "writer": { + "filename": "foo.log", + "output": "file" + } + }, + "second": { + "encoder": { + "format": "json" + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/global_options_log_sampling.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_log_sampling.caddyfiletest new file mode 100644 index 00000000000..12b73b2b7a4 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/global_options_log_sampling.caddyfiletest @@ -0,0 +1,23 @@ +{ + log { + sampling { + interval 300 + first 50 + thereafter 40 + } + } +} +---------- +{ + "logging": { + "logs": { + "default": { + "sampling": { + "interval": 300, + "first": 50, + "thereafter": 40 + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/global_options_persist_config.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_persist_config.caddyfiletest new file mode 100644 index 00000000000..c905b476eec --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/global_options_persist_config.caddyfiletest @@ -0,0 +1,25 @@ +{ + persist_config off +} + +:8881 { +} +---------- +{ + "admin": { + "config": { + "persist": false + } + }, + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8881" + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/global_options_preferred_chains.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_preferred_chains.caddyfiletest new file mode 100644 index 00000000000..1f5d0093e26 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/global_options_preferred_chains.caddyfiletest @@ -0,0 +1,50 @@ +{ + preferred_chains smallest +} + +example.com +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "example.com" + ], + "issuers": [ + { + "module": "acme", + "preferred_chains": { + "smallest": true + } + } + ] + } + ] + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/global_options_skip_install_trust.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_skip_install_trust.caddyfiletest new file mode 100644 index 00000000000..3a175a0d7d9 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/global_options_skip_install_trust.caddyfiletest @@ -0,0 +1,168 @@ +{ + skip_install_trust + pki { + ca { + name "Local" + root_cn "Custom Local Root Name" + intermediate_cn "Custom Local Intermediate Name" + root { + cert /path/to/cert.pem + key /path/to/key.pem + format pem_file + } + intermediate { + cert /path/to/cert.pem + key /path/to/key.pem + format pem_file + } + } + ca foo { + name "Foo" + root_cn "Custom Foo Root Name" + intermediate_cn "Custom Foo Intermediate Name" + } + } +} + +a.example.com { + tls internal +} + +acme.example.com { + acme_server { + ca foo + } +} + +acme-bar.example.com { + acme_server { + ca bar + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "acme-bar.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "ca": "bar", + "handler": "acme_server" + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "acme.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "ca": "foo", + "handler": "acme_server" + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "a.example.com" + ] + } + ], + "terminal": true + } + ] + } + } + }, + "pki": { + "certificate_authorities": { + "bar": { + "install_trust": false + }, + "foo": { + "name": "Foo", + "root_common_name": "Custom Foo Root Name", + "intermediate_common_name": "Custom Foo Intermediate Name", + "install_trust": false + }, + "local": { + "name": "Local", + "root_common_name": "Custom Local Root Name", + "intermediate_common_name": "Custom Local Intermediate Name", + "install_trust": false, + "root": { + "certificate": "/path/to/cert.pem", + "private_key": "/path/to/key.pem", + "format": "pem_file" + }, + "intermediate": { + "certificate": "/path/to/cert.pem", + "private_key": "/path/to/key.pem", + "format": "pem_file" + } + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "acme-bar.example.com", + "acme.example.com" + ] + }, + { + "subjects": [ + "a.example.com" + ], + "issuers": [ + { + "module": "internal" + } + ] + } + ] + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/global_server_options_multi.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_server_options_multi.caddyfiletest new file mode 100644 index 00000000000..ca5306fd7ab --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/global_server_options_multi.caddyfiletest @@ -0,0 +1,82 @@ +{ + servers { + timeouts { + idle 90s + } + strict_sni_host insecure_off + } + servers :80 { + timeouts { + idle 60s + } + } + servers :443 { + timeouts { + idle 30s + } + strict_sni_host + } +} + +foo.com { +} + +http://bar.com { +} + +:8080 { +} + +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "idle_timeout": 30000000000, + "routes": [ + { + "match": [ + { + "host": [ + "foo.com" + ] + } + ], + "terminal": true + } + ], + "strict_sni_host": true + }, + "srv1": { + "listen": [ + ":80" + ], + "idle_timeout": 60000000000, + "routes": [ + { + "match": [ + { + "host": [ + "bar.com" + ] + } + ], + "terminal": true + } + ] + }, + "srv2": { + "listen": [ + ":8080" + ], + "idle_timeout": 90000000000, + "strict_sni_host": false + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/global_server_options_single.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_server_options_single.caddyfiletest new file mode 100644 index 00000000000..2f3306fd932 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/global_server_options_single.caddyfiletest @@ -0,0 +1,92 @@ +{ + servers { + listener_wrappers { + http_redirect + tls + } + timeouts { + read_body 30s + read_header 30s + write 30s + idle 30s + } + max_header_size 100MB + enable_full_duplex + log_credentials + protocols h1 h2 h2c h3 + strict_sni_host + trusted_proxies static private_ranges + client_ip_headers Custom-Real-Client-IP X-Forwarded-For + client_ip_headers A-Third-One + } +} + +foo.com { +} + +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "listener_wrappers": [ + { + "wrapper": "http_redirect" + }, + { + "wrapper": "tls" + } + ], + "read_timeout": 30000000000, + "read_header_timeout": 30000000000, + "write_timeout": 30000000000, + "idle_timeout": 30000000000, + "max_header_bytes": 100000000, + "enable_full_duplex": true, + "routes": [ + { + "match": [ + { + "host": [ + "foo.com" + ] + } + ], + "terminal": true + } + ], + "strict_sni_host": true, + "trusted_proxies": { + "ranges": [ + "192.168.0.0/16", + "172.16.0.0/12", + "10.0.0.0/8", + "127.0.0.1/8", + "fd00::/8", + "::1" + ], + "source": "static" + }, + "client_ip_headers": [ + "Custom-Real-Client-IP", + "X-Forwarded-For", + "A-Third-One" + ], + "logs": { + "should_log_credentials": true + }, + "protocols": [ + "h1", + "h2", + "h2c", + "h3" + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/handle_nested_in_route.caddyfiletest b/caddytest/integration/caddyfile_adapt/handle_nested_in_route.caddyfiletest new file mode 100644 index 00000000000..1f77d5c4391 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/handle_nested_in_route.caddyfiletest @@ -0,0 +1,78 @@ +:8881 { + route { + handle /foo/* { + respond "Foo" + } + handle { + respond "Bar" + } + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8881" + ], + "routes": [ + { + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "group": "group2", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Foo", + "handler": "static_response" + } + ] + } + ] + } + ], + "match": [ + { + "path": [ + "/foo/*" + ] + } + ] + }, + { + "group": "group2", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Bar", + "handler": "static_response" + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/handle_path.caddyfiletest b/caddytest/integration/caddyfile_adapt/handle_path.caddyfiletest new file mode 100644 index 00000000000..f8817433988 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/handle_path.caddyfiletest @@ -0,0 +1,52 @@ +:80 +handle_path /api/v1/* { + respond "API v1" +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "match": [ + { + "path": [ + "/api/v1/*" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "rewrite", + "strip_path_prefix": "/api/v1" + } + ] + }, + { + "handle": [ + { + "body": "API v1", + "handler": "static_response" + } + ] + } + ] + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/handle_path_sorting.caddyfiletest b/caddytest/integration/caddyfile_adapt/handle_path_sorting.caddyfiletest new file mode 100644 index 00000000000..0a89f2ae58f --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/handle_path_sorting.caddyfiletest @@ -0,0 +1,105 @@ +:80 { + handle /api/* { + respond "api" + } + + handle_path /static/* { + respond "static" + } + + handle { + respond "handle" + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "group": "group3", + "match": [ + { + "path": [ + "/static/*" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "rewrite", + "strip_path_prefix": "/static" + } + ] + }, + { + "handle": [ + { + "body": "static", + "handler": "static_response" + } + ] + } + ] + } + ] + }, + { + "group": "group3", + "match": [ + { + "path": [ + "/api/*" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "api", + "handler": "static_response" + } + ] + } + ] + } + ] + }, + { + "group": "group3", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "handle", + "handler": "static_response" + } + ] + } + ] + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/header.caddyfiletest b/caddytest/integration/caddyfile_adapt/header.caddyfiletest new file mode 100644 index 00000000000..a0af24ff682 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/header.caddyfiletest @@ -0,0 +1,188 @@ +:80 { + header Denis "Ritchie" + header +Edsger "Dijkstra" + header ?John "von Neumann" + header -Wolfram + header { + Grace: "Hopper" # some users habitually suffix field names with a colon + +Ray "Solomonoff" + ?Tim "Berners-Lee" + defer + } + @images path /images/* + header @images { + Cache-Control "public, max-age=3600, stale-while-revalidate=86400" + match { + status 200 + } + } + header { + +Link "Foo" + +Link "Bar" + match status 200 + } + header >Set Defer + header >Replace Deferred Replacement +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "match": [ + { + "path": [ + "/images/*" + ] + } + ], + "handle": [ + { + "handler": "headers", + "response": { + "require": { + "status_code": [ + 200 + ] + }, + "set": { + "Cache-Control": [ + "public, max-age=3600, stale-while-revalidate=86400" + ] + } + } + } + ] + }, + { + "handle": [ + { + "handler": "headers", + "response": { + "set": { + "Denis": [ + "Ritchie" + ] + } + } + }, + { + "handler": "headers", + "response": { + "add": { + "Edsger": [ + "Dijkstra" + ] + } + } + }, + { + "handler": "headers", + "response": { + "require": { + "headers": { + "John": null + } + }, + "set": { + "John": [ + "von Neumann" + ] + } + } + }, + { + "handler": "headers", + "response": { + "deferred": true, + "delete": [ + "Wolfram" + ] + } + }, + { + "handler": "headers", + "response": { + "add": { + "Ray": [ + "Solomonoff" + ] + }, + "deferred": true, + "set": { + "Grace": [ + "Hopper" + ] + } + } + }, + { + "handler": "headers", + "response": { + "require": { + "headers": { + "Tim": null + } + }, + "set": { + "Tim": [ + "Berners-Lee" + ] + } + } + }, + { + "handler": "headers", + "response": { + "add": { + "Link": [ + "Foo", + "Bar" + ] + }, + "require": { + "status_code": [ + 200 + ] + } + } + }, + { + "handler": "headers", + "response": { + "deferred": true, + "set": { + "Set": [ + "Defer" + ] + } + } + }, + { + "handler": "headers", + "response": { + "deferred": true, + "replace": { + "Replace": [ + { + "replace": "Replacement", + "search_regexp": "Deferred" + } + ] + } + } + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/heredoc.caddyfiletest b/caddytest/integration/caddyfile_adapt/heredoc.caddyfiletest new file mode 100644 index 00000000000..f50d2b7f952 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/heredoc.caddyfiletest @@ -0,0 +1,51 @@ +example.com { + respond < + Foo + Foo + + EOF 200 +} + +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "\u003chtml\u003e\n \u003chead\u003e\u003ctitle\u003eFoo\u003c/title\u003e\n \u003cbody\u003eFoo\u003c/body\u003e\n\u003c/html\u003e", + "handler": "static_response", + "status_code": 200 + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/http_only_hostnames.caddyfiletest b/caddytest/integration/caddyfile_adapt/http_only_hostnames.caddyfiletest new file mode 100644 index 00000000000..d867e166fec --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/http_only_hostnames.caddyfiletest @@ -0,0 +1,45 @@ +# https://github.com/caddyserver/caddy/issues/3977 +http://* { + respond "Hello, world!" +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "match": [ + { + "host": [ + "*" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Hello, world!", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/http_only_on_any_address.caddyfiletest b/caddytest/integration/caddyfile_adapt/http_only_on_any_address.caddyfiletest new file mode 100644 index 00000000000..8af2c333195 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/http_only_on_any_address.caddyfiletest @@ -0,0 +1,37 @@ +:80 { + respond /version 200 { + body "hello from localhost" + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "match": [ + { + "path": [ + "/version" + ] + } + ], + "handle": [ + { + "body": "hello from localhost", + "handler": "static_response", + "status_code": 200 + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/http_only_on_domain.caddyfiletest b/caddytest/integration/caddyfile_adapt/http_only_on_domain.caddyfiletest new file mode 100644 index 00000000000..d2792423b96 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/http_only_on_domain.caddyfiletest @@ -0,0 +1,54 @@ +http://a.caddy.localhost { + respond /version 200 { + body "hello from localhost" + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "match": [ + { + "host": [ + "a.caddy.localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "hello from localhost", + "handler": "static_response", + "status_code": 200 + } + ], + "match": [ + { + "path": [ + "/version" + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/http_only_on_hostless_block.caddyfiletest b/caddytest/integration/caddyfile_adapt/http_only_on_hostless_block.caddyfiletest new file mode 100644 index 00000000000..9ccc59eb91f --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/http_only_on_hostless_block.caddyfiletest @@ -0,0 +1,28 @@ +# Issue #4113 +:80, http://example.com { + respond "foo" +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "handle": [ + { + "body": "foo", + "handler": "static_response" + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/http_only_on_localhost.caddyfiletest b/caddytest/integration/caddyfile_adapt/http_only_on_localhost.caddyfiletest new file mode 100644 index 00000000000..13326f656b4 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/http_only_on_localhost.caddyfiletest @@ -0,0 +1,54 @@ +localhost:80 { + respond /version 200 { + body "hello from localhost" + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "hello from localhost", + "handler": "static_response", + "status_code": 200 + } + ], + "match": [ + { + "path": [ + "/version" + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/http_only_on_non_standard_port.caddyfiletest b/caddytest/integration/caddyfile_adapt/http_only_on_non_standard_port.caddyfiletest new file mode 100644 index 00000000000..65d86bbeb0d --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/http_only_on_non_standard_port.caddyfiletest @@ -0,0 +1,59 @@ +http://a.caddy.localhost:81 { + respond /version 200 { + body "hello from localhost" + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":81" + ], + "routes": [ + { + "match": [ + { + "host": [ + "a.caddy.localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "hello from localhost", + "handler": "static_response", + "status_code": 200 + } + ], + "match": [ + { + "path": [ + "/version" + ] + } + ] + } + ] + } + ], + "terminal": true + } + ], + "automatic_https": { + "skip": [ + "a.caddy.localhost" + ] + } + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/http_valid_directive_like_site_address.caddyfiletest b/caddytest/integration/caddyfile_adapt/http_valid_directive_like_site_address.caddyfiletest new file mode 100644 index 00000000000..675523a57d3 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/http_valid_directive_like_site_address.caddyfiletest @@ -0,0 +1,46 @@ +http://handle { + file_server +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "match": [ + { + "host": [ + "handle" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "file_server", + "hide": [ + "./Caddyfile" + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/https_on_domain.caddyfiletest b/caddytest/integration/caddyfile_adapt/https_on_domain.caddyfiletest new file mode 100644 index 00000000000..fc584c53e7b --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/https_on_domain.caddyfiletest @@ -0,0 +1,54 @@ +a.caddy.localhost { + respond /version 200 { + body "hello from localhost" + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "a.caddy.localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "hello from localhost", + "handler": "static_response", + "status_code": 200 + } + ], + "match": [ + { + "path": [ + "/version" + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/import_args_file.caddyfiletest b/caddytest/integration/caddyfile_adapt/import_args_file.caddyfiletest new file mode 100644 index 00000000000..1eb78f1918f --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/import_args_file.caddyfiletest @@ -0,0 +1,49 @@ +example.com + +import testdata/import_respond.txt Groot Rocket +import testdata/import_respond.txt you "the confused man" +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "'I am Groot', hears Rocket", + "handler": "static_response" + }, + { + "body": "'I am you', hears the confused man", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/import_args_snippet.caddyfiletest b/caddytest/integration/caddyfile_adapt/import_args_snippet.caddyfiletest new file mode 100644 index 00000000000..9d5c2535206 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/import_args_snippet.caddyfiletest @@ -0,0 +1,87 @@ +(logging) { + log { + output file /var/log/caddy/{args[0]}.access.log + } +} + +a.example.com { + import logging a.example.com +} + +b.example.com { + import logging b.example.com +} +---------- +{ + "logging": { + "logs": { + "default": { + "exclude": [ + "http.log.access.log0", + "http.log.access.log1" + ] + }, + "log0": { + "writer": { + "filename": "/var/log/caddy/a.example.com.access.log", + "output": "file" + }, + "include": [ + "http.log.access.log0" + ] + }, + "log1": { + "writer": { + "filename": "/var/log/caddy/b.example.com.access.log", + "output": "file" + }, + "include": [ + "http.log.access.log1" + ] + } + } + }, + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "a.example.com" + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "b.example.com" + ] + } + ], + "terminal": true + } + ], + "logs": { + "logger_names": { + "a.example.com": [ + "log0" + ], + "b.example.com": [ + "log1" + ] + } + } + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/import_args_snippet_env_placeholder.caddyfiletest b/caddytest/integration/caddyfile_adapt/import_args_snippet_env_placeholder.caddyfiletest new file mode 100644 index 00000000000..1bc907e51fa --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/import_args_snippet_env_placeholder.caddyfiletest @@ -0,0 +1,31 @@ +(foo) { + respond {env.FOO} +} + +:80 { + import foo +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "handle": [ + { + "body": "{env.FOO}", + "handler": "static_response" + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/import_block_snippet.caddyfiletest b/caddytest/integration/caddyfile_adapt/import_block_snippet.caddyfiletest new file mode 100644 index 00000000000..a60c238ceb6 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/import_block_snippet.caddyfiletest @@ -0,0 +1,58 @@ +(snippet) { + header { + {block} + } +} + +example.com { + import snippet { + foo bar + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "headers", + "response": { + "set": { + "Foo": [ + "bar" + ] + } + } + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/import_block_snippet_args.caddyfiletest b/caddytest/integration/caddyfile_adapt/import_block_snippet_args.caddyfiletest new file mode 100644 index 00000000000..7f2e68b7995 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/import_block_snippet_args.caddyfiletest @@ -0,0 +1,56 @@ +(snippet) { + {block} +} + +example.com { + import snippet { + header foo bar + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "headers", + "response": { + "set": { + "Foo": [ + "bar" + ] + } + } + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/import_blocks_snippet.caddyfiletest b/caddytest/integration/caddyfile_adapt/import_blocks_snippet.caddyfiletest new file mode 100644 index 00000000000..4098f90b2c1 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/import_blocks_snippet.caddyfiletest @@ -0,0 +1,76 @@ +(snippet) { + header { + {blocks.foo} + } + header { + {blocks.bar} + } +} + +example.com { + import snippet { + foo { + foo a + } + bar { + bar b + } + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "headers", + "response": { + "set": { + "Foo": [ + "a" + ] + } + } + }, + { + "handler": "headers", + "response": { + "set": { + "Bar": [ + "b" + ] + } + } + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/import_blocks_snippet_nested.caddyfiletest b/caddytest/integration/caddyfile_adapt/import_blocks_snippet_nested.caddyfiletest new file mode 100644 index 00000000000..ac1c5226c1c --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/import_blocks_snippet_nested.caddyfiletest @@ -0,0 +1,82 @@ +(snippet) { + header { + {blocks.bar} + } + import sub_snippet { + bar { + {blocks.foo} + } + } +} +(sub_snippet) { + header { + {blocks.bar} + } +} +example.com { + import snippet { + foo { + foo a + } + bar { + bar b + } + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "headers", + "response": { + "set": { + "Bar": [ + "b" + ] + } + } + }, + { + "handler": "headers", + "response": { + "set": { + "Foo": [ + "a" + ] + } + } + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/intercept_response.caddyfiletest b/caddytest/integration/caddyfile_adapt/intercept_response.caddyfiletest new file mode 100644 index 00000000000..c92b76fe50e --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/intercept_response.caddyfiletest @@ -0,0 +1,230 @@ +localhost + +respond "To intercept" + +intercept { + @500 status 500 + replace_status @500 400 + + @all status 2xx 3xx 4xx 5xx + replace_status @all {http.error.status_code} + + replace_status {http.error.status_code} + + @accel header X-Accel-Redirect * + handle_response @accel { + respond "Header X-Accel-Redirect!" + } + + @another { + header X-Another * + } + handle_response @another { + respond "Header X-Another!" + } + + @401 status 401 + handle_response @401 { + respond "Status 401!" + } + + handle_response { + respond "Any! This should be last in the JSON!" + } + + @403 { + status 403 + } + handle_response @403 { + respond "Status 403!" + } + + @multi { + status 401 403 + status 404 + header Foo * + header Bar * + } + handle_response @multi { + respond "Headers Foo, Bar AND statuses 401, 403 and 404!" + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handle_response": [ + { + "match": { + "status_code": [ + 500 + ] + }, + "status_code": 400 + }, + { + "match": { + "status_code": [ + 2, + 3, + 4, + 5 + ] + }, + "status_code": "{http.error.status_code}" + }, + { + "match": { + "headers": { + "X-Accel-Redirect": [ + "*" + ] + } + }, + "routes": [ + { + "handle": [ + { + "body": "Header X-Accel-Redirect!", + "handler": "static_response" + } + ] + } + ] + }, + { + "match": { + "headers": { + "X-Another": [ + "*" + ] + } + }, + "routes": [ + { + "handle": [ + { + "body": "Header X-Another!", + "handler": "static_response" + } + ] + } + ] + }, + { + "match": { + "status_code": [ + 401 + ] + }, + "routes": [ + { + "handle": [ + { + "body": "Status 401!", + "handler": "static_response" + } + ] + } + ] + }, + { + "match": { + "status_code": [ + 403 + ] + }, + "routes": [ + { + "handle": [ + { + "body": "Status 403!", + "handler": "static_response" + } + ] + } + ] + }, + { + "match": { + "headers": { + "Bar": [ + "*" + ], + "Foo": [ + "*" + ] + }, + "status_code": [ + 401, + 403, + 404 + ] + }, + "routes": [ + { + "handle": [ + { + "body": "Headers Foo, Bar AND statuses 401, 403 and 404!", + "handler": "static_response" + } + ] + } + ] + }, + { + "status_code": "{http.error.status_code}" + }, + { + "routes": [ + { + "handle": [ + { + "body": "Any! This should be last in the JSON!", + "handler": "static_response" + } + ] + } + ] + } + ], + "handler": "intercept" + }, + { + "body": "To intercept", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/invoke_named_routes.caddyfiletest b/caddytest/integration/caddyfile_adapt/invoke_named_routes.caddyfiletest new file mode 100644 index 00000000000..83d9859ca93 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/invoke_named_routes.caddyfiletest @@ -0,0 +1,154 @@ +&(first) { + @first path /first + vars @first first 1 + respond "first" +} + +&(second) { + respond "second" +} + +:8881 { + invoke first + route { + invoke second + } +} + +:8882 { + handle { + invoke second + } +} + +:8883 { + respond "no invoke" +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8881" + ], + "routes": [ + { + "handle": [ + { + "handler": "invoke", + "name": "first" + }, + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "invoke", + "name": "second" + } + ] + } + ] + } + ] + } + ], + "named_routes": { + "first": { + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "first": 1, + "handler": "vars" + } + ], + "match": [ + { + "path": [ + "/first" + ] + } + ] + }, + { + "handle": [ + { + "body": "first", + "handler": "static_response" + } + ] + } + ] + } + ] + }, + "second": { + "handle": [ + { + "body": "second", + "handler": "static_response" + } + ] + } + } + }, + "srv1": { + "listen": [ + ":8882" + ], + "routes": [ + { + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "invoke", + "name": "second" + } + ] + } + ] + } + ] + } + ], + "named_routes": { + "second": { + "handle": [ + { + "body": "second", + "handler": "static_response" + } + ] + } + } + }, + "srv2": { + "listen": [ + ":8883" + ], + "routes": [ + { + "handle": [ + { + "body": "no invoke", + "handler": "static_response" + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/log_add.caddyfiletest b/caddytest/integration/caddyfile_adapt/log_add.caddyfiletest new file mode 100644 index 00000000000..4f91e4644cb --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/log_add.caddyfiletest @@ -0,0 +1,71 @@ +:80 { + log + + vars foo foo + + log_append const bar + log_append vars foo + log_append placeholder {path} + + log_append /only-for-this-path secret value +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "handle": [ + { + "foo": "foo", + "handler": "vars" + } + ] + }, + { + "match": [ + { + "path": [ + "/only-for-this-path" + ] + } + ], + "handle": [ + { + "handler": "log_append", + "key": "secret", + "value": "value" + } + ] + }, + { + "handle": [ + { + "handler": "log_append", + "key": "const", + "value": "bar" + }, + { + "handler": "log_append", + "key": "vars", + "value": "foo" + }, + { + "handler": "log_append", + "key": "placeholder", + "value": "{http.request.uri.path}" + } + ] + } + ], + "logs": {} + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/log_append_encoder.caddyfiletest b/caddytest/integration/caddyfile_adapt/log_append_encoder.caddyfiletest new file mode 100644 index 00000000000..88a6cd6be7a --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/log_append_encoder.caddyfiletest @@ -0,0 +1,63 @@ +{ + log { + format append { + wrap json + fields { + wrap "foo" + } + env {env.EXAMPLE} + int 1 + float 1.1 + bool true + string "string" + } + } +} + +:80 { + respond "Hello, World!" +} +---------- +{ + "logging": { + "logs": { + "default": { + "encoder": { + "fields": { + "bool": true, + "env": "{env.EXAMPLE}", + "float": 1.1, + "int": 1, + "string": "string", + "wrap": "foo" + }, + "format": "append", + "wrap": { + "format": "json" + } + } + } + } + }, + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "handle": [ + { + "body": "Hello, World!", + "handler": "static_response" + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/log_except_catchall_blocks.caddyfiletest b/caddytest/integration/caddyfile_adapt/log_except_catchall_blocks.caddyfiletest new file mode 100644 index 00000000000..b2a7f2afc3f --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/log_except_catchall_blocks.caddyfiletest @@ -0,0 +1,112 @@ +http://localhost:2020 { + log + log_skip /first-hidden* + log_skip /second-hidden* + respond 200 +} + +:2020 { + respond 418 +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":2020" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "vars", + "log_skip": true + } + ], + "match": [ + { + "path": [ + "/second-hidden*" + ] + } + ] + }, + { + "handle": [ + { + "handler": "vars", + "log_skip": true + } + ], + "match": [ + { + "path": [ + "/first-hidden*" + ] + } + ] + }, + { + "handle": [ + { + "handler": "static_response", + "status_code": 200 + } + ] + } + ] + } + ], + "terminal": true + }, + { + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "static_response", + "status_code": 418 + } + ] + } + ] + } + ], + "terminal": true + } + ], + "automatic_https": { + "skip": [ + "localhost" + ] + }, + "logs": { + "logger_names": { + "localhost": [ + "" + ] + }, + "skip_unmapped_hosts": true + } + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/log_filter_no_wrap.caddyfiletest b/caddytest/integration/caddyfile_adapt/log_filter_no_wrap.caddyfiletest new file mode 100644 index 00000000000..f63a1d92580 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/log_filter_no_wrap.caddyfiletest @@ -0,0 +1,52 @@ +:80 + +log { + output stdout + format filter { + fields { + request>headers>Server delete + } + } +} +---------- +{ + "logging": { + "logs": { + "default": { + "exclude": [ + "http.log.access.log0" + ] + }, + "log0": { + "writer": { + "output": "stdout" + }, + "encoder": { + "fields": { + "request\u003eheaders\u003eServer": { + "filter": "delete" + } + }, + "format": "filter" + }, + "include": [ + "http.log.access.log0" + ] + } + } + }, + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "logs": { + "default_logger_name": "log0" + } + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/log_filter_with_header.txt b/caddytest/integration/caddyfile_adapt/log_filter_with_header.txt new file mode 100644 index 00000000000..3ab6d6246cf --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/log_filter_with_header.txt @@ -0,0 +1,151 @@ +localhost { + log { + output file ./caddy.access.log + } + log health_check_log { + output file ./caddy.access.health.log + no_hostname + } + log general_log { + output file ./caddy.access.general.log + no_hostname + } + @healthCheck `header_regexp('User-Agent', '^some-regexp$') || path('/healthz*')` + handle @healthCheck { + log_name health_check_log general_log + respond "Healthy" + } + + handle { + respond "Hello World" + } +} +---------- +{ + "logging": { + "logs": { + "default": { + "exclude": [ + "http.log.access.general_log", + "http.log.access.health_check_log", + "http.log.access.log0" + ] + }, + "general_log": { + "writer": { + "filename": "./caddy.access.general.log", + "output": "file" + }, + "include": [ + "http.log.access.general_log" + ] + }, + "health_check_log": { + "writer": { + "filename": "./caddy.access.health.log", + "output": "file" + }, + "include": [ + "http.log.access.health_check_log" + ] + }, + "log0": { + "writer": { + "filename": "./caddy.access.log", + "output": "file" + }, + "include": [ + "http.log.access.log0" + ] + } + } + }, + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "group": "group2", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "access_logger_names": [ + "health_check_log", + "general_log" + ], + "handler": "vars" + }, + { + "body": "Healthy", + "handler": "static_response" + } + ] + } + ] + } + ], + "match": [ + { + "expression": { + "expr": "header_regexp('User-Agent', '^some-regexp$') || path('/healthz*')", + "name": "healthCheck" + } + } + ] + }, + { + "group": "group2", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Hello World", + "handler": "static_response" + } + ] + } + ] + } + ] + } + ] + } + ], + "terminal": true + } + ], + "logs": { + "logger_names": { + "localhost": [ + "log0" + ] + } + } + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/log_filters.caddyfiletest b/caddytest/integration/caddyfile_adapt/log_filters.caddyfiletest new file mode 100644 index 00000000000..1b2fc2e502e --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/log_filters.caddyfiletest @@ -0,0 +1,136 @@ +:80 + +log { + output stdout + format filter { + wrap console + + # long form, with "fields" wrapper + fields { + uri query { + replace foo REDACTED + delete bar + hash baz + } + } + + # short form, flatter structure + request>headers>Authorization replace REDACTED + request>headers>Server delete + request>headers>Cookie cookie { + replace foo REDACTED + delete bar + hash baz + } + request>remote_ip ip_mask { + ipv4 24 + ipv6 32 + } + request>client_ip ip_mask 16 32 + request>headers>Regexp regexp secret REDACTED + request>headers>Hash hash + } +} +---------- +{ + "logging": { + "logs": { + "default": { + "exclude": [ + "http.log.access.log0" + ] + }, + "log0": { + "writer": { + "output": "stdout" + }, + "encoder": { + "fields": { + "request\u003eclient_ip": { + "filter": "ip_mask", + "ipv4_cidr": 16, + "ipv6_cidr": 32 + }, + "request\u003eheaders\u003eAuthorization": { + "filter": "replace", + "value": "REDACTED" + }, + "request\u003eheaders\u003eCookie": { + "actions": [ + { + "name": "foo", + "type": "replace", + "value": "REDACTED" + }, + { + "name": "bar", + "type": "delete" + }, + { + "name": "baz", + "type": "hash" + } + ], + "filter": "cookie" + }, + "request\u003eheaders\u003eHash": { + "filter": "hash" + }, + "request\u003eheaders\u003eRegexp": { + "filter": "regexp", + "regexp": "secret", + "value": "REDACTED" + }, + "request\u003eheaders\u003eServer": { + "filter": "delete" + }, + "request\u003eremote_ip": { + "filter": "ip_mask", + "ipv4_cidr": 24, + "ipv6_cidr": 32 + }, + "uri": { + "actions": [ + { + "parameter": "foo", + "type": "replace", + "value": "REDACTED" + }, + { + "parameter": "bar", + "type": "delete" + }, + { + "parameter": "baz", + "type": "hash" + } + ], + "filter": "query" + } + }, + "format": "filter", + "wrap": { + "format": "console" + } + }, + "include": [ + "http.log.access.log0" + ] + } + } + }, + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "logs": { + "default_logger_name": "log0" + } + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/log_multi_logger_name.caddyfiletest b/caddytest/integration/caddyfile_adapt/log_multi_logger_name.caddyfiletest new file mode 100644 index 00000000000..be9ec188557 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/log_multi_logger_name.caddyfiletest @@ -0,0 +1,117 @@ +(log-both) { + log {args[0]}-json { + hostnames {args[0]} + output file /var/log/{args[0]}.log + format json + } + log {args[0]}-console { + hostnames {args[0]} + output file /var/log/{args[0]}.json + format console + } +} + +*.example.com { + # Subdomains log to multiple files at once, with + # different output files and formats. + import log-both foo.example.com + import log-both bar.example.com +} +---------- +{ + "logging": { + "logs": { + "bar.example.com-console": { + "writer": { + "filename": "/var/log/bar.example.com.json", + "output": "file" + }, + "encoder": { + "format": "console" + }, + "include": [ + "http.log.access.bar.example.com-console" + ] + }, + "bar.example.com-json": { + "writer": { + "filename": "/var/log/bar.example.com.log", + "output": "file" + }, + "encoder": { + "format": "json" + }, + "include": [ + "http.log.access.bar.example.com-json" + ] + }, + "default": { + "exclude": [ + "http.log.access.bar.example.com-console", + "http.log.access.bar.example.com-json", + "http.log.access.foo.example.com-console", + "http.log.access.foo.example.com-json" + ] + }, + "foo.example.com-console": { + "writer": { + "filename": "/var/log/foo.example.com.json", + "output": "file" + }, + "encoder": { + "format": "console" + }, + "include": [ + "http.log.access.foo.example.com-console" + ] + }, + "foo.example.com-json": { + "writer": { + "filename": "/var/log/foo.example.com.log", + "output": "file" + }, + "encoder": { + "format": "json" + }, + "include": [ + "http.log.access.foo.example.com-json" + ] + } + } + }, + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "*.example.com" + ] + } + ], + "terminal": true + } + ], + "logs": { + "logger_names": { + "bar.example.com": [ + "bar.example.com-json", + "bar.example.com-console" + ], + "foo.example.com": [ + "foo.example.com-json", + "foo.example.com-console" + ] + } + } + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/log_override_hostname.caddyfiletest b/caddytest/integration/caddyfile_adapt/log_override_hostname.caddyfiletest new file mode 100644 index 00000000000..b9213e65a2d --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/log_override_hostname.caddyfiletest @@ -0,0 +1,117 @@ +*.example.com { + log { + hostnames foo.example.com bar.example.com + output file /foo-bar.txt + } + log { + hostnames baz.example.com + output file /baz.txt + } +} + +example.com:8443 { + log { + output file /port.txt + } +} +---------- +{ + "logging": { + "logs": { + "default": { + "exclude": [ + "http.log.access.log0", + "http.log.access.log1", + "http.log.access.log2" + ] + }, + "log0": { + "writer": { + "filename": "/foo-bar.txt", + "output": "file" + }, + "include": [ + "http.log.access.log0" + ] + }, + "log1": { + "writer": { + "filename": "/baz.txt", + "output": "file" + }, + "include": [ + "http.log.access.log1" + ] + }, + "log2": { + "writer": { + "filename": "/port.txt", + "output": "file" + }, + "include": [ + "http.log.access.log2" + ] + } + } + }, + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "*.example.com" + ] + } + ], + "terminal": true + } + ], + "logs": { + "logger_names": { + "bar.example.com": [ + "log0" + ], + "baz.example.com": [ + "log1" + ], + "foo.example.com": [ + "log0" + ] + } + } + }, + "srv1": { + "listen": [ + ":8443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "terminal": true + } + ], + "logs": { + "logger_names": { + "example.com": [ + "log2" + ] + } + } + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/log_override_name_multiaccess.caddyfiletest b/caddytest/integration/caddyfile_adapt/log_override_name_multiaccess.caddyfiletest new file mode 100644 index 00000000000..2708503e5f1 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/log_override_name_multiaccess.caddyfiletest @@ -0,0 +1,88 @@ +{ + log access-console { + include http.log.access.foo + output file access-localhost.log + format console + } + + log access-json { + include http.log.access.foo + output file access-localhost.json + format json + } +} + +http://localhost:8881 { + log foo +} +---------- +{ + "logging": { + "logs": { + "access-console": { + "writer": { + "filename": "access-localhost.log", + "output": "file" + }, + "encoder": { + "format": "console" + }, + "include": [ + "http.log.access.foo" + ] + }, + "access-json": { + "writer": { + "filename": "access-localhost.json", + "output": "file" + }, + "encoder": { + "format": "json" + }, + "include": [ + "http.log.access.foo" + ] + }, + "default": { + "exclude": [ + "http.log.access.foo" + ] + } + } + }, + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8881" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "terminal": true + } + ], + "automatic_https": { + "skip": [ + "localhost" + ] + }, + "logs": { + "logger_names": { + "localhost": [ + "foo" + ] + } + } + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/log_override_name_multiaccess_debug.caddyfiletest b/caddytest/integration/caddyfile_adapt/log_override_name_multiaccess_debug.caddyfiletest new file mode 100644 index 00000000000..7ea6597894d --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/log_override_name_multiaccess_debug.caddyfiletest @@ -0,0 +1,93 @@ +{ + debug + + log access-console { + include http.log.access.foo + output file access-localhost.log + format console + } + + log access-json { + include http.log.access.foo + output file access-localhost.json + format json + } +} + +http://localhost:8881 { + log foo +} +---------- +{ + "logging": { + "logs": { + "access-console": { + "writer": { + "filename": "access-localhost.log", + "output": "file" + }, + "encoder": { + "format": "console" + }, + "level": "DEBUG", + "include": [ + "http.log.access.foo" + ] + }, + "access-json": { + "writer": { + "filename": "access-localhost.json", + "output": "file" + }, + "encoder": { + "format": "json" + }, + "level": "DEBUG", + "include": [ + "http.log.access.foo" + ] + }, + "default": { + "level": "DEBUG", + "exclude": [ + "http.log.access.foo" + ] + } + } + }, + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8881" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "terminal": true + } + ], + "automatic_https": { + "skip": [ + "localhost" + ] + }, + "logs": { + "logger_names": { + "localhost": [ + "foo" + ] + } + } + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/log_roll_days.caddyfiletest b/caddytest/integration/caddyfile_adapt/log_roll_days.caddyfiletest new file mode 100644 index 00000000000..3ead4ac1828 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/log_roll_days.caddyfiletest @@ -0,0 +1,51 @@ +:80 + +log { + output file /var/log/access.log { + roll_size 1gb + roll_uncompressed + roll_local_time + roll_keep 5 + roll_keep_for 90d + } +} +---------- +{ + "logging": { + "logs": { + "default": { + "exclude": [ + "http.log.access.log0" + ] + }, + "log0": { + "writer": { + "filename": "/var/log/access.log", + "output": "file", + "roll_gzip": false, + "roll_keep": 5, + "roll_keep_days": 90, + "roll_local_time": true, + "roll_size_mb": 954 + }, + "include": [ + "http.log.access.log0" + ] + } + } + }, + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "logs": { + "default_logger_name": "log0" + } + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/log_sampling.caddyfiletest b/caddytest/integration/caddyfile_adapt/log_sampling.caddyfiletest new file mode 100644 index 00000000000..b5862257235 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/log_sampling.caddyfiletest @@ -0,0 +1,45 @@ +:80 { + log { + sampling { + interval 300 + first 50 + thereafter 40 + } + } +} +---------- +{ + "logging": { + "logs": { + "default": { + "exclude": [ + "http.log.access.log0" + ] + }, + "log0": { + "sampling": { + "interval": 300, + "first": 50, + "thereafter": 40 + }, + "include": [ + "http.log.access.log0" + ] + } + } + }, + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "logs": { + "default_logger_name": "log0" + } + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/log_skip_hosts.caddyfiletest b/caddytest/integration/caddyfile_adapt/log_skip_hosts.caddyfiletest new file mode 100644 index 00000000000..c10610c2ed2 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/log_skip_hosts.caddyfiletest @@ -0,0 +1,80 @@ +one.example.com { + log +} + +two.example.com { +} + +three.example.com { +} + +example.com { +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "three.example.com" + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "one.example.com" + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "two.example.com" + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "terminal": true + } + ], + "logs": { + "logger_names": { + "one.example.com": [ + "" + ] + }, + "skip_hosts": [ + "example.com", + "three.example.com", + "two.example.com" + ] + } + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/map_and_vars_with_raw_types.caddyfiletest b/caddytest/integration/caddyfile_adapt/map_and_vars_with_raw_types.caddyfiletest new file mode 100644 index 00000000000..8b872635df4 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/map_and_vars_with_raw_types.caddyfiletest @@ -0,0 +1,127 @@ +example.com + +map {host} {my_placeholder} {magic_number} { + # Should output boolean "true" and an integer + example.com true 3 + + # Should output a string and null + foo.example.com "string value" + + # Should output two strings (quoted int) + (.*)\.example.com "${1} subdomain" "5" + + # Should output null and a string (quoted int) + ~.*\.net$ - `7` + + # Should output a float and the string "false" + ~.*\.xyz$ 123.456 "false" + + # Should output two strings, second being escaped quote + default "unknown domain" \""" +} + +vars foo bar +vars { + abc true + def 1 + ghi 2.3 + jkl "mn op" +} + +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "defaults": [ + "unknown domain", + "\"" + ], + "destinations": [ + "{my_placeholder}", + "{magic_number}" + ], + "handler": "map", + "mappings": [ + { + "input": "example.com", + "outputs": [ + true, + 3 + ] + }, + { + "input": "foo.example.com", + "outputs": [ + "string value", + null + ] + }, + { + "input": "(.*)\\.example.com", + "outputs": [ + "${1} subdomain", + "5" + ] + }, + { + "input_regexp": ".*\\.net$", + "outputs": [ + null, + "7" + ] + }, + { + "input_regexp": ".*\\.xyz$", + "outputs": [ + 123.456, + "false" + ] + } + ], + "source": "{http.request.host}" + }, + { + "abc": true, + "def": 1, + "ghi": 2.3, + "handler": "vars", + "jkl": "mn op" + }, + { + "foo": "bar", + "handler": "vars" + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/matcher_syntax.caddyfiletest b/caddytest/integration/caddyfile_adapt/matcher_syntax.caddyfiletest new file mode 100644 index 00000000000..efb66cf2c74 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/matcher_syntax.caddyfiletest @@ -0,0 +1,336 @@ +:80 { + @matcher { + method GET + } + respond @matcher "get" + + @matcher2 method POST + respond @matcher2 "post" + + @matcher3 not method PUT + respond @matcher3 "not put" + + @matcher4 vars "{http.request.uri}" "/vars-matcher" + respond @matcher4 "from vars matcher" + + @matcher5 vars_regexp static "{http.request.uri}" `\.([a-f0-9]{6})\.(css|js)$` + respond @matcher5 "from vars_regexp matcher with name" + + @matcher6 vars_regexp "{http.request.uri}" `\.([a-f0-9]{6})\.(css|js)$` + respond @matcher6 "from vars_regexp matcher without name" + + @matcher7 `path('/foo*') && method('GET')` + respond @matcher7 "inline expression matcher shortcut" + + @matcher8 { + header Foo bar + header Foo foobar + header Bar foo + } + respond @matcher8 "header matcher merging values of the same field" + + @matcher9 { + query foo=bar foo=baz bar=foo + query bar=baz + } + respond @matcher9 "query matcher merging pairs with the same keys" + + @matcher10 { + header !Foo + header Bar foo + } + respond @matcher10 "header matcher with null field matcher" + + @matcher11 remote_ip private_ranges + respond @matcher11 "remote_ip matcher with private ranges" + + @matcher12 client_ip private_ranges + respond @matcher12 "client_ip matcher with private ranges" + + @matcher13 { + remote_ip 1.1.1.1 + remote_ip 2.2.2.2 + } + respond @matcher13 "remote_ip merged" + + @matcher14 { + client_ip 1.1.1.1 + client_ip 2.2.2.2 + } + respond @matcher14 "client_ip merged" +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "match": [ + { + "method": [ + "GET" + ] + } + ], + "handle": [ + { + "body": "get", + "handler": "static_response" + } + ] + }, + { + "match": [ + { + "method": [ + "POST" + ] + } + ], + "handle": [ + { + "body": "post", + "handler": "static_response" + } + ] + }, + { + "match": [ + { + "not": [ + { + "method": [ + "PUT" + ] + } + ] + } + ], + "handle": [ + { + "body": "not put", + "handler": "static_response" + } + ] + }, + { + "match": [ + { + "vars": { + "{http.request.uri}": [ + "/vars-matcher" + ] + } + } + ], + "handle": [ + { + "body": "from vars matcher", + "handler": "static_response" + } + ] + }, + { + "match": [ + { + "vars_regexp": { + "{http.request.uri}": { + "name": "static", + "pattern": "\\.([a-f0-9]{6})\\.(css|js)$" + } + } + } + ], + "handle": [ + { + "body": "from vars_regexp matcher with name", + "handler": "static_response" + } + ] + }, + { + "match": [ + { + "vars_regexp": { + "{http.request.uri}": { + "name": "matcher6", + "pattern": "\\.([a-f0-9]{6})\\.(css|js)$" + } + } + } + ], + "handle": [ + { + "body": "from vars_regexp matcher without name", + "handler": "static_response" + } + ] + }, + { + "match": [ + { + "expression": { + "expr": "path('/foo*') \u0026\u0026 method('GET')", + "name": "matcher7" + } + } + ], + "handle": [ + { + "body": "inline expression matcher shortcut", + "handler": "static_response" + } + ] + }, + { + "match": [ + { + "header": { + "Bar": [ + "foo" + ], + "Foo": [ + "bar", + "foobar" + ] + } + } + ], + "handle": [ + { + "body": "header matcher merging values of the same field", + "handler": "static_response" + } + ] + }, + { + "match": [ + { + "query": { + "bar": [ + "foo", + "baz" + ], + "foo": [ + "bar", + "baz" + ] + } + } + ], + "handle": [ + { + "body": "query matcher merging pairs with the same keys", + "handler": "static_response" + } + ] + }, + { + "match": [ + { + "header": { + "Bar": [ + "foo" + ], + "Foo": null + } + } + ], + "handle": [ + { + "body": "header matcher with null field matcher", + "handler": "static_response" + } + ] + }, + { + "match": [ + { + "remote_ip": { + "ranges": [ + "192.168.0.0/16", + "172.16.0.0/12", + "10.0.0.0/8", + "127.0.0.1/8", + "fd00::/8", + "::1" + ] + } + } + ], + "handle": [ + { + "body": "remote_ip matcher with private ranges", + "handler": "static_response" + } + ] + }, + { + "match": [ + { + "client_ip": { + "ranges": [ + "192.168.0.0/16", + "172.16.0.0/12", + "10.0.0.0/8", + "127.0.0.1/8", + "fd00::/8", + "::1" + ] + } + } + ], + "handle": [ + { + "body": "client_ip matcher with private ranges", + "handler": "static_response" + } + ] + }, + { + "match": [ + { + "remote_ip": { + "ranges": [ + "1.1.1.1", + "2.2.2.2" + ] + } + } + ], + "handle": [ + { + "body": "remote_ip merged", + "handler": "static_response" + } + ] + }, + { + "match": [ + { + "client_ip": { + "ranges": [ + "1.1.1.1", + "2.2.2.2" + ] + } + } + ], + "handle": [ + { + "body": "client_ip merged", + "handler": "static_response" + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/matchers_in_route.caddyfiletest b/caddytest/integration/caddyfile_adapt/matchers_in_route.caddyfiletest new file mode 100644 index 00000000000..8c587b59be1 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/matchers_in_route.caddyfiletest @@ -0,0 +1,31 @@ +:80 { + route { + # unused matchers should not panic + # see https://github.com/caddyserver/caddy/issues/3745 + @matcher1 path /path1 + @matcher2 path /path2 + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "handle": [ + { + "handler": "subroute" + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/method_directive.caddyfiletest b/caddytest/integration/caddyfile_adapt/method_directive.caddyfiletest new file mode 100644 index 00000000000..786df90c8ab --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/method_directive.caddyfiletest @@ -0,0 +1,27 @@ +:8080 { + method FOO +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8080" + ], + "routes": [ + { + "handle": [ + { + "handler": "rewrite", + "method": "FOO" + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/metrics_disable_om.caddyfiletest b/caddytest/integration/caddyfile_adapt/metrics_disable_om.caddyfiletest new file mode 100644 index 00000000000..2d7b24f4d6c --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/metrics_disable_om.caddyfiletest @@ -0,0 +1,36 @@ +:80 { + metrics /metrics { + disable_openmetrics + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "match": [ + { + "path": [ + "/metrics" + ] + } + ], + "handle": [ + { + "disable_openmetrics": true, + "handler": "metrics" + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/metrics_merge_options.caddyfiletest b/caddytest/integration/caddyfile_adapt/metrics_merge_options.caddyfiletest new file mode 100644 index 00000000000..946b3d0c83a --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/metrics_merge_options.caddyfiletest @@ -0,0 +1,39 @@ +{ + metrics + servers :80 { + metrics { + per_host + } + } +} +:80 { + respond "Hello" +} + +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "handle": [ + { + "body": "Hello", + "handler": "static_response" + } + ] + } + ] + } + }, + "metrics": { + "per_host": true + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/metrics_perhost.caddyfiletest b/caddytest/integration/caddyfile_adapt/metrics_perhost.caddyfiletest new file mode 100644 index 00000000000..e362cecc93e --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/metrics_perhost.caddyfiletest @@ -0,0 +1,37 @@ +{ + servers :80 { + metrics { + per_host + } + } +} +:80 { + respond "Hello" +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "handle": [ + { + "body": "Hello", + "handler": "static_response" + } + ] + } + ] + } + }, + "metrics": { + "per_host": true + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/metrics_syntax.caddyfiletest b/caddytest/integration/caddyfile_adapt/metrics_syntax.caddyfiletest new file mode 100644 index 00000000000..ca08cea237a --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/metrics_syntax.caddyfiletest @@ -0,0 +1,33 @@ +:80 { + metrics /metrics +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "match": [ + { + "path": [ + "/metrics" + ] + } + ], + "handle": [ + { + "handler": "metrics" + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/not_block_merging.caddyfiletest b/caddytest/integration/caddyfile_adapt/not_block_merging.caddyfiletest new file mode 100644 index 00000000000..c41a7496bbd --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/not_block_merging.caddyfiletest @@ -0,0 +1,49 @@ +:80 + +@test { + not { + header Abc "123" + header Bcd "123" + } +} +respond @test 403 +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "match": [ + { + "not": [ + { + "header": { + "Abc": [ + "123" + ], + "Bcd": [ + "123" + ] + } + } + ] + } + ], + "handle": [ + { + "handler": "static_response", + "status_code": 403 + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/php_fastcgi_expanded_form.caddyfiletest b/caddytest/integration/caddyfile_adapt/php_fastcgi_expanded_form.caddyfiletest new file mode 100644 index 00000000000..df2e248852e --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/php_fastcgi_expanded_form.caddyfiletest @@ -0,0 +1,133 @@ +:8886 + +route { + # Add trailing slash for directory requests + @canonicalPath { + file { + try_files {path}/index.php + } + not path */ + } + redir @canonicalPath {orig_path}/{orig_?query} 308 + + # If the requested file does not exist, try index files + @indexFiles { + file { + try_files {path} {path}/index.php index.php + split_path .php + } + } + rewrite @indexFiles {file_match.relative} + + # Proxy PHP files to the FastCGI responder + @phpFiles { + path *.php + } + reverse_proxy @phpFiles 127.0.0.1:9000 { + transport fastcgi { + split .php + } + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8886" + ], + "routes": [ + { + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "static_response", + "headers": { + "Location": [ + "{http.request.orig_uri.path}/{http.request.orig_uri.prefixed_query}" + ] + }, + "status_code": 308 + } + ], + "match": [ + { + "file": { + "try_files": [ + "{http.request.uri.path}/index.php" + ] + }, + "not": [ + { + "path": [ + "*/" + ] + } + ] + } + ] + }, + { + "group": "group0", + "handle": [ + { + "handler": "rewrite", + "uri": "{http.matchers.file.relative}" + } + ], + "match": [ + { + "file": { + "split_path": [ + ".php" + ], + "try_files": [ + "{http.request.uri.path}", + "{http.request.uri.path}/index.php", + "index.php" + ] + } + } + ] + }, + { + "handle": [ + { + "handler": "reverse_proxy", + "transport": { + "protocol": "fastcgi", + "split_path": [ + ".php" + ] + }, + "upstreams": [ + { + "dial": "127.0.0.1:9000" + } + ] + } + ], + "match": [ + { + "path": [ + "*.php" + ] + } + ] + } + ] + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/php_fastcgi_handle_response.caddyfiletest b/caddytest/integration/caddyfile_adapt/php_fastcgi_handle_response.caddyfiletest new file mode 100644 index 00000000000..3a857654f13 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/php_fastcgi_handle_response.caddyfiletest @@ -0,0 +1,146 @@ +:8881 { + php_fastcgi app:9000 { + env FOO bar + + @error status 4xx + handle_response @error { + root * /errors + rewrite * /{http.reverse_proxy.status_code}.html + file_server + } + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8881" + ], + "routes": [ + { + "match": [ + { + "file": { + "try_files": [ + "{http.request.uri.path}/index.php" + ] + }, + "not": [ + { + "path": [ + "*/" + ] + } + ] + } + ], + "handle": [ + { + "handler": "static_response", + "headers": { + "Location": [ + "{http.request.orig_uri.path}/{http.request.orig_uri.prefixed_query}" + ] + }, + "status_code": 308 + } + ] + }, + { + "match": [ + { + "file": { + "try_files": [ + "{http.request.uri.path}", + "{http.request.uri.path}/index.php", + "index.php" + ], + "try_policy": "first_exist_fallback", + "split_path": [ + ".php" + ] + } + } + ], + "handle": [ + { + "handler": "rewrite", + "uri": "{http.matchers.file.relative}" + } + ] + }, + { + "match": [ + { + "path": [ + "*.php" + ] + } + ], + "handle": [ + { + "handle_response": [ + { + "match": { + "status_code": [ + 4 + ] + }, + "routes": [ + { + "handle": [ + { + "handler": "vars", + "root": "/errors" + } + ] + }, + { + "group": "group0", + "handle": [ + { + "handler": "rewrite", + "uri": "/{http.reverse_proxy.status_code}.html" + } + ] + }, + { + "handle": [ + { + "handler": "file_server", + "hide": [ + "./Caddyfile" + ] + } + ] + } + ] + } + ], + "handler": "reverse_proxy", + "transport": { + "env": { + "FOO": "bar" + }, + "protocol": "fastcgi", + "split_path": [ + ".php" + ] + }, + "upstreams": [ + { + "dial": "app:9000" + } + ] + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/php_fastcgi_index_off.caddyfiletest b/caddytest/integration/caddyfile_adapt/php_fastcgi_index_off.caddyfiletest new file mode 100644 index 00000000000..5ebdbd2ea93 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/php_fastcgi_index_off.caddyfiletest @@ -0,0 +1,72 @@ +:8884 + +php_fastcgi localhost:9000 { + # some php_fastcgi-specific subdirectives + split .php .php5 + env VAR1 value1 + env VAR2 value2 + root /var/www + index off + dial_timeout 3s + read_timeout 10s + write_timeout 20s + + # passed through to reverse_proxy (directive order doesn't matter!) + lb_policy random +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "routes": [ + { + "match": [ + { + "path": [ + "*.php", + "*.php5" + ] + } + ], + "handle": [ + { + "handler": "reverse_proxy", + "load_balancing": { + "selection_policy": { + "policy": "random" + } + }, + "transport": { + "dial_timeout": 3000000000, + "env": { + "VAR1": "value1", + "VAR2": "value2" + }, + "protocol": "fastcgi", + "read_timeout": 10000000000, + "root": "/var/www", + "split_path": [ + ".php", + ".php5" + ], + "write_timeout": 20000000000 + }, + "upstreams": [ + { + "dial": "localhost:9000" + } + ] + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/php_fastcgi_matcher.caddyfiletest b/caddytest/integration/caddyfile_adapt/php_fastcgi_matcher.caddyfiletest new file mode 100644 index 00000000000..4d1298fcc53 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/php_fastcgi_matcher.caddyfiletest @@ -0,0 +1,128 @@ +:8884 + +# the use of a host matcher here should cause this +# site block to be wrapped in a subroute, even though +# the site block does not have a hostname; this is +# to prevent auto-HTTPS from picking up on this host +# matcher because it is not a key on the site block +@test host example.com +php_fastcgi @test localhost:9000 +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "routes": [ + { + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "static_response", + "headers": { + "Location": [ + "{http.request.orig_uri.path}/{http.request.orig_uri.prefixed_query}" + ] + }, + "status_code": 308 + } + ], + "match": [ + { + "file": { + "try_files": [ + "{http.request.uri.path}/index.php" + ] + }, + "not": [ + { + "path": [ + "*/" + ] + } + ] + } + ] + }, + { + "handle": [ + { + "handler": "rewrite", + "uri": "{http.matchers.file.relative}" + } + ], + "match": [ + { + "file": { + "split_path": [ + ".php" + ], + "try_files": [ + "{http.request.uri.path}", + "{http.request.uri.path}/index.php", + "index.php" + ], + "try_policy": "first_exist_fallback" + } + } + ] + }, + { + "handle": [ + { + "handler": "reverse_proxy", + "transport": { + "protocol": "fastcgi", + "split_path": [ + ".php" + ] + }, + "upstreams": [ + { + "dial": "localhost:9000" + } + ] + } + ], + "match": [ + { + "path": [ + "*.php" + ] + } + ] + } + ] + } + ], + "match": [ + { + "host": [ + "example.com" + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/php_fastcgi_subdirectives.caddyfiletest b/caddytest/integration/caddyfile_adapt/php_fastcgi_subdirectives.caddyfiletest new file mode 100644 index 00000000000..9a9ab5ab0ef --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/php_fastcgi_subdirectives.caddyfiletest @@ -0,0 +1,119 @@ +:8884 + +php_fastcgi localhost:9000 { + # some php_fastcgi-specific subdirectives + split .php .php5 + env VAR1 value1 + env VAR2 value2 + root /var/www + index index.php5 + + # passed through to reverse_proxy (directive order doesn't matter!) + lb_policy random +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "routes": [ + { + "match": [ + { + "file": { + "try_files": [ + "{http.request.uri.path}/index.php5" + ] + }, + "not": [ + { + "path": [ + "*/" + ] + } + ] + } + ], + "handle": [ + { + "handler": "static_response", + "headers": { + "Location": [ + "{http.request.orig_uri.path}/{http.request.orig_uri.prefixed_query}" + ] + }, + "status_code": 308 + } + ] + }, + { + "match": [ + { + "file": { + "try_files": [ + "{http.request.uri.path}", + "{http.request.uri.path}/index.php5", + "index.php5" + ], + "try_policy": "first_exist_fallback", + "split_path": [ + ".php", + ".php5" + ] + } + } + ], + "handle": [ + { + "handler": "rewrite", + "uri": "{http.matchers.file.relative}" + } + ] + }, + { + "match": [ + { + "path": [ + "*.php", + "*.php5" + ] + } + ], + "handle": [ + { + "handler": "reverse_proxy", + "load_balancing": { + "selection_policy": { + "policy": "random" + } + }, + "transport": { + "env": { + "VAR1": "value1", + "VAR2": "value2" + }, + "protocol": "fastcgi", + "root": "/var/www", + "split_path": [ + ".php", + ".php5" + ] + }, + "upstreams": [ + { + "dial": "localhost:9000" + } + ] + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/php_fastcgi_try_files_override.caddyfiletest b/caddytest/integration/caddyfile_adapt/php_fastcgi_try_files_override.caddyfiletest new file mode 100644 index 00000000000..75487a93798 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/php_fastcgi_try_files_override.caddyfiletest @@ -0,0 +1,124 @@ +:8884 + +php_fastcgi localhost:9000 { + # some php_fastcgi-specific subdirectives + split .php .php5 + env VAR1 value1 + env VAR2 value2 + root /var/www + try_files {path} {path}/index.php =404 + dial_timeout 3s + read_timeout 10s + write_timeout 20s + + # passed through to reverse_proxy (directive order doesn't matter!) + lb_policy random +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "routes": [ + { + "match": [ + { + "file": { + "try_files": [ + "{http.request.uri.path}/index.php" + ] + }, + "not": [ + { + "path": [ + "*/" + ] + } + ] + } + ], + "handle": [ + { + "handler": "static_response", + "headers": { + "Location": [ + "{http.request.orig_uri.path}/{http.request.orig_uri.prefixed_query}" + ] + }, + "status_code": 308 + } + ] + }, + { + "match": [ + { + "file": { + "try_files": [ + "{http.request.uri.path}", + "{http.request.uri.path}/index.php", + "=404" + ], + "split_path": [ + ".php", + ".php5" + ] + } + } + ], + "handle": [ + { + "handler": "rewrite", + "uri": "{http.matchers.file.relative}" + } + ] + }, + { + "match": [ + { + "path": [ + "*.php", + "*.php5" + ] + } + ], + "handle": [ + { + "handler": "reverse_proxy", + "load_balancing": { + "selection_policy": { + "policy": "random" + } + }, + "transport": { + "dial_timeout": 3000000000, + "env": { + "VAR1": "value1", + "VAR2": "value2" + }, + "protocol": "fastcgi", + "read_timeout": 10000000000, + "root": "/var/www", + "split_path": [ + ".php", + ".php5" + ], + "write_timeout": 20000000000 + }, + "upstreams": [ + { + "dial": "localhost:9000" + } + ] + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/php_fastcgi_try_files_override_no_dir_index.caddyfiletest b/caddytest/integration/caddyfile_adapt/php_fastcgi_try_files_override_no_dir_index.caddyfiletest new file mode 100644 index 00000000000..203ab3b63b4 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/php_fastcgi_try_files_override_no_dir_index.caddyfiletest @@ -0,0 +1,95 @@ +:8884 + +php_fastcgi localhost:9000 { + # some php_fastcgi-specific subdirectives + split .php .php5 + env VAR1 value1 + env VAR2 value2 + root /var/www + try_files {path} index.php + dial_timeout 3s + read_timeout 10s + write_timeout 20s + + # passed through to reverse_proxy (directive order doesn't matter!) + lb_policy random +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "routes": [ + { + "match": [ + { + "file": { + "try_files": [ + "{http.request.uri.path}", + "index.php" + ], + "try_policy": "first_exist_fallback", + "split_path": [ + ".php", + ".php5" + ] + } + } + ], + "handle": [ + { + "handler": "rewrite", + "uri": "{http.matchers.file.relative}" + } + ] + }, + { + "match": [ + { + "path": [ + "*.php", + "*.php5" + ] + } + ], + "handle": [ + { + "handler": "reverse_proxy", + "load_balancing": { + "selection_policy": { + "policy": "random" + } + }, + "transport": { + "dial_timeout": 3000000000, + "env": { + "VAR1": "value1", + "VAR2": "value2" + }, + "protocol": "fastcgi", + "read_timeout": 10000000000, + "root": "/var/www", + "split_path": [ + ".php", + ".php5" + ], + "write_timeout": 20000000000 + }, + "upstreams": [ + { + "dial": "localhost:9000" + } + ] + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/portless_upstream.caddyfiletest b/caddytest/integration/caddyfile_adapt/portless_upstream.caddyfiletest new file mode 100644 index 00000000000..0e060ddff01 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/portless_upstream.caddyfiletest @@ -0,0 +1,113 @@ +whoami.example.com { + reverse_proxy whoami +} + +app.example.com { + reverse_proxy app:80 +} +unix.example.com { + reverse_proxy unix//path/to/socket +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "whoami.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "upstreams": [ + { + "dial": "whoami:80" + } + ] + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "unix.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "upstreams": [ + { + "dial": "unix//path/to/socket" + } + ] + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "app.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "upstreams": [ + { + "dial": "app:80" + } + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/push.caddyfiletest b/caddytest/integration/caddyfile_adapt/push.caddyfiletest new file mode 100644 index 00000000000..1fe344e0978 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/push.caddyfiletest @@ -0,0 +1,78 @@ +:80 + +push * /foo.txt + +push { + GET /foo.txt +} + +push { + GET /foo.txt + HEAD /foo.txt +} + +push { + headers { + Foo bar + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "handle": [ + { + "handler": "push", + "resources": [ + { + "target": "/foo.txt" + } + ] + }, + { + "handler": "push", + "resources": [ + { + "method": "GET", + "target": "/foo.txt" + } + ] + }, + { + "handler": "push", + "resources": [ + { + "method": "GET", + "target": "/foo.txt" + }, + { + "method": "HEAD", + "target": "/foo.txt" + } + ] + }, + { + "handler": "push", + "headers": { + "set": { + "Foo": [ + "bar" + ] + } + } + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/replaceable_upstream.caddyfiletest b/caddytest/integration/caddyfile_adapt/replaceable_upstream.caddyfiletest new file mode 100644 index 00000000000..202e3304368 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/replaceable_upstream.caddyfiletest @@ -0,0 +1,100 @@ +*.sandbox.localhost { + @sandboxPort { + header_regexp first_label Host ^([0-9]{3})\.sandbox\. + } + handle @sandboxPort { + reverse_proxy {re.first_label.1} + } + handle { + redir {scheme}://application.localhost + } +} + +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "*.sandbox.localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "group": "group2", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "upstreams": [ + { + "dial": "{http.regexp.first_label.1}" + } + ] + } + ] + } + ] + } + ], + "match": [ + { + "header_regexp": { + "Host": { + "name": "first_label", + "pattern": "^([0-9]{3})\\.sandbox\\." + } + } + } + ] + }, + { + "group": "group2", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "static_response", + "headers": { + "Location": [ + "{http.request.scheme}://application.localhost" + ] + }, + "status_code": 302 + } + ] + } + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/replaceable_upstream_partial_port.caddyfiletest b/caddytest/integration/caddyfile_adapt/replaceable_upstream_partial_port.caddyfiletest new file mode 100644 index 00000000000..7fbcb5c78eb --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/replaceable_upstream_partial_port.caddyfiletest @@ -0,0 +1,100 @@ +*.sandbox.localhost { + @sandboxPort { + header_regexp port Host ^([0-9]{3})\.sandbox\. + } + handle @sandboxPort { + reverse_proxy app:6{re.port.1} + } + handle { + redir {scheme}://application.localhost + } +} + +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "*.sandbox.localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "group": "group2", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "upstreams": [ + { + "dial": "app:6{http.regexp.port.1}" + } + ] + } + ] + } + ] + } + ], + "match": [ + { + "header_regexp": { + "Host": { + "name": "port", + "pattern": "^([0-9]{3})\\.sandbox\\." + } + } + } + ] + }, + { + "group": "group2", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "static_response", + "headers": { + "Location": [ + "{http.request.scheme}://application.localhost" + ] + }, + "status_code": 302 + } + ] + } + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/replaceable_upstream_port.caddyfiletest b/caddytest/integration/caddyfile_adapt/replaceable_upstream_port.caddyfiletest new file mode 100644 index 00000000000..8f75c5bd067 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/replaceable_upstream_port.caddyfiletest @@ -0,0 +1,100 @@ +*.sandbox.localhost { + @sandboxPort { + header_regexp port Host ^([0-9]{3})\.sandbox\. + } + handle @sandboxPort { + reverse_proxy app:{re.port.1} + } + handle { + redir {scheme}://application.localhost + } +} + +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "*.sandbox.localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "group": "group2", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "upstreams": [ + { + "dial": "app:{http.regexp.port.1}" + } + ] + } + ] + } + ] + } + ], + "match": [ + { + "header_regexp": { + "Host": { + "name": "port", + "pattern": "^([0-9]{3})\\.sandbox\\." + } + } + } + ] + }, + { + "group": "group2", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "static_response", + "headers": { + "Location": [ + "{http.request.scheme}://application.localhost" + ] + }, + "status_code": 302 + } + ] + } + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/request_body.caddyfiletest b/caddytest/integration/caddyfile_adapt/request_body.caddyfiletest new file mode 100644 index 00000000000..1e4fd47104f --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/request_body.caddyfiletest @@ -0,0 +1,46 @@ +localhost + +request_body { + max_size 1MB +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "request_body", + "max_size": 1000000 + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/request_header.caddyfiletest b/caddytest/integration/caddyfile_adapt/request_header.caddyfiletest new file mode 100644 index 00000000000..bab3fcac553 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/request_header.caddyfiletest @@ -0,0 +1,90 @@ +:80 + +@matcher path /something* +request_header @matcher Denis "Ritchie" + +request_header +Edsger "Dijkstra" +request_header -Wolfram + +@images path /images/* +request_header @images Cache-Control "public, max-age=3600, stale-while-revalidate=86400" +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "match": [ + { + "path": [ + "/something*" + ] + } + ], + "handle": [ + { + "handler": "headers", + "request": { + "set": { + "Denis": [ + "Ritchie" + ] + } + } + } + ] + }, + { + "match": [ + { + "path": [ + "/images/*" + ] + } + ], + "handle": [ + { + "handler": "headers", + "request": { + "set": { + "Cache-Control": [ + "public, max-age=3600, stale-while-revalidate=86400" + ] + } + } + } + ] + }, + { + "handle": [ + { + "handler": "headers", + "request": { + "add": { + "Edsger": [ + "Dijkstra" + ] + } + } + }, + { + "handler": "headers", + "request": { + "delete": [ + "Wolfram" + ] + } + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_buffers.caddyfiletest b/caddytest/integration/caddyfile_adapt/reverse_proxy_buffers.caddyfiletest new file mode 100644 index 00000000000..317899470e7 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_buffers.caddyfiletest @@ -0,0 +1,58 @@ +https://example.com { + reverse_proxy https://localhost:54321 { + request_buffers unlimited + response_buffers unlimited + } +} + +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "request_buffers": -1, + "response_buffers": -1, + "transport": { + "protocol": "http", + "tls": {} + }, + "upstreams": [ + { + "dial": "localhost:54321" + } + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_dynamic_upstreams.caddyfiletest b/caddytest/integration/caddyfile_adapt/reverse_proxy_dynamic_upstreams.caddyfiletest new file mode 100644 index 00000000000..384cc056262 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_dynamic_upstreams.caddyfiletest @@ -0,0 +1,120 @@ +:8884 { + reverse_proxy { + dynamic a foo 9000 + } + + reverse_proxy { + dynamic a { + name foo + port 9000 + refresh 5m + resolvers 8.8.8.8 8.8.4.4 + dial_timeout 2s + dial_fallback_delay 300ms + versions ipv6 + } + } +} + +:8885 { + reverse_proxy { + dynamic srv _api._tcp.example.com + } + + reverse_proxy { + dynamic srv { + service api + proto tcp + name example.com + refresh 5m + resolvers 8.8.8.8 8.8.4.4 + dial_timeout 1s + dial_fallback_delay -1s + } + } +} + +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "routes": [ + { + "handle": [ + { + "dynamic_upstreams": { + "name": "foo", + "port": "9000", + "source": "a" + }, + "handler": "reverse_proxy" + }, + { + "dynamic_upstreams": { + "dial_fallback_delay": 300000000, + "dial_timeout": 2000000000, + "name": "foo", + "port": "9000", + "refresh": 300000000000, + "resolver": { + "addresses": [ + "8.8.8.8", + "8.8.4.4" + ] + }, + "source": "a", + "versions": { + "ipv6": true + } + }, + "handler": "reverse_proxy" + } + ] + } + ] + }, + "srv1": { + "listen": [ + ":8885" + ], + "routes": [ + { + "handle": [ + { + "dynamic_upstreams": { + "name": "_api._tcp.example.com", + "source": "srv" + }, + "handler": "reverse_proxy" + }, + { + "dynamic_upstreams": { + "dial_fallback_delay": -1000000000, + "dial_timeout": 1000000000, + "name": "example.com", + "proto": "tcp", + "refresh": 300000000000, + "resolver": { + "addresses": [ + "8.8.8.8", + "8.8.4.4" + ] + }, + "service": "api", + "source": "srv" + }, + "handler": "reverse_proxy" + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_dynamic_upstreams_grace_period.caddyfiletest b/caddytest/integration/caddyfile_adapt/reverse_proxy_dynamic_upstreams_grace_period.caddyfiletest new file mode 100644 index 00000000000..0389b2f1273 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_dynamic_upstreams_grace_period.caddyfiletest @@ -0,0 +1,38 @@ +:8884 { + reverse_proxy { + dynamic srv { + name foo + refresh 5m + grace_period 5s + } + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "routes": [ + { + "handle": [ + { + "dynamic_upstreams": { + "grace_period": 5000000000, + "name": "foo", + "refresh": 300000000000, + "source": "srv" + }, + "handler": "reverse_proxy" + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_empty_non_http_transport.caddyfiletest b/caddytest/integration/caddyfile_adapt/reverse_proxy_empty_non_http_transport.caddyfiletest new file mode 100644 index 00000000000..bcbe29b66b3 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_empty_non_http_transport.caddyfiletest @@ -0,0 +1,36 @@ +:8884 + +reverse_proxy 127.0.0.1:65535 { + transport fastcgi +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "transport": { + "protocol": "fastcgi" + }, + "upstreams": [ + { + "dial": "127.0.0.1:65535" + } + ] + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_h2c_shorthand.caddyfiletest b/caddytest/integration/caddyfile_adapt/reverse_proxy_h2c_shorthand.caddyfiletest new file mode 100644 index 00000000000..59394673ce8 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_h2c_shorthand.caddyfiletest @@ -0,0 +1,55 @@ +:8884 + +reverse_proxy h2c://localhost:8080 + +reverse_proxy unix+h2c//run/app.sock +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "transport": { + "protocol": "http", + "versions": [ + "h2c", + "2" + ] + }, + "upstreams": [ + { + "dial": "localhost:8080" + } + ] + }, + { + "handler": "reverse_proxy", + "transport": { + "protocol": "http", + "versions": [ + "h2c", + "2" + ] + }, + "upstreams": [ + { + "dial": "unix//run/app.sock" + } + ] + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_handle_response.caddyfiletest b/caddytest/integration/caddyfile_adapt/reverse_proxy_handle_response.caddyfiletest new file mode 100644 index 00000000000..f6a26090066 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_handle_response.caddyfiletest @@ -0,0 +1,277 @@ +:8884 + +reverse_proxy 127.0.0.1:65535 { + @500 status 500 + replace_status @500 400 + + @all status 2xx 3xx 4xx 5xx + replace_status @all {http.error.status_code} + + replace_status {http.error.status_code} + + @accel header X-Accel-Redirect * + handle_response @accel { + respond "Header X-Accel-Redirect!" + } + + @another { + header X-Another * + } + handle_response @another { + respond "Header X-Another!" + } + + @401 status 401 + handle_response @401 { + respond "Status 401!" + } + + handle_response { + respond "Any! This should be last in the JSON!" + } + + @403 { + status 403 + } + handle_response @403 { + respond "Status 403!" + } + + @multi { + status 401 403 + status 404 + header Foo * + header Bar * + } + handle_response @multi { + respond "Headers Foo, Bar AND statuses 401, 403 and 404!" + } + + @200 status 200 + handle_response @200 { + copy_response_headers { + include Foo Bar + } + respond "Copied headers from the response" + } + + @201 status 201 + handle_response @201 { + header Foo "Copying the response" + copy_response 404 + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "routes": [ + { + "handle": [ + { + "handle_response": [ + { + "match": { + "status_code": [ + 500 + ] + }, + "status_code": 400 + }, + { + "match": { + "status_code": [ + 2, + 3, + 4, + 5 + ] + }, + "status_code": "{http.error.status_code}" + }, + { + "match": { + "headers": { + "X-Accel-Redirect": [ + "*" + ] + } + }, + "routes": [ + { + "handle": [ + { + "body": "Header X-Accel-Redirect!", + "handler": "static_response" + } + ] + } + ] + }, + { + "match": { + "headers": { + "X-Another": [ + "*" + ] + } + }, + "routes": [ + { + "handle": [ + { + "body": "Header X-Another!", + "handler": "static_response" + } + ] + } + ] + }, + { + "match": { + "status_code": [ + 401 + ] + }, + "routes": [ + { + "handle": [ + { + "body": "Status 401!", + "handler": "static_response" + } + ] + } + ] + }, + { + "match": { + "status_code": [ + 403 + ] + }, + "routes": [ + { + "handle": [ + { + "body": "Status 403!", + "handler": "static_response" + } + ] + } + ] + }, + { + "match": { + "headers": { + "Bar": [ + "*" + ], + "Foo": [ + "*" + ] + }, + "status_code": [ + 401, + 403, + 404 + ] + }, + "routes": [ + { + "handle": [ + { + "body": "Headers Foo, Bar AND statuses 401, 403 and 404!", + "handler": "static_response" + } + ] + } + ] + }, + { + "match": { + "status_code": [ + 200 + ] + }, + "routes": [ + { + "handle": [ + { + "handler": "copy_response_headers", + "include": [ + "Foo", + "Bar" + ] + }, + { + "body": "Copied headers from the response", + "handler": "static_response" + } + ] + } + ] + }, + { + "match": { + "status_code": [ + 201 + ] + }, + "routes": [ + { + "handle": [ + { + "handler": "headers", + "response": { + "set": { + "Foo": [ + "Copying the response" + ] + } + } + }, + { + "handler": "copy_response", + "status_code": 404 + } + ] + } + ] + }, + { + "status_code": "{http.error.status_code}" + }, + { + "routes": [ + { + "handle": [ + { + "body": "Any! This should be last in the JSON!", + "handler": "static_response" + } + ] + } + ] + } + ], + "handler": "reverse_proxy", + "upstreams": [ + { + "dial": "127.0.0.1:65535" + } + ] + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_health_headers.caddyfiletest b/caddytest/integration/caddyfile_adapt/reverse_proxy_health_headers.caddyfiletest new file mode 100644 index 00000000000..800c11f188c --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_health_headers.caddyfiletest @@ -0,0 +1,69 @@ +:8884 + +reverse_proxy 127.0.0.1:65535 { + health_headers { + Host example.com + X-Header-Key 95ca39e3cbe7 + X-Header-Keys VbG4NZwWnipo 335Q9/MhqcNU3s2TO + X-Empty-Value + Same-Key 1 + Same-Key 2 + X-System-Hostname {system.hostname} + } + health_uri /health +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "health_checks": { + "active": { + "headers": { + "Host": [ + "example.com" + ], + "Same-Key": [ + "1", + "2" + ], + "X-Empty-Value": [ + "" + ], + "X-Header-Key": [ + "95ca39e3cbe7" + ], + "X-Header-Keys": [ + "VbG4NZwWnipo", + "335Q9/MhqcNU3s2TO" + ], + "X-System-Hostname": [ + "{system.hostname}" + ] + }, + "uri": "/health" + } + }, + "upstreams": [ + { + "dial": "127.0.0.1:65535" + } + ] + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_health_method.caddyfiletest b/caddytest/integration/caddyfile_adapt/reverse_proxy_health_method.caddyfiletest new file mode 100644 index 00000000000..920702c10b4 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_health_method.caddyfiletest @@ -0,0 +1,40 @@ +:8884 + +reverse_proxy 127.0.0.1:65535 { + health_uri /health + health_method HEAD +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "health_checks": { + "active": { + "method": "HEAD", + "uri": "/health" + } + }, + "upstreams": [ + { + "dial": "127.0.0.1:65535" + } + ] + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_health_path_query.caddyfiletest b/caddytest/integration/caddyfile_adapt/reverse_proxy_health_path_query.caddyfiletest new file mode 100644 index 00000000000..80ac2de5b14 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_health_path_query.caddyfiletest @@ -0,0 +1,75 @@ +# Health with query in the uri +:8443 { + reverse_proxy localhost:54321 { + health_uri /health?ready=1 + health_status 2xx + } +} + +# Health without query in the uri +:8444 { + reverse_proxy localhost:54321 { + health_uri /health + health_status 200 + } +} + +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8443" + ], + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "health_checks": { + "active": { + "expect_status": 2, + "uri": "/health?ready=1" + } + }, + "upstreams": [ + { + "dial": "localhost:54321" + } + ] + } + ] + } + ] + }, + "srv1": { + "listen": [ + ":8444" + ], + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "health_checks": { + "active": { + "expect_status": 200, + "uri": "/health" + } + }, + "upstreams": [ + { + "dial": "localhost:54321" + } + ] + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_health_reqbody.caddyfiletest b/caddytest/integration/caddyfile_adapt/reverse_proxy_health_reqbody.caddyfiletest new file mode 100644 index 00000000000..ae5a6791ef7 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_health_reqbody.caddyfiletest @@ -0,0 +1,40 @@ +:8884 + +reverse_proxy 127.0.0.1:65535 { + health_uri /health + health_request_body "test body" +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "health_checks": { + "active": { + "body": "test body", + "uri": "/health" + } + }, + "upstreams": [ + { + "dial": "127.0.0.1:65535" + } + ] + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_http_transport_tls_file_cert.txt b/caddytest/integration/caddyfile_adapt/reverse_proxy_http_transport_tls_file_cert.txt new file mode 100644 index 00000000000..d43aa1175c3 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_http_transport_tls_file_cert.txt @@ -0,0 +1,47 @@ +:8884 +reverse_proxy 127.0.0.1:65535 { + transport http { + tls_trust_pool file { + pem_file ../caddy.ca.cer + } + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "transport": { + "protocol": "http", + "tls": { + "ca": { + "pem_files": [ + "../caddy.ca.cer" + ], + "provider": "file" + } + } + }, + "upstreams": [ + { + "dial": "127.0.0.1:65535" + } + ] + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_http_transport_tls_inline_cert.txt b/caddytest/integration/caddyfile_adapt/reverse_proxy_http_transport_tls_inline_cert.txt new file mode 100644 index 00000000000..ef9e8243af6 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_http_transport_tls_inline_cert.txt @@ -0,0 +1,47 @@ +:8884 +reverse_proxy 127.0.0.1:65535 { + transport http { + tls_trust_pool inline { + trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ== + } + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "transport": { + "protocol": "http", + "tls": { + "ca": { + "provider": "inline", + "trusted_ca_certs": [ + "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==" + ] + } + } + }, + "upstreams": [ + { + "dial": "127.0.0.1:65535" + } + ] + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_load_balance.caddyfiletest b/caddytest/integration/caddyfile_adapt/reverse_proxy_load_balance.caddyfiletest new file mode 100644 index 00000000000..5885eec1f2b --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_load_balance.caddyfiletest @@ -0,0 +1,64 @@ +:8884 + +reverse_proxy 127.0.0.1:65535 { + lb_policy first + lb_retries 5 + lb_try_duration 10s + lb_try_interval 500ms + lb_retry_match { + path /foo* + method POST + } + lb_retry_match path /bar* +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "load_balancing": { + "retries": 5, + "retry_match": [ + { + "method": [ + "POST" + ], + "path": [ + "/foo*" + ] + }, + { + "path": [ + "/bar*" + ] + } + ], + "selection_policy": { + "policy": "first" + }, + "try_duration": 10000000000, + "try_interval": 500000000 + }, + "upstreams": [ + { + "dial": "127.0.0.1:65535" + } + ] + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_load_balance_wrr.caddyfiletest b/caddytest/integration/caddyfile_adapt/reverse_proxy_load_balance_wrr.caddyfiletest new file mode 100644 index 00000000000..d41c4b8bf5c --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_load_balance_wrr.caddyfiletest @@ -0,0 +1,71 @@ +:8884 + +reverse_proxy 127.0.0.1:65535 127.0.0.1:35535 { + lb_policy weighted_round_robin 10 1 + lb_retries 5 + lb_try_duration 10s + lb_try_interval 500ms + lb_retry_match { + path /foo* + method POST + } + lb_retry_match path /bar* +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "load_balancing": { + "retries": 5, + "retry_match": [ + { + "method": [ + "POST" + ], + "path": [ + "/foo*" + ] + }, + { + "path": [ + "/bar*" + ] + } + ], + "selection_policy": { + "policy": "weighted_round_robin", + "weights": [ + 10, + 1 + ] + }, + "try_duration": 10000000000, + "try_interval": 500000000 + }, + "upstreams": [ + { + "dial": "127.0.0.1:65535" + }, + { + "dial": "127.0.0.1:35535" + } + ] + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_localaddr.caddyfiletest b/caddytest/integration/caddyfile_adapt/reverse_proxy_localaddr.caddyfiletest new file mode 100644 index 00000000000..d734c9ce09a --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_localaddr.caddyfiletest @@ -0,0 +1,57 @@ +https://example.com { + reverse_proxy http://localhost:54321 { + transport http { + local_address 192.168.0.1 + } + } +} + +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "transport": { + "local_address": "192.168.0.1", + "protocol": "http" + }, + "upstreams": [ + { + "dial": "localhost:54321" + } + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_options.caddyfiletest b/caddytest/integration/caddyfile_adapt/reverse_proxy_options.caddyfiletest new file mode 100644 index 00000000000..f6420ca015f --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_options.caddyfiletest @@ -0,0 +1,133 @@ +https://example.com { + reverse_proxy /path https://localhost:54321 { + header_up Host {upstream_hostport} + header_up Foo bar + + method GET + rewrite /rewritten?uri={uri} + + request_buffers 4KB + + transport http { + read_buffer 10MB + write_buffer 20MB + max_response_header 30MB + dial_timeout 3s + dial_fallback_delay 5s + response_header_timeout 8s + expect_continue_timeout 9s + resolvers 8.8.8.8 8.8.4.4 + + versions h2c 2 + compression off + max_conns_per_host 5 + keepalive_idle_conns_per_host 2 + keepalive_interval 30s + + tls_renegotiation freely + tls_except_ports 8181 8182 + } + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "headers": { + "request": { + "set": { + "Foo": [ + "bar" + ], + "Host": [ + "{http.reverse_proxy.upstream.hostport}" + ] + } + } + }, + "request_buffers": 4000, + "rewrite": { + "method": "GET", + "uri": "/rewritten?uri={http.request.uri}" + }, + "transport": { + "compression": false, + "dial_fallback_delay": 5000000000, + "dial_timeout": 3000000000, + "expect_continue_timeout": 9000000000, + "keep_alive": { + "max_idle_conns_per_host": 2, + "probe_interval": 30000000000 + }, + "max_conns_per_host": 5, + "max_response_header_size": 30000000, + "protocol": "http", + "read_buffer_size": 10000000, + "resolver": { + "addresses": [ + "8.8.8.8", + "8.8.4.4" + ] + }, + "response_header_timeout": 8000000000, + "tls": { + "except_ports": [ + "8181", + "8182" + ], + "renegotiation": "freely" + }, + "versions": [ + "h2c", + "2" + ], + "write_buffer_size": 20000000 + }, + "upstreams": [ + { + "dial": "localhost:54321" + } + ] + } + ], + "match": [ + { + "path": [ + "/path" + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_port_range.caddyfiletest b/caddytest/integration/caddyfile_adapt/reverse_proxy_port_range.caddyfiletest new file mode 100644 index 00000000000..978d8c96a66 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_port_range.caddyfiletest @@ -0,0 +1,67 @@ +:8884 { + # Port range + reverse_proxy localhost:8001-8002 + + # Port range with placeholder + reverse_proxy {host}:8001-8002 + + # Port range with scheme + reverse_proxy https://localhost:8001-8002 +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "upstreams": [ + { + "dial": "localhost:8001" + }, + { + "dial": "localhost:8002" + } + ] + }, + { + "handler": "reverse_proxy", + "upstreams": [ + { + "dial": "{http.request.host}:8001" + }, + { + "dial": "{http.request.host}:8002" + } + ] + }, + { + "handler": "reverse_proxy", + "transport": { + "protocol": "http", + "tls": {} + }, + "upstreams": [ + { + "dial": "localhost:8001" + }, + { + "dial": "localhost:8002" + } + ] + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_trusted_proxies.caddyfiletest b/caddytest/integration/caddyfile_adapt/reverse_proxy_trusted_proxies.caddyfiletest new file mode 100644 index 00000000000..f1e685c0010 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_trusted_proxies.caddyfiletest @@ -0,0 +1,56 @@ +:8884 + +reverse_proxy 127.0.0.1:65535 { + trusted_proxies 127.0.0.1 +} + +reverse_proxy 127.0.0.1:65535 { + trusted_proxies private_ranges +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "trusted_proxies": [ + "127.0.0.1" + ], + "upstreams": [ + { + "dial": "127.0.0.1:65535" + } + ] + }, + { + "handler": "reverse_proxy", + "trusted_proxies": [ + "192.168.0.0/16", + "172.16.0.0/12", + "10.0.0.0/8", + "127.0.0.1/8", + "fd00::/8", + "::1" + ], + "upstreams": [ + { + "dial": "127.0.0.1:65535" + } + ] + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_upstream_placeholder.caddyfiletest b/caddytest/integration/caddyfile_adapt/reverse_proxy_upstream_placeholder.caddyfiletest new file mode 100644 index 00000000000..91c8f30744c --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_upstream_placeholder.caddyfiletest @@ -0,0 +1,102 @@ +:8884 { + map {host} {upstream} { + foo.example.com 1.2.3.4 + default 2.3.4.5 + } + + # Upstream placeholder with a port should retain the port + reverse_proxy {upstream}:80 +} + +:8885 { + map {host} {upstream} { + foo.example.com 1.2.3.4:8080 + default 2.3.4.5:8080 + } + + # Upstream placeholder with no port should not have a port joined + reverse_proxy {upstream} +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "routes": [ + { + "handle": [ + { + "defaults": [ + "2.3.4.5" + ], + "destinations": [ + "{upstream}" + ], + "handler": "map", + "mappings": [ + { + "input": "foo.example.com", + "outputs": [ + "1.2.3.4" + ] + } + ], + "source": "{http.request.host}" + }, + { + "handler": "reverse_proxy", + "upstreams": [ + { + "dial": "{upstream}:80" + } + ] + } + ] + } + ] + }, + "srv1": { + "listen": [ + ":8885" + ], + "routes": [ + { + "handle": [ + { + "defaults": [ + "2.3.4.5:8080" + ], + "destinations": [ + "{upstream}" + ], + "handler": "map", + "mappings": [ + { + "input": "foo.example.com", + "outputs": [ + "1.2.3.4:8080" + ] + } + ], + "source": "{http.request.host}" + }, + { + "handler": "reverse_proxy", + "upstreams": [ + { + "dial": "{upstream}" + } + ] + } + ] + } + ] + } + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/rewrite_directive_permutations.caddyfiletest b/caddytest/integration/caddyfile_adapt/rewrite_directive_permutations.caddyfiletest new file mode 100644 index 00000000000..870e82afdcc --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/rewrite_directive_permutations.caddyfiletest @@ -0,0 +1,112 @@ +:8080 + +# With explicit wildcard matcher +route { + rewrite * /a +} + +# With path matcher +route { + rewrite /path /b +} + +# With named matcher +route { + @named method GET + rewrite @named /c +} + +# With no matcher, assumed to be wildcard +route { + rewrite /d +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8080" + ], + "routes": [ + { + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "group": "group0", + "handle": [ + { + "handler": "rewrite", + "uri": "/a" + } + ] + } + ] + }, + { + "handler": "subroute", + "routes": [ + { + "group": "group1", + "handle": [ + { + "handler": "rewrite", + "uri": "/b" + } + ], + "match": [ + { + "path": [ + "/path" + ] + } + ] + } + ] + }, + { + "handler": "subroute", + "routes": [ + { + "group": "group2", + "handle": [ + { + "handler": "rewrite", + "uri": "/c" + } + ], + "match": [ + { + "method": [ + "GET" + ] + } + ] + } + ] + }, + { + "handler": "subroute", + "routes": [ + { + "group": "group3", + "handle": [ + { + "handler": "rewrite", + "uri": "/d" + } + ] + } + ] + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/root_directive_permutations.caddyfiletest b/caddytest/integration/caddyfile_adapt/root_directive_permutations.caddyfiletest new file mode 100644 index 00000000000..b2ef86c45ac --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/root_directive_permutations.caddyfiletest @@ -0,0 +1,108 @@ +:8080 + +# With explicit wildcard matcher +route { + root * /a +} + +# With path matcher +route { + root /path /b +} + +# With named matcher +route { + @named method GET + root @named /c +} + +# With no matcher, assumed to be wildcard +route { + root /d +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8080" + ], + "routes": [ + { + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "vars", + "root": "/a" + } + ] + } + ] + }, + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "vars", + "root": "/b" + } + ], + "match": [ + { + "path": [ + "/path" + ] + } + ] + } + ] + }, + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "vars", + "root": "/c" + } + ], + "match": [ + { + "method": [ + "GET" + ] + } + ] + } + ] + }, + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "vars", + "root": "/d" + } + ] + } + ] + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/server_names.caddyfiletest b/caddytest/integration/caddyfile_adapt/server_names.caddyfiletest new file mode 100644 index 00000000000..e43eb8c03a4 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/server_names.caddyfiletest @@ -0,0 +1,77 @@ +{ + servers :443 { + name https + } + + servers :8000 { + name app1 + } + + servers :8001 { + name app2 + } + + servers 123.123.123.123:8002 { + name bind-server + } +} + +example.com { +} + +:8000 { +} + +:8001, :8002 { +} + +:8002 { + bind 123.123.123.123 222.222.222.222 +} +---------- +{ + "apps": { + "http": { + "servers": { + "app1": { + "listen": [ + ":8000" + ] + }, + "app2": { + "listen": [ + ":8001" + ] + }, + "bind-server": { + "listen": [ + "123.123.123.123:8002", + "222.222.222.222:8002" + ] + }, + "https": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "terminal": true + } + ] + }, + "srv4": { + "listen": [ + ":8002" + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/shorthand_parameterized_placeholders.caddyfiletest b/caddytest/integration/caddyfile_adapt/shorthand_parameterized_placeholders.caddyfiletest new file mode 100644 index 00000000000..ef8d2330b95 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/shorthand_parameterized_placeholders.caddyfiletest @@ -0,0 +1,63 @@ +localhost:80 + +respond * "{header.content-type} {labels.0} {query.p} {path.0} {re.name.0}" + +@match path_regexp ^/foo(.*)$ +respond @match "{re.1}" +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "{http.regexp.1}", + "handler": "static_response" + } + ], + "match": [ + { + "path_regexp": { + "name": "match", + "pattern": "^/foo(.*)$" + } + } + ] + }, + { + "handle": [ + { + "body": "{http.request.header.content-type} {http.request.host.labels.0} {http.request.uri.query.p} {http.request.uri.path.0} {http.regexp.name.0}", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/site_block_sorting.caddyfiletest b/caddytest/integration/caddyfile_adapt/site_block_sorting.caddyfiletest new file mode 100644 index 00000000000..b2c6a6d3bbe --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/site_block_sorting.caddyfiletest @@ -0,0 +1,193 @@ +# https://caddy.community/t/caddy-suddenly-directs-my-site-to-the-wrong-directive/11597/2 +abcdef { + respond "abcdef" +} + +abcdefg { + respond "abcdefg" +} + +abc { + respond "abc" +} + +abcde, http://abcde { + respond "abcde" +} + +:443, ab { + respond "443 or ab" +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "abcdefg" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "abcdefg", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "abcdef" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "abcdef", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "abcde" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "abcde", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "abc" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "abc", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + }, + { + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "443 or ab", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ] + }, + "srv1": { + "listen": [ + ":80" + ], + "routes": [ + { + "match": [ + { + "host": [ + "abcde" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "abcde", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "certificates": { + "automate": [ + "ab" + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/sort_directives_with_any_matcher_first.caddyfiletest b/caddytest/integration/caddyfile_adapt/sort_directives_with_any_matcher_first.caddyfiletest new file mode 100644 index 00000000000..3859a7e54dd --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/sort_directives_with_any_matcher_first.caddyfiletest @@ -0,0 +1,51 @@ +:80 + +respond 200 + +@untrusted not remote_ip 10.1.1.0/24 +respond @untrusted 401 +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "match": [ + { + "not": [ + { + "remote_ip": { + "ranges": [ + "10.1.1.0/24" + ] + } + } + ] + } + ], + "handle": [ + { + "handler": "static_response", + "status_code": 401 + } + ] + }, + { + "handle": [ + { + "handler": "static_response", + "status_code": 200 + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/sort_directives_within_handle.caddyfiletest b/caddytest/integration/caddyfile_adapt/sort_directives_within_handle.caddyfiletest new file mode 100644 index 00000000000..ac0d53cc979 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/sort_directives_within_handle.caddyfiletest @@ -0,0 +1,169 @@ +*.example.com { + @foo host foo.example.com + handle @foo { + handle_path /strip { + respond "this should be first" + } + handle_path /strip* { + respond "this should be second" + } + handle { + respond "this should be last" + } + } + handle { + respond "this should be last" + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "*.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "group": "group6", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "group": "group3", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "rewrite", + "strip_path_prefix": "/strip" + } + ] + }, + { + "handle": [ + { + "body": "this should be first", + "handler": "static_response" + } + ] + } + ] + } + ], + "match": [ + { + "path": [ + "/strip" + ] + } + ] + }, + { + "group": "group3", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "rewrite", + "strip_path_prefix": "/strip" + } + ] + }, + { + "handle": [ + { + "body": "this should be second", + "handler": "static_response" + } + ] + } + ] + } + ], + "match": [ + { + "path": [ + "/strip*" + ] + } + ] + }, + { + "group": "group3", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "this should be last", + "handler": "static_response" + } + ] + } + ] + } + ] + } + ] + } + ], + "match": [ + { + "host": [ + "foo.example.com" + ] + } + ] + }, + { + "group": "group6", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "this should be last", + "handler": "static_response" + } + ] + } + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/sort_vars_in_reverse.caddyfiletest b/caddytest/integration/caddyfile_adapt/sort_vars_in_reverse.caddyfiletest new file mode 100644 index 00000000000..38a912f95ec --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/sort_vars_in_reverse.caddyfiletest @@ -0,0 +1,75 @@ +:80 + +vars /foobar foo last +vars /foo foo middle-last +vars /foo* foo middle-first +vars * foo first +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "handle": [ + { + "foo": "first", + "handler": "vars" + } + ] + }, + { + "match": [ + { + "path": [ + "/foo*" + ] + } + ], + "handle": [ + { + "foo": "middle-first", + "handler": "vars" + } + ] + }, + { + "match": [ + { + "path": [ + "/foo" + ] + } + ], + "handle": [ + { + "foo": "middle-last", + "handler": "vars" + } + ] + }, + { + "match": [ + { + "path": [ + "/foobar" + ] + } + ], + "handle": [ + { + "foo": "last", + "handler": "vars" + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_acme_preferred_chains.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_acme_preferred_chains.caddyfiletest new file mode 100644 index 00000000000..d6242d7d604 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_acme_preferred_chains.caddyfiletest @@ -0,0 +1,57 @@ +localhost + +tls { + issuer acme { + preferred_chains { + any_common_name "Generic CA 1" "Generic CA 2" + } + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "localhost" + ], + "issuers": [ + { + "module": "acme", + "preferred_chains": { + "any_common_name": [ + "Generic CA 1", + "Generic CA 2" + ] + } + } + ] + } + ] + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/tls_automation_policies_1.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_automation_policies_1.caddyfiletest new file mode 100644 index 00000000000..c3fd4898249 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_automation_policies_1.caddyfiletest @@ -0,0 +1,86 @@ +{ + local_certs +} + +*.tld, *.*.tld { + tls { + on_demand + } +} + +foo.tld, www.foo.tld { +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "foo.tld", + "www.foo.tld" + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "*.tld", + "*.*.tld" + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "foo.tld", + "www.foo.tld" + ], + "issuers": [ + { + "module": "internal" + } + ] + }, + { + "subjects": [ + "*.*.tld", + "*.tld" + ], + "issuers": [ + { + "module": "internal" + } + ], + "on_demand": true + }, + { + "issuers": [ + { + "module": "internal" + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_automation_policies_10.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_automation_policies_10.caddyfiletest new file mode 100644 index 00000000000..b6832ad1b5a --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_automation_policies_10.caddyfiletest @@ -0,0 +1,58 @@ +# example from issue #4667 +{ + auto_https off +} + +https://, example.com { + tls test.crt test.key + respond "Hello World" +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "handle": [ + { + "body": "Hello World", + "handler": "static_response" + } + ] + } + ], + "tls_connection_policies": [ + { + "certificate_selection": { + "any_tag": [ + "cert0" + ] + } + } + ], + "automatic_https": { + "disable": true + } + } + } + }, + "tls": { + "certificates": { + "load_files": [ + { + "certificate": "test.crt", + "key": "test.key", + "tags": [ + "cert0" + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_automation_policies_11.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_automation_policies_11.caddyfiletest new file mode 100644 index 00000000000..9cdfd120033 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_automation_policies_11.caddyfiletest @@ -0,0 +1,67 @@ +# example from https://caddy.community/t/21415 +a.com { + tls { + get_certificate http http://foo.com/get + } +} + +b.com { +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "a.com" + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "b.com" + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "a.com" + ], + "get_certificate": [ + { + "url": "http://foo.com/get", + "via": "http" + } + ] + }, + { + "subjects": [ + "b.com" + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_automation_policies_2.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_automation_policies_2.caddyfiletest new file mode 100644 index 00000000000..17196ec07e9 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_automation_policies_2.caddyfiletest @@ -0,0 +1,92 @@ +# issue #3953 +{ + cert_issuer zerossl api_key +} + +example.com { + tls { + on_demand + key_type rsa2048 + } +} + +http://example.net { +} + +:1234 { +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":1234" + ] + }, + "srv1": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "terminal": true + } + ] + }, + "srv2": { + "listen": [ + ":80" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.net" + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "example.com" + ], + "issuers": [ + { + "api_key": "api_key", + "module": "zerossl" + } + ], + "key_type": "rsa2048", + "on_demand": true + }, + { + "issuers": [ + { + "api_key": "api_key", + "module": "zerossl" + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_automation_policies_3.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_automation_policies_3.caddyfiletest new file mode 100644 index 00000000000..9daaf436d57 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_automation_policies_3.caddyfiletest @@ -0,0 +1,90 @@ +# https://caddy.community/t/caddyfile-having-individual-sites-differ-from-global-options/11297 +{ + local_certs +} + +a.example.com { + tls internal +} + +b.example.com { + tls abc@example.com +} + +c.example.com { +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "a.example.com" + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "b.example.com" + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "c.example.com" + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "b.example.com" + ], + "issuers": [ + { + "email": "abc@example.com", + "module": "acme" + }, + { + "ca": "https://acme.zerossl.com/v2/DV90", + "email": "abc@example.com", + "module": "acme" + } + ] + }, + { + "issuers": [ + { + "module": "internal" + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_automation_policies_4.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_automation_policies_4.caddyfiletest new file mode 100644 index 00000000000..a4385a8f3d7 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_automation_policies_4.caddyfiletest @@ -0,0 +1,144 @@ +{ + email my.email@example.com +} + +:82 { + redir https://example.com{uri} +} + +:83 { + redir https://example.com{uri} +} + +:84 { + redir https://example.com{uri} +} + +abc.de { + redir https://example.com{uri} +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "abc.de" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "static_response", + "headers": { + "Location": [ + "https://example.com{http.request.uri}" + ] + }, + "status_code": 302 + } + ] + } + ] + } + ], + "terminal": true + } + ] + }, + "srv1": { + "listen": [ + ":82" + ], + "routes": [ + { + "handle": [ + { + "handler": "static_response", + "headers": { + "Location": [ + "https://example.com{http.request.uri}" + ] + }, + "status_code": 302 + } + ] + } + ] + }, + "srv2": { + "listen": [ + ":83" + ], + "routes": [ + { + "handle": [ + { + "handler": "static_response", + "headers": { + "Location": [ + "https://example.com{http.request.uri}" + ] + }, + "status_code": 302 + } + ] + } + ] + }, + "srv3": { + "listen": [ + ":84" + ], + "routes": [ + { + "handle": [ + { + "handler": "static_response", + "headers": { + "Location": [ + "https://example.com{http.request.uri}" + ] + }, + "status_code": 302 + } + ] + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "issuers": [ + { + "email": "my.email@example.com", + "module": "acme" + }, + { + "ca": "https://acme.zerossl.com/v2/DV90", + "email": "my.email@example.com", + "module": "acme" + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_automation_policies_5.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_automation_policies_5.caddyfiletest new file mode 100644 index 00000000000..87d278dbcdb --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_automation_policies_5.caddyfiletest @@ -0,0 +1,62 @@ +a.example.com { +} + +b.example.com { +} + +:443 { + tls { + on_demand + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "a.example.com" + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "b.example.com" + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "a.example.com", + "b.example.com" + ] + }, + { + "on_demand": true + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_automation_policies_6.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_automation_policies_6.caddyfiletest new file mode 100644 index 00000000000..b3ad7ff2d84 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_automation_policies_6.caddyfiletest @@ -0,0 +1,120 @@ +# (this Caddyfile is contrived, but based on issue #4161) + +example.com { + tls { + ca https://foobar + } +} + +example.com:8443 { + tls { + ca https://foobar + } +} + +example.com:8444 { + tls { + ca https://foobar + } +} + +example.com:8445 { + tls { + ca https://foobar + } +} + +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "terminal": true + } + ] + }, + "srv1": { + "listen": [ + ":8443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "terminal": true + } + ] + }, + "srv2": { + "listen": [ + ":8444" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "terminal": true + } + ] + }, + "srv3": { + "listen": [ + ":8445" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "example.com" + ], + "issuers": [ + { + "ca": "https://foobar", + "module": "acme" + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_automation_policies_7.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_automation_policies_7.caddyfiletest new file mode 100644 index 00000000000..4b17bf3d4dd --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_automation_policies_7.caddyfiletest @@ -0,0 +1,68 @@ +# (this Caddyfile is contrived, but based on issues #4176 and #4198) + +http://example.com { +} + +https://example.com { + tls internal +} + +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "terminal": true + } + ] + }, + "srv1": { + "listen": [ + ":80" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "example.com" + ], + "issuers": [ + { + "module": "internal" + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_automation_policies_8.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_automation_policies_8.caddyfiletest new file mode 100644 index 00000000000..bd1bbf2214d --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_automation_policies_8.caddyfiletest @@ -0,0 +1,99 @@ +# (this Caddyfile is contrived, but based on issues #4176 and #4198) + +http://example.com { +} + +https://example.com { + tls abc@example.com +} + +http://localhost:8081 { +} + +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "terminal": true + } + ] + }, + "srv1": { + "listen": [ + ":80" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "terminal": true + } + ] + }, + "srv2": { + "listen": [ + ":8081" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "terminal": true + } + ], + "automatic_https": { + "skip": [ + "localhost" + ] + } + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "example.com" + ], + "issuers": [ + { + "email": "abc@example.com", + "module": "acme" + }, + { + "ca": "https://acme.zerossl.com/v2/DV90", + "email": "abc@example.com", + "module": "acme" + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_automation_policies_9.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_automation_policies_9.caddyfiletest new file mode 100644 index 00000000000..bd82e96c1cf --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_automation_policies_9.caddyfiletest @@ -0,0 +1,56 @@ +# example from issue #4640 +http://foo:8447, http://127.0.0.1:8447 { + reverse_proxy 127.0.0.1:8080 +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8447" + ], + "routes": [ + { + "match": [ + { + "host": [ + "foo", + "127.0.0.1" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "upstreams": [ + { + "dial": "127.0.0.1:8080" + } + ] + } + ] + } + ] + } + ], + "terminal": true + } + ], + "automatic_https": { + "skip": [ + "foo", + "127.0.0.1" + ] + } + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_automation_policies_global_email_localhost.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_automation_policies_global_email_localhost.caddyfiletest new file mode 100644 index 00000000000..50fbf51aa83 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_automation_policies_global_email_localhost.caddyfiletest @@ -0,0 +1,67 @@ +{ + email foo@bar +} + +localhost { +} + +example.com { +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "example.com" + ], + "issuers": [ + { + "email": "foo@bar", + "module": "acme" + }, + { + "ca": "https://acme.zerossl.com/v2/DV90", + "email": "foo@bar", + "module": "acme" + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_automation_wildcard_force_automate.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_automation_wildcard_force_automate.caddyfiletest new file mode 100644 index 00000000000..4eb6c4f1cb2 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_automation_wildcard_force_automate.caddyfiletest @@ -0,0 +1,180 @@ +automated1.example.com { + tls force_automate + respond "Automated!" +} + +automated2.example.com { + tls force_automate + respond "Automated!" +} + +shadowed.example.com { + respond "Shadowed!" +} + +*.example.com { + tls cert.pem key.pem + respond "Wildcard!" +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "automated1.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Automated!", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "automated2.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Automated!", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "shadowed.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Shadowed!", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "*.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Wildcard!", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ], + "tls_connection_policies": [ + { + "match": { + "sni": [ + "automated1.example.com" + ] + } + }, + { + "match": { + "sni": [ + "automated2.example.com" + ] + } + }, + { + "match": { + "sni": [ + "*.example.com" + ] + }, + "certificate_selection": { + "any_tag": [ + "cert0" + ] + } + }, + {} + ] + } + } + }, + "tls": { + "certificates": { + "automate": [ + "automated1.example.com", + "automated2.example.com" + ], + "load_files": [ + { + "certificate": "cert.pem", + "key": "key.pem", + "tags": [ + "cert0" + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_automation_wildcard_shadowing.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_automation_wildcard_shadowing.caddyfiletest new file mode 100644 index 00000000000..2be54377936 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_automation_wildcard_shadowing.caddyfiletest @@ -0,0 +1,102 @@ +subdomain.example.com { + respond "Subdomain!" +} + +*.example.com { + tls cert.pem key.pem + respond "Wildcard!" +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "subdomain.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Subdomain!", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "*.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Wildcard!", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ], + "tls_connection_policies": [ + { + "match": { + "sni": [ + "*.example.com" + ] + }, + "certificate_selection": { + "any_tag": [ + "cert0" + ] + } + }, + {} + ] + } + } + }, + "tls": { + "certificates": { + "load_files": [ + { + "certificate": "cert.pem", + "key": "key.pem", + "tags": [ + "cert0" + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_client_auth_cert_file-legacy-with-verifier.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_client_auth_cert_file-legacy-with-verifier.caddyfiletest new file mode 100644 index 00000000000..302d8fd1e11 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_client_auth_cert_file-legacy-with-verifier.caddyfiletest @@ -0,0 +1,75 @@ +localhost + +respond "hello from localhost" +tls { + client_auth { + mode request + trusted_ca_cert_file ../caddy.ca.cer + verifier dummy + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "hello from localhost", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ], + "tls_connection_policies": [ + { + "match": { + "sni": [ + "localhost" + ] + }, + "client_authentication": { + "ca": { + "provider": "inline", + "trusted_ca_certs": [ + "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==" + ] + }, + "verifiers": [ + { + "verifier": "dummy" + } + ], + "mode": "request" + } + }, + {} + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_client_auth_cert_file-legacy.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_client_auth_cert_file-legacy.caddyfiletest new file mode 100644 index 00000000000..36fd978ee1b --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_client_auth_cert_file-legacy.caddyfiletest @@ -0,0 +1,69 @@ +localhost + +respond "hello from localhost" +tls { + client_auth { + mode request + trusted_ca_cert_file ../caddy.ca.cer + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "hello from localhost", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ], + "tls_connection_policies": [ + { + "match": { + "sni": [ + "localhost" + ] + }, + "client_authentication": { + "ca": { + "provider": "inline", + "trusted_ca_certs": [ + "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==" + ] + }, + "mode": "request" + } + }, + {} + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_client_auth_cert_file.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_client_auth_cert_file.caddyfiletest new file mode 100644 index 00000000000..dbf408fa13f --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_client_auth_cert_file.caddyfiletest @@ -0,0 +1,71 @@ +localhost + +respond "hello from localhost" +tls { + client_auth { + mode request + trust_pool file { + pem_file ../caddy.ca.cer + } + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "hello from localhost", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ], + "tls_connection_policies": [ + { + "match": { + "sni": [ + "localhost" + ] + }, + "client_authentication": { + "ca": { + "pem_files": [ + "../caddy.ca.cer" + ], + "provider": "file" + }, + "mode": "request" + } + }, + {} + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_client_auth_inline_cert-legacy.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_client_auth_inline_cert-legacy.caddyfiletest new file mode 100644 index 00000000000..3a91e832bf7 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_client_auth_inline_cert-legacy.caddyfiletest @@ -0,0 +1,69 @@ +localhost + +respond "hello from localhost" +tls { + client_auth { + mode request + trusted_ca_cert MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ== + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "hello from localhost", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ], + "tls_connection_policies": [ + { + "match": { + "sni": [ + "localhost" + ] + }, + "client_authentication": { + "ca": { + "provider": "inline", + "trusted_ca_certs": [ + "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==" + ] + }, + "mode": "request" + } + }, + {} + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_client_auth_inline_cert.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_client_auth_inline_cert.caddyfiletest new file mode 100644 index 00000000000..7b8e5a206da --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_client_auth_inline_cert.caddyfiletest @@ -0,0 +1,71 @@ +localhost + +respond "hello from localhost" +tls { + client_auth { + mode request + trust_pool inline { + trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ== + } + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "hello from localhost", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ], + "tls_connection_policies": [ + { + "match": { + "sni": [ + "localhost" + ] + }, + "client_authentication": { + "ca": { + "provider": "inline", + "trusted_ca_certs": [ + "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==" + ] + }, + "mode": "request" + } + }, + {} + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_client_auth_inline_cert_with_leaf_trust.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_client_auth_inline_cert_with_leaf_trust.caddyfiletest new file mode 100644 index 00000000000..66c3a3c36a7 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_client_auth_inline_cert_with_leaf_trust.caddyfiletest @@ -0,0 +1,75 @@ +localhost + +respond "hello from localhost" +tls { + client_auth { + mode request + trust_pool inline { + trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ== + } + trusted_leaf_cert_file ../caddy.ca.cer + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "hello from localhost", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ], + "tls_connection_policies": [ + { + "match": { + "sni": [ + "localhost" + ] + }, + "client_authentication": { + "ca": { + "provider": "inline", + "trusted_ca_certs": [ + "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==" + ] + }, + "trusted_leaf_certs": [ + "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==" + ], + "mode": "request" + } + }, + {} + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_conn_policy_consolidate.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_conn_policy_consolidate.caddyfiletest new file mode 100644 index 00000000000..68e89b0d3c8 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_conn_policy_consolidate.caddyfiletest @@ -0,0 +1,132 @@ +# https://github.com/caddyserver/caddy/issues/3906 +a.a { + tls internal + respond 403 +} + +http://b.b https://b.b:8443 { + tls internal + respond 404 +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "a.a" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "static_response", + "status_code": 403 + } + ] + } + ] + } + ], + "terminal": true + } + ] + }, + "srv1": { + "listen": [ + ":80" + ], + "routes": [ + { + "match": [ + { + "host": [ + "b.b" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "static_response", + "status_code": 404 + } + ] + } + ] + } + ], + "terminal": true + } + ] + }, + "srv2": { + "listen": [ + ":8443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "b.b" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "static_response", + "status_code": 404 + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "a.a", + "b.b" + ], + "issuers": [ + { + "module": "internal" + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_dns_ttl.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_dns_ttl.caddyfiletest new file mode 100644 index 00000000000..c452bf79f13 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_dns_ttl.caddyfiletest @@ -0,0 +1,68 @@ +localhost + +respond "hello from localhost" +tls { + dns_ttl 5m10s +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "hello from localhost", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "localhost" + ], + "issuers": [ + { + "challenges": { + "dns": { + "ttl": 310000000000 + } + }, + "module": "acme" + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_explicit_issuer_dns_ttl.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_explicit_issuer_dns_ttl.caddyfiletest new file mode 100644 index 00000000000..d552599ffe5 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_explicit_issuer_dns_ttl.caddyfiletest @@ -0,0 +1,80 @@ +localhost + +respond "hello from localhost" +tls { + issuer acme { + dns_ttl 5m10s + } + issuer zerossl api_key { + dns_ttl 10m20s + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "hello from localhost", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "localhost" + ], + "issuers": [ + { + "challenges": { + "dns": { + "ttl": 310000000000 + } + }, + "module": "acme" + }, + { + "api_key": "api_key", + "cname_validation": { + "ttl": 620000000000 + }, + "module": "zerossl" + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_explicit_issuer_propagation_options.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_explicit_issuer_propagation_options.caddyfiletest new file mode 100644 index 00000000000..206d59ca5b5 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_explicit_issuer_propagation_options.caddyfiletest @@ -0,0 +1,84 @@ +localhost + +respond "hello from localhost" +tls { + issuer acme { + propagation_delay 5m10s + propagation_timeout 10m20s + } + issuer zerossl api_key { + propagation_delay 5m30s + propagation_timeout -1 + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "hello from localhost", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "localhost" + ], + "issuers": [ + { + "challenges": { + "dns": { + "propagation_delay": 310000000000, + "propagation_timeout": 620000000000 + } + }, + "module": "acme" + }, + { + "api_key": "api_key", + "cname_validation": { + "propagation_delay": 330000000000, + "propagation_timeout": -1 + }, + "module": "zerossl" + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_internal_options.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_internal_options.caddyfiletest new file mode 100644 index 00000000000..50bbfec29cc --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_internal_options.caddyfiletest @@ -0,0 +1,54 @@ +a.example.com { + tls { + issuer internal { + ca foo + lifetime 24h + sign_with_root + } + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "a.example.com" + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "a.example.com" + ], + "issuers": [ + { + "ca": "foo", + "lifetime": 86400000000000, + "module": "internal", + "sign_with_root": true + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_propagation_options.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_propagation_options.caddyfiletest new file mode 100644 index 00000000000..43ec9774bcd --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_propagation_options.caddyfiletest @@ -0,0 +1,70 @@ +localhost + +respond "hello from localhost" +tls { + propagation_delay 5m10s + propagation_timeout 10m20s +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "hello from localhost", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "localhost" + ], + "issuers": [ + { + "challenges": { + "dns": { + "propagation_delay": 310000000000, + "propagation_timeout": 620000000000 + } + }, + "module": "acme" + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tracing.caddyfiletest b/caddytest/integration/caddyfile_adapt/tracing.caddyfiletest new file mode 100644 index 00000000000..32286600122 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tracing.caddyfiletest @@ -0,0 +1,36 @@ +:80 { + tracing /myhandler { + span my-span + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "match": [ + { + "path": [ + "/myhandler" + ] + } + ], + "handle": [ + { + "handler": "tracing", + "span": "my-span" + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/uri_query_operations.caddyfiletest b/caddytest/integration/caddyfile_adapt/uri_query_operations.caddyfiletest new file mode 100644 index 00000000000..a53462480ca --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/uri_query_operations.caddyfiletest @@ -0,0 +1,106 @@ +:9080 +uri query +foo bar +uri query -baz +uri query taz test +uri query key=value example +uri query changethis>changed +uri query { + findme value replacement + +foo1 baz +} + +respond "{query}" +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":9080" + ], + "routes": [ + { + "handle": [ + { + "handler": "rewrite", + "query": { + "add": [ + { + "key": "foo", + "val": "bar" + } + ] + } + }, + { + "handler": "rewrite", + "query": { + "delete": [ + "baz" + ] + } + }, + { + "handler": "rewrite", + "query": { + "set": [ + { + "key": "taz", + "val": "test" + } + ] + } + }, + { + "handler": "rewrite", + "query": { + "set": [ + { + "key": "key=value", + "val": "example" + } + ] + } + }, + { + "handler": "rewrite", + "query": { + "rename": [ + { + "key": "changethis", + "val": "changed" + } + ] + } + }, + { + "handler": "rewrite", + "query": { + "add": [ + { + "key": "foo1", + "val": "baz" + } + ], + "replace": [ + { + "key": "findme", + "replace": "replacement", + "search_regexp": "value" + } + ] + } + }, + { + "body": "{http.request.uri.query}", + "handler": "static_response" + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/uri_replace_brace_escape.caddyfiletest b/caddytest/integration/caddyfile_adapt/uri_replace_brace_escape.caddyfiletest new file mode 100644 index 00000000000..860b8a8df4c --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/uri_replace_brace_escape.caddyfiletest @@ -0,0 +1,47 @@ +:9080 +uri replace "\}" %7D +uri replace "\{" %7B + +respond "{query}" +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":9080" + ], + "routes": [ + { + "handle": [ + { + "handler": "rewrite", + "uri_substring": [ + { + "find": "\\}", + "replace": "%7D" + } + ] + }, + { + "handler": "rewrite", + "uri_substring": [ + { + "find": "\\{", + "replace": "%7B" + } + ] + }, + { + "body": "{http.request.uri.query}", + "handler": "static_response" + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/wildcard_pattern.caddyfiletest b/caddytest/integration/caddyfile_adapt/wildcard_pattern.caddyfiletest new file mode 100644 index 00000000000..1a9ccea741f --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/wildcard_pattern.caddyfiletest @@ -0,0 +1,157 @@ +*.example.com { + tls foo@example.com { + dns mock + } + + @foo host foo.example.com + handle @foo { + respond "Foo!" + } + + @bar host bar.example.com + handle @bar { + respond "Bar!" + } + + # Fallback for otherwise unhandled domains + handle { + abort + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "*.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "group": "group3", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Foo!", + "handler": "static_response" + } + ] + } + ] + } + ], + "match": [ + { + "host": [ + "foo.example.com" + ] + } + ] + }, + { + "group": "group3", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Bar!", + "handler": "static_response" + } + ] + } + ] + } + ], + "match": [ + { + "host": [ + "bar.example.com" + ] + } + ] + }, + { + "group": "group3", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "abort": true, + "handler": "static_response" + } + ] + } + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "*.example.com" + ], + "issuers": [ + { + "challenges": { + "dns": { + "provider": { + "name": "mock" + } + } + }, + "email": "foo@example.com", + "module": "acme" + }, + { + "ca": "https://acme.zerossl.com/v2/DV90", + "challenges": { + "dns": { + "provider": { + "name": "mock" + } + } + }, + "email": "foo@example.com", + "module": "acme" + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt_test.go b/caddytest/integration/caddyfile_adapt_test.go new file mode 100644 index 00000000000..0d9f0fa471d --- /dev/null +++ b/caddytest/integration/caddyfile_adapt_test.go @@ -0,0 +1,57 @@ +package integration + +import ( + jsonMod "encoding/json" + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + "testing" + + "github.com/caddyserver/caddy/v2/caddytest" + + _ "github.com/caddyserver/caddy/v2/internal/testmocks" +) + +func TestCaddyfileAdaptToJSON(t *testing.T) { + // load the list of test files from the dir + files, err := os.ReadDir("./caddyfile_adapt") + if err != nil { + t.Errorf("failed to read caddyfile_adapt dir: %s", err) + } + + // prep a regexp to fix strings on windows + winNewlines := regexp.MustCompile(`\r?\n`) + + for _, f := range files { + if f.IsDir() { + continue + } + + // read the test file + filename := f.Name() + data, err := os.ReadFile("./caddyfile_adapt/" + filename) + if err != nil { + t.Errorf("failed to read %s dir: %s", filename, err) + } + + // split the Caddyfile (first) and JSON (second) parts + // (append newline to Caddyfile to match formatter expectations) + parts := strings.Split(string(data), "----------") + caddyfile, json := strings.TrimSpace(parts[0])+"\n", strings.TrimSpace(parts[1]) + + // replace windows newlines in the json with unix newlines + json = winNewlines.ReplaceAllString(json, "\n") + + // replace os-specific default path for file_server's hide field + replacePath, _ := jsonMod.Marshal(fmt.Sprint(".", string(filepath.Separator), "Caddyfile")) + json = strings.ReplaceAll(json, `"./Caddyfile"`, string(replacePath)) + + // run the test + ok := caddytest.CompareAdapt(t, filename, caddyfile, "caddyfile", json) + if !ok { + t.Errorf("failed to adapt %s", filename) + } + } +} diff --git a/caddytest/integration/caddyfile_test.go b/caddytest/integration/caddyfile_test.go new file mode 100644 index 00000000000..11ffc08aeb1 --- /dev/null +++ b/caddytest/integration/caddyfile_test.go @@ -0,0 +1,811 @@ +package integration + +import ( + "net/http" + "net/url" + "testing" + + "github.com/caddyserver/caddy/v2/caddytest" +) + +func TestRespond(t *testing.T) { + // arrange + tester := caddytest.NewTester(t) + tester.InitServer(` + { + admin localhost:2999 + http_port 9080 + https_port 9443 + grace_period 1ns + } + + localhost:9080 { + respond /version 200 { + body "hello from localhost" + } + } + `, "caddyfile") + + // act and assert + tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost") +} + +func TestRedirect(t *testing.T) { + // arrange + tester := caddytest.NewTester(t) + tester.InitServer(` + { + admin localhost:2999 + http_port 9080 + https_port 9443 + grace_period 1ns + } + + localhost:9080 { + + redir / http://localhost:9080/hello 301 + + respond /hello 200 { + body "hello from localhost" + } + } + `, "caddyfile") + + // act and assert + tester.AssertRedirect("http://localhost:9080/", "http://localhost:9080/hello", 301) + + // follow redirect + tester.AssertGetResponse("http://localhost:9080/", 200, "hello from localhost") +} + +func TestDuplicateHosts(t *testing.T) { + // act and assert + caddytest.AssertLoadError(t, + ` + localhost:9080 { + } + + localhost:9080 { + } + `, + "caddyfile", + "ambiguous site definition") +} + +func TestReadCookie(t *testing.T) { + localhost, _ := url.Parse("http://localhost") + cookie := http.Cookie{ + Name: "clientname", + Value: "caddytest", + } + + // arrange + tester := caddytest.NewTester(t) + tester.Client.Jar.SetCookies(localhost, []*http.Cookie{&cookie}) + tester.InitServer(` + { + skip_install_trust + admin localhost:2999 + http_port 9080 + https_port 9443 + grace_period 1ns + } + + localhost:9080 { + templates { + root testdata + } + file_server { + root testdata + } + } + `, "caddyfile") + + // act and assert + tester.AssertGetResponse("http://localhost:9080/cookie.html", 200, "

    Cookie.ClientName caddytest

    ") +} + +func TestReplIndex(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + skip_install_trust + admin localhost:2999 + http_port 9080 + https_port 9443 + grace_period 1ns + } + + localhost:9080 { + templates { + root testdata + } + file_server { + root testdata + index "index.{host}.html" + } + } + `, "caddyfile") + + // act and assert + tester.AssertGetResponse("http://localhost:9080/", 200, "") +} + +func TestInvalidPrefix(t *testing.T) { + type testCase struct { + config, expectedError string + } + + failureCases := []testCase{ + { + config: `wss://localhost`, + expectedError: `the scheme wss:// is only supported in browsers; use https:// instead`, + }, + { + config: `ws://localhost`, + expectedError: `the scheme ws:// is only supported in browsers; use http:// instead`, + }, + { + config: `someInvalidPrefix://localhost`, + expectedError: "unsupported URL scheme someinvalidprefix://", + }, + { + config: `h2c://localhost`, + expectedError: `unsupported URL scheme h2c://`, + }, + { + config: `localhost, wss://localhost`, + expectedError: `the scheme wss:// is only supported in browsers; use https:// instead`, + }, + { + config: `localhost { + reverse_proxy ws://localhost" + }`, + expectedError: `the scheme ws:// is only supported in browsers; use http:// instead`, + }, + { + config: `localhost { + reverse_proxy someInvalidPrefix://localhost" + }`, + expectedError: `unsupported URL scheme someinvalidprefix://`, + }, + } + + for _, failureCase := range failureCases { + caddytest.AssertLoadError(t, failureCase.config, "caddyfile", failureCase.expectedError) + } +} + +func TestValidPrefix(t *testing.T) { + type testCase struct { + rawConfig, expectedResponse string + } + + successCases := []testCase{ + { + "localhost", + `{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "terminal": true + } + ] + } + } + } + } +}`, + }, + { + "https://localhost", + `{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "terminal": true + } + ] + } + } + } + } +}`, + }, + { + "http://localhost", + `{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "terminal": true + } + ] + } + } + } + } +}`, + }, + { + `localhost { + reverse_proxy http://localhost:3000 + }`, + `{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "upstreams": [ + { + "dial": "localhost:3000" + } + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +}`, + }, + { + `localhost { + reverse_proxy https://localhost:3000 + }`, + `{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "transport": { + "protocol": "http", + "tls": {} + }, + "upstreams": [ + { + "dial": "localhost:3000" + } + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +}`, + }, + { + `localhost { + reverse_proxy h2c://localhost:3000 + }`, + `{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "transport": { + "protocol": "http", + "versions": [ + "h2c", + "2" + ] + }, + "upstreams": [ + { + "dial": "localhost:3000" + } + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +}`, + }, + { + `localhost { + reverse_proxy localhost:3000 + }`, + `{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "upstreams": [ + { + "dial": "localhost:3000" + } + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +}`, + }, + } + + for _, successCase := range successCases { + caddytest.AssertAdapt(t, successCase.rawConfig, "caddyfile", successCase.expectedResponse) + } +} + +func TestUriReplace(t *testing.T) { + tester := caddytest.NewTester(t) + + tester.InitServer(` + { + admin localhost:2999 + http_port 9080 + } + :9080 + uri replace "\}" %7D + uri replace "\{" %7B + + respond "{query}"`, "caddyfile") + + tester.AssertGetResponse("http://localhost:9080/endpoint?test={%20content%20}", 200, "test=%7B%20content%20%7D") +} + +func TestUriOps(t *testing.T) { + tester := caddytest.NewTester(t) + + tester.InitServer(` + { + admin localhost:2999 + http_port 9080 + } + :9080 + uri query +foo bar + uri query -baz + uri query taz test + uri query key=value example + uri query changethis>changed + + respond "{query}"`, "caddyfile") + + tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar0&baz=buz&taz=nottest&changethis=val", 200, "changed=val&foo=bar0&foo=bar&key%3Dvalue=example&taz=test") +} + +// Tests the `http.request.local.port` placeholder. +// We don't test the very similar `http.request.local.host` placeholder, +// because depending on the host the test is running on, localhost might +// refer to 127.0.0.1 or ::1. +// TODO: Test each http version separately (especially http/3) +func TestHttpRequestLocalPortPlaceholder(t *testing.T) { + tester := caddytest.NewTester(t) + + tester.InitServer(` + { + admin localhost:2999 + http_port 9080 + } + :9080 + respond "{http.request.local.port}"`, "caddyfile") + + tester.AssertGetResponse("http://localhost:9080/", 200, "9080") +} + +func TestSetThenAddQueryParams(t *testing.T) { + tester := caddytest.NewTester(t) + + tester.InitServer(` + { + admin localhost:2999 + http_port 9080 + } + :9080 + uri query foo bar + uri query +foo baz + + respond "{query}"`, "caddyfile") + + tester.AssertGetResponse("http://localhost:9080/endpoint", 200, "foo=bar&foo=baz") +} + +func TestSetThenDeleteParams(t *testing.T) { + tester := caddytest.NewTester(t) + + tester.InitServer(` + { + admin localhost:2999 + http_port 9080 + } + :9080 + uri query bar foo{query.foo} + uri query -foo + + respond "{query}"`, "caddyfile") + + tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "bar=foobar") +} + +func TestRenameAndOtherOps(t *testing.T) { + tester := caddytest.NewTester(t) + + tester.InitServer(` + { + admin localhost:2999 + http_port 9080 + } + :9080 + uri query foo>bar + uri query bar taz + uri query +bar baz + + respond "{query}"`, "caddyfile") + + tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "bar=taz&bar=baz") +} + +func TestReplaceOps(t *testing.T) { + tester := caddytest.NewTester(t) + + tester.InitServer(` + { + admin localhost:2999 + http_port 9080 + } + :9080 + uri query foo bar baz + respond "{query}"`, "caddyfile") + + tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "foo=baz") +} + +func TestReplaceWithReplacementPlaceholder(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + admin localhost:2999 + http_port 9080 + } + :9080 + uri query foo bar {query.placeholder} + respond "{query}"`, "caddyfile") + + tester.AssertGetResponse("http://localhost:9080/endpoint?placeholder=baz&foo=bar", 200, "foo=baz&placeholder=baz") + +} + +func TestReplaceWithKeyPlaceholder(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + admin localhost:2999 + http_port 9080 + } + :9080 + uri query {query.placeholder} bar baz + respond "{query}"`, "caddyfile") + + tester.AssertGetResponse("http://localhost:9080/endpoint?placeholder=foo&foo=bar", 200, "foo=baz&placeholder=foo") +} + +func TestPartialReplacement(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + admin localhost:2999 + http_port 9080 + } + :9080 + uri query foo ar az + respond "{query}"`, "caddyfile") + + tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "foo=baz") +} + +func TestNonExistingSearch(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + admin localhost:2999 + http_port 9080 + } + :9080 + uri query foo var baz + respond "{query}"`, "caddyfile") + + tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "foo=bar") +} + +func TestReplaceAllOps(t *testing.T) { + tester := caddytest.NewTester(t) + + tester.InitServer(` + { + admin localhost:2999 + http_port 9080 + } + :9080 + uri query * bar baz + respond "{query}"`, "caddyfile") + + tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar&baz=bar", 200, "baz=baz&foo=baz") +} + +func TestUriOpsBlock(t *testing.T) { + tester := caddytest.NewTester(t) + + tester.InitServer(` + { + admin localhost:2999 + http_port 9080 + } + :9080 + uri query { + +foo bar + -baz + taz test + } + respond "{query}"`, "caddyfile") + + tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar0&baz=buz&taz=nottest", 200, "foo=bar0&foo=bar&taz=test") +} + +func TestHandleErrorSimpleCodes(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(`{ + admin localhost:2999 + http_port 9080 + } + localhost:9080 { + root * /srv + error /private* "Unauthorized" 410 + error /hidden* "Not found" 404 + + handle_errors 404 410 { + respond "404 or 410 error" + } + }`, "caddyfile") + // act and assert + tester.AssertGetResponse("http://localhost:9080/private", 410, "404 or 410 error") + tester.AssertGetResponse("http://localhost:9080/hidden", 404, "404 or 410 error") +} + +func TestHandleErrorRange(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(`{ + admin localhost:2999 + http_port 9080 + } + localhost:9080 { + root * /srv + error /private* "Unauthorized" 410 + error /hidden* "Not found" 404 + + handle_errors 4xx { + respond "Error in the [400 .. 499] range" + } + }`, "caddyfile") + // act and assert + tester.AssertGetResponse("http://localhost:9080/private", 410, "Error in the [400 .. 499] range") + tester.AssertGetResponse("http://localhost:9080/hidden", 404, "Error in the [400 .. 499] range") +} + +func TestHandleErrorSort(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(`{ + admin localhost:2999 + http_port 9080 + } + localhost:9080 { + root * /srv + error /private* "Unauthorized" 410 + error /hidden* "Not found" 404 + error /internalerr* "Internal Server Error" 500 + + handle_errors { + respond "Fallback route: code outside the [400..499] range" + } + handle_errors 4xx { + respond "Error in the [400 .. 499] range" + } + }`, "caddyfile") + // act and assert + tester.AssertGetResponse("http://localhost:9080/internalerr", 500, "Fallback route: code outside the [400..499] range") + tester.AssertGetResponse("http://localhost:9080/hidden", 404, "Error in the [400 .. 499] range") +} + +func TestHandleErrorRangeAndCodes(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(`{ + admin localhost:2999 + http_port 9080 + } + localhost:9080 { + root * /srv + error /private* "Unauthorized" 410 + error /threehundred* "Moved Permanently" 301 + error /internalerr* "Internal Server Error" 500 + + handle_errors 500 3xx { + respond "Error code is equal to 500 or in the [300..399] range" + } + handle_errors 4xx { + respond "Error in the [400 .. 499] range" + } + }`, "caddyfile") + // act and assert + tester.AssertGetResponse("http://localhost:9080/internalerr", 500, "Error code is equal to 500 or in the [300..399] range") + tester.AssertGetResponse("http://localhost:9080/threehundred", 301, "Error code is equal to 500 or in the [300..399] range") + tester.AssertGetResponse("http://localhost:9080/private", 410, "Error in the [400 .. 499] range") +} + +func TestInvalidSiteAddressesAsDirectives(t *testing.T) { + type testCase struct { + config, expectedError string + } + + failureCases := []testCase{ + { + config: ` + handle { + file_server + }`, + expectedError: `Caddyfile:2: parsed 'handle' as a site address, but it is a known directive; directives must appear in a site block`, + }, + { + config: ` + reverse_proxy localhost:9000 localhost:9001 { + file_server + }`, + expectedError: `Caddyfile:2: parsed 'reverse_proxy' as a site address, but it is a known directive; directives must appear in a site block`, + }, + } + + for _, failureCase := range failureCases { + caddytest.AssertLoadError(t, failureCase.config, "caddyfile", failureCase.expectedError) + } +} diff --git a/caddytest/integration/handler_test.go b/caddytest/integration/handler_test.go new file mode 100644 index 00000000000..afc700b02bd --- /dev/null +++ b/caddytest/integration/handler_test.go @@ -0,0 +1,59 @@ +package integration + +import ( + "bytes" + "net/http" + "testing" + + "github.com/caddyserver/caddy/v2/caddytest" +) + +func TestBrowse(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + skip_install_trust + admin localhost:2999 + http_port 9080 + https_port 9443 + grace_period 1ns + } + http://localhost:9080 { + file_server browse + } + `, "caddyfile") + + req, err := http.NewRequest(http.MethodGet, "http://localhost:9080/", nil) + if err != nil { + t.Fail() + return + } + tester.AssertResponseCode(req, 200) +} + +func TestRespondWithJSON(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + skip_install_trust + admin localhost:2999 + http_port 9080 + https_port 9443 + grace_period 1ns + } + localhost { + respond {http.request.body} + } + `, "caddyfile") + + res, _ := tester.AssertPostResponseBody("https://localhost:9443/", + nil, + bytes.NewBufferString(`{ + "greeting": "Hello, world!" + }`), 200, `{ + "greeting": "Hello, world!" + }`) + if res.Header.Get("Content-Type") != "application/json" { + t.Errorf("expected Content-Type to be application/json, but was %s", res.Header.Get("Content-Type")) + } +} diff --git a/caddytest/integration/intercept_test.go b/caddytest/integration/intercept_test.go new file mode 100644 index 00000000000..6f8ffc92959 --- /dev/null +++ b/caddytest/integration/intercept_test.go @@ -0,0 +1,40 @@ +package integration + +import ( + "testing" + + "github.com/caddyserver/caddy/v2/caddytest" +) + +func TestIntercept(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(`{ + skip_install_trust + admin localhost:2999 + http_port 9080 + https_port 9443 + grace_period 1ns + } + + localhost:9080 { + respond /intercept "I'm a teapot" 408 + header /intercept To-Intercept ok + respond /no-intercept "I'm not a teapot" + + intercept { + @teapot status 408 + handle_response @teapot { + header /intercept intercepted {resp.header.To-Intercept} + respond /intercept "I'm a combined coffee/tea pot that is temporarily out of coffee" 503 + } + } + } + `, "caddyfile") + + r, _ := tester.AssertGetResponse("http://localhost:9080/intercept", 503, "I'm a combined coffee/tea pot that is temporarily out of coffee") + if r.Header.Get("intercepted") != "ok" { + t.Fatalf(`header "intercepted" value is not "ok": %s`, r.Header.Get("intercepted")) + } + + tester.AssertGetResponse("http://localhost:9080/no-intercept", 200, "I'm not a teapot") +} diff --git a/caddytest/integration/leafcertloaders_test.go b/caddytest/integration/leafcertloaders_test.go new file mode 100644 index 00000000000..4399902eaee --- /dev/null +++ b/caddytest/integration/leafcertloaders_test.go @@ -0,0 +1,70 @@ +package integration + +import ( + "testing" + + "github.com/caddyserver/caddy/v2/caddytest" +) + +func TestLeafCertLoaders(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + "admin": { + "listen": "localhost:2999" + }, + "apps": { + "http": { + "http_port": 9080, + "https_port": 9443, + "grace_period": 1, + "servers": { + "srv0": { + "listen": [ + ":9443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "terminal": true + } + ], + "tls_connection_policies": [ + { + "client_authentication": { + "verifiers": [ + { + "verifier": "leaf", + "leaf_certs_loaders": [ + { + "loader": "file", + "files": ["../leafcert.pem"] + }, + { + "loader": "folder", + "folders": ["../"] + }, + { + "loader": "storage" + }, + { + "loader": "pem" + } + ] + } + ] + } + } + ] + } + } + } + } + }`, "json") +} diff --git a/caddytest/integration/listener_test.go b/caddytest/integration/listener_test.go new file mode 100644 index 00000000000..30642b1aed9 --- /dev/null +++ b/caddytest/integration/listener_test.go @@ -0,0 +1,94 @@ +package integration + +import ( + "bytes" + "fmt" + "math/rand" + "net" + "net/http" + "strings" + "testing" + + "github.com/caddyserver/caddy/v2/caddytest" +) + +func setupListenerWrapperTest(t *testing.T, handlerFunc http.HandlerFunc) *caddytest.Tester { + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("failed to listen: %s", err) + } + + mux := http.NewServeMux() + mux.Handle("/", handlerFunc) + srv := &http.Server{ + Handler: mux, + } + go srv.Serve(l) + t.Cleanup(func() { + _ = srv.Close() + _ = l.Close() + }) + tester := caddytest.NewTester(t) + tester.InitServer(fmt.Sprintf(` + { + skip_install_trust + admin localhost:2999 + http_port 9080 + https_port 9443 + local_certs + servers :9443 { + listener_wrappers { + http_redirect + tls + } + } + } + localhost { + reverse_proxy %s + } + `, l.Addr().String()), "caddyfile") + return tester +} + +func TestHTTPRedirectWrapperWithLargeUpload(t *testing.T) { + const uploadSize = (1024 * 1024) + 1 // 1 MB + 1 byte + // 1 more than an MB + body := make([]byte, uploadSize) + rand.New(rand.NewSource(0)).Read(body) + + tester := setupListenerWrapperTest(t, func(writer http.ResponseWriter, request *http.Request) { + buf := new(bytes.Buffer) + _, err := buf.ReadFrom(request.Body) + if err != nil { + t.Fatalf("failed to read body: %s", err) + } + + if !bytes.Equal(buf.Bytes(), body) { + t.Fatalf("body not the same") + } + + writer.WriteHeader(http.StatusNoContent) + }) + resp, err := tester.Client.Post("https://localhost:9443", "application/octet-stream", bytes.NewReader(body)) + if err != nil { + t.Fatalf("failed to post: %s", err) + } + + if resp.StatusCode != http.StatusNoContent { + t.Fatalf("unexpected status: %d != %d", resp.StatusCode, http.StatusNoContent) + } +} + +func TestLargeHttpRequest(t *testing.T) { + tester := setupListenerWrapperTest(t, func(writer http.ResponseWriter, request *http.Request) { + t.Fatal("not supposed to handle a request") + }) + + // We never read the body in any way, set an extra long header instead. + req, _ := http.NewRequest("POST", "http://localhost:9443", nil) + req.Header.Set("Long-Header", strings.Repeat("X", 1024*1024)) + _, err := tester.Client.Do(req) + if err == nil { + t.Fatal("not supposed to succeed") + } +} diff --git a/caddytest/integration/map_test.go b/caddytest/integration/map_test.go new file mode 100644 index 00000000000..eb338656469 --- /dev/null +++ b/caddytest/integration/map_test.go @@ -0,0 +1,151 @@ +package integration + +import ( + "bytes" + "testing" + + "github.com/caddyserver/caddy/v2/caddytest" +) + +func TestMap(t *testing.T) { + // arrange + tester := caddytest.NewTester(t) + tester.InitServer(`{ + skip_install_trust + admin localhost:2999 + http_port 9080 + https_port 9443 + grace_period 1ns + } + + localhost:9080 { + + map {http.request.method} {dest-1} {dest-2} { + default unknown1 unknown2 + ~G(.)(.) G${1}${2}-called + POST post-called foobar + } + + respond /version 200 { + body "hello from localhost {dest-1} {dest-2}" + } + } + `, "caddyfile") + + // act and assert + tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost GET-called unknown2") + tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost post-called foobar") +} + +func TestMapRespondWithDefault(t *testing.T) { + // arrange + tester := caddytest.NewTester(t) + tester.InitServer(`{ + skip_install_trust + admin localhost:2999 + http_port 9080 + https_port 9443 + } + + localhost:9080 { + + map {http.request.method} {dest-name} { + default unknown + GET get-called + } + + respond /version 200 { + body "hello from localhost {dest-name}" + } + } + `, "caddyfile") + + // act and assert + tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost get-called") + tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost unknown") +} + +func TestMapAsJSON(t *testing.T) { + // arrange + tester := caddytest.NewTester(t) + tester.InitServer(` + { + "admin": { + "listen": "localhost:2999" + }, + "apps": { + "pki": { + "certificate_authorities" : { + "local" : { + "install_trust": false + } + } + }, + "http": { + "http_port": 9080, + "https_port": 9443, + "servers": { + "srv0": { + "listen": [ + ":9080" + ], + "routes": [ + { + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "map", + "source": "{http.request.method}", + "destinations": ["{dest-name}"], + "defaults": ["unknown"], + "mappings": [ + { + "input": "GET", + "outputs": ["get-called"] + }, + { + "input": "POST", + "outputs": ["post-called"] + } + ] + } + ] + }, + { + "handle": [ + { + "body": "hello from localhost {dest-name}", + "handler": "static_response", + "status_code": 200 + } + ], + "match": [ + { + "path": ["/version"] + } + ] + } + ] + } + ], + "match": [ + { + "host": ["localhost"] + } + ], + "terminal": true + } + ] + } + } + } + } + }`, "json") + + tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost get-called") + tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost post-called") +} diff --git a/caddytest/integration/mockdns_test.go b/caddytest/integration/mockdns_test.go new file mode 100644 index 00000000000..615116a3aec --- /dev/null +++ b/caddytest/integration/mockdns_test.go @@ -0,0 +1,61 @@ +package integration + +import ( + "context" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/certmagic" + "github.com/libdns/libdns" +) + +func init() { + caddy.RegisterModule(MockDNSProvider{}) +} + +// MockDNSProvider is a mock DNS provider, for testing config with DNS modules. +type MockDNSProvider struct{} + +// CaddyModule returns the Caddy module information. +func (MockDNSProvider) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "dns.providers.mock", + New: func() caddy.Module { return new(MockDNSProvider) }, + } +} + +// Provision sets up the module. +func (MockDNSProvider) Provision(ctx caddy.Context) error { + return nil +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. +func (MockDNSProvider) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + return nil +} + +// AppendRecords appends DNS records to the zone. +func (MockDNSProvider) AppendRecords(ctx context.Context, zone string, recs []libdns.Record) ([]libdns.Record, error) { + return nil, nil +} + +// DeleteRecords deletes DNS records from the zone. +func (MockDNSProvider) DeleteRecords(ctx context.Context, zone string, recs []libdns.Record) ([]libdns.Record, error) { + return nil, nil +} + +// GetRecords gets DNS records from the zone. +func (MockDNSProvider) GetRecords(ctx context.Context, zone string) ([]libdns.Record, error) { + return nil, nil +} + +// SetRecords sets DNS records in the zone. +func (MockDNSProvider) SetRecords(ctx context.Context, zone string, recs []libdns.Record) ([]libdns.Record, error) { + return nil, nil +} + +// Interface guard +var _ caddyfile.Unmarshaler = (*MockDNSProvider)(nil) +var _ certmagic.DNSProvider = (*MockDNSProvider)(nil) +var _ caddy.Provisioner = (*MockDNSProvider)(nil) +var _ caddy.Module = (*MockDNSProvider)(nil) diff --git a/caddytest/integration/pki_test.go b/caddytest/integration/pki_test.go new file mode 100644 index 00000000000..8467982092f --- /dev/null +++ b/caddytest/integration/pki_test.go @@ -0,0 +1,107 @@ +package integration + +import ( + "testing" + + "github.com/caddyserver/caddy/v2/caddytest" +) + +func TestLeafCertLifetimeLessThanIntermediate(t *testing.T) { + caddytest.AssertLoadError(t, ` + { + "admin": { + "disabled": true + }, + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "ca": "internal", + "handler": "acme_server", + "lifetime": 604800000000000 + } + ] + } + ] + } + ] + } + ] + } + } + }, + "pki": { + "certificate_authorities": { + "internal": { + "install_trust": false, + "intermediate_lifetime": 604800000000000, + "name": "Internal CA" + } + } + } + } + } + `, "json", "certificate lifetime (168h0m0s) should be less than intermediate certificate lifetime (168h0m0s)") +} + +func TestIntermediateLifetimeLessThanRoot(t *testing.T) { + caddytest.AssertLoadError(t, ` + { + "admin": { + "disabled": true + }, + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "ca": "internal", + "handler": "acme_server", + "lifetime": 2592000000000000 + } + ] + } + ] + } + ] + } + ] + } + } + }, + "pki": { + "certificate_authorities": { + "internal": { + "install_trust": false, + "intermediate_lifetime": 311040000000000000, + "name": "Internal CA" + } + } + } + } + } + `, "json", "intermediate certificate lifetime must be less than root certificate lifetime (86400h0m0s)") +} diff --git a/caddytest/integration/reverseproxy_test.go b/caddytest/integration/reverseproxy_test.go new file mode 100644 index 00000000000..cbfe8433bc9 --- /dev/null +++ b/caddytest/integration/reverseproxy_test.go @@ -0,0 +1,476 @@ +package integration + +import ( + "fmt" + "net" + "net/http" + "os" + "runtime" + "strings" + "testing" + "time" + + "github.com/caddyserver/caddy/v2/caddytest" +) + +func TestSRVReverseProxy(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + "admin": { + "listen": "localhost:2999" + }, + "apps": { + "pki": { + "certificate_authorities": { + "local": { + "install_trust": false + } + } + }, + "http": { + "grace_period": 1, + "servers": { + "srv0": { + "listen": [ + ":18080" + ], + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "dynamic_upstreams": { + "source": "srv", + "name": "srv.host.service.consul" + } + } + ] + } + ] + } + } + } + } + } + `, "json") +} + +func TestDialWithPlaceholderUnix(t *testing.T) { + if runtime.GOOS == "windows" { + t.SkipNow() + } + + f, err := os.CreateTemp("", "*.sock") + if err != nil { + t.Errorf("failed to create TempFile: %s", err) + return + } + // a hack to get a file name within a valid path to use as socket + socketName := f.Name() + os.Remove(f.Name()) + + server := http.Server{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.Write([]byte("Hello, World!")) + }), + } + + unixListener, err := net.Listen("unix", socketName) + if err != nil { + t.Errorf("failed to listen on the socket: %s", err) + return + } + go server.Serve(unixListener) + t.Cleanup(func() { + server.Close() + }) + runtime.Gosched() // Allow other goroutines to run + + tester := caddytest.NewTester(t) + tester.InitServer(` + { + "admin": { + "listen": "localhost:2999" + }, + "apps": { + "pki": { + "certificate_authorities": { + "local": { + "install_trust": false + } + } + }, + "http": { + "grace_period": 1, + "servers": { + "srv0": { + "listen": [ + ":18080" + ], + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "upstreams": [ + { + "dial": "unix/{http.request.header.X-Caddy-Upstream-Dial}" + } + ] + } + ] + } + ] + } + } + } + } + } + `, "json") + + req, err := http.NewRequest(http.MethodGet, "http://localhost:18080", nil) + if err != nil { + t.Fail() + return + } + req.Header.Set("X-Caddy-Upstream-Dial", socketName) + tester.AssertResponse(req, 200, "Hello, World!") +} + +func TestReverseProxyWithPlaceholderDialAddress(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + "admin": { + "listen": "localhost:2999" + }, + "apps": { + "pki": { + "certificate_authorities": { + "local": { + "install_trust": false + } + } + }, + "http": { + "grace_period": 1, + "servers": { + "srv0": { + "listen": [ + ":18080" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "static_response", + "body": "Hello, World!" + } + ], + "terminal": true + } + ], + "automatic_https": { + "skip": [ + "localhost" + ] + } + }, + "srv1": { + "listen": [ + ":9080" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + + "handler": "reverse_proxy", + "upstreams": [ + { + "dial": "{http.request.header.X-Caddy-Upstream-Dial}" + } + ] + } + ], + "terminal": true + } + ], + "automatic_https": { + "skip": [ + "localhost" + ] + } + } + } + } + } + } + `, "json") + + req, err := http.NewRequest(http.MethodGet, "http://localhost:9080", nil) + if err != nil { + t.Fail() + return + } + req.Header.Set("X-Caddy-Upstream-Dial", "localhost:18080") + tester.AssertResponse(req, 200, "Hello, World!") +} + +func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + "admin": { + "listen": "localhost:2999" + }, + "apps": { + "pki": { + "certificate_authorities": { + "local": { + "install_trust": false + } + } + }, + "http": { + "grace_period": 1, + "servers": { + "srv0": { + "listen": [ + ":18080" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "static_response", + "body": "Hello, World!" + } + ], + "terminal": true + } + ], + "automatic_https": { + "skip": [ + "localhost" + ] + } + }, + "srv1": { + "listen": [ + ":9080" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + + "handler": "reverse_proxy", + "upstreams": [ + { + "dial": "tcp/{http.request.header.X-Caddy-Upstream-Dial}:18080" + } + ] + } + ], + "terminal": true + } + ], + "automatic_https": { + "skip": [ + "localhost" + ] + } + } + } + } + } + } + `, "json") + + req, err := http.NewRequest(http.MethodGet, "http://localhost:9080", nil) + if err != nil { + t.Fail() + return + } + req.Header.Set("X-Caddy-Upstream-Dial", "localhost") + tester.AssertResponse(req, 200, "Hello, World!") +} + +func TestReverseProxyHealthCheck(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + skip_install_trust + admin localhost:2999 + http_port 9080 + https_port 9443 + grace_period 1ns + } + http://localhost:2020 { + respond "Hello, World!" + } + http://localhost:2021 { + respond "ok" + } + http://localhost:9080 { + reverse_proxy { + to localhost:2020 + + health_uri /health + health_port 2021 + health_interval 10ms + health_timeout 100ms + health_passes 1 + health_fails 1 + } + } + `, "caddyfile") + + time.Sleep(100 * time.Millisecond) // TODO: for some reason this test seems particularly flaky, getting 503 when it should be 200, unless we wait + tester.AssertGetResponse("http://localhost:9080/", 200, "Hello, World!") +} + +func TestReverseProxyHealthCheckUnixSocket(t *testing.T) { + if runtime.GOOS == "windows" { + t.SkipNow() + } + tester := caddytest.NewTester(t) + f, err := os.CreateTemp("", "*.sock") + if err != nil { + t.Errorf("failed to create TempFile: %s", err) + return + } + // a hack to get a file name within a valid path to use as socket + socketName := f.Name() + os.Remove(f.Name()) + + server := http.Server{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if strings.HasPrefix(req.URL.Path, "/health") { + w.Write([]byte("ok")) + return + } + w.Write([]byte("Hello, World!")) + }), + } + + unixListener, err := net.Listen("unix", socketName) + if err != nil { + t.Errorf("failed to listen on the socket: %s", err) + return + } + go server.Serve(unixListener) + t.Cleanup(func() { + server.Close() + }) + runtime.Gosched() // Allow other goroutines to run + + tester.InitServer(fmt.Sprintf(` + { + skip_install_trust + admin localhost:2999 + http_port 9080 + https_port 9443 + grace_period 1ns + } + http://localhost:9080 { + reverse_proxy { + to unix/%s + + health_uri /health + health_port 2021 + health_interval 2s + health_timeout 5s + } + } + `, socketName), "caddyfile") + + tester.AssertGetResponse("http://localhost:9080/", 200, "Hello, World!") +} + +func TestReverseProxyHealthCheckUnixSocketWithoutPort(t *testing.T) { + if runtime.GOOS == "windows" { + t.SkipNow() + } + tester := caddytest.NewTester(t) + f, err := os.CreateTemp("", "*.sock") + if err != nil { + t.Errorf("failed to create TempFile: %s", err) + return + } + // a hack to get a file name within a valid path to use as socket + socketName := f.Name() + os.Remove(f.Name()) + + server := http.Server{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if strings.HasPrefix(req.URL.Path, "/health") { + w.Write([]byte("ok")) + return + } + w.Write([]byte("Hello, World!")) + }), + } + + unixListener, err := net.Listen("unix", socketName) + if err != nil { + t.Errorf("failed to listen on the socket: %s", err) + return + } + go server.Serve(unixListener) + t.Cleanup(func() { + server.Close() + }) + runtime.Gosched() // Allow other goroutines to run + + tester.InitServer(fmt.Sprintf(` + { + skip_install_trust + admin localhost:2999 + http_port 9080 + https_port 9443 + grace_period 1ns + } + http://localhost:9080 { + reverse_proxy { + to unix/%s + + health_uri /health + health_interval 2s + health_timeout 5s + } + } + `, socketName), "caddyfile") + + tester.AssertGetResponse("http://localhost:9080/", 200, "Hello, World!") +} diff --git a/caddytest/integration/sni_test.go b/caddytest/integration/sni_test.go new file mode 100644 index 00000000000..188f9354135 --- /dev/null +++ b/caddytest/integration/sni_test.go @@ -0,0 +1,337 @@ +package integration + +import ( + "testing" + + "github.com/caddyserver/caddy/v2/caddytest" +) + +func TestDefaultSNI(t *testing.T) { + // arrange + tester := caddytest.NewTester(t) + tester.InitServer(`{ + "admin": { + "listen": "localhost:2999" + }, + "apps": { + "http": { + "http_port": 9080, + "https_port": 9443, + "grace_period": 1, + "servers": { + "srv0": { + "listen": [ + ":9443" + ], + "routes": [ + { + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "hello from a.caddy.localhost", + "handler": "static_response", + "status_code": 200 + } + ], + "match": [ + { + "path": [ + "/version" + ] + } + ] + } + ] + } + ], + "match": [ + { + "host": [ + "127.0.0.1" + ] + } + ], + "terminal": true + } + ], + "tls_connection_policies": [ + { + "certificate_selection": { + "any_tag": ["cert0"] + }, + "match": { + "sni": [ + "127.0.0.1" + ] + } + }, + { + "default_sni": "*.caddy.localhost" + } + ] + } + } + }, + "tls": { + "certificates": { + "load_files": [ + { + "certificate": "/caddy.localhost.crt", + "key": "/caddy.localhost.key", + "tags": [ + "cert0" + ] + } + ] + } + }, + "pki": { + "certificate_authorities" : { + "local" : { + "install_trust": false + } + } + } + } + } + `, "json") + + // act and assert + // makes a request with no sni + tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a.caddy.localhost") +} + +func TestDefaultSNIWithNamedHostAndExplicitIP(t *testing.T) { + // arrange + tester := caddytest.NewTester(t) + tester.InitServer(` + { + "admin": { + "listen": "localhost:2999" + }, + "apps": { + "http": { + "http_port": 9080, + "https_port": 9443, + "grace_period": 1, + "servers": { + "srv0": { + "listen": [ + ":9443" + ], + "routes": [ + { + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "hello from a", + "handler": "static_response", + "status_code": 200 + } + ], + "match": [ + { + "path": [ + "/version" + ] + } + ] + } + ] + } + ], + "match": [ + { + "host": [ + "a.caddy.localhost", + "127.0.0.1" + ] + } + ], + "terminal": true + } + ], + "tls_connection_policies": [ + { + "certificate_selection": { + "any_tag": ["cert0"] + }, + "default_sni": "a.caddy.localhost", + "match": { + "sni": [ + "a.caddy.localhost", + "127.0.0.1", + "" + ] + } + }, + { + "default_sni": "a.caddy.localhost" + } + ] + } + } + }, + "tls": { + "certificates": { + "load_files": [ + { + "certificate": "/a.caddy.localhost.crt", + "key": "/a.caddy.localhost.key", + "tags": [ + "cert0" + ] + } + ] + } + }, + "pki": { + "certificate_authorities" : { + "local" : { + "install_trust": false + } + } + } + } + } + `, "json") + + // act and assert + // makes a request with no sni + tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a") +} + +func TestDefaultSNIWithPortMappingOnly(t *testing.T) { + // arrange + tester := caddytest.NewTester(t) + tester.InitServer(` + { + "admin": { + "listen": "localhost:2999" + }, + "apps": { + "http": { + "http_port": 9080, + "https_port": 9443, + "grace_period": 1, + "servers": { + "srv0": { + "listen": [ + ":9443" + ], + "routes": [ + { + "handle": [ + { + "body": "hello from a.caddy.localhost", + "handler": "static_response", + "status_code": 200 + } + ], + "match": [ + { + "path": [ + "/version" + ] + } + ] + } + ], + "tls_connection_policies": [ + { + "certificate_selection": { + "any_tag": ["cert0"] + }, + "default_sni": "a.caddy.localhost" + } + ] + } + } + }, + "tls": { + "certificates": { + "load_files": [ + { + "certificate": "/a.caddy.localhost.crt", + "key": "/a.caddy.localhost.key", + "tags": [ + "cert0" + ] + } + ] + } + }, + "pki": { + "certificate_authorities" : { + "local" : { + "install_trust": false + } + } + } + } + } + `, "json") + + // act and assert + // makes a request with no sni + tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a.caddy.localhost") +} + +func TestHttpOnlyOnDomainWithSNI(t *testing.T) { + caddytest.AssertAdapt(t, ` + { + skip_install_trust + default_sni a.caddy.localhost + } + :80 { + respond /version 200 { + body "hello from localhost" + } + } + `, "caddyfile", `{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "match": [ + { + "path": [ + "/version" + ] + } + ], + "handle": [ + { + "body": "hello from localhost", + "handler": "static_response", + "status_code": 200 + } + ] + } + ] + } + } + }, + "pki": { + "certificate_authorities": { + "local": { + "install_trust": false + } + } + } + } +}`) +} diff --git a/caddytest/integration/stream_test.go b/caddytest/integration/stream_test.go new file mode 100644 index 00000000000..d2f2fd79b95 --- /dev/null +++ b/caddytest/integration/stream_test.go @@ -0,0 +1,439 @@ +package integration + +import ( + "compress/gzip" + "context" + "crypto/rand" + "fmt" + "io" + "net/http" + "net/http/httputil" + "net/url" + "strings" + "testing" + "time" + + "github.com/caddyserver/caddy/v2/caddytest" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" +) + +// (see https://github.com/caddyserver/caddy/issues/3556 for use case) +func TestH2ToH2CStream(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + "admin": { + "listen": "localhost:2999" + }, + "apps": { + "http": { + "http_port": 9080, + "https_port": 9443, + "grace_period": 1, + "servers": { + "srv0": { + "listen": [ + ":9443" + ], + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "transport": { + "protocol": "http", + "compression": false, + "versions": [ + "h2c", + "2" + ] + }, + "upstreams": [ + { + "dial": "localhost:54321" + } + ] + } + ], + "match": [ + { + "path": [ + "/tov2ray" + ] + } + ] + } + ], + "tls_connection_policies": [ + { + "certificate_selection": { + "any_tag": ["cert0"] + }, + "default_sni": "a.caddy.localhost" + } + ] + } + } + }, + "tls": { + "certificates": { + "load_files": [ + { + "certificate": "/a.caddy.localhost.crt", + "key": "/a.caddy.localhost.key", + "tags": [ + "cert0" + ] + } + ] + } + }, + "pki": { + "certificate_authorities" : { + "local" : { + "install_trust": false + } + } + } + } + } + `, "json") + + expectedBody := "some data to be echoed" + // start the server + server := testH2ToH2CStreamServeH2C(t) + go server.ListenAndServe() + defer func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond) + defer cancel() + server.Shutdown(ctx) + }() + + r, w := io.Pipe() + req := &http.Request{ + Method: "PUT", + Body: io.NopCloser(r), + URL: &url.URL{ + Scheme: "https", + Host: "127.0.0.1:9443", + Path: "/tov2ray", + }, + Proto: "HTTP/2", + ProtoMajor: 2, + ProtoMinor: 0, + Header: make(http.Header), + } + // Disable any compression method from server. + req.Header.Set("Accept-Encoding", "identity") + + resp := tester.AssertResponseCode(req, http.StatusOK) + if resp.StatusCode != http.StatusOK { + return + } + go func() { + fmt.Fprint(w, expectedBody) + w.Close() + }() + + defer resp.Body.Close() + bytes, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("unable to read the response body %s", err) + } + + body := string(bytes) + + if !strings.Contains(body, expectedBody) { + t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", req.RequestURI, expectedBody, body) + } +} + +func testH2ToH2CStreamServeH2C(t *testing.T) *http.Server { + h2s := &http2.Server{} + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + rstring, err := httputil.DumpRequest(r, false) + if err == nil { + t.Logf("h2c server received req: %s", rstring) + } + // We only accept HTTP/2! + if r.ProtoMajor != 2 { + t.Error("Not a HTTP/2 request, rejected!") + w.WriteHeader(http.StatusInternalServerError) + return + } + + if r.Host != "127.0.0.1:9443" { + t.Errorf("r.Host doesn't match, %v!", r.Host) + w.WriteHeader(http.StatusNotFound) + return + } + + if !strings.HasPrefix(r.URL.Path, "/tov2ray") { + w.WriteHeader(http.StatusNotFound) + return + } + + w.Header().Set("Cache-Control", "no-store") + w.WriteHeader(200) + http.NewResponseController(w).Flush() + + buf := make([]byte, 4*1024) + + for { + n, err := r.Body.Read(buf) + if n > 0 { + w.Write(buf[:n]) + } + + if err != nil { + if err == io.EOF { + r.Body.Close() + } + break + } + } + }) + + server := &http.Server{ + Addr: "127.0.0.1:54321", + Handler: h2c.NewHandler(handler, h2s), + } + return server +} + +// (see https://github.com/caddyserver/caddy/issues/3606 for use case) +func TestH2ToH1ChunkedResponse(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` +{ + "admin": { + "listen": "localhost:2999" + }, + "logging": { + "logs": { + "default": { + "level": "DEBUG" + } + } + }, + "apps": { + "http": { + "http_port": 9080, + "https_port": 9443, + "grace_period": 1, + "servers": { + "srv0": { + "listen": [ + ":9443" + ], + "routes": [ + { + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "encodings": { + "gzip": {} + }, + "handler": "encode" + } + ] + }, + { + "handle": [ + { + "handler": "reverse_proxy", + "upstreams": [ + { + "dial": "localhost:54321" + } + ] + } + ], + "match": [ + { + "path": [ + "/tov2ray" + ] + } + ] + } + ] + } + ], + "terminal": true + } + ], + "tls_connection_policies": [ + { + "certificate_selection": { + "any_tag": [ + "cert0" + ] + }, + "default_sni": "a.caddy.localhost" + } + ] + } + } + }, + "tls": { + "certificates": { + "load_files": [ + { + "certificate": "/a.caddy.localhost.crt", + "key": "/a.caddy.localhost.key", + "tags": [ + "cert0" + ] + } + ] + } + }, + "pki": { + "certificate_authorities": { + "local": { + "install_trust": false + } + } + } + } +} + `, "json") + + // need a large body here to trigger caddy's compression, larger than gzip.miniLength + expectedBody, err := GenerateRandomString(1024) + if err != nil { + t.Fatalf("generate expected body failed, err: %s", err) + } + + // start the server + server := testH2ToH1ChunkedResponseServeH1(t) + go server.ListenAndServe() + defer func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond) + defer cancel() + server.Shutdown(ctx) + }() + + r, w := io.Pipe() + req := &http.Request{ + Method: "PUT", + Body: io.NopCloser(r), + URL: &url.URL{ + Scheme: "https", + Host: "127.0.0.1:9443", + Path: "/tov2ray", + }, + Proto: "HTTP/2", + ProtoMajor: 2, + ProtoMinor: 0, + Header: make(http.Header), + } + // underlying transport will automatically add gzip + // req.Header.Set("Accept-Encoding", "gzip") + go func() { + fmt.Fprint(w, expectedBody) + w.Close() + }() + resp := tester.AssertResponseCode(req, http.StatusOK) + if resp.StatusCode != http.StatusOK { + return + } + + defer resp.Body.Close() + bytes, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("unable to read the response body %s", err) + } + + body := string(bytes) + + if body != expectedBody { + t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", req.RequestURI, expectedBody, body) + } +} + +func testH2ToH1ChunkedResponseServeH1(t *testing.T) *http.Server { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Host != "127.0.0.1:9443" { + t.Errorf("r.Host doesn't match, %v!", r.Host) + w.WriteHeader(http.StatusNotFound) + return + } + + if !strings.HasPrefix(r.URL.Path, "/tov2ray") { + w.WriteHeader(http.StatusNotFound) + return + } + + defer r.Body.Close() + bytes, err := io.ReadAll(r.Body) + if err != nil { + t.Fatalf("unable to read the response body %s", err) + } + + n := len(bytes) + + var writer io.Writer + if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { + gw, err := gzip.NewWriterLevel(w, 5) + if err != nil { + t.Error("can't return gzip data") + w.WriteHeader(http.StatusInternalServerError) + return + } + defer gw.Close() + writer = gw + w.Header().Set("Content-Encoding", "gzip") + w.Header().Del("Content-Length") + w.WriteHeader(200) + } else { + writer = w + } + if n > 0 { + writer.Write(bytes[:]) + } + }) + + server := &http.Server{ + Addr: "127.0.0.1:54321", + Handler: handler, + } + return server +} + +// GenerateRandomBytes returns securely generated random bytes. +// It will return an error if the system's secure random +// number generator fails to function correctly, in which +// case the caller should not continue. +func GenerateRandomBytes(n int) ([]byte, error) { + b := make([]byte, n) + _, err := rand.Read(b) + // Note that err == nil only if we read len(b) bytes. + if err != nil { + return nil, err + } + + return b, nil +} + +// GenerateRandomString returns a securely generated random string. +// It will return an error if the system's secure random +// number generator fails to function correctly, in which +// case the caller should not continue. +func GenerateRandomString(n int) (string, error) { + const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-" + bytes, err := GenerateRandomBytes(n) + if err != nil { + return "", err + } + for i, b := range bytes { + bytes[i] = letters[b%byte(len(letters))] + } + return string(bytes), nil +} diff --git a/caddytest/integration/testdata/cookie.html b/caddytest/integration/testdata/cookie.html new file mode 100644 index 00000000000..fa53a39fc71 --- /dev/null +++ b/caddytest/integration/testdata/cookie.html @@ -0,0 +1 @@ +

    Cookie.ClientName {{.Cookie "clientname"}}

    \ No newline at end of file diff --git a/caddytest/integration/testdata/foo.txt b/caddytest/integration/testdata/foo.txt new file mode 100644 index 00000000000..19102815663 --- /dev/null +++ b/caddytest/integration/testdata/foo.txt @@ -0,0 +1 @@ +foo \ No newline at end of file diff --git a/caddytest/integration/testdata/foo_with_multiple_trailing_newlines.txt b/caddytest/integration/testdata/foo_with_multiple_trailing_newlines.txt new file mode 100644 index 00000000000..75d7bfb873a --- /dev/null +++ b/caddytest/integration/testdata/foo_with_multiple_trailing_newlines.txt @@ -0,0 +1,2 @@ +foo + diff --git a/caddytest/integration/testdata/foo_with_trailing_newline.txt b/caddytest/integration/testdata/foo_with_trailing_newline.txt new file mode 100644 index 00000000000..257cc5642cb --- /dev/null +++ b/caddytest/integration/testdata/foo_with_trailing_newline.txt @@ -0,0 +1 @@ +foo diff --git a/caddytest/integration/testdata/import_respond.txt b/caddytest/integration/testdata/import_respond.txt new file mode 100644 index 00000000000..45130885cf7 --- /dev/null +++ b/caddytest/integration/testdata/import_respond.txt @@ -0,0 +1 @@ +respond "'I am {args[0]}', hears {args[1]}" \ No newline at end of file diff --git a/caddytest/integration/testdata/index.localhost.html b/caddytest/integration/testdata/index.localhost.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/caddytest/leafcert.pem b/caddytest/leafcert.pem new file mode 100644 index 00000000000..03febfd3ae1 --- /dev/null +++ b/caddytest/leafcert.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICUTCCAfugAwIBAgIBADANBgkqhkiG9w0BAQQFADBXMQswCQYDVQQGEwJDTjEL +MAkGA1UECBMCUE4xCzAJBgNVBAcTAkNOMQswCQYDVQQKEwJPTjELMAkGA1UECxMC +VU4xFDASBgNVBAMTC0hlcm9uZyBZYW5nMB4XDTA1MDcxNTIxMTk0N1oXDTA1MDgx +NDIxMTk0N1owVzELMAkGA1UEBhMCQ04xCzAJBgNVBAgTAlBOMQswCQYDVQQHEwJD +TjELMAkGA1UEChMCT04xCzAJBgNVBAsTAlVOMRQwEgYDVQQDEwtIZXJvbmcgWWFu +ZzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCp5hnG7ogBhtlynpOS21cBewKE/B7j +V14qeyslnr26xZUsSVko36ZnhiaO/zbMOoRcKK9vEcgMtcLFuQTWDl3RAgMBAAGj +gbEwga4wHQYDVR0OBBYEFFXI70krXeQDxZgbaCQoR4jUDncEMH8GA1UdIwR4MHaA +FFXI70krXeQDxZgbaCQoR4jUDncEoVukWTBXMQswCQYDVQQGEwJDTjELMAkGA1UE +CBMCUE4xCzAJBgNVBAcTAkNOMQswCQYDVQQKEwJPTjELMAkGA1UECxMCVU4xFDAS +BgNVBAMTC0hlcm9uZyBZYW5nggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEE +BQADQQA/ugzBrjjK9jcWnDVfGHlk3icNRq0oV7Ri32z/+HQX67aRfgZu7KWdI+Ju +Wm7DCfrPNGVwFWUQOmsPue9rZBgO +-----END CERTIFICATE----- diff --git a/caddytls/certificates.go b/caddytls/certificates.go deleted file mode 100644 index d3aee8f0d12..00000000000 --- a/caddytls/certificates.go +++ /dev/null @@ -1,259 +0,0 @@ -package caddytls - -import ( - "crypto/tls" - "crypto/x509" - "errors" - "io/ioutil" - "log" - "strings" - "sync" - "time" - - "golang.org/x/crypto/ocsp" -) - -// certCache stores certificates in memory, -// keying certificates by name. -var certCache = make(map[string]Certificate) -var certCacheMu sync.RWMutex - -// Certificate is a tls.Certificate with associated metadata tacked on. -// Even if the metadata can be obtained by parsing the certificate, -// we can be more efficient by extracting the metadata once so it's -// just there, ready to use. -type Certificate struct { - tls.Certificate - - // Names is the list of names this certificate is written for. - // The first is the CommonName (if any), the rest are SAN. - Names []string - - // NotAfter is when the certificate expires. - NotAfter time.Time - - // OCSP contains the certificate's parsed OCSP response. - OCSP *ocsp.Response - - // Config is the configuration with which the certificate was - // loaded or obtained and with which it should be maintained. - Config *Config -} - -// getCertificate gets a certificate that matches name (a server name) -// from the in-memory cache. If there is no exact match for name, it -// will be checked against names of the form '*.example.com' (wildcard -// certificates) according to RFC 6125. If a match is found, matched will -// be true. If no matches are found, matched will be false and a default -// certificate will be returned with defaulted set to true. If no default -// certificate is set, defaulted will be set to false. -// -// The logic in this function is adapted from the Go standard library, -// which is by the Go Authors. -// -// This function is safe for concurrent use. -func getCertificate(name string) (cert Certificate, matched, defaulted bool) { - var ok bool - - // Not going to trim trailing dots here since RFC 3546 says, - // "The hostname is represented ... without a trailing dot." - // Just normalize to lowercase. - name = strings.ToLower(name) - - certCacheMu.RLock() - defer certCacheMu.RUnlock() - - // exact match? great, let's use it - if cert, ok = certCache[name]; ok { - matched = true - return - } - - // try replacing labels in the name with wildcards until we get a match - labels := strings.Split(name, ".") - for i := range labels { - labels[i] = "*" - candidate := strings.Join(labels, ".") - if cert, ok = certCache[candidate]; ok { - matched = true - return - } - } - - // if nothing matches, use the default certificate or bust - cert, defaulted = certCache[""] - return -} - -// CacheManagedCertificate loads the certificate for domain into the -// cache, flagging it as Managed and, if onDemand is true, as "OnDemand" -// (meaning that it was obtained or loaded during a TLS handshake). -// -// This method is safe for concurrent use. -func (cfg *Config) CacheManagedCertificate(domain string) (Certificate, error) { - storage, err := cfg.StorageFor(cfg.CAUrl) - if err != nil { - return Certificate{}, err - } - siteData, err := storage.LoadSite(domain) - if err != nil { - return Certificate{}, err - } - cert, err := makeCertificate(siteData.Cert, siteData.Key) - if err != nil { - return cert, err - } - cert.Config = cfg - cacheCertificate(cert) - return cert, nil -} - -// cacheUnmanagedCertificatePEMFile loads a certificate for host using certFile -// and keyFile, which must be in PEM format. It stores the certificate in -// memory. The Managed and OnDemand flags of the certificate will be set to -// false. -// -// This function is safe for concurrent use. -func cacheUnmanagedCertificatePEMFile(certFile, keyFile string) error { - cert, err := makeCertificateFromDisk(certFile, keyFile) - if err != nil { - return err - } - cacheCertificate(cert) - return nil -} - -// cacheUnmanagedCertificatePEMBytes makes a certificate out of the PEM bytes -// of the certificate and key, then caches it in memory. -// -// This function is safe for concurrent use. -func cacheUnmanagedCertificatePEMBytes(certBytes, keyBytes []byte) error { - cert, err := makeCertificate(certBytes, keyBytes) - if err != nil { - return err - } - cacheCertificate(cert) - return nil -} - -// makeCertificateFromDisk makes a Certificate by loading the -// certificate and key files. It fills out all the fields in -// the certificate except for the Managed and OnDemand flags. -// (It is up to the caller to set those.) -func makeCertificateFromDisk(certFile, keyFile string) (Certificate, error) { - certPEMBlock, err := ioutil.ReadFile(certFile) - if err != nil { - return Certificate{}, err - } - keyPEMBlock, err := ioutil.ReadFile(keyFile) - if err != nil { - return Certificate{}, err - } - return makeCertificate(certPEMBlock, keyPEMBlock) -} - -// makeCertificate turns a certificate PEM bundle and a key PEM block into -// a Certificate, with OCSP and other relevant metadata tagged with it, -// except for the OnDemand and Managed flags. It is up to the caller to -// set those properties. -func makeCertificate(certPEMBlock, keyPEMBlock []byte) (Certificate, error) { - var cert Certificate - - // Convert to a tls.Certificate - tlsCert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock) - if err != nil { - return cert, err - } - if len(tlsCert.Certificate) == 0 { - return cert, errors.New("certificate is empty") - } - cert.Certificate = tlsCert - - // Parse leaf certificate, extract relevant metadata, and staple OCSP - leaf, err := x509.ParseCertificate(tlsCert.Certificate[0]) - if err != nil { - return cert, err - } - err = fillCertFromLeaf(&cert, leaf) - if err != nil { - return cert, err - } - err = stapleOCSP(&cert, certPEMBlock) - if err != nil { - log.Printf("[WARNING] Stapling OCSP: %v", err) - } - - return cert, nil -} - -// fillCertFromLeaf populates cert.Names and cert.NotAfter -// using data in leaf. -func fillCertFromLeaf(cert *Certificate, leaf *x509.Certificate) error { - if leaf.Subject.CommonName != "" { - cert.Names = []string{strings.ToLower(leaf.Subject.CommonName)} - } - for _, name := range leaf.DNSNames { - if name != leaf.Subject.CommonName { - cert.Names = append(cert.Names, strings.ToLower(name)) - } - } - for _, ip := range leaf.IPAddresses { - if ipStr := ip.String(); ipStr != leaf.Subject.CommonName { - cert.Names = append(cert.Names, strings.ToLower(ipStr)) - } - } - for _, email := range leaf.EmailAddresses { - if email != leaf.Subject.CommonName { - cert.Names = append(cert.Names, strings.ToLower(email)) - } - } - if len(cert.Names) == 0 { - return errors.New("certificate has no names") - } - cert.NotAfter = leaf.NotAfter - return nil -} - -// cacheCertificate adds cert to the in-memory cache. If the cache is -// empty, cert will be used as the default certificate. If the cache is -// full, random entries are deleted until there is room to map all the -// names on the certificate. -// -// This certificate will be keyed to the names in cert.Names. Any name -// that is already a key in the cache will be replaced with this cert. -// -// This function is safe for concurrent use. -func cacheCertificate(cert Certificate) { - if cert.Config == nil { - cert.Config = new(Config) - } - certCacheMu.Lock() - if _, ok := certCache[""]; !ok { - // use as default - must be *appended* to end of list, or bad things happen! - cert.Names = append(cert.Names, "") - certCache[""] = cert - } - for len(certCache)+len(cert.Names) > 10000 { - // for simplicity, just remove random elements - for key := range certCache { - if key == "" { // ... but not the default cert - continue - } - delete(certCache, key) - break - } - } - for _, name := range cert.Names { - certCache[name] = cert - } - certCacheMu.Unlock() -} - -// uncacheCertificate deletes name's certificate from the -// cache. If name is not a key in the certificate cache, -// this function does nothing. -func uncacheCertificate(name string) { - certCacheMu.Lock() - delete(certCache, name) - certCacheMu.Unlock() -} diff --git a/caddytls/certificates_test.go b/caddytls/certificates_test.go deleted file mode 100644 index 32825500b2f..00000000000 --- a/caddytls/certificates_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package caddytls - -import "testing" - -func TestUnexportedGetCertificate(t *testing.T) { - defer func() { certCache = make(map[string]Certificate) }() - - // When cache is empty - if _, matched, defaulted := getCertificate("example.com"); matched || defaulted { - t.Errorf("Got a certificate when cache was empty; matched=%v, defaulted=%v", matched, defaulted) - } - - // When cache has one certificate in it (also is default) - defaultCert := Certificate{Names: []string{"example.com", ""}} - certCache[""] = defaultCert - certCache["example.com"] = defaultCert - if cert, matched, defaulted := getCertificate("Example.com"); !matched || defaulted || cert.Names[0] != "example.com" { - t.Errorf("Didn't get a cert for 'Example.com' or got the wrong one: %v, matched=%v, defaulted=%v", cert, matched, defaulted) - } - if cert, matched, defaulted := getCertificate(""); !matched || defaulted || cert.Names[0] != "example.com" { - t.Errorf("Didn't get a cert for '' or got the wrong one: %v, matched=%v, defaulted=%v", cert, matched, defaulted) - } - - // When retrieving wildcard certificate - certCache["*.example.com"] = Certificate{Names: []string{"*.example.com"}} - if cert, matched, defaulted := getCertificate("sub.example.com"); !matched || defaulted || cert.Names[0] != "*.example.com" { - t.Errorf("Didn't get wildcard cert for 'sub.example.com' or got the wrong one: %v, matched=%v, defaulted=%v", cert, matched, defaulted) - } - - // When no certificate matches, the default is returned - if cert, matched, defaulted := getCertificate("nomatch"); matched || !defaulted { - t.Errorf("Expected matched=false, defaulted=true; but got matched=%v, defaulted=%v (cert: %v)", matched, defaulted, cert) - } else if cert.Names[0] != "example.com" { - t.Errorf("Expected default cert, got: %v", cert) - } -} - -func TestCacheCertificate(t *testing.T) { - defer func() { certCache = make(map[string]Certificate) }() - - cacheCertificate(Certificate{Names: []string{"example.com", "sub.example.com"}}) - if _, ok := certCache["example.com"]; !ok { - t.Error("Expected first cert to be cached by key 'example.com', but it wasn't") - } - if _, ok := certCache["sub.example.com"]; !ok { - t.Error("Expected first cert to be cached by key 'sub.example.com', but it wasn't") - } - if cert, ok := certCache[""]; !ok || cert.Names[2] != "" { - t.Error("Expected first cert to be cached additionally as the default certificate with empty name added, but it wasn't") - } - - cacheCertificate(Certificate{Names: []string{"example2.com"}}) - if _, ok := certCache["example2.com"]; !ok { - t.Error("Expected second cert to be cached by key 'exmaple2.com', but it wasn't") - } - if cert, ok := certCache[""]; ok && cert.Names[0] == "example2.com" { - t.Error("Expected second cert to NOT be cached as default, but it was") - } -} diff --git a/caddytls/client.go b/caddytls/client.go deleted file mode 100644 index deb94af7afd..00000000000 --- a/caddytls/client.go +++ /dev/null @@ -1,414 +0,0 @@ -package caddytls - -import ( - "encoding/json" - "errors" - "fmt" - "log" - "net" - "net/url" - "strings" - "sync" - "time" - - "github.com/mholt/caddy" - "github.com/xenolf/lego/acme" -) - -// acmeMu ensures that only one ACME challenge occurs at a time. -var acmeMu sync.Mutex - -// ACMEClient is a wrapper over acme.Client with -// some custom state attached. It is used to obtain, -// renew, and revoke certificates with ACME. -type ACMEClient struct { - AllowPrompts bool - config *Config - acmeClient *acme.Client -} - -// newACMEClient creates a new ACMEClient given an email and whether -// prompting the user is allowed. It's a variable so we can mock in tests. -var newACMEClient = func(config *Config, allowPrompts bool) (*ACMEClient, error) { - storage, err := config.StorageFor(config.CAUrl) - if err != nil { - return nil, err - } - - // Look up or create the LE user account - leUser, err := getUser(storage, config.ACMEEmail) - if err != nil { - return nil, err - } - - // ensure key type is set - keyType := DefaultKeyType - if config.KeyType != "" { - keyType = config.KeyType - } - - // ensure CA URL (directory endpoint) is set - caURL := DefaultCAUrl - if config.CAUrl != "" { - caURL = config.CAUrl - } - - // ensure endpoint is secure (assume HTTPS if scheme is missing) - if !strings.Contains(caURL, "://") { - caURL = "https://" + caURL - } - u, err := url.Parse(caURL) - if err != nil { - return nil, err - } - if u.Scheme != "https" && !caddy.IsLoopback(u.Host) && !caddy.IsInternal(u.Host) { - return nil, fmt.Errorf("%s: insecure CA URL (HTTPS required)", caURL) - } - - // The client facilitates our communication with the CA server. - client, err := acme.NewClient(caURL, &leUser, keyType) - if err != nil { - return nil, err - } - - // If not registered, the user must register an account with the CA - // and agree to terms - if leUser.Registration == nil { - reg, err := client.Register() - if err != nil { - return nil, errors.New("registration error: " + err.Error()) - } - leUser.Registration = reg - - if allowPrompts { // can't prompt a user who isn't there - if !Agreed && reg.TosURL == "" { - Agreed = promptUserAgreement(saURL, false) // TODO - latest URL - } - if !Agreed && reg.TosURL == "" { - return nil, errors.New("user must agree to terms") - } - } - - err = client.AgreeToTOS() - if err != nil { - saveUser(storage, leUser) // Might as well try, right? - return nil, errors.New("error agreeing to terms: " + err.Error()) - } - - // save user to the file system - err = saveUser(storage, leUser) - if err != nil { - return nil, errors.New("could not save user: " + err.Error()) - } - } - - c := &ACMEClient{ - AllowPrompts: allowPrompts, - config: config, - acmeClient: client, - } - - if config.DNSProvider == "" { - // Use HTTP and TLS-SNI challenges by default - - // See if HTTP challenge needs to be proxied - useHTTPPort := HTTPChallengePort - if config.AltHTTPPort != "" { - useHTTPPort = config.AltHTTPPort - } - if caddy.HasListenerWithAddress(net.JoinHostPort(config.ListenHost, useHTTPPort)) { - useHTTPPort = DefaultHTTPAlternatePort - } - - // See which port TLS-SNI challenges will be accomplished on - useTLSSNIPort := TLSSNIChallengePort - if config.AltTLSSNIPort != "" { - useTLSSNIPort = config.AltTLSSNIPort - } - - // Always respect user's bind preferences by using config.ListenHost. - // NOTE(Sep'16): At time of writing, SetHTTPAddress() and SetTLSAddress() - // must be called before SetChallengeProvider(), since they reset the - // challenge provider back to the default one! - err := c.acmeClient.SetHTTPAddress(net.JoinHostPort(config.ListenHost, useHTTPPort)) - if err != nil { - return nil, err - } - err = c.acmeClient.SetTLSAddress(net.JoinHostPort(config.ListenHost, useTLSSNIPort)) - if err != nil { - return nil, err - } - - // See if TLS challenge needs to be handled by our own facilities - if caddy.HasListenerWithAddress(net.JoinHostPort(config.ListenHost, useTLSSNIPort)) { - c.acmeClient.SetChallengeProvider(acme.TLSSNI01, tlsSniSolver{}) - } - - // Disable any challenges that should not be used - var disabledChallenges []acme.Challenge - if DisableHTTPChallenge { - disabledChallenges = append(disabledChallenges, acme.HTTP01) - } - if DisableTLSSNIChallenge { - disabledChallenges = append(disabledChallenges, acme.TLSSNI01) - } - if len(disabledChallenges) > 0 { - c.acmeClient.ExcludeChallenges(disabledChallenges) - } - } else { - // Otherwise, use DNS challenge exclusively - - // Load provider constructor function - provFn, ok := dnsProviders[config.DNSProvider] - if !ok { - return nil, errors.New("unknown DNS provider by name '" + config.DNSProvider + "'") - } - - // We could pass credentials to create the provider, but for now - // just let the solver package get them from the environment - prov, err := provFn() - if err != nil { - return nil, err - } - - // Use the DNS challenge exclusively - c.acmeClient.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.TLSSNI01}) - c.acmeClient.SetChallengeProvider(acme.DNS01, prov) - } - - return c, nil -} - -// Obtain obtains a single certificate for name. It stores the certificate -// on the disk if successful. This function is safe for concurrent use. -// -// Right now our storage mechanism only supports one name per certificate, -// so this function (along with Renew and Revoke) only accepts one domain -// as input. It can be easily modified to support SAN certificates if our -// storage mechanism is upgraded later. -// -// Callers who have access to a Config value should use the ObtainCert -// method on that instead of this lower-level method. -func (c *ACMEClient) Obtain(name string) error { - // Get access to ACME storage - storage, err := c.config.StorageFor(c.config.CAUrl) - if err != nil { - return err - } - - waiter, err := storage.TryLock(name) - if err != nil { - return err - } - if waiter != nil { - log.Printf("[INFO] Certificate for %s is already being obtained elsewhere and stored; waiting", name) - waiter.Wait() - return nil // we assume the process with the lock succeeded, rather than hammering this execution path again - } - defer func() { - if err := storage.Unlock(name); err != nil { - log.Printf("[ERROR] Unable to unlock obtain call for %s: %v", name, err) - } - }() - -Attempts: - for attempts := 0; attempts < 2; attempts++ { - namesObtaining.Add([]string{name}) - acmeMu.Lock() - certificate, failures := c.acmeClient.ObtainCertificate([]string{name}, true, nil, c.config.MustStaple) - acmeMu.Unlock() - namesObtaining.Remove([]string{name}) - if len(failures) > 0 { - // Error - try to fix it or report it to the user and abort - var errMsg string // we'll combine all the failures into a single error message - var promptedForAgreement bool // only prompt user for agreement at most once - - for errDomain, obtainErr := range failures { - if obtainErr == nil { - continue - } - if tosErr, ok := obtainErr.(acme.TOSError); ok { - // Terms of Service agreement error; we can probably deal with this - if !Agreed && !promptedForAgreement && c.AllowPrompts { - Agreed = promptUserAgreement(tosErr.Detail, true) // TODO: Use latest URL - promptedForAgreement = true - } - if Agreed || !c.AllowPrompts { - err := c.acmeClient.AgreeToTOS() - if err != nil { - return errors.New("error agreeing to updated terms: " + err.Error()) - } - continue Attempts - } - } - - // If user did not agree or it was any other kind of error, just append to the list of errors - errMsg += "[" + errDomain + "] failed to get certificate: " + obtainErr.Error() + "\n" - } - return errors.New(errMsg) - } - - // Success - immediately save the certificate resource - err = saveCertResource(storage, certificate) - if err != nil { - return fmt.Errorf("error saving assets for %v: %v", name, err) - } - - break - } - - return nil -} - -// Renew renews the managed certificate for name. This function is -// safe for concurrent use. -// -// Callers who have access to a Config value should use the RenewCert -// method on that instead of this lower-level method. -func (c *ACMEClient) Renew(name string) error { - // Get access to ACME storage - storage, err := c.config.StorageFor(c.config.CAUrl) - if err != nil { - return err - } - - waiter, err := storage.TryLock(name) - if err != nil { - return err - } - if waiter != nil { - log.Printf("[INFO] Certificate for %s is already being renewed elsewhere and stored; waiting", name) - waiter.Wait() - return nil // we assume the process with the lock succeeded, rather than hammering this execution path again - } - defer func() { - if err := storage.Unlock(name); err != nil { - log.Printf("[ERROR] Unable to unlock renew call for %s: %v", name, err) - } - }() - - // Prepare for renewal (load PEM cert, key, and meta) - siteData, err := storage.LoadSite(name) - if err != nil { - return err - } - var certMeta acme.CertificateResource - err = json.Unmarshal(siteData.Meta, &certMeta) - certMeta.Certificate = siteData.Cert - certMeta.PrivateKey = siteData.Key - - // Perform renewal and retry if necessary, but not too many times. - var newCertMeta acme.CertificateResource - var success bool - for attempts := 0; attempts < 2; attempts++ { - namesObtaining.Add([]string{name}) - acmeMu.Lock() - newCertMeta, err = c.acmeClient.RenewCertificate(certMeta, true, c.config.MustStaple) - acmeMu.Unlock() - namesObtaining.Remove([]string{name}) - if err == nil { - success = true - break - } - - // If the legal terms were updated and need to be - // agreed to again, we can handle that. - if _, ok := err.(acme.TOSError); ok { - err := c.acmeClient.AgreeToTOS() - if err != nil { - return err - } - continue - } - - // For any other kind of error, wait 10s and try again. - wait := 10 * time.Second - log.Printf("[ERROR] Renewing: %v; trying again in %s", err, wait) - time.Sleep(wait) - } - - if !success { - return errors.New("too many renewal attempts; last error: " + err.Error()) - } - - return saveCertResource(storage, newCertMeta) -} - -// Revoke revokes the certificate for name and deltes -// it from storage. -func (c *ACMEClient) Revoke(name string) error { - storage, err := c.config.StorageFor(c.config.CAUrl) - if err != nil { - return err - } - - siteExists, err := storage.SiteExists(name) - if err != nil { - return err - } - - if !siteExists { - return errors.New("no certificate and key for " + name) - } - - siteData, err := storage.LoadSite(name) - if err != nil { - return err - } - - err = c.acmeClient.RevokeCertificate(siteData.Cert) - if err != nil { - return err - } - - err = storage.DeleteSite(name) - if err != nil { - return errors.New("certificate revoked, but unable to delete certificate file: " + err.Error()) - } - - return nil -} - -// namesObtaining is a set of hostnames with thread-safe -// methods. A name should be in this set only while this -// package is in the process of obtaining a certificate -// for the name. ACME challenges that are received for -// names which are not in this set were not initiated by -// this package and probably should not be handled by -// this package. -var namesObtaining = nameCoordinator{names: make(map[string]struct{})} - -type nameCoordinator struct { - names map[string]struct{} - mu sync.RWMutex -} - -// Add adds names to c. It is safe for concurrent use. -func (c *nameCoordinator) Add(names []string) { - c.mu.Lock() - for _, name := range names { - c.names[strings.ToLower(name)] = struct{}{} - } - c.mu.Unlock() -} - -// Remove removes names from c. It is safe for concurrent use. -func (c *nameCoordinator) Remove(names []string) { - c.mu.Lock() - for _, name := range names { - delete(c.names, strings.ToLower(name)) - } - c.mu.Unlock() -} - -// Has returns true if c has name. It is safe for concurrent use. -func (c *nameCoordinator) Has(name string) bool { - hostname, _, err := net.SplitHostPort(name) - if err != nil { - hostname = name - } - c.mu.RLock() - _, ok := c.names[strings.ToLower(hostname)] - c.mu.RUnlock() - return ok -} diff --git a/caddytls/client_test.go b/caddytls/client_test.go deleted file mode 100644 index bd9cbbc8119..00000000000 --- a/caddytls/client_test.go +++ /dev/null @@ -1,3 +0,0 @@ -package caddytls - -// TODO diff --git a/caddytls/config.go b/caddytls/config.go deleted file mode 100644 index 49b2e9c76e0..00000000000 --- a/caddytls/config.go +++ /dev/null @@ -1,532 +0,0 @@ -package caddytls - -import ( - "crypto/tls" - "crypto/x509" - "fmt" - "io/ioutil" - - "net/url" - "strings" - - "github.com/codahale/aesnicheck" - "github.com/mholt/caddy" - "github.com/xenolf/lego/acme" -) - -// Config describes how TLS should be configured and used. -type Config struct { - // The hostname or class of hostnames this config is - // designated for; can contain wildcard characters - // according to RFC 6125 §6.4.3 - this field MUST - // be set in order for things to work as expected - Hostname string - - // Whether TLS is enabled - Enabled bool - - // Minimum and maximum protocol versions to allow - ProtocolMinVersion uint16 - ProtocolMaxVersion uint16 - - // The list of cipher suites; first should be - // TLS_FALLBACK_SCSV to prevent degrade attacks - Ciphers []uint16 - - // Whether to prefer server cipher suites - PreferServerCipherSuites bool - - // The list of preferred curves - CurvePreferences []tls.CurveID - - // Client authentication policy - ClientAuth tls.ClientAuthType - - // List of client CA certificates to allow, if - // client authentication is enabled - ClientCerts []string - - // Manual means user provides own certs and keys - Manual bool - - // Managed means config qualifies for implicit, - // automatic, managed TLS; as opposed to the user - // providing and managing the certificate manually - Managed bool - - // OnDemand means the class of hostnames this - // config applies to may obtain and manage - // certificates at handshake-time (as opposed - // to pre-loaded at startup); OnDemand certs - // will be managed the same way as preloaded - // ones, however, if an OnDemand cert fails to - // renew, it is removed from the in-memory - // cache; if this is true, Managed must - // necessarily be true - OnDemand bool - - // SelfSigned means that this hostname is - // served with a self-signed certificate - // that we generated in memory for convenience - SelfSigned bool - - // The endpoint of the directory for the ACME - // CA we are to use - CAUrl string - - // The host (ONLY the host, not port) to listen - // on if necessary to start a listener to solve - // an ACME challenge - ListenHost string - - // The alternate port (ONLY port, not host) - // to use for the ACME HTTP challenge; this - // port will be used if we proxy challenges - // coming in on port 80 to this alternate port - AltHTTPPort string - - // The alternate port (ONLY port, not host) - // to use for the ACME TLS-SNI challenge. - // The system must forward the standard port - // for the TLS-SNI challenge to this port. - AltTLSSNIPort string - - // The string identifier of the DNS provider - // to use when solving the ACME DNS challenge - DNSProvider string - - // The email address to use when creating or - // using an ACME account (fun fact: if this - // is set to "off" then this config will not - // qualify for managed TLS) - ACMEEmail string - - // The type of key to use when generating - // certificates - KeyType acme.KeyType - - // The storage creator; use StorageFor() to get a guaranteed - // non-nil Storage instance. Note, Caddy may call this frequently - // so implementors are encouraged to cache any heavy instantiations. - StorageProvider string - - // The state needed to operate on-demand TLS - OnDemandState OnDemandState - - // Add the must staple TLS extension to the CSR generated by lego/acme - MustStaple bool - - // The list of protocols to choose from for Application Layer - // Protocol Negotiation (ALPN). - ALPN []string - - tlsConfig *tls.Config // the final tls.Config created with buildStandardTLSConfig() -} - -// OnDemandState contains some state relevant for providing -// on-demand TLS. -type OnDemandState struct { - // The number of certificates that have been issued on-demand - // by this config. It is only safe to modify this count atomically. - // If it reaches MaxObtain, on-demand issuances must fail. - ObtainedCount int32 - - // Set from max_certs in tls config, it specifies the - // maximum number of certificates that can be issued. - MaxObtain int32 -} - -// ObtainCert obtains a certificate for name using c, as long -// as a certificate does not already exist in storage for that -// name. The name must qualify and c must be flagged as Managed. -// This function is a no-op if storage already has a certificate -// for name. -// -// It only obtains and stores certificates (and their keys), -// it does not load them into memory. If allowPrompts is true, -// the user may be shown a prompt. -func (c *Config) ObtainCert(name string, allowPrompts bool) error { - if !c.Managed || !HostQualifies(name) { - return nil - } - - storage, err := c.StorageFor(c.CAUrl) - if err != nil { - return err - } - siteExists, err := storage.SiteExists(name) - if err != nil { - return err - } - if siteExists { - return nil - } - if c.ACMEEmail == "" { - c.ACMEEmail = getEmail(storage, allowPrompts) - } - - client, err := newACMEClient(c, allowPrompts) - if err != nil { - return err - } - return client.Obtain(name) -} - -// RenewCert renews the certificate for name using c. It stows the -// renewed certificate and its assets in storage if successful. -func (c *Config) RenewCert(name string, allowPrompts bool) error { - client, err := newACMEClient(c, allowPrompts) - if err != nil { - return err - } - return client.Renew(name) -} - -// StorageFor obtains a TLS Storage instance for the given CA URL which should -// be unique for every different ACME CA. If a StorageCreator is set on this -// Config, it will be used. Otherwise the default file storage implementation -// is used. When the error is nil, this is guaranteed to return a non-nil -// Storage instance. -func (c *Config) StorageFor(caURL string) (Storage, error) { - // Validate CA URL - if caURL == "" { - caURL = DefaultCAUrl - } - if caURL == "" { - return nil, fmt.Errorf("cannot create storage without CA URL") - } - caURL = strings.ToLower(caURL) - - // scheme required or host will be parsed as path (as of Go 1.6) - if !strings.Contains(caURL, "://") { - caURL = "https://" + caURL - } - - u, err := url.Parse(caURL) - if err != nil { - return nil, fmt.Errorf("%s: unable to parse CA URL: %v", caURL, err) - } - - if u.Host == "" { - return nil, fmt.Errorf("%s: no host in CA URL", caURL) - } - - // Create the storage based on the URL - var s Storage - if c.StorageProvider == "" { - c.StorageProvider = "file" - } - - creator, ok := storageProviders[c.StorageProvider] - if !ok { - return nil, fmt.Errorf("%s: Unknown storage: %v", caURL, c.StorageProvider) - } - - s, err = creator(u) - if err != nil { - return nil, fmt.Errorf("%s: unable to create custom storage '%v': %v", caURL, c.StorageProvider, err) - } - - return s, nil -} - -// buildStandardTLSConfig converts cfg (*caddytls.Config) to a *tls.Config -// and stores it in cfg so it can be used in servers. If TLS is disabled, -// no tls.Config is created. -func (c *Config) buildStandardTLSConfig() error { - if !c.Enabled { - return nil - } - - config := new(tls.Config) - - ciphersAdded := make(map[uint16]struct{}) - curvesAdded := make(map[tls.CurveID]struct{}) - - // add cipher suites - for _, ciph := range c.Ciphers { - if _, ok := ciphersAdded[ciph]; !ok { - ciphersAdded[ciph] = struct{}{} - config.CipherSuites = append(config.CipherSuites, ciph) - } - } - - config.PreferServerCipherSuites = c.PreferServerCipherSuites - - // add curve preferences - for _, curv := range c.CurvePreferences { - if _, ok := curvesAdded[curv]; !ok { - curvesAdded[curv] = struct{}{} - config.CurvePreferences = append(config.CurvePreferences, curv) - } - } - - config.MinVersion = c.ProtocolMinVersion - config.MaxVersion = c.ProtocolMaxVersion - config.ClientAuth = c.ClientAuth - config.NextProtos = c.ALPN - config.GetCertificate = c.GetCertificate - - // set up client authentication if enabled - if config.ClientAuth != tls.NoClientCert { - pool := x509.NewCertPool() - clientCertsAdded := make(map[string]struct{}) - - for _, caFile := range c.ClientCerts { - // don't add cert to pool more than once - if _, ok := clientCertsAdded[caFile]; ok { - continue - } - clientCertsAdded[caFile] = struct{}{} - - // Any client with a certificate from this CA will be allowed to connect - caCrt, err := ioutil.ReadFile(caFile) - if err != nil { - return err - } - - if !pool.AppendCertsFromPEM(caCrt) { - return fmt.Errorf("error loading client certificate '%s': no certificates were successfully parsed", caFile) - } - } - - config.ClientCAs = pool - } - - // default cipher suites - if len(config.CipherSuites) == 0 { - config.CipherSuites = getPreferredDefaultCiphers() - } - - // for security, ensure TLS_FALLBACK_SCSV is always included first - if len(config.CipherSuites) == 0 || config.CipherSuites[0] != tls.TLS_FALLBACK_SCSV { - config.CipherSuites = append([]uint16{tls.TLS_FALLBACK_SCSV}, config.CipherSuites...) - } - - // store the resulting new tls.Config - c.tlsConfig = config - - return nil -} - -// MakeTLSConfig makes a tls.Config from configs. The returned -// tls.Config is programmed to load the matching caddytls.Config -// based on the hostname in SNI, but that's all. -func MakeTLSConfig(configs []*Config) (*tls.Config, error) { - if len(configs) == 0 { - return nil, nil - } - - configMap := make(configGroup) - - for i, cfg := range configs { - if cfg == nil { - // avoid nil pointer dereference below this loop - configs[i] = new(Config) - continue - } - - // can't serve TLS and non-TLS on same port - if i > 0 && cfg.Enabled != configs[i-1].Enabled { - thisConfProto, lastConfProto := "not TLS", "not TLS" - if cfg.Enabled { - thisConfProto = "TLS" - } - if configs[i-1].Enabled { - lastConfProto = "TLS" - } - return nil, fmt.Errorf("cannot multiplex %s (%s) and %s (%s) on same listener", - configs[i-1].Hostname, lastConfProto, cfg.Hostname, thisConfProto) - } - - // convert each caddytls.Config into a tls.Config - if err := cfg.buildStandardTLSConfig(); err != nil { - return nil, err - } - - // Key this config by its hostname (overwriting - // configs with the same hostname pattern); during - // TLS handshakes, configs are loaded based on - // the hostname pattern, according to client's SNI. - configMap[cfg.Hostname] = cfg - } - - // Is TLS disabled? By now, we know that all - // configs agree whether it is or not, so we - // can just look at the first one. If so, - // we're done here. - if len(configs) == 0 || !configs[0].Enabled { - return nil, nil - } - - return &tls.Config{ - GetConfigForClient: configMap.GetConfigForClient, - }, nil -} - -// ConfigGetter gets a Config keyed by key. -type ConfigGetter func(c *caddy.Controller) *Config - -var configGetters = make(map[string]ConfigGetter) - -// RegisterConfigGetter registers fn as the way to get a -// Config for server type serverType. -func RegisterConfigGetter(serverType string, fn ConfigGetter) { - configGetters[serverType] = fn -} - -// SetDefaultTLSParams sets the default TLS cipher suites, protocol versions, -// and server preferences of a server.Config if they were not previously set -// (it does not overwrite; only fills in missing values). -func SetDefaultTLSParams(config *Config) { - // If no ciphers provided, use default list - if len(config.Ciphers) == 0 { - config.Ciphers = getPreferredDefaultCiphers() - } - - // Not a cipher suite, but still important for mitigating protocol downgrade attacks - // (prepend since having it at end breaks http2 due to non-h2-approved suites before it) - config.Ciphers = append([]uint16{tls.TLS_FALLBACK_SCSV}, config.Ciphers...) - - // If no curves provided, use default list - if len(config.CurvePreferences) == 0 { - config.CurvePreferences = defaultCurves - } - - // Set default protocol min and max versions - must balance compatibility and security - if config.ProtocolMinVersion == 0 { - config.ProtocolMinVersion = tls.VersionTLS11 - } - if config.ProtocolMaxVersion == 0 { - config.ProtocolMaxVersion = tls.VersionTLS12 - } - - // Prefer server cipher suites - config.PreferServerCipherSuites = true -} - -// Map of supported key types -var supportedKeyTypes = map[string]acme.KeyType{ - "P384": acme.EC384, - "P256": acme.EC256, - "RSA8192": acme.RSA8192, - "RSA4096": acme.RSA4096, - "RSA2048": acme.RSA2048, -} - -// Map of supported protocols. -// HTTP/2 only supports TLS 1.2 and higher. -var supportedProtocols = map[string]uint16{ - "tls1.0": tls.VersionTLS10, - "tls1.1": tls.VersionTLS11, - "tls1.2": tls.VersionTLS12, -} - -// Map of supported ciphers, used only for parsing config. -// -// Note that, at time of writing, HTTP/2 blacklists 276 cipher suites, -// including all but four of the suites below (the four GCM suites). -// See https://http2.github.io/http2-spec/#BadCipherSuites -// -// TLS_FALLBACK_SCSV is not in this list because we manually ensure -// it is always added (even though it is not technically a cipher suite). -// -// This map, like any map, is NOT ORDERED. Do not range over this map. -var supportedCiphersMap = map[string]uint16{ - "ECDHE-ECDSA-AES256-GCM-SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - "ECDHE-RSA-AES256-GCM-SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - "ECDHE-ECDSA-AES128-GCM-SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - "ECDHE-RSA-AES128-GCM-SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - "ECDHE-ECDSA-WITH-CHACHA20-POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, - "ECDHE-RSA-WITH-CHACHA20-POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, - "ECDHE-RSA-AES256-CBC-SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - "ECDHE-RSA-AES128-CBC-SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, - "ECDHE-ECDSA-AES256-CBC-SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, - "ECDHE-ECDSA-AES128-CBC-SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, - "RSA-AES256-CBC-SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA, - "RSA-AES128-CBC-SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA, - "ECDHE-RSA-3DES-EDE-CBC-SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, - "RSA-3DES-EDE-CBC-SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, -} - -// List of all the ciphers we want to use by default -var defaultCiphers = []uint16{ - tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, - tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, - tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, - tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, - tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, - tls.TLS_RSA_WITH_AES_256_CBC_SHA, - tls.TLS_RSA_WITH_AES_128_CBC_SHA, -} - -// List of ciphers we should prefer if native AESNI support is missing -var defaultCiphersNonAESNI = []uint16{ - tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, - tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, - tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, - tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, - tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, - tls.TLS_RSA_WITH_AES_256_CBC_SHA, - tls.TLS_RSA_WITH_AES_128_CBC_SHA, -} - -// getPreferredDefaultCiphers returns an appropriate cipher suite to use, depending on -// the hardware support available for AES-NI. -// -// See https://github.com/mholt/caddy/issues/1674 -func getPreferredDefaultCiphers() []uint16 { - if aesnicheck.HasAESNI() { - return defaultCiphers - } - - // Return a cipher suite that prefers ChaCha20 - return defaultCiphersNonAESNI -} - -// Map of supported curves -// https://golang.org/pkg/crypto/tls/#CurveID -var supportedCurvesMap = map[string]tls.CurveID{ - "X25519": tls.X25519, - "P256": tls.CurveP256, - "P384": tls.CurveP384, - "P521": tls.CurveP521, -} - -// List of all the curves we want to use by default -// -// This list should only include curves which are fast by design (e.g. X25519) -// and those for which an optimized assembly implementation exists (e.g. P256). -// The latter ones can be found here: https://github.com/golang/go/tree/master/src/crypto/elliptic -var defaultCurves = []tls.CurveID{ - tls.X25519, - tls.CurveP256, -} - -const ( - // HTTPChallengePort is the officially designated port for - // the HTTP challenge according to the ACME spec. - HTTPChallengePort = "80" - - // TLSSNIChallengePort is the officially designated port for - // the TLS-SNI challenge according to the ACME spec. - TLSSNIChallengePort = "443" - - // DefaultHTTPAlternatePort is the port on which the ACME - // client will open a listener and solve the HTTP challenge. - // If this alternate port is used instead of the default - // port, then whatever is listening on the default port must - // be capable of proxying or forwarding the request to this - // alternate port. - DefaultHTTPAlternatePort = "5033" -) diff --git a/caddytls/config_test.go b/caddytls/config_test.go deleted file mode 100644 index c4e11ba6f42..00000000000 --- a/caddytls/config_test.go +++ /dev/null @@ -1,215 +0,0 @@ -package caddytls - -import ( - "crypto/tls" - "errors" - "net/url" - "reflect" - "testing" - - "github.com/codahale/aesnicheck" -) - -func TestConvertTLSConfigProtocolVersions(t *testing.T) { - // same min and max protocol versions - config := &Config{ - Enabled: true, - ProtocolMinVersion: tls.VersionTLS12, - ProtocolMaxVersion: tls.VersionTLS12, - } - err := config.buildStandardTLSConfig() - if err != nil { - t.Fatalf("Did not expect an error, but got %v", err) - } - if got, want := config.tlsConfig.MinVersion, uint16(tls.VersionTLS12); got != want { - t.Errorf("Expected min version to be %x, got %x", want, got) - } - if got, want := config.tlsConfig.MaxVersion, uint16(tls.VersionTLS12); got != want { - t.Errorf("Expected max version to be %x, got %x", want, got) - } -} - -func TestConvertTLSConfigPreferServerCipherSuites(t *testing.T) { - // prefer server cipher suites - config := Config{Enabled: true, PreferServerCipherSuites: true} - err := config.buildStandardTLSConfig() - if err != nil { - t.Fatalf("Did not expect an error, but got %v", err) - } - if got, want := config.tlsConfig.PreferServerCipherSuites, true; got != want { - t.Errorf("Expected PreferServerCipherSuites==%v but got %v", want, got) - } -} - -func TestMakeTLSConfigTLSEnabledDisabledError(t *testing.T) { - // verify handling when Enabled is true and false - configs := []*Config{ - {Enabled: true}, - {Enabled: false}, - } - _, err := MakeTLSConfig(configs) - if err == nil { - t.Fatalf("Expected an error, but got %v", err) - } -} - -func TestConvertTLSConfigCipherSuites(t *testing.T) { - // ensure cipher suites are unioned and - // that TLS_FALLBACK_SCSV is prepended - configs := []*Config{ - {Enabled: true, Ciphers: []uint16{0xc02c, 0xc030}}, - {Enabled: true, Ciphers: []uint16{0xc012, 0xc030, 0xc00a}}, - {Enabled: true, Ciphers: nil}, - } - - defaultCiphersExpected := getPreferredDefaultCiphers() - expectedCiphers := [][]uint16{ - {tls.TLS_FALLBACK_SCSV, 0xc02c, 0xc030}, - {tls.TLS_FALLBACK_SCSV, 0xc012, 0xc030, 0xc00a}, - append([]uint16{tls.TLS_FALLBACK_SCSV}, defaultCiphersExpected...), - } - - for i, config := range configs { - err := config.buildStandardTLSConfig() - if err != nil { - t.Errorf("Test %d: Expected no error, got: %v", i, err) - } - if !reflect.DeepEqual(config.tlsConfig.CipherSuites, expectedCiphers[i]) { - t.Errorf("Test %d: Expected ciphers %v but got %v", - i, expectedCiphers[i], config.tlsConfig.CipherSuites) - } - - } -} - -func TestGetPreferredDefaultCiphers(t *testing.T) { - expectedCiphers := defaultCiphers - if !aesnicheck.HasAESNI() { - expectedCiphers = defaultCiphersNonAESNI - } - - // Ensure ordering is correct and ciphers are what we expected. - result := getPreferredDefaultCiphers() - for i, actual := range result { - if actual != expectedCiphers[i] { - t.Errorf("Expected cipher in position %d to be %0x, got %0x", i, expectedCiphers[i], actual) - } - } -} - -func TestStorageForNoURL(t *testing.T) { - c := &Config{} - if _, err := c.StorageFor(""); err == nil { - t.Fatal("Expected error on empty URL") - } -} - -func TestStorageForLowercasesAndPrefixesScheme(t *testing.T) { - resultStr := "" - RegisterStorageProvider("fake-TestStorageForLowercasesAndPrefixesScheme", func(caURL *url.URL) (Storage, error) { - resultStr = caURL.String() - return nil, nil - }) - c := &Config{ - StorageProvider: "fake-TestStorageForLowercasesAndPrefixesScheme", - } - if _, err := c.StorageFor("EXAMPLE.COM/BLAH"); err != nil { - t.Fatal(err) - } - if resultStr != "https://example.com/blah" { - t.Fatalf("Unexpected CA URL string: %v", resultStr) - } -} - -func TestStorageForBadURL(t *testing.T) { - c := &Config{} - if _, err := c.StorageFor("http://192.168.0.%31/"); err == nil { - t.Fatal("Expected error for bad URL") - } -} - -func TestStorageForDefault(t *testing.T) { - c := &Config{} - s, err := c.StorageFor("example.com") - if err != nil { - t.Fatal(err) - } - if _, ok := s.(*FileStorage); !ok { - t.Fatalf("Unexpected storage type: %#v", s) - } -} - -func TestStorageForCustom(t *testing.T) { - storage := fakeStorage("fake-TestStorageForCustom") - RegisterStorageProvider("fake-TestStorageForCustom", func(caURL *url.URL) (Storage, error) { return storage, nil }) - c := &Config{ - StorageProvider: "fake-TestStorageForCustom", - } - s, err := c.StorageFor("example.com") - if err != nil { - t.Fatal(err) - } - if s != storage { - t.Fatal("Unexpected storage") - } -} - -func TestStorageForCustomError(t *testing.T) { - RegisterStorageProvider("fake-TestStorageForCustomError", func(caURL *url.URL) (Storage, error) { return nil, errors.New("some error") }) - c := &Config{ - StorageProvider: "fake-TestStorageForCustomError", - } - if _, err := c.StorageFor("example.com"); err == nil { - t.Fatal("Expecting error") - } -} - -func TestStorageForCustomNil(t *testing.T) { - // Should fall through to the default - c := &Config{StorageProvider: ""} - s, err := c.StorageFor("example.com") - if err != nil { - t.Fatal(err) - } - if _, ok := s.(*FileStorage); !ok { - t.Fatalf("Unexpected storage type: %#v", s) - } -} - -type fakeStorage string - -func (s fakeStorage) SiteExists(domain string) (bool, error) { - panic("no impl") -} - -func (s fakeStorage) LoadSite(domain string) (*SiteData, error) { - panic("no impl") -} - -func (s fakeStorage) StoreSite(domain string, data *SiteData) error { - panic("no impl") -} - -func (s fakeStorage) DeleteSite(domain string) error { - panic("no impl") -} - -func (s fakeStorage) TryLock(domain string) (Waiter, error) { - panic("no impl") -} - -func (s fakeStorage) Unlock(domain string) error { - panic("no impl") -} - -func (s fakeStorage) LoadUser(email string) (*UserData, error) { - panic("no impl") -} - -func (s fakeStorage) StoreUser(email string, data *UserData) error { - panic("no impl") -} - -func (s fakeStorage) MostRecentUserEmail() string { - panic("no impl") -} diff --git a/caddytls/crypto.go b/caddytls/crypto.go deleted file mode 100644 index 14914eeec59..00000000000 --- a/caddytls/crypto.go +++ /dev/null @@ -1,323 +0,0 @@ -package caddytls - -import ( - "bytes" - "crypto" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/rsa" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "errors" - "fmt" - "hash/fnv" - "io" - "io/ioutil" - "log" - "math/big" - "net" - "os" - "path/filepath" - "time" - - "golang.org/x/crypto/ocsp" - - "github.com/mholt/caddy" - "github.com/xenolf/lego/acme" -) - -// loadPrivateKey loads a PEM-encoded ECC/RSA private key from an array of bytes. -func loadPrivateKey(keyBytes []byte) (crypto.PrivateKey, error) { - keyBlock, _ := pem.Decode(keyBytes) - - switch keyBlock.Type { - case "RSA PRIVATE KEY": - return x509.ParsePKCS1PrivateKey(keyBlock.Bytes) - case "EC PRIVATE KEY": - return x509.ParseECPrivateKey(keyBlock.Bytes) - } - - return nil, errors.New("unknown private key type") -} - -// savePrivateKey saves a PEM-encoded ECC/RSA private key to an array of bytes. -func savePrivateKey(key crypto.PrivateKey) ([]byte, error) { - var pemType string - var keyBytes []byte - switch key := key.(type) { - case *ecdsa.PrivateKey: - var err error - pemType = "EC" - keyBytes, err = x509.MarshalECPrivateKey(key) - if err != nil { - return nil, err - } - case *rsa.PrivateKey: - pemType = "RSA" - keyBytes = x509.MarshalPKCS1PrivateKey(key) - } - - pemKey := pem.Block{Type: pemType + " PRIVATE KEY", Bytes: keyBytes} - return pem.EncodeToMemory(&pemKey), nil -} - -// stapleOCSP staples OCSP information to cert for hostname name. -// If you have it handy, you should pass in the PEM-encoded certificate -// bundle; otherwise the DER-encoded cert will have to be PEM-encoded. -// If you don't have the PEM blocks already, just pass in nil. -// -// Errors here are not necessarily fatal, it could just be that the -// certificate doesn't have an issuer URL. -func stapleOCSP(cert *Certificate, pemBundle []byte) error { - if pemBundle == nil { - // The function in the acme package that gets OCSP requires a PEM-encoded cert - bundle := new(bytes.Buffer) - for _, derBytes := range cert.Certificate.Certificate { - pem.Encode(bundle, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) - } - pemBundle = bundle.Bytes() - } - - var ocspBytes []byte - var ocspResp *ocsp.Response - var ocspErr error - var gotNewOCSP bool - - // First try to load OCSP staple from storage and see if - // we can still use it. - // TODO: Use Storage interface instead of disk directly - var ocspFileNamePrefix string - if len(cert.Names) > 0 { - ocspFileNamePrefix = cert.Names[0] + "-" - } - ocspFileName := ocspFileNamePrefix + fastHash(pemBundle) - ocspCachePath := filepath.Join(ocspFolder, ocspFileName) - cachedOCSP, err := ioutil.ReadFile(ocspCachePath) - if err == nil { - resp, err := ocsp.ParseResponse(cachedOCSP, nil) - if err == nil { - if freshOCSP(resp) { - // staple is still fresh; use it - ocspBytes = cachedOCSP - ocspResp = resp - } - } else { - // invalid contents; delete the file - // (we do this independently of the maintenance routine because - // in this case we know for sure this should be a staple file - // because we loaded it by name, whereas the maintenance routine - // just iterates the list of files, even if somehow a non-staple - // file gets in the folder. in this case we are sure it is corrupt.) - err := os.Remove(ocspCachePath) - if err != nil { - log.Printf("[WARNING] Unable to delete invalid OCSP staple file: %v", err) - } - } - } - - // If we couldn't get a fresh staple by reading the cache, - // then we need to request it from the OCSP responder - if ocspResp == nil || len(ocspBytes) == 0 { - ocspBytes, ocspResp, ocspErr = acme.GetOCSPForCert(pemBundle) - if ocspErr != nil { - // An error here is not a problem because a certificate may simply - // not contain a link to an OCSP server. But we should log it anyway. - // There's nothing else we can do to get OCSP for this certificate, - // so we can return here with the error. - return fmt.Errorf("no OCSP stapling for %v: %v", cert.Names, ocspErr) - } - gotNewOCSP = true - } - - // By now, we should have a response. If good, staple it to - // the certificate. If the OCSP response was not loaded from - // storage, we persist it for next time. - if ocspResp.Status == ocsp.Good { - cert.Certificate.OCSPStaple = ocspBytes - cert.OCSP = ocspResp - if gotNewOCSP { - err := os.MkdirAll(filepath.Join(caddy.AssetsPath(), "ocsp"), 0700) - if err != nil { - return fmt.Errorf("unable to make OCSP staple path for %v: %v", cert.Names, err) - } - err = ioutil.WriteFile(ocspCachePath, ocspBytes, 0644) - if err != nil { - return fmt.Errorf("unable to write OCSP staple file for %v: %v", cert.Names, err) - } - } - } - - return nil -} - -// makeSelfSignedCert makes a self-signed certificate according -// to the parameters in config. It then caches the certificate -// in our cache. -func makeSelfSignedCert(config *Config) error { - // start by generating private key - var privKey interface{} - var err error - switch config.KeyType { - case "", acme.EC256: - privKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - case acme.EC384: - privKey, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) - case acme.RSA2048: - privKey, err = rsa.GenerateKey(rand.Reader, 2048) - case acme.RSA4096: - privKey, err = rsa.GenerateKey(rand.Reader, 4096) - case acme.RSA8192: - privKey, err = rsa.GenerateKey(rand.Reader, 8192) - default: - return fmt.Errorf("cannot generate private key; unknown key type %v", config.KeyType) - } - if err != nil { - return fmt.Errorf("failed to generate private key: %v", err) - } - - // create certificate structure with proper values - notBefore := time.Now() - notAfter := notBefore.Add(24 * time.Hour * 7) - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - return fmt.Errorf("failed to generate serial number: %v", err) - } - cert := &x509.Certificate{ - SerialNumber: serialNumber, - Subject: pkix.Name{Organization: []string{"Caddy Self-Signed"}}, - NotBefore: notBefore, - NotAfter: notAfter, - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - } - if ip := net.ParseIP(config.Hostname); ip != nil { - cert.IPAddresses = append(cert.IPAddresses, ip) - } else { - cert.DNSNames = append(cert.DNSNames, config.Hostname) - } - - publicKey := func(privKey interface{}) interface{} { - switch k := privKey.(type) { - case *rsa.PrivateKey: - return &k.PublicKey - case *ecdsa.PrivateKey: - return &k.PublicKey - default: - return errors.New("unknown key type") - } - } - derBytes, err := x509.CreateCertificate(rand.Reader, cert, cert, publicKey(privKey), privKey) - if err != nil { - return fmt.Errorf("could not create certificate: %v", err) - } - - cacheCertificate(Certificate{ - Certificate: tls.Certificate{ - Certificate: [][]byte{derBytes}, - PrivateKey: privKey, - Leaf: cert, - }, - Names: cert.DNSNames, - NotAfter: cert.NotAfter, - Config: config, - }) - - return nil -} - -// RotateSessionTicketKeys rotates the TLS session ticket keys -// on cfg every TicketRotateInterval. It spawns a new goroutine so -// this function does NOT block. It returns a channel you should -// close when you are ready to stop the key rotation, like when the -// server using cfg is no longer running. -func RotateSessionTicketKeys(cfg *tls.Config) chan struct{} { - ch := make(chan struct{}) - ticker := time.NewTicker(TicketRotateInterval) - go runTLSTicketKeyRotation(cfg, ticker, ch) - return ch -} - -// Functions that may be swapped out for testing -var ( - runTLSTicketKeyRotation = standaloneTLSTicketKeyRotation - setSessionTicketKeysTestHook = func(keys [][32]byte) [][32]byte { return keys } -) - -// standaloneTLSTicketKeyRotation governs over the array of TLS ticket keys used to de/crypt TLS tickets. -// It periodically sets a new ticket key as the first one, used to encrypt (and decrypt), -// pushing any old ticket keys to the back, where they are considered for decryption only. -// -// Lack of entropy for the very first ticket key results in the feature being disabled (as does Go), -// later lack of entropy temporarily disables ticket key rotation. -// Old ticket keys are still phased out, though. -// -// Stops the ticker when returning. -func standaloneTLSTicketKeyRotation(c *tls.Config, ticker *time.Ticker, exitChan chan struct{}) { - defer ticker.Stop() - - // The entire page should be marked as sticky, but Go cannot do that - // without resorting to syscall#Mlock. And, we don't have madvise (for NODUMP), too. ☹ - keys := make([][32]byte, 1, NumTickets) - - rng := c.Rand - if rng == nil { - rng = rand.Reader - } - if _, err := io.ReadFull(rng, keys[0][:]); err != nil { - c.SessionTicketsDisabled = true // bail if we don't have the entropy for the first one - return - } - c.SetSessionTicketKeys(setSessionTicketKeysTestHook(keys)) - - for { - select { - case _, isOpen := <-exitChan: - if !isOpen { - return - } - case <-ticker.C: - rng = c.Rand // could've changed since the start - if rng == nil { - rng = rand.Reader - } - var newTicketKey [32]byte - _, err := io.ReadFull(rng, newTicketKey[:]) - - if len(keys) < NumTickets { - keys = append(keys, keys[0]) // manipulates the internal length - } - for idx := len(keys) - 1; idx >= 1; idx-- { - keys[idx] = keys[idx-1] // yes, this makes copies - } - - if err == nil { - keys[0] = newTicketKey - } - // pushes the last key out, doesn't matter that we don't have a new one - c.SetSessionTicketKeys(setSessionTicketKeysTestHook(keys)) - } - } -} - -// fastHash hashes input using a hashing algorithm that -// is fast, and returns the hash as a hex-encoded string. -// Do not use this for cryptographic purposes. -func fastHash(input []byte) string { - h := fnv.New32a() - h.Write([]byte(input)) - return fmt.Sprintf("%x", h.Sum32()) -} - -const ( - // NumTickets is how many tickets to hold and consider - // to decrypt TLS sessions. - NumTickets = 4 - - // TicketRotateInterval is how often to generate - // new ticket for TLS PFS encryption - TicketRotateInterval = 10 * time.Hour -) diff --git a/caddytls/crypto_test.go b/caddytls/crypto_test.go deleted file mode 100644 index 0a9ee0db797..00000000000 --- a/caddytls/crypto_test.go +++ /dev/null @@ -1,135 +0,0 @@ -package caddytls - -import ( - "bytes" - "crypto" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/rsa" - "crypto/tls" - "crypto/x509" - "testing" - "time" -) - -func TestSaveAndLoadRSAPrivateKey(t *testing.T) { - privateKey, err := rsa.GenerateKey(rand.Reader, 128) // make tests faster; small key size OK for testing - if err != nil { - t.Fatal(err) - } - - // test save - savedBytes, err := savePrivateKey(privateKey) - if err != nil { - t.Fatal("error saving private key:", err) - } - - // test load - loadedKey, err := loadPrivateKey(savedBytes) - if err != nil { - t.Error("error loading private key:", err) - } - - // verify loaded key is correct - if !PrivateKeysSame(privateKey, loadedKey) { - t.Error("Expected key bytes to be the same, but they weren't") - } -} - -func TestSaveAndLoadECCPrivateKey(t *testing.T) { - privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) - if err != nil { - t.Fatal(err) - } - - // test save - savedBytes, err := savePrivateKey(privateKey) - if err != nil { - t.Fatal("error saving private key:", err) - } - - // test load - loadedKey, err := loadPrivateKey(savedBytes) - if err != nil { - t.Error("error loading private key:", err) - } - - // verify loaded key is correct - if !PrivateKeysSame(privateKey, loadedKey) { - t.Error("Expected key bytes to be the same, but they weren't") - } -} - -// PrivateKeysSame compares the bytes of a and b and returns true if they are the same. -func PrivateKeysSame(a, b crypto.PrivateKey) bool { - return bytes.Equal(PrivateKeyBytes(a), PrivateKeyBytes(b)) -} - -// PrivateKeyBytes returns the bytes of DER-encoded key. -func PrivateKeyBytes(key crypto.PrivateKey) []byte { - var keyBytes []byte - switch key := key.(type) { - case *rsa.PrivateKey: - keyBytes = x509.MarshalPKCS1PrivateKey(key) - case *ecdsa.PrivateKey: - keyBytes, _ = x509.MarshalECPrivateKey(key) - } - return keyBytes -} - -func TestStandaloneTLSTicketKeyRotation(t *testing.T) { - type syncPkt struct { - ticketKey [32]byte - keysInUse int - } - - tlsGovChan := make(chan struct{}) - defer close(tlsGovChan) - callSync := make(chan *syncPkt, 1) - defer close(callSync) - - oldHook := setSessionTicketKeysTestHook - defer func() { - setSessionTicketKeysTestHook = oldHook - }() - setSessionTicketKeysTestHook = func(keys [][32]byte) [][32]byte { - callSync <- &syncPkt{keys[0], len(keys)} - return keys - } - - c := new(tls.Config) - timer := time.NewTicker(time.Millisecond * 1) - - go standaloneTLSTicketKeyRotation(c, timer, tlsGovChan) - - rounds := 0 - var lastTicketKey [32]byte - for { - select { - case pkt := <-callSync: - if lastTicketKey == pkt.ticketKey { - close(tlsGovChan) - t.Errorf("The same TLS ticket key has been used again (not rotated): %x.", lastTicketKey) - return - } - lastTicketKey = pkt.ticketKey - rounds++ - if rounds <= NumTickets && pkt.keysInUse != rounds { - close(tlsGovChan) - t.Errorf("Expected TLS ticket keys in use: %d; Got instead: %d.", rounds, pkt.keysInUse) - return - } - if c.SessionTicketsDisabled { - t.Error("Session tickets have been disabled unexpectedly.") - return - } - if rounds >= NumTickets+1 { - return - } - case <-time.After(time.Second * 1): - t.Errorf("Timeout after %d rounds.", rounds) - return - } - } -} diff --git a/caddytls/filestorage.go b/caddytls/filestorage.go deleted file mode 100644 index e3d0fb26304..00000000000 --- a/caddytls/filestorage.go +++ /dev/null @@ -1,295 +0,0 @@ -package caddytls - -import ( - "fmt" - "io/ioutil" - "log" - "net/url" - "os" - "path/filepath" - "strings" - "sync" - - "github.com/mholt/caddy" -) - -func init() { - RegisterStorageProvider("file", NewFileStorage) -} - -// storageBasePath is the root path in which all TLS/ACME assets are -// stored. Do not change this value during the lifetime of the program. -var storageBasePath = filepath.Join(caddy.AssetsPath(), "acme") - -// NewFileStorage is a StorageConstructor function that creates a new -// Storage instance backed by the local disk. The resulting Storage -// instance is guaranteed to be non-nil if there is no error. -func NewFileStorage(caURL *url.URL) (Storage, error) { - return &FileStorage{ - Path: filepath.Join(storageBasePath, caURL.Host), - nameLocks: make(map[string]*sync.WaitGroup), - }, nil -} - -// FileStorage facilitates forming file paths derived from a root -// directory. It is used to get file paths in a consistent, -// cross-platform way or persisting ACME assets on the file system. -type FileStorage struct { - Path string - nameLocks map[string]*sync.WaitGroup - nameLocksMu sync.Mutex -} - -// sites gets the directory that stores site certificate and keys. -func (s *FileStorage) sites() string { - return filepath.Join(s.Path, "sites") -} - -// site returns the path to the folder containing assets for domain. -func (s *FileStorage) site(domain string) string { - domain = strings.ToLower(domain) - return filepath.Join(s.sites(), domain) -} - -// siteCertFile returns the path to the certificate file for domain. -func (s *FileStorage) siteCertFile(domain string) string { - domain = strings.ToLower(domain) - return filepath.Join(s.site(domain), domain+".crt") -} - -// siteKeyFile returns the path to domain's private key file. -func (s *FileStorage) siteKeyFile(domain string) string { - domain = strings.ToLower(domain) - return filepath.Join(s.site(domain), domain+".key") -} - -// siteMetaFile returns the path to the domain's asset metadata file. -func (s *FileStorage) siteMetaFile(domain string) string { - domain = strings.ToLower(domain) - return filepath.Join(s.site(domain), domain+".json") -} - -// users gets the directory that stores account folders. -func (s *FileStorage) users() string { - return filepath.Join(s.Path, "users") -} - -// user gets the account folder for the user with email -func (s *FileStorage) user(email string) string { - if email == "" { - email = emptyEmail - } - email = strings.ToLower(email) - return filepath.Join(s.users(), email) -} - -// emailUsername returns the username portion of an email address (part before -// '@') or the original input if it can't find the "@" symbol. -func emailUsername(email string) string { - at := strings.Index(email, "@") - if at == -1 { - return email - } else if at == 0 { - return email[1:] - } - return email[:at] -} - -// userRegFile gets the path to the registration file for the user with the -// given email address. -func (s *FileStorage) userRegFile(email string) string { - if email == "" { - email = emptyEmail - } - email = strings.ToLower(email) - fileName := emailUsername(email) - if fileName == "" { - fileName = "registration" - } - return filepath.Join(s.user(email), fileName+".json") -} - -// userKeyFile gets the path to the private key file for the user with the -// given email address. -func (s *FileStorage) userKeyFile(email string) string { - if email == "" { - email = emptyEmail - } - email = strings.ToLower(email) - fileName := emailUsername(email) - if fileName == "" { - fileName = "private" - } - return filepath.Join(s.user(email), fileName+".key") -} - -// readFile abstracts a simple ioutil.ReadFile, making sure to return an -// ErrNotExist instance when the file is not found. -func (s *FileStorage) readFile(file string) ([]byte, error) { - b, err := ioutil.ReadFile(file) - if os.IsNotExist(err) { - return nil, ErrNotExist(err) - } - return b, err -} - -// SiteExists implements Storage.SiteExists by checking for the presence of -// cert and key files. -func (s *FileStorage) SiteExists(domain string) (bool, error) { - _, err := os.Stat(s.siteCertFile(domain)) - if os.IsNotExist(err) { - return false, nil - } else if err != nil { - return false, err - } - - _, err = os.Stat(s.siteKeyFile(domain)) - if err != nil { - return false, err - } - return true, nil -} - -// LoadSite implements Storage.LoadSite by loading it from disk. If it is not -// present, an instance of ErrNotExist is returned. -func (s *FileStorage) LoadSite(domain string) (*SiteData, error) { - var err error - siteData := new(SiteData) - siteData.Cert, err = s.readFile(s.siteCertFile(domain)) - if err != nil { - return nil, err - } - siteData.Key, err = s.readFile(s.siteKeyFile(domain)) - if err != nil { - return nil, err - } - siteData.Meta, err = s.readFile(s.siteMetaFile(domain)) - if err != nil { - return nil, err - } - return siteData, nil -} - -// StoreSite implements Storage.StoreSite by writing it to disk. The base -// directories needed for the file are automatically created as needed. -func (s *FileStorage) StoreSite(domain string, data *SiteData) error { - err := os.MkdirAll(s.site(domain), 0700) - if err != nil { - return fmt.Errorf("making site directory: %v", err) - } - err = ioutil.WriteFile(s.siteCertFile(domain), data.Cert, 0600) - if err != nil { - return fmt.Errorf("writing certificate file: %v", err) - } - err = ioutil.WriteFile(s.siteKeyFile(domain), data.Key, 0600) - if err != nil { - return fmt.Errorf("writing key file: %v", err) - } - err = ioutil.WriteFile(s.siteMetaFile(domain), data.Meta, 0600) - if err != nil { - return fmt.Errorf("writing cert meta file: %v", err) - } - log.Printf("[INFO][%v] Certificate written to disk: %v", domain, s.siteCertFile(domain)) - return nil -} - -// DeleteSite implements Storage.DeleteSite by deleting just the cert from -// disk. If it is not present, an instance of ErrNotExist is returned. -func (s *FileStorage) DeleteSite(domain string) error { - err := os.Remove(s.siteCertFile(domain)) - if err != nil { - if os.IsNotExist(err) { - return ErrNotExist(err) - } - return err - } - return nil -} - -// LoadUser implements Storage.LoadUser by loading it from disk. If it is not -// present, an instance of ErrNotExist is returned. -func (s *FileStorage) LoadUser(email string) (*UserData, error) { - var err error - userData := new(UserData) - userData.Reg, err = s.readFile(s.userRegFile(email)) - if err != nil { - return nil, err - } - userData.Key, err = s.readFile(s.userKeyFile(email)) - if err != nil { - return nil, err - } - return userData, nil -} - -// StoreUser implements Storage.StoreUser by writing it to disk. The base -// directories needed for the file are automatically created as needed. -func (s *FileStorage) StoreUser(email string, data *UserData) error { - err := os.MkdirAll(s.user(email), 0700) - if err != nil { - return fmt.Errorf("making user directory: %v", err) - } - err = ioutil.WriteFile(s.userRegFile(email), data.Reg, 0600) - if err != nil { - return fmt.Errorf("writing user registration file: %v", err) - } - err = ioutil.WriteFile(s.userKeyFile(email), data.Key, 0600) - if err != nil { - return fmt.Errorf("writing user key file: %v", err) - } - return nil -} - -// TryLock attempts to get a lock for name, otherwise it returns -// a Waiter value to wait until the other process is finished. -func (s *FileStorage) TryLock(name string) (Waiter, error) { - s.nameLocksMu.Lock() - defer s.nameLocksMu.Unlock() - wg, ok := s.nameLocks[name] - if ok { - // lock already obtained, let caller wait on it - return wg, nil - } - // caller gets lock - wg = new(sync.WaitGroup) - wg.Add(1) - s.nameLocks[name] = wg - return nil, nil -} - -// Unlock unlocks name. -func (s *FileStorage) Unlock(name string) error { - s.nameLocksMu.Lock() - defer s.nameLocksMu.Unlock() - wg, ok := s.nameLocks[name] - if !ok { - return fmt.Errorf("FileStorage: no lock to release for %s", name) - } - wg.Done() - delete(s.nameLocks, name) - return nil -} - -// MostRecentUserEmail implements Storage.MostRecentUserEmail by finding the -// most recently written sub directory in the users' directory. It is named -// after the email address. This corresponds to the most recent call to -// StoreUser. -func (s *FileStorage) MostRecentUserEmail() string { - userDirs, err := ioutil.ReadDir(s.users()) - if err != nil { - return "" - } - var mostRecent os.FileInfo - for _, dir := range userDirs { - if !dir.IsDir() { - continue - } - if mostRecent == nil || dir.ModTime().After(mostRecent.ModTime()) { - mostRecent = dir - } - } - if mostRecent != nil { - return mostRecent.Name() - } - return "" -} diff --git a/caddytls/filestorage_test.go b/caddytls/filestorage_test.go deleted file mode 100644 index b77fd97dd51..00000000000 --- a/caddytls/filestorage_test.go +++ /dev/null @@ -1,6 +0,0 @@ -package caddytls - -// *********************************** NOTE ******************************** -// Due to circular package dependencies with the storagetest sub package and -// the fact that we want to use that harness to test file storage, the tests -// for file storage are done in the storagetest package. diff --git a/caddytls/handshake.go b/caddytls/handshake.go deleted file mode 100644 index 52d0a0a7dc0..00000000000 --- a/caddytls/handshake.go +++ /dev/null @@ -1,325 +0,0 @@ -package caddytls - -import ( - "crypto/tls" - "errors" - "fmt" - "log" - "strings" - "sync" - "sync/atomic" - "time" -) - -// configGroup is a type that keys configs by their hostname -// (hostnames can have wildcard characters; use the getConfig -// method to get a config by matching its hostname). -type configGroup map[string]*Config - -// getConfig gets the config by the first key match for name. -// In other words, "sub.foo.bar" will get the config for "*.foo.bar" -// if that is the closest match. If no match is found, the first -// (random) config will be loaded, which will defer any TLS alerts -// to the certificate validation (this may or may not be ideal; -// let's talk about it if this becomes problematic). -// -// This function follows nearly the same logic to lookup -// a hostname as the getCertificate function uses. -func (cg configGroup) getConfig(name string) *Config { - name = strings.ToLower(name) - - // exact match? great, let's use it - if config, ok := cg[name]; ok { - return config - } - - // try replacing labels in the name with wildcards until we get a match - labels := strings.Split(name, ".") - for i := range labels { - labels[i] = "*" - candidate := strings.Join(labels, ".") - if config, ok := cg[candidate]; ok { - return config - } - } - - // as a fallback, try a config that serves all names - if config, ok := cg[""]; ok { - return config - } - - // as a last resort, use a random config - // (even if the config isn't for that hostname, - // it should help us serve clients without SNI - // or at least defer TLS alerts to the cert) - for _, config := range cg { - return config - } - - return nil -} - -// GetConfigForClient gets a TLS configuration satisfying clientHello. -// In getting the configuration, it abides the rules and settings -// defined in the Config that matches clientHello.ServerName. If no -// tls.Config is set on the matching Config, a nil value is returned. -// -// This method is safe for use as a tls.Config.GetConfigForClient callback. -func (cg configGroup) GetConfigForClient(clientHello *tls.ClientHelloInfo) (*tls.Config, error) { - config := cg.getConfig(clientHello.ServerName) - if config != nil { - return config.tlsConfig, nil - } - return nil, nil -} - -// GetCertificate gets a certificate to satisfy clientHello. In getting -// the certificate, it abides the rules and settings defined in the -// Config that matches clientHello.ServerName. It first checks the in- -// memory cache, then, if the config enables "OnDemand", it accesses -// disk, then accesses the network if it must obtain a new certificate -// via ACME. -// -// This method is safe for use as a tls.Config.GetCertificate callback. -func (cfg *Config) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { - cert, err := cfg.getCertDuringHandshake(strings.ToLower(clientHello.ServerName), true, true) - return &cert.Certificate, err -} - -// getCertDuringHandshake will get a certificate for name. It first tries -// the in-memory cache. If no certificate for name is in the cache, the -// config most closely corresponding to name will be loaded. If that config -// allows it (OnDemand==true) and if loadIfNecessary == true, it goes to disk -// to load it into the cache and serve it. If it's not on disk and if -// obtainIfNecessary == true, the certificate will be obtained from the CA, -// cached, and served. If obtainIfNecessary is true, then loadIfNecessary -// must also be set to true. An error will be returned if and only if no -// certificate is available. -// -// This function is safe for concurrent use. -func (cfg *Config) getCertDuringHandshake(name string, loadIfNecessary, obtainIfNecessary bool) (Certificate, error) { - // First check our in-memory cache to see if we've already loaded it - cert, matched, defaulted := getCertificate(name) - if matched { - return cert, nil - } - - // If OnDemand is enabled, then we might be able to load or - // obtain a needed certificate - if cfg.OnDemand && loadIfNecessary { - // Then check to see if we have one on disk - loadedCert, err := cfg.CacheManagedCertificate(name) - if err == nil { - loadedCert, err = cfg.handshakeMaintenance(name, loadedCert) - if err != nil { - log.Printf("[ERROR] Maintaining newly-loaded certificate for %s: %v", name, err) - } - return loadedCert, nil - } - if obtainIfNecessary { - // By this point, we need to ask the CA for a certificate - - name = strings.ToLower(name) - - // Make sure aren't over any applicable limits - err := cfg.checkLimitsForObtainingNewCerts(name) - if err != nil { - return Certificate{}, err - } - - // Name has to qualify for a certificate - if !HostQualifies(name) { - return cert, errors.New("hostname '" + name + "' does not qualify for certificate") - } - - // Obtain certificate from the CA - return cfg.obtainOnDemandCertificate(name) - } - } - - // Fall back to the default certificate if there is one - if defaulted { - return cert, nil - } - - return Certificate{}, fmt.Errorf("no certificate available for %s", name) -} - -// checkLimitsForObtainingNewCerts checks to see if name can be issued right -// now according to mitigating factors we keep track of and preferences the -// user has set. If a non-nil error is returned, do not issue a new certificate -// for name. -func (cfg *Config) checkLimitsForObtainingNewCerts(name string) error { - // User can set hard limit for number of certs for the process to issue - if cfg.OnDemandState.MaxObtain > 0 && - atomic.LoadInt32(&cfg.OnDemandState.ObtainedCount) >= cfg.OnDemandState.MaxObtain { - return fmt.Errorf("%s: maximum certificates issued (%d)", name, cfg.OnDemandState.MaxObtain) - } - - // Make sure name hasn't failed a challenge recently - failedIssuanceMu.RLock() - when, ok := failedIssuance[name] - failedIssuanceMu.RUnlock() - if ok { - return fmt.Errorf("%s: throttled; refusing to issue cert since last attempt on %s failed", name, when.String()) - } - - // Make sure, if we've issued a few certificates already, that we haven't - // issued any recently - lastIssueTimeMu.Lock() - since := time.Since(lastIssueTime) - lastIssueTimeMu.Unlock() - if atomic.LoadInt32(&cfg.OnDemandState.ObtainedCount) >= 10 && since < 10*time.Minute { - return fmt.Errorf("%s: throttled; last certificate was obtained %v ago", name, since) - } - - // Good to go 👍 - return nil -} - -// obtainOnDemandCertificate obtains a certificate for name for the given -// name. If another goroutine has already started obtaining a cert for -// name, it will wait and use what the other goroutine obtained. -// -// This function is safe for use by multiple concurrent goroutines. -func (cfg *Config) obtainOnDemandCertificate(name string) (Certificate, error) { - // We must protect this process from happening concurrently, so synchronize. - obtainCertWaitChansMu.Lock() - wait, ok := obtainCertWaitChans[name] - if ok { - // lucky us -- another goroutine is already obtaining the certificate. - // wait for it to finish obtaining the cert and then we'll use it. - obtainCertWaitChansMu.Unlock() - <-wait - return cfg.getCertDuringHandshake(name, true, false) - } - - // looks like it's up to us to do all the work and obtain the cert. - // make a chan others can wait on if needed - wait = make(chan struct{}) - obtainCertWaitChans[name] = wait - obtainCertWaitChansMu.Unlock() - - // do the obtain - log.Printf("[INFO] Obtaining new certificate for %s", name) - err := cfg.ObtainCert(name, false) - - // immediately unblock anyone waiting for it; doing this in - // a defer would risk deadlock because of the recursive call - // to getCertDuringHandshake below when we return! - obtainCertWaitChansMu.Lock() - close(wait) - delete(obtainCertWaitChans, name) - obtainCertWaitChansMu.Unlock() - - if err != nil { - // Failed to solve challenge, so don't allow another on-demand - // issue for this name to be attempted for a little while. - failedIssuanceMu.Lock() - failedIssuance[name] = time.Now() - go func(name string) { - time.Sleep(5 * time.Minute) - failedIssuanceMu.Lock() - delete(failedIssuance, name) - failedIssuanceMu.Unlock() - }(name) - failedIssuanceMu.Unlock() - return Certificate{}, err - } - - // Success - update counters and stuff - atomic.AddInt32(&cfg.OnDemandState.ObtainedCount, 1) - lastIssueTimeMu.Lock() - lastIssueTime = time.Now() - lastIssueTimeMu.Unlock() - - // certificate is already on disk; now just start over to load it and serve it - return cfg.getCertDuringHandshake(name, true, false) -} - -// handshakeMaintenance performs a check on cert for expiration and OCSP -// validity. -// -// This function is safe for use by multiple concurrent goroutines. -func (cfg *Config) handshakeMaintenance(name string, cert Certificate) (Certificate, error) { - // Check cert expiration - timeLeft := cert.NotAfter.Sub(time.Now().UTC()) - if timeLeft < RenewDurationBefore { - log.Printf("[INFO] Certificate for %v expires in %v; attempting renewal", cert.Names, timeLeft) - return cfg.renewDynamicCertificate(name) - } - - // Check OCSP staple validity - if cert.OCSP != nil { - refreshTime := cert.OCSP.ThisUpdate.Add(cert.OCSP.NextUpdate.Sub(cert.OCSP.ThisUpdate) / 2) - if time.Now().After(refreshTime) { - err := stapleOCSP(&cert, nil) - if err != nil { - // An error with OCSP stapling is not the end of the world, and in fact, is - // quite common considering not all certs have issuer URLs that support it. - log.Printf("[ERROR] Getting OCSP for %s: %v", name, err) - } - certCacheMu.Lock() - certCache[name] = cert - certCacheMu.Unlock() - } - } - - return cert, nil -} - -// renewDynamicCertificate renews the certificate for name using cfg. It returns the -// certificate to use and an error, if any. currentCert may be returned even if an -// error occurs, since we perform renewals before they expire and it may still be -// usable. name should already be lower-cased before calling this function. -// -// This function is safe for use by multiple concurrent goroutines. -func (cfg *Config) renewDynamicCertificate(name string) (Certificate, error) { - obtainCertWaitChansMu.Lock() - wait, ok := obtainCertWaitChans[name] - if ok { - // lucky us -- another goroutine is already renewing the certificate. - // wait for it to finish, then we'll use the new one. - obtainCertWaitChansMu.Unlock() - <-wait - return cfg.getCertDuringHandshake(name, true, false) - } - - // looks like it's up to us to do all the work and renew the cert - wait = make(chan struct{}) - obtainCertWaitChans[name] = wait - obtainCertWaitChansMu.Unlock() - - // do the renew - log.Printf("[INFO] Renewing certificate for %s", name) - err := cfg.RenewCert(name, false) - - // immediately unblock anyone waiting for it; doing this in - // a defer would risk deadlock because of the recursive call - // to getCertDuringHandshake below when we return! - obtainCertWaitChansMu.Lock() - close(wait) - delete(obtainCertWaitChans, name) - obtainCertWaitChansMu.Unlock() - - if err != nil { - return Certificate{}, err - } - - return cfg.getCertDuringHandshake(name, true, false) -} - -// obtainCertWaitChans is used to coordinate obtaining certs for each hostname. -var obtainCertWaitChans = make(map[string]chan struct{}) -var obtainCertWaitChansMu sync.Mutex - -// failedIssuance is a set of names that we recently failed to get a -// certificate for from the ACME CA. They are removed after some time. -// When a name is in this map, do not issue a certificate for it on-demand. -var failedIssuance = make(map[string]time.Time) -var failedIssuanceMu sync.RWMutex - -// lastIssueTime records when we last obtained a certificate successfully. -// If this value is recent, do not make any on-demand certificate requests. -var lastIssueTime time.Time -var lastIssueTimeMu sync.Mutex diff --git a/caddytls/handshake_test.go b/caddytls/handshake_test.go deleted file mode 100644 index eca8dfcb3cf..00000000000 --- a/caddytls/handshake_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package caddytls - -import ( - "crypto/tls" - "crypto/x509" - "testing" -) - -func TestGetCertificate(t *testing.T) { - defer func() { certCache = make(map[string]Certificate) }() - - cfg := new(Config) - - hello := &tls.ClientHelloInfo{ServerName: "example.com"} - helloSub := &tls.ClientHelloInfo{ServerName: "sub.example.com"} - helloNoSNI := &tls.ClientHelloInfo{} - helloNoMatch := &tls.ClientHelloInfo{ServerName: "nomatch"} - - // When cache is empty - if cert, err := cfg.GetCertificate(hello); err == nil { - t.Errorf("GetCertificate should return error when cache is empty, got: %v", cert) - } - if cert, err := cfg.GetCertificate(helloNoSNI); err == nil { - t.Errorf("GetCertificate should return error when cache is empty even if server name is blank, got: %v", cert) - } - - // When cache has one certificate in it (also is default) - defaultCert := Certificate{Names: []string{"example.com", ""}, Certificate: tls.Certificate{Leaf: &x509.Certificate{DNSNames: []string{"example.com"}}}} - certCache[""] = defaultCert - certCache["example.com"] = defaultCert - if cert, err := cfg.GetCertificate(hello); err != nil { - t.Errorf("Got an error but shouldn't have, when cert exists in cache: %v", err) - } else if cert.Leaf.DNSNames[0] != "example.com" { - t.Errorf("Got wrong certificate with exact match; expected 'example.com', got: %v", cert) - } - if cert, err := cfg.GetCertificate(helloNoSNI); err != nil { - t.Errorf("Got an error with no SNI but shouldn't have, when cert exists in cache: %v", err) - } else if cert.Leaf.DNSNames[0] != "example.com" { - t.Errorf("Got wrong certificate for no SNI; expected 'example.com' as default, got: %v", cert) - } - - // When retrieving wildcard certificate - certCache["*.example.com"] = Certificate{Names: []string{"*.example.com"}, Certificate: tls.Certificate{Leaf: &x509.Certificate{DNSNames: []string{"*.example.com"}}}} - if cert, err := cfg.GetCertificate(helloSub); err != nil { - t.Errorf("Didn't get wildcard cert, got: cert=%v, err=%v ", cert, err) - } else if cert.Leaf.DNSNames[0] != "*.example.com" { - t.Errorf("Got wrong certificate, expected wildcard: %v", cert) - } - - // When no certificate matches, the default is returned - if cert, err := cfg.GetCertificate(helloNoMatch); err != nil { - t.Errorf("Expected default certificate with no error when no matches, got err: %v", err) - } else if cert.Leaf.DNSNames[0] != "example.com" { - t.Errorf("Expected default cert with no matches, got: %v", cert) - } -} diff --git a/caddytls/httphandler.go b/caddytls/httphandler.go deleted file mode 100644 index db3c93a01f3..00000000000 --- a/caddytls/httphandler.go +++ /dev/null @@ -1,53 +0,0 @@ -package caddytls - -import ( - "crypto/tls" - "fmt" - "log" - "net/http" - "net/http/httputil" - "net/url" - "strings" -) - -const challengeBasePath = "/.well-known/acme-challenge" - -// HTTPChallengeHandler proxies challenge requests to ACME client if the -// request path starts with challengeBasePath. It returns true if it -// handled the request and no more needs to be done; it returns false -// if this call was a no-op and the request still needs handling. -func HTTPChallengeHandler(w http.ResponseWriter, r *http.Request, listenHost, altPort string) bool { - if !strings.HasPrefix(r.URL.Path, challengeBasePath) { - return false - } - if DisableHTTPChallenge { - return false - } - if !namesObtaining.Has(r.Host) { - return false - } - - scheme := "http" - if r.TLS != nil { - scheme = "https" - } - - if listenHost == "" { - listenHost = "localhost" - } - - upstream, err := url.Parse(fmt.Sprintf("%s://%s:%s", scheme, listenHost, altPort)) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - log.Printf("[ERROR] ACME proxy handler: %v", err) - return true - } - - proxy := httputil.NewSingleHostReverseProxy(upstream) - proxy.Transport = &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - proxy.ServeHTTP(w, r) - - return true -} diff --git a/caddytls/httphandler_test.go b/caddytls/httphandler_test.go deleted file mode 100644 index 48f4a971b25..00000000000 --- a/caddytls/httphandler_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package caddytls - -import ( - "net" - "net/http" - "net/http/httptest" - "testing" -) - -func TestHTTPChallengeHandlerNoOp(t *testing.T) { - namesObtaining.Add([]string{"localhost"}) - - // try base paths and host names that aren't - // handled by this handler - for _, url := range []string{ - "http://localhost/", - "http://localhost/foo.html", - "http://localhost/.git", - "http://localhost/.well-known/", - "http://localhost/.well-known/acme-challenging", - "http://other/.well-known/acme-challenge/foo", - } { - req, err := http.NewRequest("GET", url, nil) - if err != nil { - t.Fatalf("Could not craft request, got error: %v", err) - } - rw := httptest.NewRecorder() - if HTTPChallengeHandler(rw, req, "", DefaultHTTPAlternatePort) { - t.Errorf("Got true with this URL, but shouldn't have: %s", url) - } - } -} - -func TestHTTPChallengeHandlerSuccess(t *testing.T) { - expectedPath := challengeBasePath + "/asdf" - - // Set up fake acme handler backend to make sure proxying succeeds - var proxySuccess bool - ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - proxySuccess = true - if r.URL.Path != expectedPath { - t.Errorf("Expected path '%s' but got '%s' instead", expectedPath, r.URL.Path) - } - })) - - // Custom listener that uses the port we expect - ln, err := net.Listen("tcp", "127.0.0.1:"+DefaultHTTPAlternatePort) - if err != nil { - t.Fatalf("Unable to start test server listener: %v", err) - } - ts.Listener = ln - - // Tell this package that we are handling a challenge for 127.0.0.1 - namesObtaining.Add([]string{"127.0.0.1"}) - - // Start our engines and run the test - ts.Start() - defer ts.Close() - req, err := http.NewRequest("GET", "http://127.0.0.1:"+DefaultHTTPAlternatePort+expectedPath, nil) - if err != nil { - t.Fatalf("Could not craft request, got error: %v", err) - } - rw := httptest.NewRecorder() - - HTTPChallengeHandler(rw, req, "", DefaultHTTPAlternatePort) - - if !proxySuccess { - t.Fatal("Expected request to be proxied, but it wasn't") - } -} diff --git a/caddytls/maintain.go b/caddytls/maintain.go deleted file mode 100644 index fe9b0b87288..00000000000 --- a/caddytls/maintain.go +++ /dev/null @@ -1,311 +0,0 @@ -package caddytls - -import ( - "io/ioutil" - "log" - "os" - "path/filepath" - "time" - - "github.com/mholt/caddy" - - "golang.org/x/crypto/ocsp" -) - -func init() { - // maintain assets while this package is imported, which is - // always. we don't ever stop it, since we need it running. - go maintainAssets(make(chan struct{})) -} - -const ( - // RenewInterval is how often to check certificates for renewal. - RenewInterval = 12 * time.Hour - - // RenewDurationBefore is how long before expiration to renew certificates. - RenewDurationBefore = (24 * time.Hour) * 30 - - // RenewDurationBeforeAtStartup is how long before expiration to require - // a renewed certificate when the process is first starting up (see #1680). - // A wider window between RenewDurationBefore and this value will allow - // Caddy to start under duress but hopefully this duration will give it - // enough time for the blockage to be relieved. - RenewDurationBeforeAtStartup = (24 * time.Hour) * 7 - - // OCSPInterval is how often to check if OCSP stapling needs updating. - OCSPInterval = 1 * time.Hour -) - -// maintainAssets is a permanently-blocking function -// that loops indefinitely and, on a regular schedule, checks -// certificates for expiration and initiates a renewal of certs -// that are expiring soon. It also updates OCSP stapling and -// performs other maintenance of assets. It should only be -// called once per process. -// -// You must pass in the channel which you'll close when -// maintenance should stop, to allow this goroutine to clean up -// after itself and unblock. (Not that you HAVE to stop it...) -func maintainAssets(stopChan chan struct{}) { - renewalTicker := time.NewTicker(RenewInterval) - ocspTicker := time.NewTicker(OCSPInterval) - - for { - select { - case <-renewalTicker.C: - log.Println("[INFO] Scanning for expiring certificates") - RenewManagedCertificates(false) - log.Println("[INFO] Done checking certificates") - case <-ocspTicker.C: - log.Println("[INFO] Scanning for stale OCSP staples") - UpdateOCSPStaples() - DeleteOldStapleFiles() - log.Println("[INFO] Done checking OCSP staples") - case <-stopChan: - renewalTicker.Stop() - ocspTicker.Stop() - log.Println("[INFO] Stopped background maintenance routine") - return - } - } -} - -// RenewManagedCertificates renews managed certificates. -func RenewManagedCertificates(allowPrompts bool) (err error) { - var renewQueue, deleteQueue []Certificate - visitedNames := make(map[string]struct{}) - - certCacheMu.RLock() - for name, cert := range certCache { - if !cert.Config.Managed || cert.Config.SelfSigned { - continue - } - - // the list of names on this cert should never be empty... - if cert.Names == nil || len(cert.Names) == 0 { - log.Printf("[WARNING] Certificate keyed by '%s' has no names: %v - removing from cache", name, cert.Names) - deleteQueue = append(deleteQueue, cert) - continue - } - - // skip names whose certificate we've already renewed - if _, ok := visitedNames[name]; ok { - continue - } - for _, name := range cert.Names { - visitedNames[name] = struct{}{} - } - - // if its time is up or ending soon, we need to try to renew it - timeLeft := cert.NotAfter.Sub(time.Now().UTC()) - if timeLeft < RenewDurationBefore { - log.Printf("[INFO] Certificate for %v expires in %v; attempting renewal", cert.Names, timeLeft) - - if cert.Config == nil { - log.Printf("[ERROR] %s: No associated TLS config; unable to renew", name) - continue - } - - // queue for renewal when we aren't in a read lock anymore - // (the TLS-SNI challenge will need a write lock in order to - // present the certificate, so we renew outside of read lock) - renewQueue = append(renewQueue, cert) - } - } - certCacheMu.RUnlock() - - // Perform renewals that are queued - for _, cert := range renewQueue { - // Get the name which we should use to renew this certificate; - // we only support managing certificates with one name per cert, - // so this should be easy. We can't rely on cert.Config.Hostname - // because it may be a wildcard value from the Caddyfile (e.g. - // *.something.com) which, as of Jan. 2017, is not supported by ACME. - var renewName string - for _, name := range cert.Names { - if name != "" { - renewName = name - break - } - } - - // perform renewal - err := cert.Config.RenewCert(renewName, allowPrompts) - if err != nil { - if allowPrompts { - // Certificate renewal failed and the operator is present. See a discussion - // about this in issue 642. For a while, we only stopped if the certificate - // was expired, but in reality, there is no difference between reporting - // it now versus later, except that there's somebody present to deal with - // it right now. - timeLeft := cert.NotAfter.Sub(time.Now().UTC()) - if timeLeft < RenewDurationBeforeAtStartup { - // See issue 1680. Only fail at startup if the certificate is dangerously - // close to expiration. - return err - } - } - log.Printf("[ERROR] %v", err) - if cert.Config.OnDemand { - deleteQueue = append(deleteQueue, cert) - } - } else { - // successful renewal, so update in-memory cache by loading - // renewed certificate so it will be used with handshakes - if cert.Names[len(cert.Names)-1] == "" { - // Special case: This is the default certificate. We must - // flush it out of the cache so that we no longer point to - // the old, un-renewed certificate. Otherwise it will be - // renewed on every scan, which is too often. The next cert - // to be cached (probably this one) will become the default. - certCacheMu.Lock() - delete(certCache, "") - certCacheMu.Unlock() - } - _, err := cert.Config.CacheManagedCertificate(cert.Names[0]) - if err != nil { - if allowPrompts { - return err // operator is present, so report error immediately - } - log.Printf("[ERROR] %v", err) - } - } - } - - // Apply queued deletion changes to the cache - for _, cert := range deleteQueue { - certCacheMu.Lock() - for _, name := range cert.Names { - delete(certCache, name) - } - certCacheMu.Unlock() - } - - return nil -} - -// UpdateOCSPStaples updates the OCSP stapling in all -// eligible, cached certificates. -// -// OCSP maintenance strives to abide the relevant points on -// Ryan Sleevi's recommendations for good OCSP support: -// https://gist.github.com/sleevi/5efe9ef98961ecfb4da8 -func UpdateOCSPStaples() { - // Create a temporary place to store updates - // until we release the potentially long-lived - // read lock and use a short-lived write lock. - type ocspUpdate struct { - rawBytes []byte - parsed *ocsp.Response - } - updated := make(map[string]ocspUpdate) - - // A single SAN certificate maps to multiple names, so we use this - // set to make sure we don't waste cycles checking OCSP for the same - // certificate multiple times. - visited := make(map[string]struct{}) - - certCacheMu.RLock() - for name, cert := range certCache { - // skip this certificate if we've already visited it, - // and if not, mark all the names as visited - if _, ok := visited[name]; ok { - continue - } - for _, n := range cert.Names { - visited[n] = struct{}{} - } - - // no point in updating OCSP for expired certificates - if time.Now().After(cert.NotAfter) { - continue - } - - var lastNextUpdate time.Time - if cert.OCSP != nil { - lastNextUpdate = cert.OCSP.NextUpdate - if freshOCSP(cert.OCSP) { - // no need to update staple if ours is still fresh - continue - } - } - - err := stapleOCSP(&cert, nil) - if err != nil { - if cert.OCSP != nil { - // if there was no staple before, that's fine; otherwise we should log the error - log.Printf("[ERROR] Checking OCSP: %v", err) - } - continue - } - - // By this point, we've obtained the latest OCSP response. - // If there was no staple before, or if the response is updated, make - // sure we apply the update to all names on the certificate. - if cert.OCSP != nil && (lastNextUpdate.IsZero() || lastNextUpdate != cert.OCSP.NextUpdate) { - log.Printf("[INFO] Advancing OCSP staple for %v from %s to %s", - cert.Names, lastNextUpdate, cert.OCSP.NextUpdate) - for _, n := range cert.Names { - updated[n] = ocspUpdate{rawBytes: cert.Certificate.OCSPStaple, parsed: cert.OCSP} - } - } - } - certCacheMu.RUnlock() - - // This write lock should be brief since we have all the info we need now. - certCacheMu.Lock() - for name, update := range updated { - cert := certCache[name] - cert.OCSP = update.parsed - cert.Certificate.OCSPStaple = update.rawBytes - certCache[name] = cert - } - certCacheMu.Unlock() -} - -// DeleteOldStapleFiles deletes cached OCSP staples that have expired. -// TODO: Should we do this for certificates too? -func DeleteOldStapleFiles() { - files, err := ioutil.ReadDir(ocspFolder) - if err != nil { - // maybe just hasn't been created yet; no big deal - return - } - for _, file := range files { - if file.IsDir() { - // weird, what's a folder doing inside the OCSP cache? - continue - } - stapleFile := filepath.Join(ocspFolder, file.Name()) - ocspBytes, err := ioutil.ReadFile(stapleFile) - if err != nil { - continue - } - resp, err := ocsp.ParseResponse(ocspBytes, nil) - if err != nil { - // contents are invalid; delete it - err = os.Remove(stapleFile) - if err != nil { - log.Printf("[ERROR] Purging corrupt staple file %s: %v", stapleFile, err) - } - } - if time.Now().After(resp.NextUpdate) { - // response has expired; delete it - err = os.Remove(stapleFile) - if err != nil { - log.Printf("[ERROR] Purging expired staple file %s: %v", stapleFile, err) - } - } - } -} - -// freshOCSP returns true if resp is still fresh, -// meaning that it is not expedient to get an -// updated response from the OCSP server. -func freshOCSP(resp *ocsp.Response) bool { - // start checking OCSP staple about halfway through validity period for good measure - refreshTime := resp.ThisUpdate.Add(resp.NextUpdate.Sub(resp.ThisUpdate) / 2) - return time.Now().Before(refreshTime) -} - -var ocspFolder = filepath.Join(caddy.AssetsPath(), "ocsp") diff --git a/caddytls/setup.go b/caddytls/setup.go deleted file mode 100644 index 4966277e91c..00000000000 --- a/caddytls/setup.go +++ /dev/null @@ -1,316 +0,0 @@ -package caddytls - -import ( - "bytes" - "crypto/tls" - "encoding/pem" - "fmt" - "io/ioutil" - "log" - "os" - "path/filepath" - "strconv" - "strings" - - "github.com/mholt/caddy" -) - -func init() { - caddy.RegisterPlugin("tls", caddy.Plugin{Action: setupTLS}) -} - -// setupTLS sets up the TLS configuration and installs certificates that -// are specified by the user in the config file. All the automatic HTTPS -// stuff comes later outside of this function. -func setupTLS(c *caddy.Controller) error { - configGetter, ok := configGetters[c.ServerType()] - if !ok { - return fmt.Errorf("no caddytls.ConfigGetter for %s server type; must call RegisterConfigGetter", c.ServerType()) - } - config := configGetter(c) - if config == nil { - return fmt.Errorf("no caddytls.Config to set up for %s", c.Key) - } - - config.Enabled = true - - for c.Next() { - var certificateFile, keyFile, loadDir, maxCerts string - - args := c.RemainingArgs() - switch len(args) { - case 1: - // even if the email is one of the special values below, - // it is still necessary for future analysis that we store - // that value in the ACMEEmail field. - config.ACMEEmail = args[0] - - // user can force-disable managed TLS this way - if args[0] == "off" { - config.Enabled = false - return nil - } - - // user might want a temporary, in-memory, self-signed cert - if args[0] == "self_signed" { - config.SelfSigned = true - } - case 2: - certificateFile = args[0] - keyFile = args[1] - config.Manual = true - } - - // Optional block with extra parameters - var hadBlock bool - for c.NextBlock() { - hadBlock = true - switch c.Val() { - case "ca": - arg := c.RemainingArgs() - if len(arg) != 1 { - return c.ArgErr() - } - config.CAUrl = arg[0] - case "key_type": - arg := c.RemainingArgs() - value, ok := supportedKeyTypes[strings.ToUpper(arg[0])] - if !ok { - return c.Errf("Wrong key type name or key type not supported: '%s'", c.Val()) - } - config.KeyType = value - case "protocols": - args := c.RemainingArgs() - if len(args) == 1 { - value, ok := supportedProtocols[strings.ToLower(args[0])] - if !ok { - return c.Errf("Wrong protocol name or protocol not supported: '%s'", args[0]) - } - - config.ProtocolMinVersion, config.ProtocolMaxVersion = value, value - } else { - value, ok := supportedProtocols[strings.ToLower(args[0])] - if !ok { - return c.Errf("Wrong protocol name or protocol not supported: '%s'", args[0]) - } - config.ProtocolMinVersion = value - value, ok = supportedProtocols[strings.ToLower(args[1])] - if !ok { - return c.Errf("Wrong protocol name or protocol not supported: '%s'", args[1]) - } - config.ProtocolMaxVersion = value - if config.ProtocolMinVersion > config.ProtocolMaxVersion { - return c.Errf("Minimum protocol version cannot be higher than maximum (reverse the order)") - } - } - case "ciphers": - for c.NextArg() { - value, ok := supportedCiphersMap[strings.ToUpper(c.Val())] - if !ok { - return c.Errf("Wrong cipher name or cipher not supported: '%s'", c.Val()) - } - config.Ciphers = append(config.Ciphers, value) - } - case "curves": - for c.NextArg() { - value, ok := supportedCurvesMap[strings.ToUpper(c.Val())] - if !ok { - return c.Errf("Wrong curve name or curve not supported: '%s'", c.Val()) - } - config.CurvePreferences = append(config.CurvePreferences, value) - } - case "clients": - clientCertList := c.RemainingArgs() - if len(clientCertList) == 0 { - return c.ArgErr() - } - - listStart, mustProvideCA := 1, true - switch clientCertList[0] { - case "request": - config.ClientAuth = tls.RequestClientCert - mustProvideCA = false - case "require": - config.ClientAuth = tls.RequireAnyClientCert - mustProvideCA = false - case "verify_if_given": - config.ClientAuth = tls.VerifyClientCertIfGiven - default: - config.ClientAuth = tls.RequireAndVerifyClientCert - listStart = 0 - } - if mustProvideCA && len(clientCertList) <= listStart { - return c.ArgErr() - } - - config.ClientCerts = clientCertList[listStart:] - case "load": - c.Args(&loadDir) - config.Manual = true - case "max_certs": - c.Args(&maxCerts) - config.OnDemand = true - case "dns": - args := c.RemainingArgs() - if len(args) != 1 { - return c.ArgErr() - } - dnsProvName := args[0] - if _, ok := dnsProviders[dnsProvName]; !ok { - return c.Errf("Unsupported DNS provider '%s'", args[0]) - } - config.DNSProvider = args[0] - case "storage": - args := c.RemainingArgs() - if len(args) != 1 { - return c.ArgErr() - } - storageProvName := args[0] - if _, ok := storageProviders[storageProvName]; !ok { - return c.Errf("Unsupported Storage provider '%s'", args[0]) - } - config.StorageProvider = args[0] - case "alpn": - args := c.RemainingArgs() - if len(args) == 0 { - return c.ArgErr() - } - for _, arg := range args { - config.ALPN = append(config.ALPN, arg) - } - case "must_staple": - config.MustStaple = true - default: - return c.Errf("Unknown keyword '%s'", c.Val()) - } - } - - // tls requires at least one argument if a block is not opened - if len(args) == 0 && !hadBlock { - return c.ArgErr() - } - - // set certificate limit if on-demand TLS is enabled - if maxCerts != "" { - maxCertsNum, err := strconv.Atoi(maxCerts) - if err != nil || maxCertsNum < 1 { - return c.Err("max_certs must be a positive integer") - } - config.OnDemandState.MaxObtain = int32(maxCertsNum) - } - - // don't try to load certificates unless we're supposed to - if !config.Enabled || !config.Manual { - continue - } - - // load a single certificate and key, if specified - if certificateFile != "" && keyFile != "" { - err := cacheUnmanagedCertificatePEMFile(certificateFile, keyFile) - if err != nil { - return c.Errf("Unable to load certificate and key files for '%s': %v", c.Key, err) - } - log.Printf("[INFO] Successfully loaded TLS assets from %s and %s", certificateFile, keyFile) - } - - // load a directory of certificates, if specified - if loadDir != "" { - err := loadCertsInDir(c, loadDir) - if err != nil { - return err - } - } - } - - SetDefaultTLSParams(config) - - // generate self-signed cert if needed - if config.SelfSigned { - err := makeSelfSignedCert(config) - if err != nil { - return fmt.Errorf("self-signed: %v", err) - } - } - - return nil -} - -// loadCertsInDir loads all the certificates/keys in dir, as long as -// the file ends with .pem. This method of loading certificates is -// modeled after haproxy, which expects the certificate and key to -// be bundled into the same file: -// https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#5.1-crt -// -// This function may write to the log as it walks the directory tree. -func loadCertsInDir(c *caddy.Controller, dir string) error { - return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if err != nil { - log.Printf("[WARNING] Unable to traverse into %s; skipping", path) - return nil - } - if info.IsDir() { - return nil - } - if strings.HasSuffix(strings.ToLower(info.Name()), ".pem") { - certBuilder, keyBuilder := new(bytes.Buffer), new(bytes.Buffer) - var foundKey bool // use only the first key in the file - - bundle, err := ioutil.ReadFile(path) - if err != nil { - return err - } - - for { - // Decode next block so we can see what type it is - var derBlock *pem.Block - derBlock, bundle = pem.Decode(bundle) - if derBlock == nil { - break - } - - if derBlock.Type == "CERTIFICATE" { - // Re-encode certificate as PEM, appending to certificate chain - pem.Encode(certBuilder, derBlock) - } else if derBlock.Type == "EC PARAMETERS" { - // EC keys generated from openssl can be composed of two blocks: - // parameters and key (parameter block should come first) - if !foundKey { - // Encode parameters - pem.Encode(keyBuilder, derBlock) - - // Key must immediately follow - derBlock, bundle = pem.Decode(bundle) - if derBlock == nil || derBlock.Type != "EC PRIVATE KEY" { - return c.Errf("%s: expected elliptic private key to immediately follow EC parameters", path) - } - pem.Encode(keyBuilder, derBlock) - foundKey = true - } - } else if derBlock.Type == "PRIVATE KEY" || strings.HasSuffix(derBlock.Type, " PRIVATE KEY") { - // RSA key - if !foundKey { - pem.Encode(keyBuilder, derBlock) - foundKey = true - } - } else { - return c.Errf("%s: unrecognized PEM block type: %s", path, derBlock.Type) - } - } - - certPEMBytes, keyPEMBytes := certBuilder.Bytes(), keyBuilder.Bytes() - if len(certPEMBytes) == 0 { - return c.Errf("%s: failed to parse PEM data", path) - } - if len(keyPEMBytes) == 0 { - return c.Errf("%s: no private key block found", path) - } - - err = cacheUnmanagedCertificatePEMBytes(certPEMBytes, keyPEMBytes) - if err != nil { - return c.Errf("%s: failed to load cert and key for '%s': %v", path, c.Key, err) - } - log.Printf("[INFO] Successfully loaded TLS assets from %s", path) - } - return nil - }) -} diff --git a/caddytls/setup_test.go b/caddytls/setup_test.go deleted file mode 100644 index 08555f7f330..00000000000 --- a/caddytls/setup_test.go +++ /dev/null @@ -1,413 +0,0 @@ -package caddytls - -import ( - "crypto/tls" - "io/ioutil" - "log" - "os" - "testing" - - "github.com/mholt/caddy" - "github.com/xenolf/lego/acme" -) - -func TestMain(m *testing.M) { - // Write test certificates to disk before tests, and clean up - // when we're done. - err := ioutil.WriteFile(certFile, testCert, 0644) - if err != nil { - log.Fatal(err) - } - err = ioutil.WriteFile(keyFile, testKey, 0644) - if err != nil { - os.Remove(certFile) - log.Fatal(err) - } - - result := m.Run() - - os.Remove(certFile) - os.Remove(keyFile) - os.Exit(result) -} - -func TestSetupParseBasic(t *testing.T) { - cfg := new(Config) - RegisterConfigGetter("", func(c *caddy.Controller) *Config { return cfg }) - c := caddy.NewTestController("", `tls `+certFile+` `+keyFile+``) - - err := setupTLS(c) - if err != nil { - t.Errorf("Expected no errors, got: %v", err) - } - - // Basic checks - if !cfg.Manual { - t.Error("Expected TLS Manual=true, but was false") - } - if !cfg.Enabled { - t.Error("Expected TLS Enabled=true, but was false") - } - - // Security defaults - if cfg.ProtocolMinVersion != tls.VersionTLS11 { - t.Errorf("Expected 'tls1.1 (0x0302)' as ProtocolMinVersion, got %#v", cfg.ProtocolMinVersion) - } - if cfg.ProtocolMaxVersion != tls.VersionTLS12 { - t.Errorf("Expected 'tls1.2 (0x0303)' as ProtocolMaxVersion, got %v", cfg.ProtocolMaxVersion) - } - - // Cipher checks - expectedCiphers := append([]uint16{tls.TLS_FALLBACK_SCSV}, getPreferredDefaultCiphers()...) - - // Ensure count is correct (plus one for TLS_FALLBACK_SCSV) - if len(cfg.Ciphers) != len(expectedCiphers) { - t.Errorf("Expected %v Ciphers (including TLS_FALLBACK_SCSV), got %v", - len(expectedCiphers), len(cfg.Ciphers)) - } - - // Ensure ordering is correct - for i, actual := range cfg.Ciphers { - if actual != expectedCiphers[i] { - t.Errorf("Expected cipher in position %d to be %0x, got %0x", i, expectedCiphers[i], actual) - } - } - - if !cfg.PreferServerCipherSuites { - t.Error("Expected PreferServerCipherSuites = true, but was false") - } - - if len(cfg.ALPN) != 0 { - t.Error("Expected ALPN empty by default") - } - - // Ensure curve count is correct - if len(cfg.CurvePreferences) != len(defaultCurves) { - t.Errorf("Expected %v Curves, got %v", len(defaultCurves), len(cfg.CurvePreferences)) - } - - // Ensure curve ordering is correct - for i, actual := range cfg.CurvePreferences { - if actual != defaultCurves[i] { - t.Errorf("Expected curve in position %d to be %0x, got %0x", i, defaultCurves[i], actual) - } - } -} - -func TestSetupParseIncompleteParams(t *testing.T) { - // Using tls without args is an error because it's unnecessary. - c := caddy.NewTestController("", `tls`) - err := setupTLS(c) - if err == nil { - t.Error("Expected an error, but didn't get one") - } -} - -func TestSetupParseWithOptionalParams(t *testing.T) { - params := `tls ` + certFile + ` ` + keyFile + ` { - protocols tls1.0 tls1.2 - ciphers RSA-AES256-CBC-SHA ECDHE-RSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 - must_staple - alpn http/1.1 - }` - cfg := new(Config) - RegisterConfigGetter("", func(c *caddy.Controller) *Config { return cfg }) - c := caddy.NewTestController("", params) - - err := setupTLS(c) - if err != nil { - t.Errorf("Expected no errors, got: %v", err) - } - - if cfg.ProtocolMinVersion != tls.VersionTLS10 { - t.Errorf("Expected 'tls1.0 (0x0301)' as ProtocolMinVersion, got %#v", cfg.ProtocolMinVersion) - } - - if cfg.ProtocolMaxVersion != tls.VersionTLS12 { - t.Errorf("Expected 'tls1.2 (0x0303)' as ProtocolMaxVersion, got %#v", cfg.ProtocolMaxVersion) - } - - if len(cfg.Ciphers)-1 != 3 { - t.Errorf("Expected 3 Ciphers (not including TLS_FALLBACK_SCSV), got %v", len(cfg.Ciphers)-1) - } - - if !cfg.MustStaple { - t.Error("Expected must staple to be true") - } - - if len(cfg.ALPN) != 1 || cfg.ALPN[0] != "http/1.1" { - t.Errorf("Expected ALPN to contain only 'http/1.1' but got: %v", cfg.ALPN) - } -} - -func TestSetupDefaultWithOptionalParams(t *testing.T) { - params := `tls { - ciphers RSA-3DES-EDE-CBC-SHA - }` - cfg := new(Config) - RegisterConfigGetter("", func(c *caddy.Controller) *Config { return cfg }) - c := caddy.NewTestController("", params) - - err := setupTLS(c) - if err != nil { - t.Errorf("Expected no errors, got: %v", err) - } - if len(cfg.Ciphers)-1 != 1 { - t.Errorf("Expected 1 ciphers (not including TLS_FALLBACK_SCSV), got %v", len(cfg.Ciphers)-1) - } -} - -func TestSetupParseWithWrongOptionalParams(t *testing.T) { - // Test protocols wrong params - params := `tls ` + certFile + ` ` + keyFile + ` { - protocols ssl tls - }` - cfg := new(Config) - RegisterConfigGetter("", func(c *caddy.Controller) *Config { return cfg }) - c := caddy.NewTestController("", params) - err := setupTLS(c) - if err == nil { - t.Errorf("Expected errors, but no error returned") - } - - // Test ciphers wrong params - params = `tls ` + certFile + ` ` + keyFile + ` { - ciphers not-valid-cipher - }` - cfg = new(Config) - RegisterConfigGetter("", func(c *caddy.Controller) *Config { return cfg }) - c = caddy.NewTestController("", params) - err = setupTLS(c) - if err == nil { - t.Error("Expected errors, but no error returned") - } - - // Test key_type wrong params - params = `tls { - key_type ab123 - }` - cfg = new(Config) - RegisterConfigGetter("", func(c *caddy.Controller) *Config { return cfg }) - c = caddy.NewTestController("", params) - err = setupTLS(c) - if err == nil { - t.Error("Expected errors, but no error returned") - } - - // Test curves wrong params - params = `tls { - curves ab123, cd456, ef789 - }` - cfg = new(Config) - RegisterConfigGetter("", func(c *caddy.Controller) *Config { return cfg }) - c = caddy.NewTestController("", params) - err = setupTLS(c) - if err == nil { - t.Error("Expected errors, but no error returned") - } -} - -func TestSetupParseWithClientAuth(t *testing.T) { - // Test missing client cert file - params := `tls ` + certFile + ` ` + keyFile + ` { - clients - }` - cfg := new(Config) - RegisterConfigGetter("", func(c *caddy.Controller) *Config { return cfg }) - c := caddy.NewTestController("", params) - err := setupTLS(c) - if err == nil { - t.Error("Expected an error, but no error returned") - } - - noCAs, twoCAs := []string{}, []string{"client_ca.crt", "client2_ca.crt"} - for caseNumber, caseData := range []struct { - params string - clientAuthType tls.ClientAuthType - expectedErr bool - expectedCAs []string - }{ - {"", tls.NoClientCert, false, noCAs}, - {`tls ` + certFile + ` ` + keyFile + ` { - clients client_ca.crt client2_ca.crt - }`, tls.RequireAndVerifyClientCert, false, twoCAs}, - // now come modifier - {`tls ` + certFile + ` ` + keyFile + ` { - clients request - }`, tls.RequestClientCert, false, noCAs}, - {`tls ` + certFile + ` ` + keyFile + ` { - clients require - }`, tls.RequireAnyClientCert, false, noCAs}, - {`tls ` + certFile + ` ` + keyFile + ` { - clients verify_if_given client_ca.crt client2_ca.crt - }`, tls.VerifyClientCertIfGiven, false, twoCAs}, - {`tls ` + certFile + ` ` + keyFile + ` { - clients verify_if_given - }`, tls.VerifyClientCertIfGiven, true, noCAs}, - } { - cfg := new(Config) - RegisterConfigGetter("", func(c *caddy.Controller) *Config { return cfg }) - c := caddy.NewTestController("", caseData.params) - err := setupTLS(c) - if caseData.expectedErr { - if err == nil { - t.Errorf("In case %d: Expected an error, got: %v", caseNumber, err) - } - continue - } - if err != nil { - t.Errorf("In case %d: Expected no errors, got: %v", caseNumber, err) - } - - if caseData.clientAuthType != cfg.ClientAuth { - t.Errorf("In case %d: Expected TLS client auth type %v, got: %v", - caseNumber, caseData.clientAuthType, cfg.ClientAuth) - } - - if count := len(cfg.ClientCerts); count < len(caseData.expectedCAs) { - t.Fatalf("In case %d: Expected %d client certs, had %d", caseNumber, len(caseData.expectedCAs), count) - } - - for idx, expected := range caseData.expectedCAs { - if actual := cfg.ClientCerts[idx]; actual != expected { - t.Errorf("In case %d: Expected %dth client cert file to be '%s', but was '%s'", - caseNumber, idx, expected, actual) - } - } - } -} - -func TestSetupParseWithCAUrl(t *testing.T) { - testURL := "https://acme-staging.api.letsencrypt.org/directory" - for caseNumber, caseData := range []struct { - params string - expectedErr bool - expectedCAUrl string - }{ - // Test working case - {`tls { - ca ` + testURL + ` - }`, false, testURL}, - // Test too few args - {`tls { - ca - }`, true, ""}, - // Test too many args - {`tls { - ca 1 2 - }`, true, ""}, - } { - cfg := new(Config) - RegisterConfigGetter("", func(c *caddy.Controller) *Config { return cfg }) - c := caddy.NewTestController("", caseData.params) - err := setupTLS(c) - if caseData.expectedErr { - if err == nil { - t.Errorf("In case %d: Expected an error, got: %v", caseNumber, err) - } - continue - } - if err != nil { - t.Errorf("In case %d: Expected no errors, got: %v", caseNumber, err) - } - - if cfg.CAUrl != caseData.expectedCAUrl { - t.Errorf("Expected '%v' as CAUrl, got %#v", caseData.expectedCAUrl, cfg.CAUrl) - } - } -} - -func TestSetupParseWithKeyType(t *testing.T) { - params := `tls { - key_type p384 - }` - cfg := new(Config) - RegisterConfigGetter("", func(c *caddy.Controller) *Config { return cfg }) - c := caddy.NewTestController("", params) - - err := setupTLS(c) - if err != nil { - t.Errorf("Expected no errors, got: %v", err) - } - - if cfg.KeyType != acme.EC384 { - t.Errorf("Expected 'P384' as KeyType, got %#v", cfg.KeyType) - } -} - -func TestSetupParseWithCurves(t *testing.T) { - params := `tls { - curves x25519 p256 p384 p521 - }` - cfg := new(Config) - RegisterConfigGetter("", func(c *caddy.Controller) *Config { return cfg }) - c := caddy.NewTestController("", params) - - err := setupTLS(c) - if err != nil { - t.Errorf("Expected no errors, got: %v", err) - } - - if len(cfg.CurvePreferences) != 4 { - t.Errorf("Expected 4 curves, got %v", len(cfg.CurvePreferences)) - } - - expectedCurves := []tls.CurveID{tls.X25519, tls.CurveP256, tls.CurveP384, tls.CurveP521} - - // Ensure ordering is correct - for i, actual := range cfg.CurvePreferences { - if actual != expectedCurves[i] { - t.Errorf("Expected curve in position %d to be %v, got %v", i, expectedCurves[i], actual) - } - } -} - -func TestSetupParseWithOneTLSProtocol(t *testing.T) { - params := `tls { - protocols tls1.2 - }` - cfg := new(Config) - RegisterConfigGetter("", func(c *caddy.Controller) *Config { return cfg }) - c := caddy.NewTestController("", params) - - err := setupTLS(c) - if err != nil { - t.Errorf("Expected no errors, got: %v", err) - } - - if cfg.ProtocolMinVersion != cfg.ProtocolMaxVersion { - t.Errorf("Expected ProtocolMinVersion to be the same as ProtocolMaxVersion") - } - - if cfg.ProtocolMinVersion != tls.VersionTLS12 && cfg.ProtocolMaxVersion != tls.VersionTLS12 { - t.Errorf("Expected 'tls1.2 (0x0303)' as ProtocolMinVersion/ProtocolMaxVersion, got %v/%v", cfg.ProtocolMinVersion, cfg.ProtocolMaxVersion) - } -} - -const ( - certFile = "test_cert.pem" - keyFile = "test_key.pem" -) - -var testCert = []byte(`-----BEGIN CERTIFICATE----- -MIIBkjCCATmgAwIBAgIJANfFCBcABL6LMAkGByqGSM49BAEwFDESMBAGA1UEAxMJ -bG9jYWxob3N0MB4XDTE2MDIxMDIyMjAyNFoXDTE4MDIwOTIyMjAyNFowFDESMBAG -A1UEAxMJbG9jYWxob3N0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEs22MtnG7 -9K1mvIyjEO9GLx7BFD0tBbGnwQ0VPsuCxC6IeVuXbQDLSiVQvFZ6lUszTlczNxVk -pEfqrM6xAupB7qN1MHMwHQYDVR0OBBYEFHxYDvAxUwL4XrjPev6qZ/BiLDs5MEQG -A1UdIwQ9MDuAFHxYDvAxUwL4XrjPev6qZ/BiLDs5oRikFjAUMRIwEAYDVQQDEwls -b2NhbGhvc3SCCQDXxQgXAAS+izAMBgNVHRMEBTADAQH/MAkGByqGSM49BAEDSAAw -RQIgRvBqbyJM2JCJqhA1FmcoZjeMocmhxQHTt1c+1N2wFUgCIQDtvrivbBPA688N -Qh3sMeAKNKPsx5NxYdoWuu9KWcKz9A== ------END CERTIFICATE----- -`) - -var testKey = []byte(`-----BEGIN EC PARAMETERS----- -BggqhkjOPQMBBw== ------END EC PARAMETERS----- ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIGLtRmwzYVcrH3J0BnzYbGPdWVF10i9p6mxkA4+b2fURoAoGCCqGSM49 -AwEHoUQDQgAEs22MtnG79K1mvIyjEO9GLx7BFD0tBbGnwQ0VPsuCxC6IeVuXbQDL -SiVQvFZ6lUszTlczNxVkpEfqrM6xAupB7g== ------END EC PRIVATE KEY----- -`) diff --git a/caddytls/storage.go b/caddytls/storage.go deleted file mode 100644 index 27df4b3a88c..00000000000 --- a/caddytls/storage.go +++ /dev/null @@ -1,113 +0,0 @@ -package caddytls - -import "net/url" - -// StorageConstructor is a function type that is used in the Config to -// instantiate a new Storage instance. This function can return a nil -// Storage even without an error. -type StorageConstructor func(caURL *url.URL) (Storage, error) - -// SiteData contains persisted items pertaining to an individual site. -type SiteData struct { - // Cert is the public cert byte array. - Cert []byte - // Key is the private key byte array. - Key []byte - // Meta is metadata about the site used by Caddy. - Meta []byte -} - -// UserData contains persisted items pertaining to a user. -type UserData struct { - // Reg is the user registration byte array. - Reg []byte - // Key is the user key byte array. - Key []byte -} - -// Storage is an interface abstracting all storage used by Caddy's TLS -// subsystem. Implementations of this interface store both site and -// user data. -type Storage interface { - // SiteExists returns true if this site exists in storage. - // Site data is considered present when StoreSite has been called - // successfully (without DeleteSite having been called, of course). - SiteExists(domain string) (bool, error) - - // TryLock is called before Caddy attempts to obtain or renew a - // certificate for a certain name and store it. From the perspective - // of this method and its companion Unlock, the actions of - // obtaining/renewing and then storing the certificate are atomic, - // and both should occur within a lock. This prevents multiple - // processes -- maybe distributed ones -- from stepping on each - // other's space in the same shared storage, and from spamming - // certificate providers with multiple, redundant requests. - // - // If a lock could be obtained, (nil, nil) is returned and you may - // continue normally. If not (meaning another process is already - // working on that name), a Waiter value will be returned upon - // which you can Wait() until it is finished, and then return - // when it unblocks. If waiting, do not unlock! - // - // To prevent deadlocks, all implementations (where this concern - // is relevant) should put a reasonable expiration on the lock in - // case Unlock is unable to be called due to some sort of storage - // system failure or crash. - TryLock(name string) (Waiter, error) - - // Unlock unlocks the mutex for name. Only callers of TryLock who - // successfully obtained the lock (no Waiter value was returned) - // should call this method, and it should be called only after - // the obtain/renew and store are finished, even if there was - // an error (or a timeout). - Unlock(name string) error - - // LoadSite obtains the site data from storage for the given domain and - // returns it. If data for the domain does not exist, an error value - // of type ErrNotExist is returned. For multi-server storage, care - // should be taken to make this load atomic to prevent race conditions - // that happen with multiple data loads. - LoadSite(domain string) (*SiteData, error) - - // StoreSite persists the given site data for the given domain in - // storage. For multi-server storage, care should be taken to make this - // call atomic to prevent half-written data on failure of an internal - // intermediate storage step. Implementers can trust that at runtime - // this function will only be invoked after LockRegister and before - // UnlockRegister of the same domain. - StoreSite(domain string, data *SiteData) error - - // DeleteSite deletes the site for the given domain from storage. - // Multi-server implementations should attempt to make this atomic. If - // the site does not exist, an error value of type ErrNotExist is returned. - DeleteSite(domain string) error - - // LoadUser obtains user data from storage for the given email and - // returns it. If data for the email does not exist, an error value - // of type ErrNotExist is returned. Multi-server implementations - // should take care to make this operation atomic for all loaded - // data items. - LoadUser(email string) (*UserData, error) - - // StoreUser persists the given user data for the given email in - // storage. Multi-server implementations should take care to make this - // operation atomic for all stored data items. - StoreUser(email string, data *UserData) error - - // MostRecentUserEmail provides the most recently used email parameter - // in StoreUser. The result is an empty string if there are no - // persisted users in storage. - MostRecentUserEmail() string -} - -// ErrNotExist is returned by Storage implementations when -// a resource is not found. It is similar to os.ErrNotExist -// except this is a type, not a variable. -type ErrNotExist interface { - error -} - -// Waiter is a type that can block until a storage lock is released. -type Waiter interface { - Wait() -} diff --git a/caddytls/storagetest/memorystorage.go b/caddytls/storagetest/memorystorage.go deleted file mode 100644 index d2831590dcd..00000000000 --- a/caddytls/storagetest/memorystorage.go +++ /dev/null @@ -1,134 +0,0 @@ -package storagetest - -import ( - "errors" - "net/url" - "sync" - - "github.com/mholt/caddy/caddytls" -) - -// memoryMutex is a mutex used to control access to memoryStoragesByCAURL. -var memoryMutex sync.Mutex - -// memoryStoragesByCAURL is a map keyed by a CA URL string with values of -// instantiated memory stores. Do not access this directly, it is used by -// InMemoryStorageCreator. -var memoryStoragesByCAURL = make(map[string]*InMemoryStorage) - -// InMemoryStorageCreator is a caddytls.Storage.StorageCreator to create -// InMemoryStorage instances for testing. -func InMemoryStorageCreator(caURL *url.URL) (caddytls.Storage, error) { - urlStr := caURL.String() - memoryMutex.Lock() - defer memoryMutex.Unlock() - storage := memoryStoragesByCAURL[urlStr] - if storage == nil { - storage = NewInMemoryStorage() - memoryStoragesByCAURL[urlStr] = storage - } - return storage, nil -} - -// InMemoryStorage is a caddytls.Storage implementation for use in testing. -// It simply stores information in runtime memory. -type InMemoryStorage struct { - // Sites are exposed for testing purposes. - Sites map[string]*caddytls.SiteData - // Users are exposed for testing purposes. - Users map[string]*caddytls.UserData - // LastUserEmail is exposed for testing purposes. - LastUserEmail string -} - -// NewInMemoryStorage constructs an InMemoryStorage instance. For use with -// caddytls, the InMemoryStorageCreator should be used instead. -func NewInMemoryStorage() *InMemoryStorage { - return &InMemoryStorage{ - Sites: make(map[string]*caddytls.SiteData), - Users: make(map[string]*caddytls.UserData), - } -} - -// SiteExists implements caddytls.Storage.SiteExists in memory. -func (s *InMemoryStorage) SiteExists(domain string) (bool, error) { - _, siteExists := s.Sites[domain] - return siteExists, nil -} - -// Clear completely clears all values associated with this storage. -func (s *InMemoryStorage) Clear() { - s.Sites = make(map[string]*caddytls.SiteData) - s.Users = make(map[string]*caddytls.UserData) - s.LastUserEmail = "" -} - -// LoadSite implements caddytls.Storage.LoadSite in memory. -func (s *InMemoryStorage) LoadSite(domain string) (*caddytls.SiteData, error) { - siteData, ok := s.Sites[domain] - if !ok { - return nil, caddytls.ErrNotExist(errors.New("not found")) - } - return siteData, nil -} - -func copyBytes(from []byte) []byte { - copiedBytes := make([]byte, len(from)) - copy(copiedBytes, from) - return copiedBytes -} - -// StoreSite implements caddytls.Storage.StoreSite in memory. -func (s *InMemoryStorage) StoreSite(domain string, data *caddytls.SiteData) error { - copiedData := new(caddytls.SiteData) - copiedData.Cert = copyBytes(data.Cert) - copiedData.Key = copyBytes(data.Key) - copiedData.Meta = copyBytes(data.Meta) - s.Sites[domain] = copiedData - return nil -} - -// DeleteSite implements caddytls.Storage.DeleteSite in memory. -func (s *InMemoryStorage) DeleteSite(domain string) error { - if _, ok := s.Sites[domain]; !ok { - return caddytls.ErrNotExist(errors.New("not found")) - } - delete(s.Sites, domain) - return nil -} - -// TryLock implements Storage.TryLock by returning nil values because it -// is not a multi-server storage implementation. -func (s *InMemoryStorage) TryLock(domain string) (caddytls.Waiter, error) { - return nil, nil -} - -// Unlock implements Storage.Unlock as a no-op because it is -// not a multi-server storage implementation. -func (s *InMemoryStorage) Unlock(domain string) error { - return nil -} - -// LoadUser implements caddytls.Storage.LoadUser in memory. -func (s *InMemoryStorage) LoadUser(email string) (*caddytls.UserData, error) { - userData, ok := s.Users[email] - if !ok { - return nil, caddytls.ErrNotExist(errors.New("not found")) - } - return userData, nil -} - -// StoreUser implements caddytls.Storage.StoreUser in memory. -func (s *InMemoryStorage) StoreUser(email string, data *caddytls.UserData) error { - copiedData := new(caddytls.UserData) - copiedData.Reg = copyBytes(data.Reg) - copiedData.Key = copyBytes(data.Key) - s.Users[email] = copiedData - s.LastUserEmail = email - return nil -} - -// MostRecentUserEmail implements caddytls.Storage.MostRecentUserEmail in memory. -func (s *InMemoryStorage) MostRecentUserEmail() string { - return s.LastUserEmail -} diff --git a/caddytls/storagetest/memorystorage_test.go b/caddytls/storagetest/memorystorage_test.go deleted file mode 100644 index 286fbb4246d..00000000000 --- a/caddytls/storagetest/memorystorage_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package storagetest - -import "testing" - -func TestMemoryStorage(t *testing.T) { - storage := NewInMemoryStorage() - storageTest := &StorageTest{ - Storage: storage, - PostTest: storage.Clear, - } - storageTest.Test(t, false) -} diff --git a/caddytls/storagetest/storagetest.go b/caddytls/storagetest/storagetest.go deleted file mode 100644 index c759dee84fd..00000000000 --- a/caddytls/storagetest/storagetest.go +++ /dev/null @@ -1,292 +0,0 @@ -// Package storagetest provides utilities to assist in testing caddytls.Storage -// implementations. -package storagetest - -import ( - "bytes" - "errors" - "fmt" - "testing" - - "github.com/mholt/caddy/caddytls" -) - -// StorageTest is a test harness that contains tests to execute all exposed -// parts of a Storage implementation. -type StorageTest struct { - // Storage is the implementation to use during tests. This must be - // present. - caddytls.Storage - - // PreTest, if present, is called before every test. Any error returned - // is returned from the test and the test does not continue. - PreTest func() error - - // PostTest, if present, is executed after every test via defer which - // means it executes even on failure of the test (but not on failure of - // PreTest). - PostTest func() - - // AfterUserEmailStore, if present, is invoked during - // TestMostRecentUserEmail after each storage just in case anything - // needs to be mocked. - AfterUserEmailStore func(email string) error -} - -// TestFunc holds information about a test. -type TestFunc struct { - // Name is the friendly name of the test. - Name string - - // Fn is the function that is invoked for the test. - Fn func() error -} - -// runPreTest runs the PreTest function if present. -func (s *StorageTest) runPreTest() error { - if s.PreTest != nil { - return s.PreTest() - } - return nil -} - -// runPostTest runs the PostTest function if present. -func (s *StorageTest) runPostTest() { - if s.PostTest != nil { - s.PostTest() - } -} - -// AllFuncs returns all test functions that are part of this harness. -func (s *StorageTest) AllFuncs() []TestFunc { - return []TestFunc{ - {"TestSiteInfoExists", s.TestSiteExists}, - {"TestSite", s.TestSite}, - {"TestUser", s.TestUser}, - {"TestMostRecentUserEmail", s.TestMostRecentUserEmail}, - } -} - -// Test executes the entire harness using the testing package. Failures are -// reported via T.Fatal. If eagerFail is true, the first failure causes all -// testing to stop immediately. -func (s *StorageTest) Test(t *testing.T, eagerFail bool) { - if errs := s.TestAll(eagerFail); len(errs) > 0 { - ifaces := make([]interface{}, len(errs)) - for i, err := range errs { - ifaces[i] = err - } - t.Fatal(ifaces...) - } -} - -// TestAll executes the entire harness and returns the results as an array of -// errors. If eagerFail is true, the first failure causes all testing to stop -// immediately. -func (s *StorageTest) TestAll(eagerFail bool) (errs []error) { - for _, fn := range s.AllFuncs() { - if err := fn.Fn(); err != nil { - errs = append(errs, fmt.Errorf("%v failed: %v", fn.Name, err)) - if eagerFail { - return - } - } - } - return -} - -var simpleSiteData = &caddytls.SiteData{ - Cert: []byte("foo"), - Key: []byte("bar"), - Meta: []byte("baz"), -} -var simpleSiteDataAlt = &caddytls.SiteData{ - Cert: []byte("qux"), - Key: []byte("quux"), - Meta: []byte("corge"), -} - -// TestSiteExists tests Storage.SiteExists. -func (s *StorageTest) TestSiteExists() error { - if err := s.runPreTest(); err != nil { - return err - } - defer s.runPostTest() - - // Should not exist at first - siteExists, err := s.SiteExists("example.com") - if err != nil { - return err - } - - if siteExists { - return errors.New("Site should not exist") - } - - // Should exist after we store it - if err := s.StoreSite("example.com", simpleSiteData); err != nil { - return err - } - - siteExists, err = s.SiteExists("example.com") - if err != nil { - return err - } - - if !siteExists { - return errors.New("Expected site to exist") - } - - // Site should no longer exist after we delete it - if err := s.DeleteSite("example.com"); err != nil { - return err - } - - siteExists, err = s.SiteExists("example.com") - if err != nil { - return err - } - - if siteExists { - return errors.New("Site should not exist after delete") - } - return nil -} - -// TestSite tests Storage.LoadSite, Storage.StoreSite, and Storage.DeleteSite. -func (s *StorageTest) TestSite() error { - if err := s.runPreTest(); err != nil { - return err - } - defer s.runPostTest() - - // Should be a not-found error at first - _, err := s.LoadSite("example.com") - if _, ok := err.(caddytls.ErrNotExist); !ok { - return fmt.Errorf("Expected caddytls.ErrNotExist from load, got %T: %v", err, err) - } - - // Delete should also be a not-found error at first - err = s.DeleteSite("example.com") - if _, ok := err.(caddytls.ErrNotExist); !ok { - return fmt.Errorf("Expected ErrNotExist from delete, got: %v", err) - } - - // Should store successfully and then load just fine - if err := s.StoreSite("example.com", simpleSiteData); err != nil { - return err - } - if siteData, err := s.LoadSite("example.com"); err != nil { - return err - } else if !bytes.Equal(siteData.Cert, simpleSiteData.Cert) { - return errors.New("Unexpected cert returned after store") - } else if !bytes.Equal(siteData.Key, simpleSiteData.Key) { - return errors.New("Unexpected key returned after store") - } else if !bytes.Equal(siteData.Meta, simpleSiteData.Meta) { - return errors.New("Unexpected meta returned after store") - } - - // Overwrite should work just fine - if err := s.StoreSite("example.com", simpleSiteDataAlt); err != nil { - return err - } - if siteData, err := s.LoadSite("example.com"); err != nil { - return err - } else if !bytes.Equal(siteData.Cert, simpleSiteDataAlt.Cert) { - return errors.New("Unexpected cert returned after overwrite") - } - - // It should delete fine and then not be there - if err := s.DeleteSite("example.com"); err != nil { - return err - } - _, err = s.LoadSite("example.com") - if _, ok := err.(caddytls.ErrNotExist); !ok { - return fmt.Errorf("Expected caddytls.ErrNotExist after delete, got %T: %v", err, err) - } - - return nil -} - -var simpleUserData = &caddytls.UserData{ - Reg: []byte("foo"), - Key: []byte("bar"), -} -var simpleUserDataAlt = &caddytls.UserData{ - Reg: []byte("baz"), - Key: []byte("qux"), -} - -// TestUser tests Storage.LoadUser and Storage.StoreUser. -func (s *StorageTest) TestUser() error { - if err := s.runPreTest(); err != nil { - return err - } - defer s.runPostTest() - - // Should be a not-found error at first - _, err := s.LoadUser("foo@example.com") - if _, ok := err.(caddytls.ErrNotExist); !ok { - return fmt.Errorf("Expected caddytls.ErrNotExist from load, got %T: %v", err, err) - } - - // Should store successfully and then load just fine - if err := s.StoreUser("foo@example.com", simpleUserData); err != nil { - return err - } - if userData, err := s.LoadUser("foo@example.com"); err != nil { - return err - } else if !bytes.Equal(userData.Reg, simpleUserData.Reg) { - return errors.New("Unexpected reg returned after store") - } else if !bytes.Equal(userData.Key, simpleUserData.Key) { - return errors.New("Unexpected key returned after store") - } - - // Overwrite should work just fine - if err := s.StoreUser("foo@example.com", simpleUserDataAlt); err != nil { - return err - } - if userData, err := s.LoadUser("foo@example.com"); err != nil { - return err - } else if !bytes.Equal(userData.Reg, simpleUserDataAlt.Reg) { - return errors.New("Unexpected reg returned after overwrite") - } - - return nil -} - -// TestMostRecentUserEmail tests Storage.MostRecentUserEmail. -func (s *StorageTest) TestMostRecentUserEmail() error { - if err := s.runPreTest(); err != nil { - return err - } - defer s.runPostTest() - - // Should be empty on first run - if e := s.MostRecentUserEmail(); e != "" { - return fmt.Errorf("Expected empty most recent user on first run, got: %v", e) - } - - // If we store user, then that one should be returned - if err := s.StoreUser("foo1@example.com", simpleUserData); err != nil { - return err - } - if s.AfterUserEmailStore != nil { - s.AfterUserEmailStore("foo1@example.com") - } - if e := s.MostRecentUserEmail(); e != "foo1@example.com" { - return fmt.Errorf("Unexpected most recent email after first store: %v", e) - } - - // If we store another user, then that one should be returned - if err := s.StoreUser("foo2@example.com", simpleUserDataAlt); err != nil { - return err - } - if s.AfterUserEmailStore != nil { - s.AfterUserEmailStore("foo2@example.com") - } - if e := s.MostRecentUserEmail(); e != "foo2@example.com" { - return fmt.Errorf("Unexpected most recent email after user key: %v", e) - } - return nil -} diff --git a/caddytls/storagetest/storagetest_test.go b/caddytls/storagetest/storagetest_test.go deleted file mode 100644 index 7902ee74bb2..00000000000 --- a/caddytls/storagetest/storagetest_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package storagetest - -import ( - "fmt" - "os" - "path/filepath" - "testing" - "time" - - "github.com/mholt/caddy/caddytls" -) - -// TestFileStorage tests the file storage set with the test harness in this -// package. -func TestFileStorage(t *testing.T) { - emailCounter := 0 - storageTest := &StorageTest{ - Storage: &caddytls.FileStorage{Path: "./testdata"}, // nameLocks isn't made here, but it's okay because the tests don't call TryLock or Unlock - PostTest: func() { os.RemoveAll("./testdata") }, - AfterUserEmailStore: func(email string) error { - // We need to change the dir mod time to show a - // that certain dirs are newer. - emailCounter++ - fp := filepath.Join("./testdata", "users", email) - - // What we will do is subtract 10 days from today and - // then add counter * seconds to make the later - // counters newer. We accept that this isn't exactly - // how the file storage works because it only changes - // timestamps on *newly seen* users, but it achieves - // the result that the harness expects. - chTime := time.Now().AddDate(0, 0, -10).Add(time.Duration(emailCounter) * time.Second) - if err := os.Chtimes(fp, chTime, chTime); err != nil { - return fmt.Errorf("Unable to change file time for %v: %v", fp, err) - } - return nil - }, - } - storageTest.Test(t, false) -} diff --git a/caddytls/tls.go b/caddytls/tls.go deleted file mode 100644 index 396c63a3a6a..00000000000 --- a/caddytls/tls.go +++ /dev/null @@ -1,193 +0,0 @@ -// Package caddytls facilitates the management of TLS assets and integrates -// Let's Encrypt functionality into Caddy with first-class support for -// creating and renewing certificates automatically. It also implements -// the tls directive. -// -// This package is meant to be used by Caddy server types. To use the -// tls directive, a server type must import this package and call -// RegisterConfigGetter(). The server type must make and keep track of -// the caddytls.Config structs that this package produces. It must also -// add tls to its list of directives. When it comes time to make the -// server instances, the server type can call MakeTLSConfig() to convert -// a []caddytls.Config to a single tls.Config for use in tls.NewListener(). -// It is also recommended to call RotateSessionTicketKeys() when -// starting a new listener. -package caddytls - -import ( - "encoding/json" - "net" - "strings" - - "github.com/mholt/caddy" - "github.com/xenolf/lego/acme" -) - -// HostQualifies returns true if the hostname alone -// appears eligible for automatic HTTPS. For example, -// localhost, empty hostname, and IP addresses are -// not eligible because we cannot obtain certificates -// for those names. -func HostQualifies(hostname string) bool { - return hostname != "localhost" && // localhost is ineligible - - // hostname must not be empty - strings.TrimSpace(hostname) != "" && - - // must not contain wildcard (*) characters (until CA supports it) - !strings.Contains(hostname, "*") && - - // must not start or end with a dot - !strings.HasPrefix(hostname, ".") && - !strings.HasSuffix(hostname, ".") && - - // cannot be an IP address, see - // https://community.letsencrypt.org/t/certificate-for-static-ip/84/2?u=mholt - net.ParseIP(hostname) == nil -} - -// saveCertResource saves the certificate resource to disk. This -// includes the certificate file itself, the private key, and the -// metadata file. -func saveCertResource(storage Storage, cert acme.CertificateResource) error { - // Save cert, private key, and metadata - siteData := &SiteData{ - Cert: cert.Certificate, - Key: cert.PrivateKey, - } - var err error - siteData.Meta, err = json.MarshalIndent(&cert, "", "\t") - if err == nil { - err = storage.StoreSite(cert.Domain, siteData) - } - return err -} - -// Revoke revokes the certificate for host via ACME protocol. -// It assumes the certificate was obtained from the -// CA at DefaultCAUrl. -func Revoke(host string) error { - client, err := newACMEClient(new(Config), true) - if err != nil { - return err - } - return client.Revoke(host) -} - -// tlsSniSolver is a type that can solve tls-sni challenges using -// an existing listener and our custom, in-memory certificate cache. -type tlsSniSolver struct{} - -// Present adds the challenge certificate to the cache. -func (s tlsSniSolver) Present(domain, token, keyAuth string) error { - cert, acmeDomain, err := acme.TLSSNI01ChallengeCert(keyAuth) - if err != nil { - return err - } - cacheCertificate(Certificate{ - Certificate: cert, - Names: []string{acmeDomain}, - }) - return nil -} - -// CleanUp removes the challenge certificate from the cache. -func (s tlsSniSolver) CleanUp(domain, token, keyAuth string) error { - _, acmeDomain, err := acme.TLSSNI01ChallengeCert(keyAuth) - if err != nil { - return err - } - uncacheCertificate(acmeDomain) - return nil -} - -// ConfigHolder is any type that has a Config; it presumably is -// connected to a hostname and port on which it is serving. -type ConfigHolder interface { - TLSConfig() *Config - Host() string - Port() string -} - -// QualifiesForManagedTLS returns true if c qualifies for -// for managed TLS (but not on-demand TLS specifically). -// It does NOT check to see if a cert and key already exist -// for the config. If the return value is true, you should -// be OK to set c.TLSConfig().Managed to true; then you should -// check that value in the future instead, because the process -// of setting up the config may make it look like it doesn't -// qualify even though it originally did. -func QualifiesForManagedTLS(c ConfigHolder) bool { - if c == nil { - return false - } - tlsConfig := c.TLSConfig() - if tlsConfig == nil { - return false - } - - return (!tlsConfig.Manual || tlsConfig.OnDemand) && // user might provide own cert and key - - // if self-signed, we've already generated one to use - !tlsConfig.SelfSigned && - - // user can force-disable managed TLS - c.Port() != "80" && - tlsConfig.ACMEEmail != "off" && - - // we get can't certs for some kinds of hostnames, but - // on-demand TLS allows empty hostnames at startup - (HostQualifies(c.Host()) || tlsConfig.OnDemand) -} - -// ChallengeProvider defines an own type that should be used in Caddy plugins -// over acme.ChallengeProvider. Using acme.ChallengeProvider causes version mismatches -// with vendored dependencies (see https://github.com/mattfarina/golang-broken-vendor) -// -// acme.ChallengeProvider is an interface that allows the implementation of custom -// challenge providers. For more details, see: -// https://godoc.org/github.com/xenolf/lego/acme#ChallengeProvider -type ChallengeProvider acme.ChallengeProvider - -// DNSProviderConstructor is a function that takes credentials and -// returns a type that can solve the ACME DNS challenges. -type DNSProviderConstructor func(credentials ...string) (ChallengeProvider, error) - -// dnsProviders is the list of DNS providers that have been plugged in. -var dnsProviders = make(map[string]DNSProviderConstructor) - -// RegisterDNSProvider registers provider by name for solving the ACME DNS challenge. -func RegisterDNSProvider(name string, provider DNSProviderConstructor) { - dnsProviders[name] = provider - caddy.RegisterPlugin("tls.dns."+name, caddy.Plugin{}) -} - -var ( - // DefaultEmail represents the Let's Encrypt account email to use if none provided. - DefaultEmail string - - // Agreed indicates whether user has agreed to the Let's Encrypt SA. - Agreed bool - - // DefaultCAUrl is the default URL to the CA's ACME directory endpoint. - // It's very important to set this unless you set it in every Config. - DefaultCAUrl string - - // DefaultKeyType is used as the type of key for new certificates - // when no other key type is specified. - DefaultKeyType = acme.RSA2048 - - // DisableHTTPChallenge will disable all HTTP challenges. - DisableHTTPChallenge bool - - // DisableTLSSNIChallenge will disable all TLS-SNI challenges. - DisableTLSSNIChallenge bool -) - -var storageProviders = make(map[string]StorageConstructor) - -// RegisterStorageProvider registers provider by name for storing tls data -func RegisterStorageProvider(name string, provider StorageConstructor) { - storageProviders[name] = provider - caddy.RegisterPlugin("tls.storage."+name, caddy.Plugin{}) -} diff --git a/caddytls/tls_test.go b/caddytls/tls_test.go deleted file mode 100644 index dccb8ec965e..00000000000 --- a/caddytls/tls_test.go +++ /dev/null @@ -1,165 +0,0 @@ -package caddytls - -import ( - "os" - "sync" - "testing" - - "github.com/xenolf/lego/acme" -) - -func TestHostQualifies(t *testing.T) { - for i, test := range []struct { - host string - expect bool - }{ - {"example.com", true}, - {"sub.example.com", true}, - {"Sub.Example.COM", true}, - {"127.0.0.1", false}, - {"127.0.1.5", false}, - {"69.123.43.94", false}, - {"::1", false}, - {"::", false}, - {"0.0.0.0", false}, - {"", false}, - {" ", false}, - {"*.example.com", false}, - {".com", false}, - {"example.com.", false}, - {"localhost", false}, - {"local", true}, - {"devsite", true}, - {"192.168.1.3", false}, - {"10.0.2.1", false}, - {"169.112.53.4", false}, - } { - actual := HostQualifies(test.host) - if actual != test.expect { - t.Errorf("Test %d: Expected HostQualifies(%s)=%v, but got %v", - i, test.host, test.expect, actual) - } - } -} - -type holder struct { - host, port string - cfg *Config -} - -func (h holder) TLSConfig() *Config { return h.cfg } -func (h holder) Host() string { return h.host } -func (h holder) Port() string { return h.port } - -func TestQualifiesForManagedTLS(t *testing.T) { - for i, test := range []struct { - cfg ConfigHolder - expect bool - }{ - {holder{host: ""}, false}, - {holder{host: "localhost"}, false}, - {holder{host: "123.44.3.21"}, false}, - {holder{host: "example.com"}, false}, - {holder{host: "", cfg: new(Config)}, false}, - {holder{host: "localhost", cfg: new(Config)}, false}, - {holder{host: "123.44.3.21", cfg: new(Config)}, false}, - {holder{host: "example.com", cfg: new(Config)}, true}, - {holder{host: "*.example.com", cfg: new(Config)}, false}, - {holder{host: "example.com", cfg: &Config{Manual: true}}, false}, - {holder{host: "example.com", cfg: &Config{ACMEEmail: "off"}}, false}, - {holder{host: "example.com", cfg: &Config{ACMEEmail: "foo@bar.com"}}, true}, - {holder{host: "example.com", port: "80"}, false}, - {holder{host: "example.com", port: "1234", cfg: new(Config)}, true}, - {holder{host: "example.com", port: "443", cfg: new(Config)}, true}, - {holder{host: "example.com", port: "80"}, false}, - } { - if got, want := QualifiesForManagedTLS(test.cfg), test.expect; got != want { - t.Errorf("Test %d: Expected %v but got %v", i, want, got) - } - } -} - -func TestSaveCertResource(t *testing.T) { - storage := &FileStorage{Path: "./le_test_save", nameLocks: make(map[string]*sync.WaitGroup)} - defer func() { - err := os.RemoveAll(storage.Path) - if err != nil { - t.Fatalf("Could not remove temporary storage directory (%s): %v", storage.Path, err) - } - }() - - domain := "example.com" - certContents := "certificate" - keyContents := "private key" - metaContents := `{ - "domain": "example.com", - "certUrl": "https://example.com/cert", - "certStableUrl": "https://example.com/cert/stable" -}` - - cert := acme.CertificateResource{ - Domain: domain, - CertURL: "https://example.com/cert", - CertStableURL: "https://example.com/cert/stable", - PrivateKey: []byte(keyContents), - Certificate: []byte(certContents), - } - - err := saveCertResource(storage, cert) - if err != nil { - t.Fatalf("Expected no error, got: %v", err) - } - - siteData, err := storage.LoadSite(domain) - if err != nil { - t.Errorf("Expected no error reading site, got: %v", err) - } - if string(siteData.Cert) != certContents { - t.Errorf("Expected certificate file to contain '%s', got '%s'", certContents, string(siteData.Cert)) - } - if string(siteData.Key) != keyContents { - t.Errorf("Expected private key file to contain '%s', got '%s'", keyContents, string(siteData.Key)) - } - if string(siteData.Meta) != metaContents { - t.Errorf("Expected meta file to contain '%s', got '%s'", metaContents, string(siteData.Meta)) - } -} - -func TestExistingCertAndKey(t *testing.T) { - storage := &FileStorage{Path: "./le_test_existing", nameLocks: make(map[string]*sync.WaitGroup)} - defer func() { - err := os.RemoveAll(storage.Path) - if err != nil { - t.Fatalf("Could not remove temporary storage directory (%s): %v", storage.Path, err) - } - }() - - domain := "example.com" - - siteExists, err := storage.SiteExists(domain) - if err != nil { - t.Fatalf("Could not determine whether site exists: %v", err) - } - - if siteExists { - t.Errorf("Did NOT expect %v to have existing cert or key, but it did", domain) - } - - err = saveCertResource(storage, acme.CertificateResource{ - Domain: domain, - PrivateKey: []byte("key"), - Certificate: []byte("cert"), - }) - if err != nil { - t.Fatalf("Expected no error, got: %v", err) - } - - siteExists, err = storage.SiteExists(domain) - if err != nil { - t.Fatalf("Could not determine whether site exists: %v", err) - } - - if !siteExists { - t.Errorf("Expected %v to have existing cert and key, but it did NOT", domain) - } -} diff --git a/caddytls/user.go b/caddytls/user.go deleted file mode 100644 index 4fa89aaa463..00000000000 --- a/caddytls/user.go +++ /dev/null @@ -1,176 +0,0 @@ -package caddytls - -import ( - "bufio" - "crypto" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "encoding/json" - "errors" - "fmt" - "io" - "os" - "strings" - - "github.com/xenolf/lego/acme" -) - -// User represents a Let's Encrypt user account. -type User struct { - Email string - Registration *acme.RegistrationResource - key crypto.PrivateKey -} - -// GetEmail gets u's email. -func (u User) GetEmail() string { - return u.Email -} - -// GetRegistration gets u's registration resource. -func (u User) GetRegistration() *acme.RegistrationResource { - return u.Registration -} - -// GetPrivateKey gets u's private key. -func (u User) GetPrivateKey() crypto.PrivateKey { - return u.key -} - -// newUser creates a new User for the given email address -// with a new private key. This function does NOT save the -// user to disk or register it via ACME. If you want to use -// a user account that might already exist, call getUser -// instead. It does NOT prompt the user. -func newUser(email string) (User, error) { - user := User{Email: email} - privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) - if err != nil { - return user, errors.New("error generating private key: " + err.Error()) - } - user.key = privateKey - return user, nil -} - -// getEmail does everything it can to obtain an email -// address from the user within the scope of storage -// to use for ACME TLS. If it cannot get an email -// address, it returns empty string. (It will warn the -// user of the consequences of an empty email.) This -// function MAY prompt the user for input. If userPresent -// is false, the operator will NOT be prompted and an -// empty email may be returned. -func getEmail(storage Storage, userPresent bool) string { - // First try memory (command line flag or typed by user previously) - leEmail := DefaultEmail - if leEmail == "" { - // Then try to get most recent user email - leEmail = storage.MostRecentUserEmail() - // Save for next time - DefaultEmail = leEmail - } - if leEmail == "" && userPresent { - // Alas, we must bother the user and ask for an email address; - // if they proceed they also agree to the SA. - reader := bufio.NewReader(stdin) - fmt.Println("\nYour sites will be served over HTTPS automatically using Let's Encrypt.") - fmt.Println("By continuing, you agree to the Let's Encrypt Subscriber Agreement at:") - fmt.Println(" " + saURL) // TODO: Show current SA link - fmt.Println("Please enter your email address so you can recover your account if needed.") - fmt.Println("You can leave it blank, but you'll lose the ability to recover your account.") - fmt.Print("Email address: ") - var err error - leEmail, err = reader.ReadString('\n') - if err != nil { - return "" - } - leEmail = strings.TrimSpace(leEmail) - DefaultEmail = leEmail - Agreed = true - } - return strings.ToLower(leEmail) -} - -// getUser loads the user with the given email from disk -// using the provided storage. If the user does not exist, -// it will create a new one, but it does NOT save new -// users to the disk or register them via ACME. It does -// NOT prompt the user. -func getUser(storage Storage, email string) (User, error) { - var user User - - // open user reg - userData, err := storage.LoadUser(email) - if err != nil { - if _, ok := err.(ErrNotExist); ok { - // create a new user - return newUser(email) - } - return user, err - } - - // load user information - err = json.Unmarshal(userData.Reg, &user) - if err != nil { - return user, err - } - - // load their private key - user.key, err = loadPrivateKey(userData.Key) - return user, err -} - -// saveUser persists a user's key and account registration -// to the file system. It does NOT register the user via ACME -// or prompt the user. You must also pass in the storage -// wherein the user should be saved. It should be the storage -// for the CA with which user has an account. -func saveUser(storage Storage, user User) error { - // Save the private key and registration - userData := new(UserData) - var err error - userData.Key, err = savePrivateKey(user.key) - if err == nil { - userData.Reg, err = json.MarshalIndent(&user, "", "\t") - } - if err == nil { - err = storage.StoreUser(user.Email, userData) - } - return err -} - -// promptUserAgreement prompts the user to agree to the agreement -// at agreementURL via stdin. If the agreement has changed, then pass -// true as the second argument. If this is the user's first time -// agreeing, pass false. It returns whether the user agreed or not. -func promptUserAgreement(agreementURL string, changed bool) bool { - if changed { - fmt.Printf("The Let's Encrypt Subscriber Agreement has changed:\n %s\n", agreementURL) - fmt.Print("Do you agree to the new terms? (y/n): ") - } else { - fmt.Printf("To continue, you must agree to the Let's Encrypt Subscriber Agreement:\n %s\n", agreementURL) - fmt.Print("Do you agree to the terms? (y/n): ") - } - - reader := bufio.NewReader(stdin) - answer, err := reader.ReadString('\n') - if err != nil { - return false - } - answer = strings.ToLower(strings.TrimSpace(answer)) - - return answer == "y" || answer == "yes" -} - -// stdin is used to read the user's input if prompted; -// this is changed by tests during tests. -var stdin = io.ReadWriter(os.Stdin) - -// The name of the folder for accounts where the email -// address was not provided; default 'username' if you will. -const emptyEmail = "default" - -// TODO: After Boulder implements the 'meta' field of the directory, -// we can get this link dynamically. -const saURL = "https://acme-v01.api.letsencrypt.org/terms" diff --git a/caddytls/user_test.go b/caddytls/user_test.go deleted file mode 100644 index 0b7490a68c6..00000000000 --- a/caddytls/user_test.go +++ /dev/null @@ -1,189 +0,0 @@ -package caddytls - -import ( - "bytes" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "io" - "strings" - "sync" - "testing" - "time" - - "os" - - "github.com/xenolf/lego/acme" -) - -func TestUser(t *testing.T) { - defer testStorage.clean() - - privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) - if err != nil { - t.Fatalf("Could not generate test private key: %v", err) - } - u := User{ - Email: "me@mine.com", - Registration: new(acme.RegistrationResource), - key: privateKey, - } - - if expected, actual := "me@mine.com", u.GetEmail(); actual != expected { - t.Errorf("Expected email '%s' but got '%s'", expected, actual) - } - if u.GetRegistration() == nil { - t.Error("Expected a registration resource, but got nil") - } - if expected, actual := privateKey, u.GetPrivateKey(); actual != expected { - t.Errorf("Expected the private key at address %p but got one at %p instead ", expected, actual) - } -} - -func TestNewUser(t *testing.T) { - email := "me@foobar.com" - user, err := newUser(email) - if err != nil { - t.Fatalf("Error creating user: %v", err) - } - if user.key == nil { - t.Error("Private key is nil") - } - if user.Email != email { - t.Errorf("Expected email to be %s, but was %s", email, user.Email) - } - if user.Registration != nil { - t.Error("New user already has a registration resource; it shouldn't") - } -} - -func TestSaveUser(t *testing.T) { - defer testStorage.clean() - - email := "me@foobar.com" - user, err := newUser(email) - if err != nil { - t.Fatalf("Error creating user: %v", err) - } - - err = saveUser(testStorage, user) - if err != nil { - t.Fatalf("Error saving user: %v", err) - } - _, err = testStorage.LoadUser(email) - if err != nil { - t.Errorf("Cannot access user data, error: %v", err) - } -} - -func TestGetUserDoesNotAlreadyExist(t *testing.T) { - defer testStorage.clean() - - user, err := getUser(testStorage, "user_does_not_exist@foobar.com") - if err != nil { - t.Fatalf("Error getting user: %v", err) - } - - if user.key == nil { - t.Error("Expected user to have a private key, but it was nil") - } -} - -func TestGetUserAlreadyExists(t *testing.T) { - defer testStorage.clean() - - email := "me@foobar.com" - - // Set up test - user, err := newUser(email) - if err != nil { - t.Fatalf("Error creating user: %v", err) - } - err = saveUser(testStorage, user) - if err != nil { - t.Fatalf("Error saving user: %v", err) - } - - // Expect to load user from disk - user2, err := getUser(testStorage, email) - if err != nil { - t.Fatalf("Error getting user: %v", err) - } - - // Assert keys are the same - if !PrivateKeysSame(user.key, user2.key) { - t.Error("Expected private key to be the same after loading, but it wasn't") - } - - // Assert emails are the same - if user.Email != user2.Email { - t.Errorf("Expected emails to be equal, but was '%s' before and '%s' after loading", user.Email, user2.Email) - } -} - -func TestGetEmail(t *testing.T) { - storageBasePath = testStorage.Path // to contain calls that create a new Storage... - - // let's not clutter up the output - origStdout := os.Stdout - os.Stdout = nil - defer func() { os.Stdout = origStdout }() - - defer testStorage.clean() - DefaultEmail = "test2@foo.com" - - // Test1: Use default email from flag (or user previously typing it) - actual := getEmail(testStorage, true) - if actual != DefaultEmail { - t.Errorf("Did not get correct email from memory; expected '%s' but got '%s'", DefaultEmail, actual) - } - - // Test2: Get input from user - DefaultEmail = "" - stdin = new(bytes.Buffer) - _, err := io.Copy(stdin, strings.NewReader("test3@foo.com\n")) - if err != nil { - t.Fatalf("Could not simulate user input, error: %v", err) - } - actual = getEmail(testStorage, true) - if actual != "test3@foo.com" { - t.Errorf("Did not get correct email from user input prompt; expected '%s' but got '%s'", "test3@foo.com", actual) - } - - // Test3: Get most recent email from before - DefaultEmail = "" - for i, eml := range []string{ - "TEST4-3@foo.com", // test case insensitivity - "test4-2@foo.com", - "test4-1@foo.com", - } { - u, err := newUser(eml) - if err != nil { - t.Fatalf("Error creating user %d: %v", i, err) - } - err = saveUser(testStorage, u) - if err != nil { - t.Fatalf("Error saving user %d: %v", i, err) - } - - // Change modified time so they're all different and the test becomes more deterministic - f, err := os.Stat(testStorage.user(eml)) - if err != nil { - t.Fatalf("Could not access user folder for '%s': %v", eml, err) - } - chTime := f.ModTime().Add(-(time.Duration(i) * time.Hour)) // 1 second isn't always enough space! - if err := os.Chtimes(testStorage.user(eml), chTime, chTime); err != nil { - t.Fatalf("Could not change user folder mod time for '%s': %v", eml, err) - } - } - actual = getEmail(testStorage, true) - if actual != "test4-3@foo.com" { - t.Errorf("Did not get correct email from storage; expected '%s' but got '%s'", "test4-3@foo.com", actual) - } -} - -var testStorage = &FileStorage{Path: "./testdata", nameLocks: make(map[string]*sync.WaitGroup)} - -func (s *FileStorage) clean() error { - return os.RemoveAll(s.Path) -} diff --git a/cmd/caddy/main.go b/cmd/caddy/main.go new file mode 100644 index 00000000000..48fa149aa08 --- /dev/null +++ b/cmd/caddy/main.go @@ -0,0 +1,40 @@ +// 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 main is the entry point of the Caddy application. +// Most of Caddy's functionality is provided through modules, +// which can be plugged in by adding their import below. +// +// There is no need to modify the Caddy source code to customize your +// builds. You can easily build a custom Caddy with these simple steps: +// +// 1. Copy this file (main.go) into a new folder +// 2. Edit the imports below to include the modules you want plugged in +// 3. Run `go mod init caddy` +// 4. Run `go install` or `go build` - you now have a custom binary! +// +// Or you can use xcaddy which does it all for you as a command: +// https://github.com/caddyserver/xcaddy +package main + +import ( + caddycmd "github.com/caddyserver/caddy/v2/cmd" + + // plug in Caddy modules here + _ "github.com/caddyserver/caddy/v2/modules/standard" +) + +func main() { + caddycmd.Main() +} diff --git a/cmd/caddy/setcap.sh b/cmd/caddy/setcap.sh new file mode 100755 index 00000000000..39aea2d6087 --- /dev/null +++ b/cmd/caddy/setcap.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +# USAGE: +# go run -exec ./setcap.sh main.go +# +# (Example: `go run -exec ./setcap.sh main.go run --config caddy.json`) +# +# For some reason this does not work on my Arch system, so if you find that's +# the case, you can instead do: +# +# go build && ./setcap.sh ./caddy +# +# but this will leave the ./caddy binary laying around. +# + +sudo setcap cap_net_bind_service=+ep "$1" +"$@" diff --git a/cmd/cobra.go b/cmd/cobra.go new file mode 100644 index 00000000000..9ecb389e2aa --- /dev/null +++ b/cmd/cobra.go @@ -0,0 +1,161 @@ +package caddycmd + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/caddyserver/caddy/v2" +) + +var defaultFactory = newRootCommandFactory(func() *cobra.Command { + return &cobra.Command{ + Use: "caddy", + Long: `Caddy is an extensible server platform written in Go. + +At its core, Caddy merely manages configuration. Modules are plugged +in statically at compile-time to provide useful functionality. Caddy's +standard distribution includes common modules to serve HTTP, TLS, +and PKI applications, including the automation of certificates. + +To run Caddy, use: + + - 'caddy run' to run Caddy in the foreground (recommended). + - 'caddy start' to start Caddy in the background; only do this + if you will be keeping the terminal window open until you run + 'caddy stop' to close the server. + +When Caddy is started, it opens a locally-bound administrative socket +to which configuration can be POSTed via a restful HTTP API (see +https://caddyserver.com/docs/api). + +Caddy's native configuration format is JSON. However, config adapters +can be used to convert other config formats to JSON when Caddy receives +its configuration. The Caddyfile is a built-in config adapter that is +popular for hand-written configurations due to its straightforward +syntax (see https://caddyserver.com/docs/caddyfile). Many third-party +adapters are available (see https://caddyserver.com/docs/config-adapters). +Use 'caddy adapt' to see how a config translates to JSON. + +For convenience, the CLI can act as an HTTP client to give Caddy its +initial configuration for you. If a file named Caddyfile is in the +current working directory, it will do this automatically. Otherwise, +you can use the --config flag to specify the path to a config file. + +Some special-purpose subcommands build and load a configuration file +for you directly from command line input; for example: + + - caddy file-server + - caddy reverse-proxy + - caddy respond + +These commands disable the administration endpoint because their +configuration is specified solely on the command line. + +In general, the most common way to run Caddy is simply: + + $ caddy run + +Or, with a configuration file: + + $ caddy run --config caddy.json + +If running interactively in a terminal, running Caddy in the +background may be more convenient: + + $ caddy start + ... + $ caddy stop + +This allows you to run other commands while Caddy stays running. +Be sure to stop Caddy before you close the terminal! + +Depending on the system, Caddy may need permission to bind to low +ports. One way to do this on Linux is to use setcap: + + $ sudo setcap cap_net_bind_service=+ep $(which caddy) + +Remember to run that command again after replacing the binary. + +See the Caddy website for tutorials, configuration structure, +syntax, and module documentation: https://caddyserver.com/docs/ + +Custom Caddy builds are available on the Caddy download page at: +https://caddyserver.com/download + +The xcaddy command can be used to build Caddy from source with or +without additional plugins: https://github.com/caddyserver/xcaddy + +Where possible, Caddy should be installed using officially-supported +package installers: https://caddyserver.com/docs/install + +Instructions for running Caddy in production are also available: +https://caddyserver.com/docs/running +`, + Example: ` $ caddy run + $ caddy run --config caddy.json + $ caddy reload --config caddy.json + $ caddy stop`, + + // kind of annoying to have all the help text printed out if + // caddy has an error provisioning its modules, for instance... + SilenceUsage: true, + Version: onlyVersionText(), + } +}) + +const fullDocsFooter = `Full documentation is available at: +https://caddyserver.com/docs/command-line` + +func init() { + defaultFactory.Use(func(rootCmd *cobra.Command) { + rootCmd.SetVersionTemplate("{{.Version}}\n") + rootCmd.SetHelpTemplate(rootCmd.HelpTemplate() + "\n" + fullDocsFooter + "\n") + }) +} + +func onlyVersionText() string { + _, f := caddy.Version() + return f +} + +func caddyCmdToCobra(caddyCmd Command) *cobra.Command { + cmd := &cobra.Command{ + Use: caddyCmd.Name + " " + caddyCmd.Usage, + Short: caddyCmd.Short, + Long: caddyCmd.Long, + } + if caddyCmd.CobraFunc != nil { + caddyCmd.CobraFunc(cmd) + } else { + cmd.RunE = WrapCommandFuncForCobra(caddyCmd.Func) + cmd.Flags().AddGoFlagSet(caddyCmd.Flags) + } + return cmd +} + +// WrapCommandFuncForCobra wraps a Caddy CommandFunc for use +// in a cobra command's RunE field. +func WrapCommandFuncForCobra(f CommandFunc) func(cmd *cobra.Command, _ []string) error { + return func(cmd *cobra.Command, _ []string) error { + status, err := f(Flags{cmd.Flags()}) + if status > 1 { + cmd.SilenceErrors = true + return &exitError{ExitCode: status, Err: err} + } + return err + } +} + +// exitError carries the exit code from CommandFunc to Main() +type exitError struct { + ExitCode int + Err error +} + +func (e *exitError) Error() string { + if e.Err == nil { + return fmt.Sprintf("exiting with status %d", e.ExitCode) + } + return e.Err.Error() +} diff --git a/cmd/commandfactory.go b/cmd/commandfactory.go new file mode 100644 index 00000000000..ac571a21ae1 --- /dev/null +++ b/cmd/commandfactory.go @@ -0,0 +1,28 @@ +package caddycmd + +import ( + "github.com/spf13/cobra" +) + +type rootCommandFactory struct { + constructor func() *cobra.Command + options []func(*cobra.Command) +} + +func newRootCommandFactory(fn func() *cobra.Command) *rootCommandFactory { + return &rootCommandFactory{ + constructor: fn, + } +} + +func (f *rootCommandFactory) Use(fn func(cmd *cobra.Command)) { + f.options = append(f.options, fn) +} + +func (f *rootCommandFactory) Build() *cobra.Command { + o := f.constructor() + for _, v := range f.options { + v(o) + } + return o +} diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go new file mode 100644 index 00000000000..2adf95bb3de --- /dev/null +++ b/cmd/commandfuncs.go @@ -0,0 +1,813 @@ +// 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 caddycmd + +import ( + "bytes" + "context" + "crypto/rand" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "log" + "net" + "net/http" + "os" + "os/exec" + "runtime" + "runtime/debug" + "strings" + + "github.com/aryann/difflib" + "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/internal" +) + +func cmdStart(fl Flags) (int, error) { + configFlag := fl.String("config") + configAdapterFlag := fl.String("adapter") + pidfileFlag := fl.String("pidfile") + watchFlag := fl.Bool("watch") + + var err error + var envfileFlag []string + envfileFlag, err = fl.GetStringSlice("envfile") + if err != nil { + return caddy.ExitCodeFailedStartup, + fmt.Errorf("reading envfile flag: %v", err) + } + + // open a listener to which the child process will connect when + // it is ready to confirm that it has successfully started + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return caddy.ExitCodeFailedStartup, + fmt.Errorf("opening listener for success confirmation: %v", err) + } + defer ln.Close() + + // craft the command with a pingback address and with a + // pipe for its stdin, so we can tell it our confirmation + // code that we expect so that some random port scan at + // the most unfortunate time won't fool us into thinking + // the child succeeded (i.e. the alternative is to just + // wait for any connection on our listener, but better to + // ensure it's the process we're expecting - we can be + // sure by giving it some random bytes and having it echo + // them back to us) + cmd := exec.Command(os.Args[0], "run", "--pingback", ln.Addr().String()) + // we should be able to run caddy in relative paths + if errors.Is(cmd.Err, exec.ErrDot) { + cmd.Err = nil + } + if configFlag != "" { + cmd.Args = append(cmd.Args, "--config", configFlag) + } + + for _, envfile := range envfileFlag { + cmd.Args = append(cmd.Args, "--envfile", envfile) + } + if configAdapterFlag != "" { + cmd.Args = append(cmd.Args, "--adapter", configAdapterFlag) + } + if watchFlag { + cmd.Args = append(cmd.Args, "--watch") + } + if pidfileFlag != "" { + cmd.Args = append(cmd.Args, "--pidfile", pidfileFlag) + } + stdinPipe, err := cmd.StdinPipe() + if err != nil { + return caddy.ExitCodeFailedStartup, + fmt.Errorf("creating stdin pipe: %v", err) + } + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + // generate the random bytes we'll send to the child process + expect := make([]byte, 32) + _, err = rand.Read(expect) + if err != nil { + return caddy.ExitCodeFailedStartup, + fmt.Errorf("generating random confirmation bytes: %v", err) + } + + // begin writing the confirmation bytes to the child's + // stdin; use a goroutine since the child hasn't been + // started yet, and writing synchronously would result + // in a deadlock + go func() { + _, _ = stdinPipe.Write(expect) + stdinPipe.Close() + }() + + // start the process + err = cmd.Start() + if err != nil { + return caddy.ExitCodeFailedStartup, + fmt.Errorf("starting caddy process: %v", err) + } + + // there are two ways we know we're done: either + // the process will connect to our listener, or + // it will exit with an error + success, exit := make(chan struct{}), make(chan error) + + // in one goroutine, we await the success of the child process + go func() { + for { + conn, err := ln.Accept() + if err != nil { + if !errors.Is(err, net.ErrClosed) { + log.Println(err) + } + break + } + err = handlePingbackConn(conn, expect) + if err == nil { + close(success) + break + } + log.Println(err) + } + }() + + // in another goroutine, we await the failure of the child process + go func() { + err := cmd.Wait() // don't send on this line! Wait blocks, but send starts before it unblocks + exit <- err // sending on separate line ensures select won't trigger until after Wait unblocks + }() + + // when one of the goroutines unblocks, we're done and can exit + select { + case <-success: + fmt.Printf("Successfully started Caddy (pid=%d) - Caddy is running in the background\n", cmd.Process.Pid) + case err := <-exit: + return caddy.ExitCodeFailedStartup, + fmt.Errorf("caddy process exited with error: %v", err) + } + + return caddy.ExitCodeSuccess, nil +} + +func cmdRun(fl Flags) (int, error) { + caddy.TrapSignals() + + configFlag := fl.String("config") + configAdapterFlag := fl.String("adapter") + resumeFlag := fl.Bool("resume") + printEnvFlag := fl.Bool("environ") + watchFlag := fl.Bool("watch") + pidfileFlag := fl.String("pidfile") + pingbackFlag := fl.String("pingback") + + // load all additional envs as soon as possible + err := handleEnvFileFlag(fl) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + // if we are supposed to print the environment, do that first + if printEnvFlag { + printEnvironment() + } + + // load the config, depending on flags + var config []byte + if resumeFlag { + config, err = os.ReadFile(caddy.ConfigAutosavePath) + if errors.Is(err, fs.ErrNotExist) { + // not a bad error; just can't resume if autosave file doesn't exist + caddy.Log().Info("no autosave file exists", zap.String("autosave_file", caddy.ConfigAutosavePath)) + resumeFlag = false + } else if err != nil { + return caddy.ExitCodeFailedStartup, err + } else { + if configFlag == "" { + caddy.Log().Info("resuming from last configuration", + zap.String("autosave_file", caddy.ConfigAutosavePath)) + } else { + // if they also specified a config file, user should be aware that we're not + // using it (doing so could lead to data/config loss by overwriting!) + caddy.Log().Warn("--config and --resume flags were used together; ignoring --config and resuming from last configuration", + zap.String("autosave_file", caddy.ConfigAutosavePath)) + } + } + } + // we don't use 'else' here since this value might have been changed in 'if' block; i.e. not mutually exclusive + var configFile string + if !resumeFlag { + config, configFile, err = LoadConfig(configFlag, configAdapterFlag) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + } + + // create pidfile now, in case loading config takes a while (issue #5477) + if pidfileFlag != "" { + err := caddy.PIDFile(pidfileFlag) + if err != nil { + caddy.Log().Error("unable to write PID file", + zap.String("pidfile", pidfileFlag), + zap.Error(err)) + } + } + + // run the initial config + err = caddy.Load(config, true) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("loading initial config: %v", err) + } + caddy.Log().Info("serving initial configuration") + + // if we are to report to another process the successful start + // of the server, do so now by echoing back contents of stdin + if pingbackFlag != "" { + confirmationBytes, err := io.ReadAll(os.Stdin) + if err != nil { + return caddy.ExitCodeFailedStartup, + fmt.Errorf("reading confirmation bytes from stdin: %v", err) + } + conn, err := net.Dial("tcp", pingbackFlag) + if err != nil { + return caddy.ExitCodeFailedStartup, + fmt.Errorf("dialing confirmation address: %v", err) + } + defer conn.Close() + _, err = conn.Write(confirmationBytes) + if err != nil { + return caddy.ExitCodeFailedStartup, + fmt.Errorf("writing confirmation bytes to %s: %v", pingbackFlag, err) + } + } + + // if enabled, reload config file automatically on changes + // (this better only be used in dev!) + if watchFlag { + go watchConfigFile(configFile, configAdapterFlag) + } + + // warn if the environment does not provide enough information about the disk + hasXDG := os.Getenv("XDG_DATA_HOME") != "" && + os.Getenv("XDG_CONFIG_HOME") != "" && + os.Getenv("XDG_CACHE_HOME") != "" + switch runtime.GOOS { + case "windows": + if os.Getenv("HOME") == "" && os.Getenv("USERPROFILE") == "" && !hasXDG { + caddy.Log().Warn("neither HOME nor USERPROFILE environment variables are set - please fix; some assets might be stored in ./caddy") + } + case "plan9": + if os.Getenv("home") == "" && !hasXDG { + caddy.Log().Warn("$home environment variable is empty - please fix; some assets might be stored in ./caddy") + } + default: + if os.Getenv("HOME") == "" && !hasXDG { + caddy.Log().Warn("$HOME environment variable is empty - please fix; some assets might be stored in ./caddy") + } + } + + select {} +} + +func cmdStop(fl Flags) (int, error) { + addressFlag := fl.String("address") + configFlag := fl.String("config") + configAdapterFlag := fl.String("adapter") + + adminAddr, err := DetermineAdminAPIAddress(addressFlag, nil, configFlag, configAdapterFlag) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err) + } + + resp, err := AdminAPIRequest(adminAddr, http.MethodPost, "/stop", nil, nil) + if err != nil { + caddy.Log().Warn("failed using API to stop instance", zap.Error(err)) + return caddy.ExitCodeFailedStartup, err + } + defer resp.Body.Close() + + return caddy.ExitCodeSuccess, nil +} + +func cmdReload(fl Flags) (int, error) { + configFlag := fl.String("config") + configAdapterFlag := fl.String("adapter") + addressFlag := fl.String("address") + forceFlag := fl.Bool("force") + + // get the config in caddy's native format + config, configFile, err := LoadConfig(configFlag, configAdapterFlag) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + if configFile == "" { + return caddy.ExitCodeFailedStartup, fmt.Errorf("no config file to load") + } + + adminAddr, err := DetermineAdminAPIAddress(addressFlag, config, configFlag, configAdapterFlag) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err) + } + + // optionally force a config reload + headers := make(http.Header) + if forceFlag { + headers.Set("Cache-Control", "must-revalidate") + } + + resp, err := AdminAPIRequest(adminAddr, http.MethodPost, "/load", headers, bytes.NewReader(config)) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("sending configuration to instance: %v", err) + } + defer resp.Body.Close() + + return caddy.ExitCodeSuccess, nil +} + +func cmdVersion(_ Flags) (int, error) { + _, full := caddy.Version() + fmt.Println(full) + return caddy.ExitCodeSuccess, nil +} + +func cmdBuildInfo(_ Flags) (int, error) { + bi, ok := debug.ReadBuildInfo() + if !ok { + return caddy.ExitCodeFailedStartup, fmt.Errorf("no build information") + } + fmt.Println(bi) + return caddy.ExitCodeSuccess, nil +} + +func cmdListModules(fl Flags) (int, error) { + packages := fl.Bool("packages") + versions := fl.Bool("versions") + skipStandard := fl.Bool("skip-standard") + + printModuleInfo := func(mi moduleInfo) { + fmt.Print(mi.caddyModuleID) + if versions && mi.goModule != nil { + fmt.Print(" " + mi.goModule.Version) + } + if packages && mi.goModule != nil { + fmt.Print(" " + mi.goModule.Path) + if mi.goModule.Replace != nil { + fmt.Print(" => " + mi.goModule.Replace.Path) + } + } + if mi.err != nil { + fmt.Printf(" [%v]", mi.err) + } + fmt.Println() + } + + // organize modules by whether they come with the standard distribution + standard, nonstandard, unknown, err := getModules() + if err != nil { + // oh well, just print the module IDs and exit + for _, m := range caddy.Modules() { + fmt.Println(m) + } + return caddy.ExitCodeSuccess, nil + } + + // Standard modules (always shipped with Caddy) + if !skipStandard { + if len(standard) > 0 { + for _, mod := range standard { + printModuleInfo(mod) + } + } + fmt.Printf("\n Standard modules: %d\n", len(standard)) + } + + // Non-standard modules (third party plugins) + if len(nonstandard) > 0 { + if len(standard) > 0 && !skipStandard { + fmt.Println() + } + for _, mod := range nonstandard { + printModuleInfo(mod) + } + } + fmt.Printf("\n Non-standard modules: %d\n", len(nonstandard)) + + // Unknown modules (couldn't get Caddy module info) + if len(unknown) > 0 { + if (len(standard) > 0 && !skipStandard) || len(nonstandard) > 0 { + fmt.Println() + } + for _, mod := range unknown { + printModuleInfo(mod) + } + } + fmt.Printf("\n Unknown modules: %d\n", len(unknown)) + + return caddy.ExitCodeSuccess, nil +} + +func cmdEnviron(fl Flags) (int, error) { + // load all additional envs as soon as possible + err := handleEnvFileFlag(fl) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + printEnvironment() + return caddy.ExitCodeSuccess, nil +} + +func cmdAdaptConfig(fl Flags) (int, error) { + inputFlag := fl.String("config") + adapterFlag := fl.String("adapter") + prettyFlag := fl.Bool("pretty") + validateFlag := fl.Bool("validate") + + var err error + inputFlag, err = configFileWithRespectToDefault(caddy.Log(), inputFlag) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + // load all additional envs as soon as possible + err = handleEnvFileFlag(fl) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + if adapterFlag == "" { + return caddy.ExitCodeFailedStartup, + fmt.Errorf("adapter name is required (use --adapt flag or leave unspecified for default)") + } + + cfgAdapter := caddyconfig.GetAdapter(adapterFlag) + if cfgAdapter == nil { + return caddy.ExitCodeFailedStartup, + fmt.Errorf("unrecognized config adapter: %s", adapterFlag) + } + + input, err := os.ReadFile(inputFlag) + if err != nil { + return caddy.ExitCodeFailedStartup, + fmt.Errorf("reading input file: %v", err) + } + + opts := map[string]any{"filename": inputFlag} + + adaptedConfig, warnings, err := cfgAdapter.Adapt(input, opts) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + if prettyFlag { + var prettyBuf bytes.Buffer + err = json.Indent(&prettyBuf, adaptedConfig, "", "\t") + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + adaptedConfig = prettyBuf.Bytes() + } + + // print result to stdout + fmt.Println(string(adaptedConfig)) + + // print warnings to stderr + for _, warn := range warnings { + msg := warn.Message + if warn.Directive != "" { + msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message) + } + caddy.Log().Named(adapterFlag).Warn(msg, + zap.String("file", warn.File), + zap.Int("line", warn.Line)) + } + + // validate output if requested + if validateFlag { + var cfg *caddy.Config + err = caddy.StrictUnmarshalJSON(adaptedConfig, &cfg) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("decoding config: %v", err) + } + err = caddy.Validate(cfg) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("validation: %v", err) + } + } + + return caddy.ExitCodeSuccess, nil +} + +func cmdValidateConfig(fl Flags) (int, error) { + configFlag := fl.String("config") + adapterFlag := fl.String("adapter") + + // load all additional envs as soon as possible + err := handleEnvFileFlag(fl) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + // use default config and ensure a config file is specified + configFlag, err = configFileWithRespectToDefault(caddy.Log(), configFlag) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + if configFlag == "" { + return caddy.ExitCodeFailedStartup, + fmt.Errorf("input file required when there is no Caddyfile in current directory (use --config flag)") + } + + input, _, err := LoadConfig(configFlag, adapterFlag) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + input = caddy.RemoveMetaFields(input) + + var cfg *caddy.Config + err = caddy.StrictUnmarshalJSON(input, &cfg) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("decoding config: %v", err) + } + + err = caddy.Validate(cfg) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + fmt.Println("Valid configuration") + + return caddy.ExitCodeSuccess, nil +} + +func cmdFmt(fl Flags) (int, error) { + configFile := fl.Arg(0) + configFlag := fl.String("config") + if (len(fl.Args()) > 1) || (configFlag != "" && configFile != "") { + return caddy.ExitCodeFailedStartup, fmt.Errorf("fmt does not support multiple files %s %s", configFlag, strings.Join(fl.Args(), " ")) + } + if configFile == "" && configFlag == "" { + configFile = "Caddyfile" + } else if configFile == "" { + configFile = configFlag + } + // as a special case, read from stdin if the file name is "-" + if configFile == "-" { + input, err := io.ReadAll(os.Stdin) + if err != nil { + return caddy.ExitCodeFailedStartup, + fmt.Errorf("reading stdin: %v", err) + } + fmt.Print(string(caddyfile.Format(input))) + return caddy.ExitCodeSuccess, nil + } + + input, err := os.ReadFile(configFile) + if err != nil { + return caddy.ExitCodeFailedStartup, + fmt.Errorf("reading input file: %v", err) + } + + output := caddyfile.Format(input) + + if fl.Bool("overwrite") { + if err := os.WriteFile(configFile, output, 0o600); err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("overwriting formatted file: %v", err) + } + return caddy.ExitCodeSuccess, nil + } + + if fl.Bool("diff") { + diff := difflib.Diff( + strings.Split(string(input), "\n"), + strings.Split(string(output), "\n")) + for _, d := range diff { + switch d.Delta { + case difflib.Common: + fmt.Printf(" %s\n", d.Payload) + case difflib.LeftOnly: + fmt.Printf("- %s\n", d.Payload) + case difflib.RightOnly: + fmt.Printf("+ %s\n", d.Payload) + } + } + } else { + fmt.Print(string(output)) + } + + if warning, diff := caddyfile.FormattingDifference(configFile, input); diff { + return caddy.ExitCodeFailedStartup, fmt.Errorf(`%s:%d: Caddyfile input is not formatted; Tip: use '--overwrite' to update your Caddyfile in-place instead of previewing it. Consult '--help' for more options`, + warning.File, + warning.Line, + ) + } + + return caddy.ExitCodeSuccess, nil +} + +// handleEnvFileFlag loads the environment variables from the given --envfile +// flag if specified. This should be called as early in the command function. +func handleEnvFileFlag(fl Flags) error { + var err error + var envfileFlag []string + envfileFlag, err = fl.GetStringSlice("envfile") + if err != nil { + return fmt.Errorf("reading envfile flag: %v", err) + } + + for _, envfile := range envfileFlag { + if err := loadEnvFromFile(envfile); err != nil { + return fmt.Errorf("loading additional environment variables: %v", err) + } + } + + return nil +} + +// AdminAPIRequest makes an API request according to the CLI flags given, +// with the given HTTP method and request URI. If body is non-nil, it will +// be assumed to be Content-Type application/json. The caller should close +// the response body. Should only be used by Caddy CLI commands which +// need to interact with a running instance of Caddy via the admin API. +func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io.Reader) (*http.Response, error) { + parsedAddr, err := caddy.ParseNetworkAddress(adminAddr) + if err != nil || parsedAddr.PortRangeSize() > 1 { + return nil, fmt.Errorf("invalid admin address %s: %v", adminAddr, err) + } + origin := "http://" + parsedAddr.JoinHostPort(0) + if parsedAddr.IsUnixNetwork() { + origin = "http://127.0.0.1" // bogus host is a hack so that http.NewRequest() is happy + + // the unix address at this point might still contain the optional + // unix socket permissions, which are part of the address/host. + // those need to be removed first, as they aren't part of the + // resulting unix file path + addr, _, err := internal.SplitUnixSocketPermissionsBits(parsedAddr.Host) + if err != nil { + return nil, err + } + parsedAddr.Host = addr + } else if parsedAddr.IsFdNetwork() { + origin = "http://127.0.0.1" + } + + // form the request + req, err := http.NewRequest(method, origin+uri, body) + if err != nil { + return nil, fmt.Errorf("making request: %v", err) + } + if parsedAddr.IsUnixNetwork() || parsedAddr.IsFdNetwork() { + // We used to conform to RFC 2616 Section 14.26 which requires + // an empty host header when there is no host, as is the case + // with unix sockets and socket fds. However, Go required a + // Host value so we used a hack of a space character as the host + // (it would see the Host was non-empty, then trim the space later). + // As of Go 1.20.6 (July 2023), this hack no longer works. See: + // https://github.com/golang/go/issues/60374 + // See also the discussion here: + // https://github.com/golang/go/issues/61431 + // + // After that, we now require a Host value of either 127.0.0.1 + // or ::1 if one is set. Above I choose to use 127.0.0.1. Even + // though the value should be completely irrelevant (it could be + // "srldkjfsd"), if for some reason the Host *is* used, at least + // we can have some reasonable assurance it will stay on the local + // machine and that browsers, if they ever allow access to unix + // sockets, can still enforce CORS, ensuring it is still coming + // from the local machine. + } else { + req.Header.Set("Origin", origin) + } + if body != nil { + req.Header.Set("Content-Type", "application/json") + } + for k, v := range headers { + req.Header[k] = v + } + + // make an HTTP client that dials our network type, since admin + // endpoints aren't always TCP, which is what the default transport + // expects; reuse is not of particular concern here + client := http.Client{ + Transport: &http.Transport{ + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { + return net.Dial(parsedAddr.Network, parsedAddr.JoinHostPort(0)) + }, + }, + } + + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("performing request: %v", err) + } + + // if it didn't work, let the user know + if resp.StatusCode >= 400 { + respBody, err := io.ReadAll(io.LimitReader(resp.Body, 1024*1024*2)) + if err != nil { + return nil, fmt.Errorf("HTTP %d: reading error message: %v", resp.StatusCode, err) + } + return nil, fmt.Errorf("caddy responded with error: HTTP %d: %s", resp.StatusCode, respBody) + } + + return resp, nil +} + +// DetermineAdminAPIAddress determines which admin API endpoint address should +// be used based on the inputs. By priority: if `address` is specified, then +// it is returned; if `config` is specified, then that config will be used for +// finding the admin address; if `configFile` (and `configAdapter`) are specified, +// then that config will be loaded to find the admin address; otherwise, the +// default admin listen address will be returned. +func DetermineAdminAPIAddress(address string, config []byte, configFile, configAdapter string) (string, error) { + // Prefer the address if specified and non-empty + if address != "" { + return address, nil + } + + // Try to load the config from file if specified, with the given adapter name + if configFile != "" { + var loadedConfigFile string + var err error + + // use the provided loaded config if non-empty + // otherwise, load it from the specified file/adapter + loadedConfig := config + if len(loadedConfig) == 0 { + // get the config in caddy's native format + loadedConfig, loadedConfigFile, err = LoadConfig(configFile, configAdapter) + if err != nil { + return "", err + } + if loadedConfigFile == "" { + return "", fmt.Errorf("no config file to load; either use --config flag or ensure Caddyfile exists in current directory") + } + } + + // get the address of the admin listener from the config + if len(loadedConfig) > 0 { + var tmpStruct struct { + Admin caddy.AdminConfig `json:"admin"` + } + err := json.Unmarshal(loadedConfig, &tmpStruct) + if err != nil { + return "", fmt.Errorf("unmarshaling admin listener address from config: %v", err) + } + if tmpStruct.Admin.Listen != "" { + return tmpStruct.Admin.Listen, nil + } + } + } + + // Fallback to the default listen address otherwise + return caddy.DefaultAdminListen, nil +} + +// configFileWithRespectToDefault returns the filename to use for loading the config, based +// on whether a config file is already specified and a supported default config file exists. +func configFileWithRespectToDefault(logger *zap.Logger, configFile string) (string, error) { + const defaultCaddyfile = "Caddyfile" + + // if no input file was specified, try a default Caddyfile if the Caddyfile adapter is plugged in + if configFile == "" && caddyconfig.GetAdapter("caddyfile") != nil { + _, err := os.Stat(defaultCaddyfile) + if err == nil { + // default Caddyfile exists + if logger != nil { + logger.Info("using adjacent Caddyfile") + } + return defaultCaddyfile, nil + } + if !errors.Is(err, fs.ErrNotExist) { + // problem checking + return configFile, fmt.Errorf("checking if default Caddyfile exists: %v", err) + } + } + + // default config file does not exist or is irrelevant + return configFile, nil +} + +type moduleInfo struct { + caddyModuleID string + goModule *debug.Module + err error +} diff --git a/cmd/commands.go b/cmd/commands.go new file mode 100644 index 00000000000..259dd358fee --- /dev/null +++ b/cmd/commands.go @@ -0,0 +1,575 @@ +// 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 caddycmd + +import ( + "flag" + "fmt" + "os" + "regexp" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/cobra/doc" + + "github.com/caddyserver/caddy/v2" +) + +// Command represents a subcommand. Name, Func, +// and Short are required. +type Command struct { + // The name of the subcommand. Must conform to the + // format described by the RegisterCommand() godoc. + // Required. + Name string + + // Usage is a brief message describing the syntax of + // the subcommand's flags and args. Use [] to indicate + // optional parameters and <> to enclose literal values + // intended to be replaced by the user. Do not prefix + // the string with "caddy" or the name of the command + // since these will be prepended for you; only include + // the actual parameters for this command. + Usage string + + // Short is a one-line message explaining what the + // command does. Should not end with punctuation. + // Required. + Short string + + // Long is the full help text shown to the user. + // Will be trimmed of whitespace on both ends before + // being printed. + Long string + + // Flags is the flagset for command. + // This is ignored if CobraFunc is set. + Flags *flag.FlagSet + + // Func is a function that executes a subcommand using + // the parsed flags. It returns an exit code and any + // associated error. + // Required if CobraFunc is not set. + Func CommandFunc + + // CobraFunc allows further configuration of the command + // via cobra's APIs. If this is set, then Func and Flags + // are ignored, with the assumption that they are set in + // this function. A caddycmd.WrapCommandFuncForCobra helper + // exists to simplify porting CommandFunc to Cobra's RunE. + CobraFunc func(*cobra.Command) +} + +// CommandFunc is a command's function. It runs the +// command and returns the proper exit code along with +// any error that occurred. +type CommandFunc func(Flags) (int, error) + +// Commands returns a list of commands initialised by +// RegisterCommand +func Commands() map[string]Command { + return commands +} + +var commands = make(map[string]Command) + +func init() { + RegisterCommand(Command{ + Name: "start", + Usage: "[--config [--adapter ]] [--envfile ] [--watch] [--pidfile ]", + Short: "Starts the Caddy process in the background and then returns", + Long: ` +Starts the Caddy process, optionally bootstrapped with an initial config file. +This command unblocks after the server starts running or fails to run. + +If --envfile is specified, an environment file with environment variables +in the KEY=VALUE format will be loaded into the Caddy process. + +On Windows, the spawned child process will remain attached to the terminal, so +closing the window will forcefully stop Caddy; to avoid forgetting this, try +using 'caddy run' instead to keep it in the foreground. +`, + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("config", "c", "", "Configuration file") + cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply") + cmd.Flags().StringSliceP("envfile", "", []string{}, "Environment file(s) to load") + cmd.Flags().BoolP("watch", "w", false, "Reload changed config file automatically") + cmd.Flags().StringP("pidfile", "", "", "Path of file to which to write process ID") + cmd.RunE = WrapCommandFuncForCobra(cmdStart) + }, + }) + + RegisterCommand(Command{ + Name: "run", + Usage: "[--config [--adapter ]] [--envfile ] [--environ] [--resume] [--watch] [--pidfile ]", + Short: `Starts the Caddy process and blocks indefinitely`, + Long: ` +Starts the Caddy process, optionally bootstrapped with an initial config file, +and blocks indefinitely until the server is stopped; i.e. runs Caddy in +"daemon" mode (foreground). + +If a config file is specified, it will be applied immediately after the process +is running. If the config file is not in Caddy's native JSON format, you can +specify an adapter with --adapter to adapt the given config file to +Caddy's native format. The config adapter must be a registered module. Any +warnings will be printed to the log, but beware that any adaptation without +errors will immediately be used. If you want to review the results of the +adaptation first, use the 'adapt' subcommand. + +As a special case, if the current working directory has a file called +"Caddyfile" and the caddyfile config adapter is plugged in (default), then +that file will be loaded and used to configure Caddy, even without any command +line flags. + +If --envfile is specified, an environment file with environment variables +in the KEY=VALUE format will be loaded into the Caddy process. + +If --environ is specified, the environment as seen by the Caddy process will +be printed before starting. This is the same as the environ command but does +not quit after printing, and can be useful for troubleshooting. + +The --resume flag will override the --config flag if there is a config auto- +save file. It is not an error if --resume is used and no autosave file exists. + +If --watch is specified, the config file will be loaded automatically after +changes. ⚠️ This can make unintentional config changes easier; only use this +option in a local development environment. +`, + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("config", "c", "", "Configuration file") + cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply") + cmd.Flags().StringSliceP("envfile", "", []string{}, "Environment file(s) to load") + cmd.Flags().BoolP("environ", "e", false, "Print environment") + cmd.Flags().BoolP("resume", "r", false, "Use saved config, if any (and prefer over --config file)") + cmd.Flags().BoolP("watch", "w", false, "Watch config file for changes and reload it automatically") + cmd.Flags().StringP("pidfile", "", "", "Path of file to which to write process ID") + cmd.Flags().StringP("pingback", "", "", "Echo confirmation bytes to this address on success") + cmd.RunE = WrapCommandFuncForCobra(cmdRun) + }, + }) + + RegisterCommand(Command{ + Name: "stop", + Usage: "[--config [--adapter ]] [--address ]", + Short: "Gracefully stops a started Caddy process", + Long: ` +Stops the background Caddy process as gracefully as possible. + +It requires that the admin API is enabled and accessible, since it will +use the API's /stop endpoint. The address of this request can be customized +using the --address flag, or from the given --config, if not the default. +`, + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("config", "c", "", "Configuration file to use to parse the admin address, if --address is not used") + cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply (when --config is used)") + cmd.Flags().StringP("address", "", "", "The address to use to reach the admin API endpoint, if not the default") + cmd.RunE = WrapCommandFuncForCobra(cmdStop) + }, + }) + + RegisterCommand(Command{ + Name: "reload", + Usage: "--config [--adapter ] [--address ]", + Short: "Changes the config of the running Caddy instance", + Long: ` +Gives the running Caddy instance a new configuration. This has the same effect +as POSTing a document to the /load API endpoint, but is convenient for simple +workflows revolving around config files. + +Since the admin endpoint is configurable, the endpoint configuration is loaded +from the --address flag if specified; otherwise it is loaded from the given +config file; otherwise the default is assumed. +`, + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("config", "c", "", "Configuration file (required)") + cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply") + cmd.Flags().StringP("address", "", "", "Address of the administration listener, if different from config") + cmd.Flags().BoolP("force", "f", false, "Force config reload, even if it is the same") + cmd.RunE = WrapCommandFuncForCobra(cmdReload) + }, + }) + + RegisterCommand(Command{ + Name: "version", + Short: "Prints the version", + Long: ` +Prints the version of this Caddy binary. + +Version information must be embedded into the binary at compile-time in +order for Caddy to display anything useful with this command. If Caddy +is built from within a version control repository, the Go command will +embed the revision hash if available. However, if Caddy is built in the +way specified by our online documentation (or by using xcaddy), more +detailed version information is printed as given by Go modules. + +For more details about the full version string, see the Go module +documentation: https://go.dev/doc/modules/version-numbers +`, + Func: cmdVersion, + }) + + RegisterCommand(Command{ + Name: "list-modules", + Usage: "[--packages] [--versions] [--skip-standard]", + Short: "Lists the installed Caddy modules", + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().BoolP("packages", "", false, "Print package paths") + cmd.Flags().BoolP("versions", "", false, "Print version information") + cmd.Flags().BoolP("skip-standard", "s", false, "Skip printing standard modules") + cmd.RunE = WrapCommandFuncForCobra(cmdListModules) + }, + }) + + RegisterCommand(Command{ + Name: "build-info", + Short: "Prints information about this build", + Func: cmdBuildInfo, + }) + + RegisterCommand(Command{ + Name: "environ", + Usage: "[--envfile ]", + Short: "Prints the environment", + Long: ` +Prints the environment as seen by this Caddy process. + +The environment includes variables set in the system. If your Caddy +configuration uses environment variables (e.g. "{env.VARIABLE}") then +this command can be useful for verifying that the variables will have +the values you expect in your config. + +If --envfile is specified, an environment file with environment variables +in the KEY=VALUE format will be loaded into the Caddy process. + +Note that environments may be different depending on how you run Caddy. +Environments for Caddy instances started by service managers such as +systemd are often different than the environment inherited from your +shell or terminal. + +You can also print the environment the same time you use "caddy run" +by adding the "--environ" flag. + +Environments may contain sensitive data. +`, + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringSliceP("envfile", "", []string{}, "Environment file(s) to load") + cmd.RunE = WrapCommandFuncForCobra(cmdEnviron) + }, + }) + + RegisterCommand(Command{ + Name: "adapt", + Usage: "--config [--adapter ] [--pretty] [--validate] [--envfile ]", + Short: "Adapts a configuration to Caddy's native JSON", + Long: ` +Adapts a configuration to Caddy's native JSON format and writes the +output to stdout, along with any warnings to stderr. + +If --pretty is specified, the output will be formatted with indentation +for human readability. + +If --validate is used, the adapted config will be checked for validity. +If the config is invalid, an error will be printed to stderr and a non- +zero exit status will be returned. + +If --envfile is specified, an environment file with environment variables +in the KEY=VALUE format will be loaded into the Caddy process. +`, + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("config", "c", "", "Configuration file to adapt (required)") + cmd.Flags().StringP("adapter", "a", "caddyfile", "Name of config adapter") + cmd.Flags().BoolP("pretty", "p", false, "Format the output for human readability") + cmd.Flags().BoolP("validate", "", false, "Validate the output") + cmd.Flags().StringSliceP("envfile", "", []string{}, "Environment file(s) to load") + cmd.RunE = WrapCommandFuncForCobra(cmdAdaptConfig) + }, + }) + + RegisterCommand(Command{ + Name: "validate", + Usage: "--config [--adapter ] [--envfile ]", + Short: "Tests whether a configuration file is valid", + Long: ` +Loads and provisions the provided config, but does not start running it. +This reveals any errors with the configuration through the loading and +provisioning stages. + +If --envfile is specified, an environment file with environment variables +in the KEY=VALUE format will be loaded into the Caddy process. +`, + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("config", "c", "", "Input configuration file") + cmd.Flags().StringP("adapter", "a", "", "Name of config adapter") + cmd.Flags().StringSliceP("envfile", "", []string{}, "Environment file(s) to load") + cmd.RunE = WrapCommandFuncForCobra(cmdValidateConfig) + }, + }) + + RegisterCommand(Command{ + Name: "storage", + Short: "Commands for working with Caddy's storage (EXPERIMENTAL)", + Long: ` +Allows exporting and importing Caddy's storage contents. The two commands can be +combined in a pipeline to transfer directly from one storage to another: + +$ caddy storage export --config Caddyfile.old --output - | +> caddy storage import --config Caddyfile.new --input - + +The - argument refers to stdout and stdin, respectively. + +NOTE: When importing to or exporting from file_system storage (the default), the command +should be run as the user that owns the associated root path. + +EXPERIMENTAL: May be changed or removed. +`, + CobraFunc: func(cmd *cobra.Command) { + exportCmd := &cobra.Command{ + Use: "export --config --output ", + Short: "Exports storage assets as a tarball", + Long: ` +The contents of the configured storage module (TLS certificates, etc) +are exported via a tarball. + +--output is required, - can be given for stdout. +`, + RunE: WrapCommandFuncForCobra(cmdExportStorage), + } + exportCmd.Flags().StringP("config", "c", "", "Input configuration file (required)") + exportCmd.Flags().StringP("output", "o", "", "Output path") + cmd.AddCommand(exportCmd) + + importCmd := &cobra.Command{ + Use: "import --config --input ", + Short: "Imports storage assets from a tarball.", + Long: ` +Imports storage assets to the configured storage module. The import file must be +a tar archive. + +--input is required, - can be given for stdin. +`, + RunE: WrapCommandFuncForCobra(cmdImportStorage), + } + importCmd.Flags().StringP("config", "c", "", "Configuration file to load (required)") + importCmd.Flags().StringP("input", "i", "", "Tar of assets to load (required)") + cmd.AddCommand(importCmd) + }, + }) + + RegisterCommand(Command{ + Name: "fmt", + Usage: "[--overwrite] [--diff] []", + Short: "Formats a Caddyfile", + Long: ` +Formats the Caddyfile by adding proper indentation and spaces to improve +human readability. It prints the result to stdout. + +If --overwrite is specified, the output will be written to the config file +directly instead of printing it. + +If --diff is specified, the output will be compared against the input, and +lines will be prefixed with '-' and '+' where they differ. Note that +unchanged lines are prefixed with two spaces for alignment, and that this +is not a valid patch format. + +If you wish you use stdin instead of a regular file, use - as the path. +When reading from stdin, the --overwrite flag has no effect: the result +is always printed to stdout. +`, + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("config", "c", "", "Configuration file") + cmd.Flags().BoolP("overwrite", "w", false, "Overwrite the input file with the results") + cmd.Flags().BoolP("diff", "d", false, "Print the differences between the input file and the formatted output") + cmd.RunE = WrapCommandFuncForCobra(cmdFmt) + }, + }) + + RegisterCommand(Command{ + Name: "upgrade", + Short: "Upgrade Caddy (EXPERIMENTAL)", + Long: ` +Downloads an updated Caddy binary with the same modules/plugins at the +latest versions. EXPERIMENTAL: May be changed or removed. +`, + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().BoolP("keep-backup", "k", false, "Keep the backed up binary, instead of deleting it") + cmd.RunE = WrapCommandFuncForCobra(cmdUpgrade) + }, + }) + + RegisterCommand(Command{ + Name: "add-package", + Usage: "", + Short: "Adds Caddy packages (EXPERIMENTAL)", + Long: ` +Downloads an updated Caddy binary with the specified packages (module/plugin) +added, with an optional version specified (e.g., "package@version"). Retains +existing packages. Returns an error if any of the specified packages are already +included. EXPERIMENTAL: May be changed or removed. +`, + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().BoolP("keep-backup", "k", false, "Keep the backed up binary, instead of deleting it") + cmd.RunE = WrapCommandFuncForCobra(cmdAddPackage) + }, + }) + + RegisterCommand(Command{ + Name: "remove-package", + Func: cmdRemovePackage, + Usage: "", + Short: "Removes Caddy packages (EXPERIMENTAL)", + Long: ` +Downloads an updated Caddy binaries without the specified packages (module/plugin). +Returns an error if any of the packages are not included. +EXPERIMENTAL: May be changed or removed. +`, + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().BoolP("keep-backup", "k", false, "Keep the backed up binary, instead of deleting it") + cmd.RunE = WrapCommandFuncForCobra(cmdRemovePackage) + }, + }) + + defaultFactory.Use(func(rootCmd *cobra.Command) { + rootCmd.AddCommand(caddyCmdToCobra(Command{ + Name: "manpage", + Usage: "--directory ", + Short: "Generates the manual pages for Caddy commands", + Long: ` +Generates the manual pages for Caddy commands into the designated directory +tagged into section 8 (System Administration). + +The manual page files are generated into the directory specified by the +argument of --directory. If the directory does not exist, it will be created. +`, + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("directory", "o", "", "The output directory where the manpages are generated") + cmd.RunE = WrapCommandFuncForCobra(func(fl Flags) (int, error) { + dir := strings.TrimSpace(fl.String("directory")) + if dir == "" { + return caddy.ExitCodeFailedQuit, fmt.Errorf("designated output directory and specified section are required") + } + if err := os.MkdirAll(dir, 0o755); err != nil { + return caddy.ExitCodeFailedQuit, err + } + if err := doc.GenManTree(rootCmd, &doc.GenManHeader{ + Title: "Caddy", + Section: "8", // https://en.wikipedia.org/wiki/Man_page#Manual_sections + }, dir); err != nil { + return caddy.ExitCodeFailedQuit, err + } + return caddy.ExitCodeSuccess, nil + }) + }, + })) + + // source: https://github.com/spf13/cobra/blob/main/shell_completions.md + rootCmd.AddCommand(&cobra.Command{ + Use: "completion [bash|zsh|fish|powershell]", + Short: "Generate completion script", + Long: fmt.Sprintf(`To load completions: + + Bash: + + $ source <(%[1]s completion bash) + + # To load completions for each session, execute once: + # Linux: + $ %[1]s completion bash > /etc/bash_completion.d/%[1]s + # macOS: + $ %[1]s completion bash > $(brew --prefix)/etc/bash_completion.d/%[1]s + + Zsh: + + # If shell completion is not already enabled in your environment, + # you will need to enable it. You can execute the following once: + + $ echo "autoload -U compinit; compinit" >> ~/.zshrc + + # To load completions for each session, execute once: + $ %[1]s completion zsh > "${fpath[1]}/_%[1]s" + + # You will need to start a new shell for this setup to take effect. + + fish: + + $ %[1]s completion fish | source + + # To load completions for each session, execute once: + $ %[1]s completion fish > ~/.config/fish/completions/%[1]s.fish + + PowerShell: + + PS> %[1]s completion powershell | Out-String | Invoke-Expression + + # To load completions for every new session, run: + PS> %[1]s completion powershell > %[1]s.ps1 + # and source this file from your PowerShell profile. + `, rootCmd.Root().Name()), + DisableFlagsInUseLine: true, + ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, + Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), + RunE: func(cmd *cobra.Command, args []string) error { + switch args[0] { + case "bash": + return cmd.Root().GenBashCompletion(os.Stdout) + case "zsh": + return cmd.Root().GenZshCompletion(os.Stdout) + case "fish": + return cmd.Root().GenFishCompletion(os.Stdout, true) + case "powershell": + return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout) + default: + return fmt.Errorf("unrecognized shell: %s", args[0]) + } + }, + }) + }) +} + +// RegisterCommand registers the command cmd. +// cmd.Name must be unique and conform to the +// following format: +// +// - lowercase +// - alphanumeric and hyphen characters only +// - cannot start or end with a hyphen +// - hyphen cannot be adjacent to another hyphen +// +// This function panics if the name is already registered, +// if the name does not meet the described format, or if +// any of the fields are missing from cmd. +// +// This function should be used in init(). +func RegisterCommand(cmd Command) { + if cmd.Name == "" { + panic("command name is required") + } + if cmd.Func == nil && cmd.CobraFunc == nil { + panic("command function missing") + } + if cmd.Short == "" { + panic("command short string is required") + } + if _, exists := commands[cmd.Name]; exists { + panic("command already registered: " + cmd.Name) + } + if !commandNameRegex.MatchString(cmd.Name) { + panic("invalid command name") + } + defaultFactory.Use(func(rootCmd *cobra.Command) { + rootCmd.AddCommand(caddyCmdToCobra(cmd)) + }) +} + +var commandNameRegex = regexp.MustCompile(`^[a-z0-9]$|^([a-z0-9]+-?[a-z0-9]*)+[a-z0-9]$`) diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 00000000000..c41738be045 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,502 @@ +// 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 caddycmd + +import ( + "bufio" + "bytes" + "encoding/json" + "errors" + "flag" + "fmt" + "io" + "io/fs" + "log" + "log/slog" + "net" + "os" + "path/filepath" + "runtime" + "runtime/debug" + "strconv" + "strings" + "time" + + "github.com/KimMachineGun/automemlimit/memlimit" + "github.com/caddyserver/certmagic" + "github.com/spf13/pflag" + "go.uber.org/automaxprocs/maxprocs" + "go.uber.org/zap" + "go.uber.org/zap/exp/zapslog" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" +) + +func init() { + // set a fitting User-Agent for ACME requests + version, _ := caddy.Version() + cleanModVersion := strings.TrimPrefix(version, "v") + ua := "Caddy/" + cleanModVersion + if uaEnv, ok := os.LookupEnv("USERAGENT"); ok { + ua = uaEnv + " " + ua + } + certmagic.UserAgent = ua + + // by using Caddy, user indicates agreement to CA terms + // (very important, as Caddy is often non-interactive + // and thus ACME account creation will fail!) + certmagic.DefaultACME.Agreed = true +} + +// Main implements the main function of the caddy command. +// Call this if Caddy is to be the main() of your program. +func Main() { + if len(os.Args) == 0 { + fmt.Printf("[FATAL] no arguments provided by OS; args[0] must be command\n") + os.Exit(caddy.ExitCodeFailedStartup) + } + + logger := caddy.Log() + + // Configure the maximum number of CPUs to use to match the Linux container quota (if any) + // See https://pkg.go.dev/runtime#GOMAXPROCS + undo, err := maxprocs.Set(maxprocs.Logger(logger.Sugar().Infof)) + defer undo() + if err != nil { + caddy.Log().Warn("failed to set GOMAXPROCS", zap.Error(err)) + } + + // Configure the maximum memory to use to match the Linux container quota (if any) or system memory + // See https://pkg.go.dev/runtime/debug#SetMemoryLimit + _, _ = memlimit.SetGoMemLimitWithOpts( + memlimit.WithLogger( + slog.New(zapslog.NewHandler(logger.Core())), + ), + memlimit.WithProvider( + memlimit.ApplyFallback( + memlimit.FromCgroup, + memlimit.FromSystem, + ), + ), + ) + + if err := defaultFactory.Build().Execute(); err != nil { + var exitError *exitError + if errors.As(err, &exitError) { + os.Exit(exitError.ExitCode) + } + os.Exit(1) + } +} + +// handlePingbackConn reads from conn and ensures it matches +// the bytes in expect, or returns an error if it doesn't. +func handlePingbackConn(conn net.Conn, expect []byte) error { + defer conn.Close() + confirmationBytes, err := io.ReadAll(io.LimitReader(conn, 32)) + if err != nil { + return err + } + if !bytes.Equal(confirmationBytes, expect) { + return fmt.Errorf("wrong confirmation: %x", confirmationBytes) + } + return nil +} + +// LoadConfig loads the config from configFile and adapts it +// using adapterName. If adapterName is specified, configFile +// must be also. If no configFile is specified, it tries +// loading a default config file. The lack of a config file is +// not treated as an error, but false will be returned if +// there is no config available. It prints any warnings to stderr, +// and returns the resulting JSON config bytes along with +// the name of the loaded config file (if any). +func LoadConfig(configFile, adapterName string) ([]byte, string, error) { + return loadConfigWithLogger(caddy.Log(), configFile, adapterName) +} + +func isCaddyfile(configFile, adapterName string) (bool, error) { + if adapterName == "caddyfile" { + return true, nil + } + + // as a special case, if a config file starts with "caddyfile" or + // has a ".caddyfile" extension, and no adapter is specified, and + // no adapter module name matches the extension, assume + // caddyfile adapter for convenience + baseConfig := strings.ToLower(filepath.Base(configFile)) + baseConfigExt := filepath.Ext(baseConfig) + startsOrEndsInCaddyfile := strings.HasPrefix(baseConfig, "caddyfile") || strings.HasSuffix(baseConfig, ".caddyfile") + + if baseConfigExt == ".json" { + return false, nil + } + + // If the adapter is not specified, + // the config file starts with "caddyfile", + // the config file has an extension, + // and isn't a JSON file (e.g. Caddyfile.yaml), + // then we don't know what the config format is. + if adapterName == "" && startsOrEndsInCaddyfile { + return true, nil + } + + // adapter is not empty, + // adapter is not "caddyfile", + // extension is not ".json", + // extension is not ".caddyfile" + // file does not start with "Caddyfile" + return false, nil +} + +func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([]byte, string, error) { + // if no logger is provided, use a nop logger + // just so we don't have to check for nil + if logger == nil { + logger = zap.NewNop() + } + + // specifying an adapter without a config file is ambiguous + if adapterName != "" && configFile == "" { + return nil, "", fmt.Errorf("cannot adapt config without config file (use --config)") + } + + // load initial config and adapter + var config []byte + var cfgAdapter caddyconfig.Adapter + var err error + if configFile != "" { + if configFile == "-" { + config, err = io.ReadAll(os.Stdin) + if err != nil { + return nil, "", fmt.Errorf("reading config from stdin: %v", err) + } + logger.Info("using config from stdin") + } else { + config, err = os.ReadFile(configFile) + if err != nil { + return nil, "", fmt.Errorf("reading config from file: %v", err) + } + logger.Info("using config from file", zap.String("file", configFile)) + } + } else if adapterName == "" { + // if the Caddyfile adapter is plugged in, we can try using an + // adjacent Caddyfile by default + cfgAdapter = caddyconfig.GetAdapter("caddyfile") + if cfgAdapter != nil { + config, err = os.ReadFile("Caddyfile") + if errors.Is(err, fs.ErrNotExist) { + // okay, no default Caddyfile; pretend like this never happened + cfgAdapter = nil + } else if err != nil { + // default Caddyfile exists, but error reading it + return nil, "", fmt.Errorf("reading default Caddyfile: %v", err) + } else { + // success reading default Caddyfile + configFile = "Caddyfile" + logger.Info("using adjacent Caddyfile") + } + } + } + + if yes, err := isCaddyfile(configFile, adapterName); yes { + adapterName = "caddyfile" + } else if err != nil { + return nil, "", err + } + + // load config adapter + if adapterName != "" { + cfgAdapter = caddyconfig.GetAdapter(adapterName) + if cfgAdapter == nil { + return nil, "", fmt.Errorf("unrecognized config adapter: %s", adapterName) + } + } + + // adapt config + if cfgAdapter != nil { + adaptedConfig, warnings, err := cfgAdapter.Adapt(config, map[string]any{ + "filename": configFile, + }) + if err != nil { + return nil, "", fmt.Errorf("adapting config using %s: %v", adapterName, err) + } + logger.Info("adapted config to JSON", zap.String("adapter", adapterName)) + for _, warn := range warnings { + msg := warn.Message + if warn.Directive != "" { + msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message) + } + logger.Warn(msg, + zap.String("adapter", adapterName), + zap.String("file", warn.File), + zap.Int("line", warn.Line)) + } + config = adaptedConfig + } else if len(config) != 0 { + // validate that the config is at least valid JSON + err = json.Unmarshal(config, new(any)) + if err != nil { + return nil, "", fmt.Errorf("config is not valid JSON: %v; did you mean to use a config adapter (the --adapter flag)?", err) + } + } + + return config, configFile, nil +} + +// watchConfigFile watches the config file at filename for changes +// and reloads the config if the file was updated. This function +// blocks indefinitely; it only quits if the poller has errors for +// long enough time. The filename passed in must be the actual +// config file used, not one to be discovered. +// Each second the config files is loaded and parsed into an object +// and is compared to the last config object that was loaded +func watchConfigFile(filename, adapterName string) { + defer func() { + if err := recover(); err != nil { + log.Printf("[PANIC] watching config file: %v\n%s", err, debug.Stack()) + } + }() + + // make our logger; since config reloads can change the + // default logger, we need to get it dynamically each time + logger := func() *zap.Logger { + return caddy.Log(). + Named("watcher"). + With(zap.String("config_file", filename)) + } + + // get current config + lastCfg, _, err := loadConfigWithLogger(nil, filename, adapterName) + if err != nil { + logger().Error("unable to load latest config", zap.Error(err)) + return + } + + logger().Info("watching config file for changes") + + // begin poller + //nolint:staticcheck + for range time.Tick(1 * time.Second) { + // get current config + newCfg, _, err := loadConfigWithLogger(nil, filename, adapterName) + if err != nil { + logger().Error("unable to load latest config", zap.Error(err)) + return + } + + // if it hasn't changed, nothing to do + if bytes.Equal(lastCfg, newCfg) { + continue + } + logger().Info("config file changed; reloading") + + // remember the current config + lastCfg = newCfg + + // apply the updated config + err = caddy.Load(lastCfg, false) + if err != nil { + logger().Error("applying latest config", zap.Error(err)) + continue + } + } +} + +// Flags wraps a FlagSet so that typed values +// from flags can be easily retrieved. +type Flags struct { + *pflag.FlagSet +} + +// String returns the string representation of the +// flag given by name. It panics if the flag is not +// in the flag set. +func (f Flags) String(name string) string { + return f.FlagSet.Lookup(name).Value.String() +} + +// Bool returns the boolean representation of the +// flag given by name. It returns false if the flag +// is not a boolean type. It panics if the flag is +// not in the flag set. +func (f Flags) Bool(name string) bool { + val, _ := strconv.ParseBool(f.String(name)) + return val +} + +// Int returns the integer representation of the +// flag given by name. It returns 0 if the flag +// is not an integer type. It panics if the flag is +// not in the flag set. +func (f Flags) Int(name string) int { + val, _ := strconv.ParseInt(f.String(name), 0, strconv.IntSize) + return int(val) +} + +// Float64 returns the float64 representation of the +// flag given by name. It returns false if the flag +// is not a float64 type. It panics if the flag is +// not in the flag set. +func (f Flags) Float64(name string) float64 { + val, _ := strconv.ParseFloat(f.String(name), 64) + return val +} + +// Duration returns the duration representation of the +// flag given by name. It returns false if the flag +// is not a duration type. It panics if the flag is +// not in the flag set. +func (f Flags) Duration(name string) time.Duration { + val, _ := caddy.ParseDuration(f.String(name)) + return val +} + +func loadEnvFromFile(envFile string) error { + file, err := os.Open(envFile) + if err != nil { + return fmt.Errorf("reading environment file: %v", err) + } + defer file.Close() + + envMap, err := parseEnvFile(file) + if err != nil { + return fmt.Errorf("parsing environment file: %v", err) + } + + for k, v := range envMap { + // do not overwrite existing environment variables + _, exists := os.LookupEnv(k) + if !exists { + if err := os.Setenv(k, v); err != nil { + return fmt.Errorf("setting environment variables: %v", err) + } + } + } + + // Update the storage paths to ensure they have the proper + // value after loading a specified env file. + caddy.ConfigAutosavePath = filepath.Join(caddy.AppConfigDir(), "autosave.json") + caddy.DefaultStorage = &certmagic.FileStorage{Path: caddy.AppDataDir()} + + return nil +} + +// parseEnvFile parses an env file from KEY=VALUE format. +// It's pretty naive. Limited value quotation is supported, +// but variable and command expansions are not supported. +func parseEnvFile(envInput io.Reader) (map[string]string, error) { + envMap := make(map[string]string) + + scanner := bufio.NewScanner(envInput) + var lineNumber int + + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + lineNumber++ + + // skip empty lines and lines starting with comment + if line == "" || strings.HasPrefix(line, "#") { + continue + } + + // split line into key and value + before, after, isCut := strings.Cut(line, "=") + if !isCut { + return nil, fmt.Errorf("can't parse line %d; line should be in KEY=VALUE format", lineNumber) + } + key, val := before, after + + // sometimes keys are prefixed by "export " so file can be sourced in bash; ignore it here + key = strings.TrimPrefix(key, "export ") + + // validate key and value + if key == "" { + return nil, fmt.Errorf("missing or empty key on line %d", lineNumber) + } + if strings.Contains(key, " ") { + return nil, fmt.Errorf("invalid key on line %d: contains whitespace: %s", lineNumber, key) + } + if strings.HasPrefix(val, " ") || strings.HasPrefix(val, "\t") { + return nil, fmt.Errorf("invalid value on line %d: whitespace before value: '%s'", lineNumber, val) + } + + // remove any trailing comment after value + if commentStart, _, found := strings.Cut(val, "#"); found { + val = strings.TrimRight(commentStart, " \t") + } + + // quoted value: support newlines + if strings.HasPrefix(val, `"`) || strings.HasPrefix(val, "'") { + quote := string(val[0]) + for !(strings.HasSuffix(line, quote) && !strings.HasSuffix(line, `\`+quote)) { + val = strings.ReplaceAll(val, `\`+quote, quote) + if !scanner.Scan() { + break + } + lineNumber++ + line = strings.ReplaceAll(scanner.Text(), `\`+quote, quote) + val += "\n" + line + } + val = strings.TrimPrefix(val, quote) + val = strings.TrimSuffix(val, quote) + } + + envMap[key] = val + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + return envMap, nil +} + +func printEnvironment() { + _, version := caddy.Version() + fmt.Printf("caddy.HomeDir=%s\n", caddy.HomeDir()) + fmt.Printf("caddy.AppDataDir=%s\n", caddy.AppDataDir()) + fmt.Printf("caddy.AppConfigDir=%s\n", caddy.AppConfigDir()) + fmt.Printf("caddy.ConfigAutosavePath=%s\n", caddy.ConfigAutosavePath) + fmt.Printf("caddy.Version=%s\n", version) + fmt.Printf("runtime.GOOS=%s\n", runtime.GOOS) + fmt.Printf("runtime.GOARCH=%s\n", runtime.GOARCH) + fmt.Printf("runtime.Compiler=%s\n", runtime.Compiler) + fmt.Printf("runtime.NumCPU=%d\n", runtime.NumCPU()) + fmt.Printf("runtime.GOMAXPROCS=%d\n", runtime.GOMAXPROCS(0)) + fmt.Printf("runtime.Version=%s\n", runtime.Version()) + cwd, err := os.Getwd() + if err != nil { + cwd = fmt.Sprintf("", err) + } + fmt.Printf("os.Getwd=%s\n\n", cwd) + for _, v := range os.Environ() { + fmt.Println(v) + } +} + +// StringSlice is a flag.Value that enables repeated use of a string flag. +type StringSlice []string + +func (ss StringSlice) String() string { return "[" + strings.Join(ss, ", ") + "]" } + +func (ss *StringSlice) Set(value string) error { + *ss = append(*ss, value) + return nil +} + +// Interface guard +var _ flag.Value = (*StringSlice)(nil) diff --git a/cmd/main_test.go b/cmd/main_test.go new file mode 100644 index 00000000000..3b2412c57e9 --- /dev/null +++ b/cmd/main_test.go @@ -0,0 +1,280 @@ +package caddycmd + +import ( + "reflect" + "strings" + "testing" +) + +func TestParseEnvFile(t *testing.T) { + for i, tc := range []struct { + input string + expect map[string]string + shouldErr bool + }{ + { + input: `KEY=value`, + expect: map[string]string{ + "KEY": "value", + }, + }, + { + input: ` + KEY=value + OTHER_KEY=Some Value + `, + expect: map[string]string{ + "KEY": "value", + "OTHER_KEY": "Some Value", + }, + }, + { + input: ` + KEY=value + INVALID KEY=asdf + OTHER_KEY=Some Value + `, + shouldErr: true, + }, + { + input: ` + KEY=value + SIMPLE_QUOTED="quoted value" + OTHER_KEY=Some Value + `, + expect: map[string]string{ + "KEY": "value", + "SIMPLE_QUOTED": "quoted value", + "OTHER_KEY": "Some Value", + }, + }, + { + input: ` + KEY=value + NEWLINES="foo + bar" + OTHER_KEY=Some Value + `, + expect: map[string]string{ + "KEY": "value", + "NEWLINES": "foo\n\tbar", + "OTHER_KEY": "Some Value", + }, + }, + { + input: ` + KEY=value + ESCAPED="\"escaped quotes\" +here" + OTHER_KEY=Some Value + `, + expect: map[string]string{ + "KEY": "value", + "ESCAPED": "\"escaped quotes\"\nhere", + "OTHER_KEY": "Some Value", + }, + }, + { + input: ` + export KEY=value + OTHER_KEY=Some Value + `, + expect: map[string]string{ + "KEY": "value", + "OTHER_KEY": "Some Value", + }, + }, + { + input: ` + =value + OTHER_KEY=Some Value + `, + shouldErr: true, + }, + { + input: ` + EMPTY= + OTHER_KEY=Some Value + `, + expect: map[string]string{ + "EMPTY": "", + "OTHER_KEY": "Some Value", + }, + }, + { + input: ` + EMPTY="" + OTHER_KEY=Some Value + `, + expect: map[string]string{ + "EMPTY": "", + "OTHER_KEY": "Some Value", + }, + }, + { + input: ` + KEY=value + #OTHER_KEY=Some Value + `, + expect: map[string]string{ + "KEY": "value", + }, + }, + { + input: ` + KEY=value + COMMENT=foo bar # some comment here + OTHER_KEY=Some Value + `, + expect: map[string]string{ + "KEY": "value", + "COMMENT": "foo bar", + "OTHER_KEY": "Some Value", + }, + }, + { + input: ` + KEY=value + WHITESPACE= foo + OTHER_KEY=Some Value + `, + shouldErr: true, + }, + { + input: ` + KEY=value + WHITESPACE=" foo bar " + OTHER_KEY=Some Value + `, + expect: map[string]string{ + "KEY": "value", + "WHITESPACE": " foo bar ", + "OTHER_KEY": "Some Value", + }, + }, + } { + actual, err := parseEnvFile(strings.NewReader(tc.input)) + if err != nil && !tc.shouldErr { + t.Errorf("Test %d: Got error but shouldn't have: %v", i, err) + } + if err == nil && tc.shouldErr { + t.Errorf("Test %d: Did not get error but should have", i) + } + if tc.shouldErr { + continue + } + if !reflect.DeepEqual(tc.expect, actual) { + t.Errorf("Test %d: Expected %v but got %v", i, tc.expect, actual) + } + } +} + +func Test_isCaddyfile(t *testing.T) { + type args struct { + configFile string + adapterName string + } + tests := []struct { + name string + args args + want bool + wantErr bool + }{ + { + name: "bare Caddyfile without adapter", + args: args{ + configFile: "Caddyfile", + adapterName: "", + }, + want: true, + wantErr: false, + }, + { + name: "local Caddyfile without adapter", + args: args{ + configFile: "./Caddyfile", + adapterName: "", + }, + want: true, + wantErr: false, + }, + { + name: "local caddyfile with adapter", + args: args{ + configFile: "./Caddyfile", + adapterName: "caddyfile", + }, + want: true, + wantErr: false, + }, + { + name: "ends with .caddyfile with adapter", + args: args{ + configFile: "./conf.caddyfile", + adapterName: "caddyfile", + }, + want: true, + wantErr: false, + }, + { + name: "ends with .caddyfile without adapter", + args: args{ + configFile: "./conf.caddyfile", + adapterName: "", + }, + want: true, + wantErr: false, + }, + { + name: "config is Caddyfile.yaml with adapter", + args: args{ + configFile: "./Caddyfile.yaml", + adapterName: "yaml", + }, + want: false, + wantErr: false, + }, + { + + name: "json is not caddyfile but not error", + args: args{ + configFile: "./Caddyfile.json", + adapterName: "", + }, + want: false, + wantErr: false, + }, + { + + name: "prefix of Caddyfile and ./ with any extension is Caddyfile", + args: args{ + configFile: "./Caddyfile.prd", + adapterName: "", + }, + want: true, + wantErr: false, + }, + { + + name: "prefix of Caddyfile without ./ with any extension is Caddyfile", + args: args{ + configFile: "Caddyfile.prd", + adapterName: "", + }, + want: true, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := isCaddyfile(tt.args.configFile, tt.args.adapterName) + if (err != nil) != tt.wantErr { + t.Errorf("isCaddyfile() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("isCaddyfile() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/cmd/packagesfuncs.go b/cmd/packagesfuncs.go new file mode 100644 index 00000000000..69523200179 --- /dev/null +++ b/cmd/packagesfuncs.go @@ -0,0 +1,354 @@ +// 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 caddycmd + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "os" + "os/exec" + "path/filepath" + "reflect" + "runtime" + "runtime/debug" + "strings" + + "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2" +) + +func cmdUpgrade(fl Flags) (int, error) { + _, nonstandard, _, err := getModules() + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("unable to enumerate installed plugins: %v", err) + } + pluginPkgs, err := getPluginPackages(nonstandard) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + return upgradeBuild(pluginPkgs, fl) +} + +func splitModule(arg string) (module, version string, err error) { + const versionSplit = "@" + + // accommodate module paths that have @ in them, but we can only tolerate that if there's also + // a version, otherwise we don't know if it's a version separator or part of the file path + lastVersionSplit := strings.LastIndex(arg, versionSplit) + if lastVersionSplit < 0 { + module = arg + } else { + module, version = arg[:lastVersionSplit], arg[lastVersionSplit+1:] + } + + if module == "" { + err = fmt.Errorf("module name is required") + } + + return +} + +func cmdAddPackage(fl Flags) (int, error) { + if len(fl.Args()) == 0 { + return caddy.ExitCodeFailedStartup, fmt.Errorf("at least one package name must be specified") + } + _, nonstandard, _, err := getModules() + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("unable to enumerate installed plugins: %v", err) + } + pluginPkgs, err := getPluginPackages(nonstandard) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + for _, arg := range fl.Args() { + module, version, err := splitModule(arg) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid module name: %v", err) + } + // only allow a version to be specified if it's different from the existing version + if _, ok := pluginPkgs[module]; ok && !(version != "" && pluginPkgs[module].Version != version) { + return caddy.ExitCodeFailedStartup, fmt.Errorf("package is already added") + } + pluginPkgs[module] = pluginPackage{Version: version, Path: module} + } + + return upgradeBuild(pluginPkgs, fl) +} + +func cmdRemovePackage(fl Flags) (int, error) { + if len(fl.Args()) == 0 { + return caddy.ExitCodeFailedStartup, fmt.Errorf("at least one package name must be specified") + } + _, nonstandard, _, err := getModules() + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("unable to enumerate installed plugins: %v", err) + } + pluginPkgs, err := getPluginPackages(nonstandard) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + for _, arg := range fl.Args() { + module, _, err := splitModule(arg) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid module name: %v", err) + } + if _, ok := pluginPkgs[module]; !ok { + // package does not exist + return caddy.ExitCodeFailedStartup, fmt.Errorf("package is not added") + } + delete(pluginPkgs, arg) + } + + return upgradeBuild(pluginPkgs, fl) +} + +func upgradeBuild(pluginPkgs map[string]pluginPackage, fl Flags) (int, error) { + l := caddy.Log() + + thisExecPath, err := os.Executable() + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("determining current executable path: %v", err) + } + thisExecStat, err := os.Stat(thisExecPath) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("retrieving current executable permission bits: %v", err) + } + if thisExecStat.Mode()&os.ModeSymlink == os.ModeSymlink { + symSource := thisExecPath + // we are a symlink; resolve it + thisExecPath, err = filepath.EvalSymlinks(thisExecPath) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("resolving current executable symlink: %v", err) + } + l.Info("this executable is a symlink", zap.String("source", symSource), zap.String("target", thisExecPath)) + } + l.Info("this executable will be replaced", zap.String("path", thisExecPath)) + + // build the request URL to download this custom build + qs := url.Values{ + "os": {runtime.GOOS}, + "arch": {runtime.GOARCH}, + } + for _, pkgInfo := range pluginPkgs { + qs.Add("p", pkgInfo.String()) + } + + // initiate the build + resp, err := downloadBuild(qs) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("download failed: %v", err) + } + defer resp.Body.Close() + + // back up the current binary, in case something goes wrong we can replace it + backupExecPath := thisExecPath + ".tmp" + l.Info("build acquired; backing up current executable", + zap.String("current_path", thisExecPath), + zap.String("backup_path", backupExecPath)) + err = os.Rename(thisExecPath, backupExecPath) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("backing up current binary: %v", err) + } + defer func() { + if err != nil { + err2 := os.Rename(backupExecPath, thisExecPath) + if err2 != nil { + l.Error("restoring original executable failed; will need to be restored manually", + zap.String("backup_path", backupExecPath), + zap.String("original_path", thisExecPath), + zap.Error(err2)) + } + } + }() + + // download the file; do this in a closure to close reliably before we execute it + err = writeCaddyBinary(thisExecPath, &resp.Body, thisExecStat) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + l.Info("download successful; displaying new binary details", zap.String("location", thisExecPath)) + + // use the new binary to print out version and module info + fmt.Print("\nModule versions:\n\n") + if err = listModules(thisExecPath); err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("download succeeded, but unable to execute 'caddy list-modules': %v", err) + } + fmt.Println("\nVersion:") + if err = showVersion(thisExecPath); err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("download succeeded, but unable to execute 'caddy version': %v", err) + } + fmt.Println() + + // clean up the backup file + if !fl.Bool("keep-backup") { + if err = removeCaddyBinary(backupExecPath); err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("download succeeded, but unable to clean up backup binary: %v", err) + } + } else { + l.Info("skipped cleaning up the backup file", zap.String("backup_path", backupExecPath)) + } + + l.Info("upgrade successful; please restart any running Caddy instances", zap.String("executable", thisExecPath)) + + return caddy.ExitCodeSuccess, nil +} + +func getModules() (standard, nonstandard, unknown []moduleInfo, err error) { + bi, ok := debug.ReadBuildInfo() + if !ok { + err = fmt.Errorf("no build info") + return + } + + for _, modID := range caddy.Modules() { + modInfo, err := caddy.GetModule(modID) + if err != nil { + // that's weird, shouldn't happen + unknown = append(unknown, moduleInfo{caddyModuleID: modID, err: err}) + continue + } + + // to get the Caddy plugin's version info, we need to know + // the package that the Caddy module's value comes from; we + // can use reflection but we need a non-pointer value (I'm + // not sure why), and since New() should return a pointer + // value, we need to dereference it first + iface := any(modInfo.New()) + if rv := reflect.ValueOf(iface); rv.Kind() == reflect.Ptr { + iface = reflect.New(reflect.TypeOf(iface).Elem()).Elem().Interface() + } + modPkgPath := reflect.TypeOf(iface).PkgPath() + + // now we find the Go module that the Caddy module's package + // belongs to; we assume the Caddy module package path will + // be prefixed by its Go module path, and we will choose the + // longest matching prefix in case there are nested modules + var matched *debug.Module + for _, dep := range bi.Deps { + if strings.HasPrefix(modPkgPath, dep.Path) { + if matched == nil || len(dep.Path) > len(matched.Path) { + matched = dep + } + } + } + + caddyModGoMod := moduleInfo{caddyModuleID: modID, goModule: matched} + + if strings.HasPrefix(modPkgPath, caddy.ImportPath) { + standard = append(standard, caddyModGoMod) + } else { + nonstandard = append(nonstandard, caddyModGoMod) + } + } + return +} + +func listModules(path string) error { + cmd := exec.Command(path, "list-modules", "--versions", "--skip-standard") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +func showVersion(path string) error { + cmd := exec.Command(path, "version") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +func downloadBuild(qs url.Values) (*http.Response, error) { + l := caddy.Log() + l.Info("requesting build", + zap.String("os", qs.Get("os")), + zap.String("arch", qs.Get("arch")), + zap.Strings("packages", qs["p"])) + resp, err := http.Get(fmt.Sprintf("%s?%s", downloadPath, qs.Encode())) + if err != nil { + return nil, fmt.Errorf("secure request failed: %v", err) + } + if resp.StatusCode >= 400 { + var details struct { + StatusCode int `json:"status_code"` + Error struct { + Message string `json:"message"` + ID string `json:"id"` + } `json:"error"` + } + err2 := json.NewDecoder(resp.Body).Decode(&details) + if err2 != nil { + return nil, fmt.Errorf("download and error decoding failed: HTTP %d: %v", resp.StatusCode, err2) + } + return nil, fmt.Errorf("download failed: HTTP %d: %s (id=%s)", resp.StatusCode, details.Error.Message, details.Error.ID) + } + return resp, nil +} + +func getPluginPackages(modules []moduleInfo) (map[string]pluginPackage, error) { + pluginPkgs := make(map[string]pluginPackage) + for _, mod := range modules { + if mod.goModule.Replace != nil { + return nil, fmt.Errorf("cannot auto-upgrade when Go module has been replaced: %s => %s", + mod.goModule.Path, mod.goModule.Replace.Path) + } + pluginPkgs[mod.goModule.Path] = pluginPackage{Version: mod.goModule.Version, Path: mod.goModule.Path} + } + return pluginPkgs, nil +} + +func writeCaddyBinary(path string, body *io.ReadCloser, fileInfo os.FileInfo) error { + l := caddy.Log() + destFile, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fileInfo.Mode()) + if err != nil { + return fmt.Errorf("unable to open destination file: %v", err) + } + defer destFile.Close() + + l.Info("downloading binary", zap.String("destination", path)) + + _, err = io.Copy(destFile, *body) + if err != nil { + return fmt.Errorf("unable to download file: %v", err) + } + + err = destFile.Sync() + if err != nil { + return fmt.Errorf("syncing downloaded file to device: %v", err) + } + + return nil +} + +const downloadPath = "https://caddyserver.com/api/download" + +type pluginPackage struct { + Version string + Path string +} + +func (p pluginPackage) String() string { + if p.Version == "" { + return p.Path + } + return p.Path + "@" + p.Version +} diff --git a/cmd/removebinary.go b/cmd/removebinary.go new file mode 100644 index 00000000000..c74d2b2e212 --- /dev/null +++ b/cmd/removebinary.go @@ -0,0 +1,29 @@ +// 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 !windows + +package caddycmd + +import ( + "os" +) + +// removeCaddyBinary removes the Caddy binary at the given path. +// +// On any non-Windows OS, this simply calls os.Remove, since they should +// probably not exhibit any issue with processes deleting themselves. +func removeCaddyBinary(path string) error { + return os.Remove(path) +} diff --git a/cmd/removebinary_windows.go b/cmd/removebinary_windows.go new file mode 100644 index 00000000000..8cc271ad32a --- /dev/null +++ b/cmd/removebinary_windows.go @@ -0,0 +1,39 @@ +// 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 caddycmd + +import ( + "os" + "path/filepath" + "syscall" +) + +// removeCaddyBinary removes the Caddy binary at the given path. +// +// On Windows, this uses a syscall to indirectly remove the file, +// because otherwise we get an "Access is denied." error when trying +// to delete the binary while Caddy is still running and performing +// the upgrade. "cmd.exe /C" executes a command specified by the +// following arguments, i.e. "del" which will run as a separate process, +// which avoids the "Access is denied." error. +func removeCaddyBinary(path string) error { + var sI syscall.StartupInfo + var pI syscall.ProcessInformation + argv, err := syscall.UTF16PtrFromString(filepath.Join(os.Getenv("windir"), "system32", "cmd.exe") + " /C del " + path) + if err != nil { + return err + } + return syscall.CreateProcess(nil, argv, nil, nil, true, 0, nil, nil, &sI, &pI) +} diff --git a/cmd/storagefuncs.go b/cmd/storagefuncs.go new file mode 100644 index 00000000000..3c421971912 --- /dev/null +++ b/cmd/storagefuncs.go @@ -0,0 +1,231 @@ +// 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 caddycmd + +import ( + "archive/tar" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "os" + + "github.com/caddyserver/certmagic" + + "github.com/caddyserver/caddy/v2" +) + +type storVal struct { + StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"` +} + +// determineStorage returns the top-level storage module from the given config. +// It may return nil even if no error. +func determineStorage(configFile string, configAdapter string) (*storVal, error) { + cfg, _, err := LoadConfig(configFile, configAdapter) + if err != nil { + return nil, err + } + + // storage defaults to FileStorage if not explicitly + // defined in the config, so the config can be valid + // json but unmarshaling will fail. + if !json.Valid(cfg) { + return nil, &json.SyntaxError{} + } + var tmpStruct storVal + err = json.Unmarshal(cfg, &tmpStruct) + if err != nil { + // default case, ignore the error + var jsonError *json.SyntaxError + if errors.As(err, &jsonError) { + return nil, nil + } + return nil, err + } + + return &tmpStruct, nil +} + +func cmdImportStorage(fl Flags) (int, error) { + importStorageCmdConfigFlag := fl.String("config") + importStorageCmdImportFile := fl.String("input") + + if importStorageCmdConfigFlag == "" { + return caddy.ExitCodeFailedStartup, errors.New("--config is required") + } + if importStorageCmdImportFile == "" { + return caddy.ExitCodeFailedStartup, errors.New("--input is required") + } + + // extract storage from config if possible + storageCfg, err := determineStorage(importStorageCmdConfigFlag, "") + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + // load specified storage or fallback to default + var stor certmagic.Storage + ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + if storageCfg != nil && storageCfg.StorageRaw != nil { + val, err := ctx.LoadModule(storageCfg, "StorageRaw") + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + stor, err = val.(caddy.StorageConverter).CertMagicStorage() + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + } else { + stor = caddy.DefaultStorage + } + + // setup input + var f *os.File + if importStorageCmdImportFile == "-" { + f = os.Stdin + } else { + f, err = os.Open(importStorageCmdImportFile) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("opening input file: %v", err) + } + defer f.Close() + } + + // store each archive element + tr := tar.NewReader(f) + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + return caddy.ExitCodeFailedQuit, fmt.Errorf("reading archive: %v", err) + } + + b, err := io.ReadAll(tr) + if err != nil { + return caddy.ExitCodeFailedQuit, fmt.Errorf("reading archive: %v", err) + } + + err = stor.Store(ctx, hdr.Name, b) + if err != nil { + return caddy.ExitCodeFailedQuit, fmt.Errorf("reading archive: %v", err) + } + } + + fmt.Println("Successfully imported storage") + return caddy.ExitCodeSuccess, nil +} + +func cmdExportStorage(fl Flags) (int, error) { + exportStorageCmdConfigFlag := fl.String("config") + exportStorageCmdOutputFlag := fl.String("output") + + if exportStorageCmdConfigFlag == "" { + return caddy.ExitCodeFailedStartup, errors.New("--config is required") + } + if exportStorageCmdOutputFlag == "" { + return caddy.ExitCodeFailedStartup, errors.New("--output is required") + } + + // extract storage from config if possible + storageCfg, err := determineStorage(exportStorageCmdConfigFlag, "") + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + // load specified storage or fallback to default + var stor certmagic.Storage + ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + if storageCfg != nil && storageCfg.StorageRaw != nil { + val, err := ctx.LoadModule(storageCfg, "StorageRaw") + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + stor, err = val.(caddy.StorageConverter).CertMagicStorage() + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + } else { + stor = caddy.DefaultStorage + } + + // enumerate all keys + keys, err := stor.List(ctx, "", true) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + // setup output + var f *os.File + if exportStorageCmdOutputFlag == "-" { + f = os.Stdout + } else { + f, err = os.Create(exportStorageCmdOutputFlag) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("opening output file: %v", err) + } + defer f.Close() + } + + // `IsTerminal: true` keys hold the values we + // care about, write them out + tw := tar.NewWriter(f) + for _, k := range keys { + info, err := stor.Stat(ctx, k) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + caddy.Log().Warn(fmt.Sprintf("key: %s removed while export is in-progress", k)) + continue + } + return caddy.ExitCodeFailedQuit, err + } + + if info.IsTerminal { + v, err := stor.Load(ctx, k) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + caddy.Log().Warn(fmt.Sprintf("key: %s removed while export is in-progress", k)) + continue + } + return caddy.ExitCodeFailedQuit, err + } + + hdr := &tar.Header{ + Name: k, + Mode: 0o600, + Size: int64(len(v)), + ModTime: info.Modified, + } + + if err = tw.WriteHeader(hdr); err != nil { + return caddy.ExitCodeFailedQuit, fmt.Errorf("writing archive: %v", err) + } + if _, err = tw.Write(v); err != nil { + return caddy.ExitCodeFailedQuit, fmt.Errorf("writing archive: %v", err) + } + } + } + if err = tw.Close(); err != nil { + return caddy.ExitCodeFailedQuit, fmt.Errorf("writing archive: %v", err) + } + + return caddy.ExitCodeSuccess, nil +} diff --git a/cmd/x509rootsfallback.go b/cmd/x509rootsfallback.go new file mode 100644 index 00000000000..935a48ec1ce --- /dev/null +++ b/cmd/x509rootsfallback.go @@ -0,0 +1,33 @@ +// 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 caddycmd + +import ( + // For running in minimal environments, this can ease + // headaches related to establishing TLS connections. + // "Package fallback embeds a set of fallback X.509 trusted + // roots in the application by automatically invoking + // x509.SetFallbackRoots. This allows the application to + // work correctly even if the operating system does not + // provide a verifier or system roots pool. ... It's + // recommended that only binaries, and not libraries, + // import this package. This package must be kept up to + // date for security and compatibility reasons." + // + // This is in its own file only because of conflicts + // between gci and goimports when in main.go. + // See https://github.com/daixiang0/gci/issues/76 + _ "golang.org/x/crypto/x509roots/fallback" +) diff --git a/commands.go b/commands.go deleted file mode 100644 index 3e64c90b92b..00000000000 --- a/commands.go +++ /dev/null @@ -1,119 +0,0 @@ -package caddy - -import ( - "errors" - "runtime" - "unicode" - - "github.com/flynn/go-shlex" -) - -var runtimeGoos = runtime.GOOS - -// SplitCommandAndArgs takes a command string and parses it shell-style into the -// command and its separate arguments. -func SplitCommandAndArgs(command string) (cmd string, args []string, err error) { - var parts []string - - if runtimeGoos == "windows" { - parts = parseWindowsCommand(command) // parse it Windows-style - } else { - parts, err = parseUnixCommand(command) // parse it Unix-style - if err != nil { - err = errors.New("error parsing command: " + err.Error()) - return - } - } - - if len(parts) == 0 { - err = errors.New("no command contained in '" + command + "'") - return - } - - cmd = parts[0] - if len(parts) > 1 { - args = parts[1:] - } - - return -} - -// parseUnixCommand parses a unix style command line and returns the -// command and its arguments or an error -func parseUnixCommand(cmd string) ([]string, error) { - return shlex.Split(cmd) -} - -// parseWindowsCommand parses windows command lines and -// returns the command and the arguments as an array. It -// should be able to parse commonly used command lines. -// Only basic syntax is supported: -// - spaces in double quotes are not token delimiters -// - double quotes are escaped by either backspace or another double quote -// - except for the above case backspaces are path separators (not special) -// -// Many sources point out that escaping quotes using backslash can be unsafe. -// Use two double quotes when possible. (Source: http://stackoverflow.com/a/31413730/2616179 ) -// -// This function has to be used on Windows instead -// of the shlex package because this function treats backslash -// characters properly. -func parseWindowsCommand(cmd string) []string { - const backslash = '\\' - const quote = '"' - - var parts []string - var part string - var inQuotes bool - var lastRune rune - - for i, ch := range cmd { - - if i != 0 { - lastRune = rune(cmd[i-1]) - } - - if ch == backslash { - // put it in the part - for now we don't know if it's an - // escaping char or path separator - part += string(ch) - continue - } - - if ch == quote { - if lastRune == backslash { - // remove the backslash from the part and add the escaped quote instead - part = part[:len(part)-1] - part += string(ch) - continue - } - - if lastRune == quote { - // revert the last change of the inQuotes state - // it was an escaping quote - inQuotes = !inQuotes - part += string(ch) - continue - } - - // normal escaping quotes - inQuotes = !inQuotes - continue - - } - - if unicode.IsSpace(ch) && !inQuotes && len(part) > 0 { - parts = append(parts, part) - part = "" - continue - } - - part += string(ch) - } - - if len(part) > 0 { - parts = append(parts, part) - } - - return parts -} diff --git a/commands_test.go b/commands_test.go deleted file mode 100644 index cafc1dcc76c..00000000000 --- a/commands_test.go +++ /dev/null @@ -1,291 +0,0 @@ -package caddy - -import ( - "fmt" - "runtime" - "strings" - "testing" -) - -func TestParseUnixCommand(t *testing.T) { - tests := []struct { - input string - expected []string - }{ - // 0 - empty command - { - input: ``, - expected: []string{}, - }, - // 1 - command without arguments - { - input: `command`, - expected: []string{`command`}, - }, - // 2 - command with single argument - { - input: `command arg1`, - expected: []string{`command`, `arg1`}, - }, - // 3 - command with multiple arguments - { - input: `command arg1 arg2`, - expected: []string{`command`, `arg1`, `arg2`}, - }, - // 4 - command with single argument with space character - in quotes - { - input: `command "arg1 arg1"`, - expected: []string{`command`, `arg1 arg1`}, - }, - // 5 - command with multiple spaces and tab character - { - input: "command arg1 arg2\targ3", - expected: []string{`command`, `arg1`, `arg2`, `arg3`}, - }, - // 6 - command with single argument with space character - escaped with backspace - { - input: `command arg1\ arg2`, - expected: []string{`command`, `arg1 arg2`}, - }, - // 7 - single quotes should escape special chars - { - input: `command 'arg1\ arg2'`, - expected: []string{`command`, `arg1\ arg2`}, - }, - } - - for i, test := range tests { - errorPrefix := fmt.Sprintf("Test [%d]: ", i) - errorSuffix := fmt.Sprintf(" Command to parse: [%s]", test.input) - actual, _ := parseUnixCommand(test.input) - if len(actual) != len(test.expected) { - t.Errorf(errorPrefix+"Expected %d parts, got %d: %#v."+errorSuffix, len(test.expected), len(actual), actual) - continue - } - for j := 0; j < len(actual); j++ { - if expectedPart, actualPart := test.expected[j], actual[j]; expectedPart != actualPart { - t.Errorf(errorPrefix+"Expected: %v Actual: %v (index %d)."+errorSuffix, expectedPart, actualPart, j) - } - } - } -} - -func TestParseWindowsCommand(t *testing.T) { - tests := []struct { - input string - expected []string - }{ - { // 0 - empty command - do not fail - input: ``, - expected: []string{}, - }, - { // 1 - cmd without args - input: `cmd`, - expected: []string{`cmd`}, - }, - { // 2 - multiple args - input: `cmd arg1 arg2`, - expected: []string{`cmd`, `arg1`, `arg2`}, - }, - { // 3 - multiple args with space - input: `cmd "combined arg" arg2`, - expected: []string{`cmd`, `combined arg`, `arg2`}, - }, - { // 4 - path without spaces - input: `mkdir C:\Windows\foo\bar`, - expected: []string{`mkdir`, `C:\Windows\foo\bar`}, - }, - { // 5 - command with space in quotes - input: `"command here"`, - expected: []string{`command here`}, - }, - { // 6 - argument with escaped quotes (two quotes) - input: `cmd ""arg""`, - expected: []string{`cmd`, `"arg"`}, - }, - { // 7 - argument with escaped quotes (backslash) - input: `cmd \"arg\"`, - expected: []string{`cmd`, `"arg"`}, - }, - { // 8 - two quotes (escaped) inside an inQuote element - input: `cmd "a ""quoted value"`, - expected: []string{`cmd`, `a "quoted value`}, - }, - // TODO - see how many quotes are dislayed if we use "", """, """"""" - { // 9 - two quotes outside an inQuote element - input: `cmd a ""quoted value`, - expected: []string{`cmd`, `a`, `"quoted`, `value`}, - }, - { // 10 - path with space in quotes - input: `mkdir "C:\directory name\foobar"`, - expected: []string{`mkdir`, `C:\directory name\foobar`}, - }, - { // 11 - space without quotes - input: `mkdir C:\ space`, - expected: []string{`mkdir`, `C:\`, `space`}, - }, - { // 12 - space in quotes - input: `mkdir "C:\ space"`, - expected: []string{`mkdir`, `C:\ space`}, - }, - { // 13 - UNC - input: `mkdir \\?\C:\Users`, - expected: []string{`mkdir`, `\\?\C:\Users`}, - }, - { // 14 - UNC with space - input: `mkdir "\\?\C:\Program Files"`, - expected: []string{`mkdir`, `\\?\C:\Program Files`}, - }, - - { // 15 - unclosed quotes - treat as if the path ends with quote - input: `mkdir "c:\Program files`, - expected: []string{`mkdir`, `c:\Program files`}, - }, - { // 16 - quotes used inside the argument - input: `mkdir "c:\P"rogra"m f"iles`, - expected: []string{`mkdir`, `c:\Program files`}, - }, - } - - for i, test := range tests { - errorPrefix := fmt.Sprintf("Test [%d]: ", i) - errorSuffix := fmt.Sprintf(" Command to parse: [%s]", test.input) - - actual := parseWindowsCommand(test.input) - if len(actual) != len(test.expected) { - t.Errorf(errorPrefix+"Expected %d parts, got %d: %#v."+errorSuffix, len(test.expected), len(actual), actual) - continue - } - for j := 0; j < len(actual); j++ { - if expectedPart, actualPart := test.expected[j], actual[j]; expectedPart != actualPart { - t.Errorf(errorPrefix+"Expected: %v Actual: %v (index %d)."+errorSuffix, expectedPart, actualPart, j) - } - } - } -} - -func TestSplitCommandAndArgs(t *testing.T) { - - // force linux parsing. It's more robust and covers error cases - runtimeGoos = "linux" - defer func() { - runtimeGoos = runtime.GOOS - }() - - var parseErrorContent = "error parsing command:" - var noCommandErrContent = "no command contained in" - - tests := []struct { - input string - expectedCommand string - expectedArgs []string - expectedErrContent string - }{ - // 0 - empty command - { - input: ``, - expectedCommand: ``, - expectedArgs: nil, - expectedErrContent: noCommandErrContent, - }, - // 1 - command without arguments - { - input: `command`, - expectedCommand: `command`, - expectedArgs: nil, - expectedErrContent: ``, - }, - // 2 - command with single argument - { - input: `command arg1`, - expectedCommand: `command`, - expectedArgs: []string{`arg1`}, - expectedErrContent: ``, - }, - // 3 - command with multiple arguments - { - input: `command arg1 arg2`, - expectedCommand: `command`, - expectedArgs: []string{`arg1`, `arg2`}, - expectedErrContent: ``, - }, - // 4 - command with unclosed quotes - { - input: `command "arg1 arg2`, - expectedCommand: "", - expectedArgs: nil, - expectedErrContent: parseErrorContent, - }, - // 5 - command with unclosed quotes - { - input: `command 'arg1 arg2"`, - expectedCommand: "", - expectedArgs: nil, - expectedErrContent: parseErrorContent, - }, - } - - for i, test := range tests { - errorPrefix := fmt.Sprintf("Test [%d]: ", i) - errorSuffix := fmt.Sprintf(" Command to parse: [%s]", test.input) - actualCommand, actualArgs, actualErr := SplitCommandAndArgs(test.input) - - // test if error matches expectation - if test.expectedErrContent != "" { - if actualErr == nil { - t.Errorf(errorPrefix+"Expected error with content [%s], found no error."+errorSuffix, test.expectedErrContent) - } else if !strings.Contains(actualErr.Error(), test.expectedErrContent) { - t.Errorf(errorPrefix+"Expected error with content [%s], found [%v]."+errorSuffix, test.expectedErrContent, actualErr) - } - } else if actualErr != nil { - t.Errorf(errorPrefix+"Expected no error, found [%v]."+errorSuffix, actualErr) - } - - // test if command matches - if test.expectedCommand != actualCommand { - t.Errorf(errorPrefix+"Expected command: [%s], actual: [%s]."+errorSuffix, test.expectedCommand, actualCommand) - } - - // test if arguments match - if len(test.expectedArgs) != len(actualArgs) { - t.Errorf(errorPrefix+"Wrong number of arguments! Expected [%v], actual [%v]."+errorSuffix, test.expectedArgs, actualArgs) - } else { - // test args only if the count matches. - for j, actualArg := range actualArgs { - expectedArg := test.expectedArgs[j] - if actualArg != expectedArg { - t.Errorf(errorPrefix+"Argument at position [%d] differ! Expected [%s], actual [%s]"+errorSuffix, j, expectedArg, actualArg) - } - } - } - } -} - -func ExampleSplitCommandAndArgs() { - var commandLine string - var command string - var args []string - - // just for the test - change GOOS and reset it at the end of the test - runtimeGoos = "windows" - defer func() { - runtimeGoos = runtime.GOOS - }() - - commandLine = `mkdir /P "C:\Program Files"` - command, args, _ = SplitCommandAndArgs(commandLine) - - fmt.Printf("Windows: %s: %s [%s]\n", commandLine, command, strings.Join(args, ",")) - - // set GOOS to linux - runtimeGoos = "linux" - - commandLine = `mkdir -p /path/with\ space` - command, args, _ = SplitCommandAndArgs(commandLine) - - fmt.Printf("Linux: %s: %s [%s]\n", commandLine, command, strings.Join(args, ",")) - - // Output: - // Windows: mkdir /P "C:\Program Files": mkdir [/P,C:\Program Files] - // Linux: mkdir -p /path/with\ space: mkdir [-p,/path/with space] -} diff --git a/context.go b/context.go new file mode 100644 index 00000000000..d4d7afacf70 --- /dev/null +++ b/context.go @@ -0,0 +1,592 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddy + +import ( + "context" + "encoding/json" + "fmt" + "log" + "log/slog" + "reflect" + + "github.com/caddyserver/certmagic" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/collectors" + "go.uber.org/zap" + "go.uber.org/zap/exp/zapslog" + + "github.com/caddyserver/caddy/v2/internal/filesystems" +) + +// Context is a type which defines the lifetime of modules that +// are loaded and provides access to the parent configuration +// that spawned the modules which are loaded. It should be used +// with care and wrapped with derivation functions from the +// standard context package only if you don't need the Caddy +// specific features. These contexts are canceled when the +// lifetime of the modules loaded from it is over. +// +// Use NewContext() to get a valid value (but most modules will +// not actually need to do this). +type Context struct { + context.Context + + moduleInstances map[string][]Module + cfg *Config + ancestry []Module + cleanupFuncs []func() // invoked at every config unload + exitFuncs []func(context.Context) // invoked at config unload ONLY IF the process is exiting (EXPERIMENTAL) + metricsRegistry *prometheus.Registry +} + +// NewContext provides a new context derived from the given +// context ctx. Normally, you will not need to call this +// function unless you are loading modules which have a +// different lifespan than the ones for the context the +// module was provisioned with. Be sure to call the cancel +// func when the context is to be cleaned up so that +// modules which are loaded will be properly unloaded. +// See standard library context package's documentation. +func NewContext(ctx Context) (Context, context.CancelFunc) { + newCtx := Context{moduleInstances: make(map[string][]Module), cfg: ctx.cfg, metricsRegistry: prometheus.NewPedanticRegistry()} + c, cancel := context.WithCancel(ctx.Context) + wrappedCancel := func() { + cancel() + + for _, f := range ctx.cleanupFuncs { + f() + } + + for modName, modInstances := range newCtx.moduleInstances { + for _, inst := range modInstances { + if cu, ok := inst.(CleanerUpper); ok { + err := cu.Cleanup() + if err != nil { + log.Printf("[ERROR] %s (%p): cleanup: %v", modName, inst, err) + } + } + } + } + } + newCtx.Context = c + newCtx.initMetrics() + return newCtx, wrappedCancel +} + +// OnCancel executes f when ctx is canceled. +func (ctx *Context) OnCancel(f func()) { + ctx.cleanupFuncs = append(ctx.cleanupFuncs, f) +} + +// Filesystems returns a ref to the FilesystemMap. +// EXPERIMENTAL: This API is subject to change. +func (ctx *Context) Filesystems() FileSystems { + // if no config is loaded, we use a default filesystemmap, which includes the osfs + if ctx.cfg == nil { + return &filesystems.FilesystemMap{} + } + return ctx.cfg.filesystems +} + +// Returns the active metrics registry for the context +// EXPERIMENTAL: This API is subject to change. +func (ctx *Context) GetMetricsRegistry() *prometheus.Registry { + return ctx.metricsRegistry +} + +func (ctx *Context) initMetrics() { + ctx.metricsRegistry.MustRegister( + collectors.NewBuildInfoCollector(), + collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}), + collectors.NewGoCollector(), + adminMetrics.requestCount, + adminMetrics.requestErrors, + globalMetrics.configSuccess, + globalMetrics.configSuccessTime, + ) +} + +// OnExit executes f when the process exits gracefully. +// The function is only executed if the process is gracefully +// shut down while this context is active. +// +// EXPERIMENTAL API: subject to change or removal. +func (ctx *Context) OnExit(f func(context.Context)) { + ctx.exitFuncs = append(ctx.exitFuncs, f) +} + +// LoadModule loads the Caddy module(s) from the specified field of the parent struct +// pointer and returns the loaded module(s). The struct pointer and its field name as +// a string are necessary so that reflection can be used to read the struct tag on the +// field to get the module namespace and inline module name key (if specified). +// +// The field can be any one of the supported raw module types: json.RawMessage, +// []json.RawMessage, map[string]json.RawMessage, or []map[string]json.RawMessage. +// ModuleMap may be used in place of map[string]json.RawMessage. The return value's +// underlying type mirrors the input field's type: +// +// json.RawMessage => any +// []json.RawMessage => []any +// [][]json.RawMessage => [][]any +// map[string]json.RawMessage => map[string]any +// []map[string]json.RawMessage => []map[string]any +// +// The field must have a "caddy" struct tag in this format: +// +// caddy:"key1=val1 key2=val2" +// +// To load modules, a "namespace" key is required. For example, to load modules +// in the "http.handlers" namespace, you'd put: `namespace=http.handlers` in the +// Caddy struct tag. +// +// The module name must also be available. If the field type is a map or slice of maps, +// then key is assumed to be the module name if an "inline_key" is NOT specified in the +// caddy struct tag. In this case, the module name does NOT need to be specified in-line +// with the module itself. +// +// If not a map, or if inline_key is non-empty, then the module name must be embedded +// into the values, which must be objects; then there must be a key in those objects +// where its associated value is the module name. This is called the "inline key", +// meaning the key containing the module's name that is defined inline with the module +// itself. You must specify the inline key in a struct tag, along with the namespace: +// +// caddy:"namespace=http.handlers inline_key=handler" +// +// This will look for a key/value pair like `"handler": "..."` in the json.RawMessage +// in order to know the module name. +// +// To make use of the loaded module(s) (the return value), you will probably want +// to type-assert each 'any' value(s) to the types that are useful to you +// and store them on the same struct. Storing them on the same struct makes for +// easy garbage collection when your host module is no longer needed. +// +// Loaded modules have already been provisioned and validated. Upon returning +// successfully, this method clears the json.RawMessage(s) in the field since +// the raw JSON is no longer needed, and this allows the GC to free up memory. +func (ctx Context) LoadModule(structPointer any, fieldName string) (any, error) { + val := reflect.ValueOf(structPointer).Elem().FieldByName(fieldName) + typ := val.Type() + + field, ok := reflect.TypeOf(structPointer).Elem().FieldByName(fieldName) + if !ok { + panic(fmt.Sprintf("field %s does not exist in %#v", fieldName, structPointer)) + } + + opts, err := ParseStructTag(field.Tag.Get("caddy")) + if err != nil { + panic(fmt.Sprintf("malformed tag on field %s: %v", fieldName, err)) + } + + moduleNamespace, ok := opts["namespace"] + if !ok { + panic(fmt.Sprintf("missing 'namespace' key in struct tag on field %s", fieldName)) + } + inlineModuleKey := opts["inline_key"] + + var result any + + switch val.Kind() { + case reflect.Slice: + if isJSONRawMessage(typ) { + // val is `json.RawMessage` ([]uint8 under the hood) + + if inlineModuleKey == "" { + panic("unable to determine module name without inline_key when type is not a ModuleMap") + } + val, err := ctx.loadModuleInline(inlineModuleKey, moduleNamespace, val.Interface().(json.RawMessage)) + if err != nil { + return nil, err + } + result = val + } else if isJSONRawMessage(typ.Elem()) { + // val is `[]json.RawMessage` + + if inlineModuleKey == "" { + panic("unable to determine module name without inline_key because type is not a ModuleMap") + } + var all []any + for i := 0; i < val.Len(); i++ { + val, err := ctx.loadModuleInline(inlineModuleKey, moduleNamespace, val.Index(i).Interface().(json.RawMessage)) + if err != nil { + return nil, fmt.Errorf("position %d: %v", i, err) + } + all = append(all, val) + } + result = all + } else if typ.Elem().Kind() == reflect.Slice && isJSONRawMessage(typ.Elem().Elem()) { + // val is `[][]json.RawMessage` + + if inlineModuleKey == "" { + panic("unable to determine module name without inline_key because type is not a ModuleMap") + } + var all [][]any + for i := 0; i < val.Len(); i++ { + innerVal := val.Index(i) + var allInner []any + for j := 0; j < innerVal.Len(); j++ { + innerInnerVal, err := ctx.loadModuleInline(inlineModuleKey, moduleNamespace, innerVal.Index(j).Interface().(json.RawMessage)) + if err != nil { + return nil, fmt.Errorf("position %d: %v", j, err) + } + allInner = append(allInner, innerInnerVal) + } + all = append(all, allInner) + } + result = all + } else if isModuleMapType(typ.Elem()) { + // val is `[]map[string]json.RawMessage` + + var all []map[string]any + for i := 0; i < val.Len(); i++ { + thisSet, err := ctx.loadModulesFromSomeMap(moduleNamespace, inlineModuleKey, val.Index(i)) + if err != nil { + return nil, err + } + all = append(all, thisSet) + } + result = all + } + + case reflect.Map: + // val is a ModuleMap or some other kind of map + result, err = ctx.loadModulesFromSomeMap(moduleNamespace, inlineModuleKey, val) + if err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unrecognized type for module: %s", typ) + } + + // we're done with the raw bytes; allow GC to deallocate + val.Set(reflect.Zero(typ)) + + return result, nil +} + +// loadModulesFromSomeMap loads modules from val, which must be a type of map[string]any. +// Depending on inlineModuleKey, it will be interpreted as either a ModuleMap (key is the module +// name) or as a regular map (key is not the module name, and module name is defined inline). +func (ctx Context) loadModulesFromSomeMap(namespace, inlineModuleKey string, val reflect.Value) (map[string]any, error) { + // if no inline_key is specified, then val must be a ModuleMap, + // where the key is the module name + if inlineModuleKey == "" { + if !isModuleMapType(val.Type()) { + panic(fmt.Sprintf("expected ModuleMap because inline_key is empty; but we do not recognize this type: %s", val.Type())) + } + return ctx.loadModuleMap(namespace, val) + } + + // otherwise, val is a map with modules, but the module name is + // inline with each value (the key means something else) + return ctx.loadModulesFromRegularMap(namespace, inlineModuleKey, val) +} + +// loadModulesFromRegularMap loads modules from val, where val is a map[string]json.RawMessage. +// Map keys are NOT interpreted as module names, so module names are still expected to appear +// inline with the objects. +func (ctx Context) loadModulesFromRegularMap(namespace, inlineModuleKey string, val reflect.Value) (map[string]any, error) { + mods := make(map[string]any) + iter := val.MapRange() + for iter.Next() { + k := iter.Key() + v := iter.Value() + mod, err := ctx.loadModuleInline(inlineModuleKey, namespace, v.Interface().(json.RawMessage)) + if err != nil { + return nil, fmt.Errorf("key %s: %v", k, err) + } + mods[k.String()] = mod + } + return mods, nil +} + +// loadModuleMap loads modules from a ModuleMap, i.e. map[string]any, where the key is the +// module name. With a module map, module names do not need to be defined inline with their values. +func (ctx Context) loadModuleMap(namespace string, val reflect.Value) (map[string]any, error) { + all := make(map[string]any) + iter := val.MapRange() + for iter.Next() { + k := iter.Key().Interface().(string) + v := iter.Value().Interface().(json.RawMessage) + moduleName := namespace + "." + k + if namespace == "" { + moduleName = k + } + val, err := ctx.LoadModuleByID(moduleName, v) + if err != nil { + return nil, fmt.Errorf("module name '%s': %v", k, err) + } + all[k] = val + } + return all, nil +} + +// LoadModuleByID decodes rawMsg into a new instance of mod and +// returns the value. If mod.New is nil, an error is returned. +// If the module implements Validator or Provisioner interfaces, +// those methods are invoked to ensure the module is fully +// configured and valid before being used. +// +// This is a lower-level method and will usually not be called +// directly by most modules. However, this method is useful when +// dynamically loading/unloading modules in their own context, +// like from embedded scripts, etc. +func (ctx Context) LoadModuleByID(id string, rawMsg json.RawMessage) (any, error) { + modulesMu.RLock() + modInfo, ok := modules[id] + modulesMu.RUnlock() + if !ok { + return nil, fmt.Errorf("unknown module: %s", id) + } + + if modInfo.New == nil { + return nil, fmt.Errorf("module '%s' has no constructor", modInfo.ID) + } + + val := modInfo.New() + + // value must be a pointer for unmarshaling into concrete type, even if + // the module's concrete type is a slice or map; New() *should* return + // a pointer, otherwise unmarshaling errors or panics will occur + if rv := reflect.ValueOf(val); rv.Kind() != reflect.Ptr { + log.Printf("[WARNING] ModuleInfo.New() for module '%s' did not return a pointer,"+ + " so we are using reflection to make a pointer instead; please fix this by"+ + " using new(Type) or &Type notation in your module's New() function.", id) + val = reflect.New(rv.Type()).Elem().Addr().Interface().(Module) + } + + // fill in its config only if there is a config to fill in + if len(rawMsg) > 0 { + err := StrictUnmarshalJSON(rawMsg, &val) + if err != nil { + return nil, fmt.Errorf("decoding module config: %s: %v", modInfo, err) + } + } + + if val == nil { + // returned module values are almost always type-asserted + // before being used, so a nil value would panic; and there + // is no good reason to explicitly declare null modules in + // a config; it might be because the user is trying to achieve + // a result the developer isn't expecting, which is a smell + return nil, fmt.Errorf("module value cannot be null") + } + + ctx.ancestry = append(ctx.ancestry, val) + + if prov, ok := val.(Provisioner); ok { + err := prov.Provision(ctx) + if err != nil { + // incomplete provisioning could have left state + // dangling, so make sure it gets cleaned up + if cleanerUpper, ok := val.(CleanerUpper); ok { + err2 := cleanerUpper.Cleanup() + if err2 != nil { + err = fmt.Errorf("%v; additionally, cleanup: %v", err, err2) + } + } + return nil, fmt.Errorf("provision %s: %v", modInfo, err) + } + } + + if validator, ok := val.(Validator); ok { + err := validator.Validate() + if err != nil { + // since the module was already provisioned, make sure we clean up + if cleanerUpper, ok := val.(CleanerUpper); ok { + err2 := cleanerUpper.Cleanup() + if err2 != nil { + err = fmt.Errorf("%v; additionally, cleanup: %v", err, err2) + } + } + return nil, fmt.Errorf("%s: invalid configuration: %v", modInfo, err) + } + } + + ctx.moduleInstances[id] = append(ctx.moduleInstances[id], val) + + return val, nil +} + +// loadModuleInline loads a module from a JSON raw message which decodes to +// a map[string]any, where one of the object keys is moduleNameKey +// and the corresponding value is the module name (as a string) which can +// be found in the given scope. In other words, the module name is declared +// in-line with the module itself. +// +// This allows modules to be decoded into their concrete types and used when +// their names cannot be the unique key in a map, such as when there are +// multiple instances in the map or it appears in an array (where there are +// no custom keys). In other words, the key containing the module name is +// treated special/separate from all the other keys in the object. +func (ctx Context) loadModuleInline(moduleNameKey, moduleScope string, raw json.RawMessage) (any, error) { + moduleName, raw, err := getModuleNameInline(moduleNameKey, raw) + if err != nil { + return nil, err + } + + val, err := ctx.LoadModuleByID(moduleScope+"."+moduleName, raw) + if err != nil { + return nil, fmt.Errorf("loading module '%s': %v", moduleName, err) + } + + return val, nil +} + +// App returns the configured app named name. If that app has +// not yet been loaded and provisioned, it will be immediately +// loaded and provisioned. If no app with that name is +// configured, a new empty one will be instantiated instead. +// (The app module must still be registered.) This must not be +// called during the Provision/Validate phase to reference a +// module's own host app (since the parent app module is still +// in the process of being provisioned, it is not yet ready). +// +// We return any type instead of the App type because it is NOT +// intended for the caller of this method to be the one to start +// or stop App modules. The caller is expected to assert to the +// concrete type. +func (ctx Context) App(name string) (any, error) { + if app, ok := ctx.cfg.apps[name]; ok { + return app, nil + } + appRaw := ctx.cfg.AppsRaw[name] + modVal, err := ctx.LoadModuleByID(name, appRaw) + if err != nil { + return nil, fmt.Errorf("loading %s app module: %v", name, err) + } + if appRaw != nil { + ctx.cfg.AppsRaw[name] = nil // allow GC to deallocate + } + ctx.cfg.apps[name] = modVal.(App) + return modVal, nil +} + +// AppIfConfigured is like App, but it returns an error if the +// app has not been configured. This is useful when the app is +// required and its absence is a configuration error; or when +// the app is optional and you don't want to instantiate a +// new one that hasn't been explicitly configured. If the app +// is not in the configuration, the error wraps ErrNotConfigured. +func (ctx Context) AppIfConfigured(name string) (any, error) { + if ctx.cfg == nil { + return nil, fmt.Errorf("app module %s: %w", name, ErrNotConfigured) + } + if app, ok := ctx.cfg.apps[name]; ok { + return app, nil + } + appRaw := ctx.cfg.AppsRaw[name] + if appRaw == nil { + return nil, fmt.Errorf("app module %s: %w", name, ErrNotConfigured) + } + return ctx.App(name) +} + +// ErrNotConfigured indicates a module is not configured. +var ErrNotConfigured = fmt.Errorf("module not configured") + +// Storage returns the configured Caddy storage implementation. +func (ctx Context) Storage() certmagic.Storage { + return ctx.cfg.storage +} + +// Logger returns a logger that is intended for use by the most +// recent module associated with the context. Callers should not +// pass in any arguments unless they want to associate with a +// different module; it panics if more than 1 value is passed in. +// +// Originally, this method's signature was `Logger(mod Module)`, +// requiring that an instance of a Caddy module be passed in. +// However, that is no longer necessary, as the closest module +// most recently associated with the context will be automatically +// assumed. To prevent a sudden breaking change, this method's +// signature has been changed to be variadic, but we may remove +// the parameter altogether in the future. Callers should not +// pass in any argument. If there is valid need to specify a +// different module, please open an issue to discuss. +// +// PARTIALLY DEPRECATED: The Logger(module) form is deprecated and +// may be removed in the future. Do not pass in any arguments. +func (ctx Context) Logger(module ...Module) *zap.Logger { + if len(module) > 1 { + panic("more than 1 module passed in") + } + if ctx.cfg == nil { + // often the case in tests; just use a dev logger + l, err := zap.NewDevelopment() + if err != nil { + panic("config missing, unable to create dev logger: " + err.Error()) + } + return l + } + mod := ctx.Module() + if len(module) > 0 { + mod = module[0] + } + if mod == nil { + return Log() + } + return ctx.cfg.Logging.Logger(mod) +} + +// Slogger returns a slog logger that is intended for use by +// the most recent module associated with the context. +func (ctx Context) Slogger() *slog.Logger { + if ctx.cfg == nil { + // often the case in tests; just use a dev logger + l, err := zap.NewDevelopment() + if err != nil { + panic("config missing, unable to create dev logger: " + err.Error()) + } + return slog.New(zapslog.NewHandler(l.Core(), nil)) + } + mod := ctx.Module() + if mod == nil { + return slog.New(zapslog.NewHandler(Log().Core(), nil)) + } + return slog.New(zapslog.NewHandler(ctx.cfg.Logging.Logger(mod).Core(), + zapslog.WithName(string(mod.CaddyModule().ID)), + )) +} + +// Modules returns the lineage of modules that this context provisioned, +// with the most recent/current module being last in the list. +func (ctx Context) Modules() []Module { + mods := make([]Module, len(ctx.ancestry)) + copy(mods, ctx.ancestry) + return mods +} + +// Module returns the current module, or the most recent one +// provisioned by the context. +func (ctx Context) Module() Module { + if len(ctx.ancestry) == 0 { + return nil + } + return ctx.ancestry[len(ctx.ancestry)-1] +} + +// WithValue returns a new context with the given key-value pair. +func (ctx *Context) WithValue(key, value any) Context { + return Context{ + Context: context.WithValue(ctx.Context, key, value), + moduleInstances: ctx.moduleInstances, + cfg: ctx.cfg, + ancestry: ctx.ancestry, + cleanupFuncs: ctx.cleanupFuncs, + exitFuncs: ctx.exitFuncs, + } +} diff --git a/context_test.go b/context_test.go new file mode 100644 index 00000000000..27395612c76 --- /dev/null +++ b/context_test.go @@ -0,0 +1,118 @@ +// 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" + "io" +) + +func ExampleContext_LoadModule() { + // this whole first part is just setting up for the example; + // note the struct tags - very important; we specify inline_key + // because that is the only way to know the module name + var ctx Context + myStruct := &struct { + // This godoc comment will appear in module documentation. + GuestModuleRaw json.RawMessage `json:"guest_module,omitempty" caddy:"namespace=example inline_key=name"` + + // this is where the decoded module will be stored; in this + // example, we pretend we need an io.Writer but it can be + // any interface type that is useful to you + guestModule io.Writer + }{ + GuestModuleRaw: json.RawMessage(`{"name":"module_name","foo":"bar"}`), + } + + // if a guest module is provided, we can load it easily + if myStruct.GuestModuleRaw != nil { + mod, err := ctx.LoadModule(myStruct, "GuestModuleRaw") + if err != nil { + // you'd want to actually handle the error here + // return fmt.Errorf("loading guest module: %v", err) + } + // mod contains the loaded and provisioned module, + // it is now ready for us to use + myStruct.guestModule = mod.(io.Writer) + } + + // use myStruct.guestModule from now on +} + +func ExampleContext_LoadModule_array() { + // this whole first part is just setting up for the example; + // note the struct tags - very important; we specify inline_key + // because that is the only way to know the module name + var ctx Context + myStruct := &struct { + // This godoc comment will appear in module documentation. + GuestModulesRaw []json.RawMessage `json:"guest_modules,omitempty" caddy:"namespace=example inline_key=name"` + + // this is where the decoded module will be stored; in this + // example, we pretend we need an io.Writer but it can be + // any interface type that is useful to you + guestModules []io.Writer + }{ + GuestModulesRaw: []json.RawMessage{ + json.RawMessage(`{"name":"module1_name","foo":"bar1"}`), + json.RawMessage(`{"name":"module2_name","foo":"bar2"}`), + }, + } + + // since our input is []json.RawMessage, the output will be []any + mods, err := ctx.LoadModule(myStruct, "GuestModulesRaw") + if err != nil { + // you'd want to actually handle the error here + // return fmt.Errorf("loading guest modules: %v", err) + } + for _, mod := range mods.([]any) { + myStruct.guestModules = append(myStruct.guestModules, mod.(io.Writer)) + } + + // use myStruct.guestModules from now on +} + +func ExampleContext_LoadModule_map() { + // this whole first part is just setting up for the example; + // note the struct tags - very important; we don't specify + // inline_key because the map key is the module name + var ctx Context + myStruct := &struct { + // This godoc comment will appear in module documentation. + GuestModulesRaw ModuleMap `json:"guest_modules,omitempty" caddy:"namespace=example"` + + // this is where the decoded module will be stored; in this + // example, we pretend we need an io.Writer but it can be + // any interface type that is useful to you + guestModules map[string]io.Writer + }{ + GuestModulesRaw: ModuleMap{ + "module1_name": json.RawMessage(`{"foo":"bar1"}`), + "module2_name": json.RawMessage(`{"foo":"bar2"}`), + }, + } + + // since our input is map[string]json.RawMessage, the output will be map[string]any + mods, err := ctx.LoadModule(myStruct, "GuestModulesRaw") + if err != nil { + // you'd want to actually handle the error here + // return fmt.Errorf("loading guest modules: %v", err) + } + for modName, mod := range mods.(map[string]any) { + myStruct.guestModules[modName] = mod.(io.Writer) + } + + // use myStruct.guestModules from now on +} diff --git a/controller.go b/controller.go deleted file mode 100644 index 890534f9ba7..00000000000 --- a/controller.go +++ /dev/null @@ -1,111 +0,0 @@ -package caddy - -import ( - "strings" - - "github.com/mholt/caddy/caddyfile" -) - -// Controller is given to the setup function of directives which -// gives them access to be able to read tokens with which to -// configure themselves. It also stores state for the setup -// functions, can get the current context, and can be used to -// identify a particular server block using the Key field. -type Controller struct { - caddyfile.Dispenser - - // The instance in which the setup is occurring - instance *Instance - - // Key is the key from the top of the server block, usually - // an address, hostname, or identifier of some sort. - Key string - - // OncePerServerBlock is a function that executes f - // exactly once per server block, no matter how many - // hosts are associated with it. If it is the first - // time, the function f is executed immediately - // (not deferred) and may return an error which is - // returned by OncePerServerBlock. - OncePerServerBlock func(f func() error) error - - // ServerBlockIndex is the 0-based index of the - // server block as it appeared in the input. - ServerBlockIndex int - - // ServerBlockKeyIndex is the 0-based index of this - // key as it appeared in the input at the head of the - // server block. - ServerBlockKeyIndex int - - // ServerBlockKeys is a list of keys that are - // associated with this server block. All these - // keys, consequently, share the same tokens. - ServerBlockKeys []string - - // ServerBlockStorage is used by a directive's - // setup function to persist state between all - // the keys on a server block. - ServerBlockStorage interface{} -} - -// ServerType gets the name of the server type that is being set up. -func (c *Controller) ServerType() string { - return c.instance.serverType -} - -// OnFirstStartup adds fn to the list of callback functions to execute -// when the server is about to be started NOT as part of a restart. -func (c *Controller) OnFirstStartup(fn func() error) { - c.instance.onFirstStartup = append(c.instance.onFirstStartup, fn) -} - -// OnStartup adds fn to the list of callback functions to execute -// when the server is about to be started (including restarts). -func (c *Controller) OnStartup(fn func() error) { - c.instance.onStartup = append(c.instance.onStartup, fn) -} - -// OnRestart adds fn to the list of callback functions to execute -// when the server is about to be restarted. -func (c *Controller) OnRestart(fn func() error) { - c.instance.onRestart = append(c.instance.onRestart, fn) -} - -// OnShutdown adds fn to the list of callback functions to execute -// when the server is about to be shut down (including restarts). -func (c *Controller) OnShutdown(fn func() error) { - c.instance.onShutdown = append(c.instance.onShutdown, fn) -} - -// OnFinalShutdown adds fn to the list of callback functions to execute -// when the server is about to be shut down NOT as part of a restart. -func (c *Controller) OnFinalShutdown(fn func() error) { - c.instance.onFinalShutdown = append(c.instance.onFinalShutdown, fn) -} - -// Context gets the context associated with the instance associated with c. -func (c *Controller) Context() Context { - return c.instance.context -} - -// NewTestController creates a new Controller for -// the server type and input specified. The filename -// is "Testfile". If the server type is not empty and -// is plugged in, a context will be created so that -// the results of setup functions can be checked for -// correctness. -// -// Used only for testing, but exported so plugins can -// use this for convenience. -func NewTestController(serverType, input string) *Controller { - var ctx Context - if stype, err := getServerType(serverType); err == nil { - ctx = stype.NewContext() - } - return &Controller{ - instance: &Instance{serverType: serverType, context: ctx}, - Dispenser: caddyfile.NewDispenser("Testfile", strings.NewReader(input)), - OncePerServerBlock: func(f func() error) error { return f() }, - } -} diff --git a/dist/CHANGES.txt b/dist/CHANGES.txt deleted file mode 100644 index 38055ed1577..00000000000 --- a/dist/CHANGES.txt +++ /dev/null @@ -1,407 +0,0 @@ -CHANGES - -0.10.6 (July 28, 2017) -- fastcgi: Fix runtime error for 32-bit and ARM architectures - - -0.10.5 (July 27, 2017) -- Renamed requestid directive to request_id -- Set default idle timeout of 5 minutes -- New 3rd-party plugin directives: cache, nobots, webdav -- New Unix timestamp placeholder {when_unix} -- Improved MITM detection on iOS clients -- errors, log: Fix log rolling parsing -- gzip: Convert any ETag header to weak etag -- fastcgi: Reverted persistent connections (issue #1736) -- proxy: Added header loaded balancing policy -- proxy: Fix hang on chunked WebSockets (e.g. with HomeAssistant) -- Several other bug fixes and minor internal improvements - - -0.10.4 (June 28, 2017) -- Vendor all dependencies -- Improve MITM detection, add experimental Tor browser support -- New requestid directive to add request IDs to each request -- New HTTP plugins supported: authz, grpc, gopkg, reauth, restic -- browse: Refreshed default UI and added symlink indicators -- errors, log: Added rotate_compress directive to compress rolled logs -- markdown: Template files loaded at each request instead of just once -- proxy: Allow multiple Server header fields on downstream response -- proxy: Perform health checks by body substring -- rewrite,redir: Added 'not_starts_with' and 'not_ends_with' operators -- tls: New ca subdirective to specify CA endpoint per-site -- Several bug fixes - - -0.10.3 (May 19, 2017) -- Replace 'maxrequestbody' directive with 'limits' directive -- proxy: Configurable port for health check -- proxy: New load balance policy: uri_hash -- templates: Renamed .Push context action to .AddLink -- tls: Allow narrower certificate renewal window at startup (#1680) -- tls: Prefer ChaCha20 if hardware does not have AES-NI - - -0.10.2 (May 2, 2017) -- Hot fix for rule paths of "/" so that they match every request -- fastcgi: Match request paths that don't start with "/" even if rule does - - -0.10.1 (May 1, 2017) -- Reduced memory usage for gzip, templates, and MITM detection -- Fixed automatic HTTP->HTTPS redirects for sites with wildcard labels -- proxy: Fix 'without' subdirective -- A few other minor bug fixes and improvements - - -0.10 (April 20, 2017) -- Built on Go 1.8.1 -- HTTPS interception detection -- Updated QUIC -- SIGUSR1 (reload) now works with QUIC servers -- New 'push' directive for HTTP/2 server push -- New 'index' directive to change the names of index files -- New -http-port and -https-port flags to change protocol ports -- New -disable-http-challenge and -disable-tls-sni-challenge flags -- New event hook plugin type -- New listener middleware plugin type -- New placeholders for cookie, query, and rewritten URI values -- basicauth: Ability to customize realm -- browse: Default template now sorts by name with directories first -- errors, log: Roll all logs by default -- errors, log: Ability to write to remote syslog -- errors, log: Standardized, simplified directive syntax -- log: Patched common log format by adding missing "-" -- proxy: New 'max_conns' setting to limit connections to upstreams -- proxy: New 'first' load balancing policy for first available host -- proxy: Health checks respect Host and insecure_skip_verify settings -- templates: New .RandomString action to add random padding to page -- timeouts: Disabled default HTTP timeouts -- tls: Settings now apply per-site rather than for entire listener -- tls: New 'alpn' setting to disable either HTTP/2 or HTTP/1.1 on per-site basis -- tls: Added curve X25519 -- tls: Added ChaCha20-Poly1305 cipher suites -- tls: Renamed muststaple to must_staple -- tls: Setting max_certs obtains certs during handshakes for all hostnames -- Dozens of miscellaneous bug fixes and improvements -- New website -- New build infrastructure -- New deployment system - - -0.9.5 (January 24, 2017) -- New -validate flag to only check a Caddyfile, then exit -- New {when_iso} placeholder for timestamp ISO 8601 in UTC -- New {rewrite_path} and {rewrite_path_escaped} placeholders -- New 'timeouts' directive to configure or disable HTTP timeouts -- HTTP-level timeouts enabled by default -- basicauth: Authorization header stripped upon successful login -- browse: Added textbox to filter listing in default template -- browse: Sanitize file names and links in default template -- browse: Ensure active Caddyfile is hidden regardless of cwd -- fastcgi: New 'root' property, mainly for use with containers -- markdown: Apply some front matter fields as tags -- proxy: Fixed HTTP/2 upstream to backend; honors -http CLI flag -- proxy: Fixed websockets over HTTPS -- proxy: Reduced memory usage and improved performance -- proxy: Added support for HTTP trailers -- tls: Fixed deadlock that affected some background renewals -- Several other smaller bugs squashed and improvements made - - -0.9.4 (December 21, 2016) -- Updated QUIC -- New maxrequestbody directive to limit size of request body -- New {latency_ms} placeholder for latency always in ms -- Serve statically compressed .gz and .br files -- fastcgi: Support for multiple backends with basic load balancing -- proxy: Fixed handling of encoded 'without' paths -- proxy: Preserve trailing slash if present in request -- proxy: Fix HTTP/2 upstreams -- templates: New .Files action to list files in a directory -- templates: .Include can now pass arguments to included file -- tls: Added ability to customize preferred curves -- tls: Added support for Must-Staple on managed certificates -- tls: Fixed subtle edge case bug with TLS-SNI challenge -- Lots of minor fixes and improvements - - -0.9.3 (September 28, 2016) -- Updated QUIC to newer version -- import: Glob pattern matching 0 files is no longer an error -- fastcgi: Fixed persistent connections (disabled by default) -- fastcgi: Configurable connection pool size parameter -- proxy: Improved failover load balancing logic -- proxy: Avoids duplicating header fields that would be confusing -- proxy: New try_duration and try_interval parameters -- proxy: Fix for IP hash policy when downed hosts come back up -- Several other bug fixes and new tests - - -0.9.2 (September 20, 2016) -- New -catimeout option to customize ACME CA HTTP timeout -- import: Fix nested import absolute/relative paths -- log: Fix multiple log outputs -- proxy: Fix for keepalive in certain cases -- tls: Fix for PreferServerCipherSuites -- Numerous other bug fixes and internal improvements - - -0.9.1 (August 17, 2016) -- New {request_body} placeholder to log request body -- {remote} placeholder no longer uses X-Forwarded-For header -- {latency} placeholder rounds to nice looking number -- Add support for ratelimit plugin -- basicauth: Declaring realm named "Restricted" -- errors: Define catch-all/default error page with * character -- header: More control to add, set, or remove headers -- proxy: New keepalive setting to help accommodate busy servers -- proxy: New load balancing policy ip_hash -- proxy: Fixed WebSocket connections -- proxy: Fixed broken header logic -- proxy: Reuse existing connection for Upgrade requests -- proxy: Support for basic auth from header or upstream address -- templates: New .Env action to access environment variables -- tls: OCSP staples persisted to disk -- tls: ACME challenges honor bind directive -- tls: Fix default protocol version (minimum TLS 1.1) -- tls: Consume challenge requests only for names Caddy is solving for -- tls: The protocol syntax allows just one value if desired -- tls: Scoped max_certs limit to site instead of global maximum -- Many other bug fixes and minor enhancements - - -0.9 (July 18, 2016) -- New core -- New experimental QUIC support with -quic flag (HTTPS only) -- New -type option to specify other server types -- Moved ~/.caddy/letsencrypt to ~/.caddy/acme and reorganized assets -- Moved caddy package to top level folder, and pushed main to subfolder -- New {request} placeholder to dump entire request (without body) -- New {hostonly} placeholder for only hostname portion of host value -- Site addresses can have paths -- Site addresses can make some use of wildcards in domains -- Renamed -directives flag to -plugins -- Restarting no longer requires spawning a new process -- Removed -restart option -- fastcgi: Env variables now support placeholders -- import: Import paths now relative to Caddyfile, not current working dir -- markdown: Overhauled; removed site generation features -- proxy: More control of headers; deprecating proxy_header subdirective -- proxy: Specify multiple upstreams with optional port ranges -- proxy: New preset 'transparent' to simplify common pass-thru headers -- proxy: Chooses longest matching path; order declared is irrelevant -- redir: Added if and if_op subdirectives to make conditional redirects -- rewrite: Support for if_op to change how conditions are evaluated -- tls: Generate self-signed certificates in memory -- tls: Support for ACME DNS challenge with 10 providers -- tls: Support for TLS-SNI challenge during restarts -- Various bug fixes and enhancements - - -0.8.3 (April 26, 2016) -- Built with Go 1.6.2 -- New pprof middleware for exposing process profiling endpoints -- New expvar middleware for exposing memory/GC performance -- New -restart option to force in-process restarts on Unix systems -- Only fail to start if managed certificate is expired (issue #642) -- Toggle case-sensitive path matching with environment variable -- File server now adds ETag header for static files -- browse: Replace .LinkedPath action with .BreadcrumbMap -- fastcgi: New except clause to exclude paths -- proxy: New max_conns setting to limit max connections per upstream -- proxy: New replaceable value for name of upstream host -- templates: New utility actions for dealing with strings -- tls: Customize certificate key with key_type (+ECC) -- tls: Session ticket keys are now rotated -- Many other minor internal improvements and bug fixes - - -0.8.2 (February 25, 2016) -- On-demand TLS can obtain certificates during handshakes -- Built with Go 1.6 -- Process log (-log) is rotated when it gets large -- Managed certificates get renewed 30 days early instead of just 14 -- fastcgi: Allow scheme prefix before address -- markdown: Support for definition lists -- proxy: Allow proxy to insecure HTTPS backends -- proxy: Support proxy to unix socket -- rewrite: Status code can be 2xx or 4xx -- templates: New .Markdown action to interpret included file as Markdown -- templates: .Truncate now truncates from end of string when length is negative -- tls: Set hard limit for certificates obtained with on-demand TLS -- tls: Load certificates from directory -- tls: Add SHA384 cipher suites -- Multiple bug fixes and internal changes - - -0.8.1 (January 12, 2016) -- Improved OCSP stapling -- Better graceful reload when new hosts need certificates from Let's Encrypt -- Current pidfile is now deleted when Caddy exits -- browse: New default template -- gzip: Added min_length setting -- import: Support for glob patterns (*) to import multiple files -- rewrite: New complex rules with conditions, regex captures, and status code -- tls: Removed DES ciphers from default cipher suite list -- tls: All supported certificates are OCSP-stapled -- tls: Allow custom configuration without specifying certificate and key -- tls: No longer allow HTTPS over port 80 -- Dozens of bug fixes, improvements, and more tests across the board - - -0.8 (December 4, 2015) -- HTTPS by default via Let's Encrypt (certs & keys are fully managed) -- Graceful restarts (on POSIX-compliant systems) -- Major internal refactoring to allow use of Caddy as library -- New directive 'mime' to customize Content-Type based on file extension -- New -accept flag to accept Let's Encrypt SA without prompt -- New -email flag to customize default email used for ACME transactions -- New -ca flag to customize ACME CA server URL -- New -revoke flag to revoke a certificate -- New -log flag to enable process log -- New -pidfile flag to enable writing pidfile -- New -grace flag to customize the graceful shutdown timeout -- New support for SIGHUP, SIGTERM, and SIGQUIT signals -- browse: Render filenames with multiple whitespace properly -- core: Use environment variables in Caddyfile -- markdown: Include Last-Modified header in response -- markdown: Render tables, strikethrough, and fenced code blocks -- proxy: Ability to exclude/ignore paths from proxying -- startup, shutdown: Better Windows support -- templates: Bug fix for .Host when port is absent -- templates: Include Last-Modified header in response -- templates: Support for custom delimiters -- tls: For non-local hosts, default port is now 443 unless specified -- tls: Force-disable HTTPS -- tls: Specify Let's Encrypt email address -- Many, many more tests and numerous bug fixes and improvements - - -0.7.6 (September 28, 2015) -- Pass in simple Caddyfile as command line arguments -- basicauth: Support for legacy htpasswd files -- browse: JSON response with file listing -- core: Caddyfile as command line argument -- errors: Can write full stack trace to HTTP response for debugging -- errors, log: Roll log files after certain size or age -- proxy: Fix for 32-bit architectures -- rewrite: Better compatibility with fastcgi and PHP apps -- templates: Added .StripExt and .StripHTML methods -- Internal improvements and minor bug fixes - - -0.7.5 (August 5, 2015) -- core: All listeners bind to 0.0.0.0 unless 'bind' directive is used -- fastcgi: Set HTTPS env variable if connection is secure -- log: Output to system log (except Windows) -- markdown: Added dev command to disable caching during development -- markdown: Fixed error reporting during initial site generation -- markdown: Fixed crash if path does not exist when server starts -- markdown: Fixed site generation and link indexing when files change -- templates: Added .NowDate for use in date-related functions -- Several bug fixes related to startup and shutdown functions - - -0.7.4 (July 30, 2015) -- browse: Sorting preference persisted in cookie -- browse: Added index.txt and default.txt to list of default files -- browse: Template files may now use Caddy template actions -- markdown: Template files may now use Caddy template actions -- markdown: Several bug fixes, especially for large and empty Markdown files -- markdown: Generate index pages to link to markdown pages (sitegen only) -- markdown: Flatten structure of front matter, changed template variables -- redir: Can use variables (placeholders) like log formats can -- redir: Catch-all redirects no longer preserve path; use {uri} instead -- redir: Syntax supports redirect tables by opening a block -- templates: Renamed .Date to .Now and added .Truncate, .Replace actions -- Other minor internal improvements and more tests - - -0.7.3 (July 15, 2015) -- errors: Error log now shows timestamp with each entry -- gzip: Fixed; Default filtering is by extension; removed MIME type filter -- import: Fixed; works inside and outside server blocks -- redir: Query string preserved on catch-all redirects -- templates: Proper 403 or 404 errors for restricted or missing files - - -0.7.2 (July 1, 2015) -- Custom builds through caddyserver.com - extend Caddy by writing addons -- browse: Sort by clicking column heading or using query string -- core: Serving hostname that doesn't resolve issues warning then listens on 0.0.0.0 -- errors: Missing error page during parse time is warning, not error -- ext: Extension only appended if request path does not end in / -- fastcgi: Fix for backend responding without status text -- fastcgi: Fix PATH_TRANSLATED when PATH_INFO is empty (RFC 3875) -- git: Removed from core (available as add-on) -- gzip: Enable by file path and/or extension -- gzip: Customize compression level -- log: Fix for missing status in log entry when error unhandled -- proxy: Strip prefix from path for proxy to path -- redir: Meta tag redirects -- templates: Support for nested includes -- Internal improvements and more tests - - -0.7.1 (June 2, 2015) -- basicauth: Patched timing vulnerability -- proxy: Support for WebSocket backends -- tls: Client authentication - - -0.7 (May 25, 2015) -- New directive 'internal' to protect resources with X-Accel-Redirect -- New -version flag to show program name and version -- core: Fixed escaped backslash characters inside quoted strings -- core: Fixed parsing Caddyfile for IPv6 addresses missing ports -- core: A notice is shown when non-local address resolves to loopback interface -- core: Warns if file descriptor limit is too low for production site (Mac/Linux) -- fastcgi: Support for Unix sockets -- git: Fixed issue that prevented pulling at designated interval -- header: Remove a header field by prefixing field name with "-" -- markdown: Simple static site generation -- markdown: Support for metadata ("front matter") at beginning of files -- rewrite: Experimental support for regular expressions -- tls: Customize cipher suites and protocols -- tls: Removed RC4 ciphers -- Other internal improvements that are not user-facing (more tests, etc.) - - -0.6 (May 7, 2015) -- New directive 'git' to automatically pull changes -- New directive 'bind' to override host server binds to -- New -root flag to specify root path to default site -- Ability to receive config data piped through stdin -- core: Warning if root directory doesn't exist at startup -- core: Entire process dies if any server fails to start -- gzip: Fixed Content-Length value when proxying requests -- errors: Error log now includes file and line number of panics -- fastcgi: Pass custom environment variables -- fastcgi: Support for HEAD, OPTIONS, PUT, PATCH, and DELETE methods -- fastcgi: Fixed SERVER_SOFTWARE variables -- markdown: Support for index files when URL points to a directory -- proxy: Load balancing with multiple backends, health checks, failovers, and multiple policies -- proxy: Add custom headers -- startup/shutdown: Run command in background with '&' at end -- templates: Added .tpl and .tmpl as default extensions -- templates: Support for index files when URL points to a directory -- templates: Changed .RemoteAddr to .IP and stripped out remote port -- tls: TLS disabled (with warning) for servers that are explicitly http:// -- websocket: Fixed SERVER_SOFTWARE and GATEWAY_INTERFACE variables -- Many internal improvements - - -0.5.1 (April 30, 2015) -- Default host is now 0.0.0.0 (wildcard) -- New -host and -port flags to override default host and port -- core: Support for binding to 0.0.0.0 -- core: Graceful error handling during heavy load; proper error responses -- errors: Fixed file path handling -- errors: Fixed panic due to nil log file -- fastcgi: Support for index files -- fastcgi: Fix for handling errors that come from responder - - -0.5 (April 28, 2015) -- Initial release diff --git a/dist/LICENSES.txt b/dist/LICENSES.txt deleted file mode 100644 index c6ca2e2b024..00000000000 --- a/dist/LICENSES.txt +++ /dev/null @@ -1,539 +0,0 @@ -The enclosed software makes use of third-party libraries either in full -or in part, original or modified. This file is part of your download so -as to be in full compliance with the licenses of all bundled property. - - - -### -### github.com/mholt/caddy -### - - 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. - - - - - - - - - - -### -### Go standard library and http2 -### - - -Copyright (c) 2012 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - - - - - -### -### github.com/russross/blackfriday -### - - -Blackfriday is distributed under the Simplified BSD License: - -> Copyright © 2011 Russ Ross -> All rights reserved. -> -> Redistribution and use in source and binary forms, with or without -> modification, are permitted provided that the following conditions -> are met: -> -> 1. Redistributions of source code must retain the above copyright -> notice, this list of conditions and the following disclaimer. -> -> 2. Redistributions in binary form must reproduce the above -> copyright notice, this list of conditions and the following -> disclaimer in the documentation and/or other materials provided with -> the distribution. -> -> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -> "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -> LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -> FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -> COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -> INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -> BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -> LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -> LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -> ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -> POSSIBILITY OF SUCH DAMAGE. - - - - - - - - -### -### github.com/dustin/go-humanize -### - - -Copyright (c) 2005-2008 Dustin Sallings - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - - - - - - - - - -### -### github.com/flynn/go-shlex -### - -Apache 2.0 license as found in this file - - - - - - -### -### github.com/go-yaml/yaml -### - - -Copyright (c) 2011-2014 - Canonical Inc. - -This software is licensed under the LGPLv3, included below. - -As a special exception to the GNU Lesser General Public License version 3 -("LGPL3"), the copyright holders of this Library give you permission to -convey to a third party a Combined Work that links statically or dynamically -to this Library without providing any Minimal Corresponding Source or -Minimal Application Code as set out in 4d or providing the installation -information set out in section 4e, provided that you comply with the other -provisions of LGPL3 and provided that you meet, for the Application the -terms and conditions of the license(s) which apply to the Application. - -Except as stated in this special exception, the provisions of LGPL3 will -continue to comply in full to this Library. If you modify this Library, you -may apply this exception to your version of this Library, but you are not -obliged to do so. If you do not wish to do so, delete this exception -statement from your version. This exception does not (and cannot) modify any -license terms which apply to the Application, with which you must still -comply. - - - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/dist/README.txt b/dist/README.txt deleted file mode 100644 index 694975d0119..00000000000 --- a/dist/README.txt +++ /dev/null @@ -1,39 +0,0 @@ -CADDY 0.10.6 - -Website - https://caddyserver.com - -Community Forum - https://caddy.community - -Twitter - @caddyserver - -Source Code - https://github.com/mholt/caddy - https://github.com/caddyserver - - -For instructions on using Caddy, please see the user guide on -the website. For a list of what's new in this version, see -CHANGES.txt. - -For a good time, follow @mholt6 on Twitter. - -Want to get involved with Caddy's development? We love to have -contributions! Please file an issue on GitHub to discuss a -change or fix you'd like to make, then submit a pull request -and we'll review it! Your contributions will reach millions -of people who connect to sites served by Caddy. - -Extend Caddy by developing a plugin for it! Instructions on -the project wiki: https://github.com/mholt/caddy/wiki - -And thanks - you're awesome! - -If you think Caddy is awesome too, consider sponsoring it: -https://caddyserver.com/pricing - and help keep Caddy free. - - ---- -(c) 2015-2017 Light Code Labs, LLC. diff --git a/dist/gitcookie.sh.enc b/dist/gitcookie.sh.enc deleted file mode 100644 index 1a51f392b5d..00000000000 --- a/dist/gitcookie.sh.enc +++ /dev/null @@ -1,3 +0,0 @@ -( #劕- Dr}`:,0@9pK8Z ˎVm0^B%լ@9]lП)8"Z_͑:g7v]7#`޸'޾ޅǦB9z^'5\*N -k~= w^$-Df(*Cd/uk _vM U+LgJ#[] -.wQ/٫B\BJi'"v{y)x\8'2렋ta} /srv/www/localhost/index.html' -sudo bash -c 'echo "http://localhost { - root /srv/www/localhost -}" >> /etc/caddy/Caddyfile' -``` - -Start and Stop the Caddy launchd service using the following commands: - -```bash -launchctl load /Library/LaunchDaemons/com.caddyserver.web.plist -launchctl unload /Library/LaunchDaemons/com.caddyserver.web.plist -``` - -To start on every boot use the `-w` flag (to write): - -```bash -launchctl load -w /Library/LaunchAgents/com.caddyserver.web.plist -``` - -More information can be found in this blogpost: [Running Caddy as a service on macOS X server](https://denbeke.be/blog/software/running-caddy-as-a-service-on-macos-os-x-server/) diff --git a/dist/init/mac-launchd/com.caddyserver.web.plist b/dist/init/mac-launchd/com.caddyserver.web.plist deleted file mode 100644 index 980bfb2a14b..00000000000 --- a/dist/init/mac-launchd/com.caddyserver.web.plist +++ /dev/null @@ -1,53 +0,0 @@ - - - - - Label - Caddy - ProgramArguments - - /usr/local/bin/caddy - -agree - -conf - /etc/caddy/Caddyfile - -root - /var/tmp - - EnvironmentVariables - - CADDYPATH - /etc/ssl/caddy - - - UserName - root - GroupName - wheel - InitGroups - - - RunAtLoad - - KeepAlive - - Crashed - - - - SoftResourceLimits - - NumberOfFiles - 8192 - - HardResourceLimits - - - WorkingDirectory - /etc/ssl/caddy - - StandardErrorPath - /var/log/caddy/error.log - StandardOutPath - /var/log/caddy/info.log - - diff --git a/duration_fuzz.go b/duration_fuzz.go new file mode 100644 index 00000000000..8a1f0c7cba5 --- /dev/null +++ b/duration_fuzz.go @@ -0,0 +1,25 @@ +// 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 caddy + +func FuzzParseDuration(data []byte) int { + _, err := ParseDuration(string(data)) + if err != nil { + return 0 + } + return 1 +} diff --git a/filepath.go b/filepath.go new file mode 100644 index 00000000000..aad90779949 --- /dev/null +++ b/filepath.go @@ -0,0 +1,39 @@ +// 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 !windows + +package caddy + +import ( + "os" + "path/filepath" +) + +// FastAbs is an optimized version of filepath.Abs for Unix systems, +// since we don't expect the working directory to ever change once +// Caddy is running. Avoid the os.Getwd() syscall overhead. +// It's overall the same as stdlib's implementation, the difference +// being cached working directory. +func FastAbs(path string) (string, error) { + if filepath.IsAbs(path) { + return filepath.Clean(path), nil + } + if wderr != nil { + return "", wderr + } + return filepath.Join(wd, path), nil +} + +var wd, wderr = os.Getwd() diff --git a/filepath_windows.go b/filepath_windows.go new file mode 100644 index 00000000000..aa70955e99c --- /dev/null +++ b/filepath_windows.go @@ -0,0 +1,27 @@ +// 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 ( + "path/filepath" +) + +// FastAbs can't be optimized on Windows because there +// are special file paths that require the use of syscall.FullPath +// to handle correctly. +// Just call stdlib's implementation which uses that function. +func FastAbs(path string) (string, error) { + return filepath.Abs(path) +} diff --git a/filesystem.go b/filesystem.go new file mode 100644 index 00000000000..d6679e90b15 --- /dev/null +++ b/filesystem.go @@ -0,0 +1,24 @@ +// 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 "io/fs" + +type FileSystems interface { + Register(k string, v fs.FS) + Unregister(k string) + Get(k string) (v fs.FS, ok bool) + Default() fs.FS +} diff --git a/go.mod b/go.mod new file mode 100644 index 00000000000..deff086961d --- /dev/null +++ b/go.mod @@ -0,0 +1,156 @@ +module github.com/caddyserver/caddy/v2 + +go 1.24 + +require ( + github.com/BurntSushi/toml v1.4.0 + github.com/KimMachineGun/automemlimit v0.7.0 + github.com/Masterminds/sprig/v3 v3.3.0 + github.com/alecthomas/chroma/v2 v2.14.0 + github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b + github.com/caddyserver/certmagic v0.21.7 + github.com/caddyserver/zerossl v0.1.3 + github.com/dustin/go-humanize v1.0.1 + github.com/go-chi/chi/v5 v5.0.12 + github.com/google/cel-go v0.21.0 + github.com/google/uuid v1.6.0 + github.com/klauspost/compress v1.17.11 + github.com/klauspost/cpuid/v2 v2.2.9 + github.com/mholt/acmez/v3 v3.0.1 + github.com/prometheus/client_golang v1.19.1 + github.com/quic-go/quic-go v0.50.0 + github.com/smallstep/certificates v0.26.1 + github.com/smallstep/nosql v0.6.1 + github.com/smallstep/truststore v0.13.0 + github.com/spf13/cobra v1.8.1 + github.com/spf13/pflag v1.0.5 + github.com/stretchr/testify v1.9.0 + github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 + github.com/yuin/goldmark v1.7.8 + github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 + go.opentelemetry.io/contrib/propagators/autoprop v0.42.0 + go.opentelemetry.io/otel v1.31.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 + go.opentelemetry.io/otel/sdk v1.31.0 + go.uber.org/automaxprocs v1.6.0 + go.uber.org/zap v1.27.0 + go.uber.org/zap/exp v0.3.0 + golang.org/x/crypto v0.31.0 + golang.org/x/crypto/x509roots/fallback v0.0.0-20241104001025-71ed71b4faf9 + golang.org/x/net v0.33.0 + golang.org/x/sync v0.10.0 + golang.org/x/term v0.27.0 + golang.org/x/time v0.7.0 + gopkg.in/natefinch/lumberjack.v2 v2.2.1 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + dario.cat/mergo v1.0.1 // indirect + github.com/Microsoft/go-winio v0.6.0 // indirect + github.com/antlr4-go/antlr/v4 v4.13.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/francoispqt/gojay v1.2.13 // indirect + github.com/fxamacker/cbor/v2 v2.6.0 // indirect + github.com/go-jose/go-jose/v3 v3.0.3 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 // indirect + github.com/google/go-tpm v0.9.0 // indirect + github.com/google/go-tspi v0.3.0 // indirect + github.com/google/pprof v0.0.0-20231212022811-ec68065c825e // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/onsi/ginkgo/v2 v2.13.2 // indirect + github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 // indirect + github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 // indirect + github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect + github.com/x448/float16 v0.8.4 // indirect + github.com/zeebo/blake3 v0.2.4 // indirect + go.opentelemetry.io/contrib/propagators/aws v1.17.0 // indirect + go.opentelemetry.io/contrib/propagators/b3 v1.17.0 // indirect + go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 // indirect + go.opentelemetry.io/contrib/propagators/ot v1.17.0 // indirect + go.uber.org/mock v0.5.0 // indirect + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.3.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 + github.com/chzyer/readline v1.5.1 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/dgraph-io/badger v1.6.2 // indirect + github.com/dgraph-io/badger/v2 v2.2007.4 // indirect + github.com/dgraph-io/ristretto v0.2.0 // indirect + github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect + github.com/dlclark/regexp2 v1.11.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-kit/kit v0.13.0 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-sql-driver/mysql v1.7.1 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/huandu/xstrings v1.5.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.14.3 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.3 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgtype v1.14.0 // indirect + github.com/jackc/pgx/v4 v4.18.3 // indirect + github.com/libdns/libdns v0.2.2 + github.com/manifoldco/promptui v0.9.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect + github.com/miekg/dns v1.1.62 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/pires/go-proxyproto v0.7.1-0.20240628150027-b718e7ce4964 + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_model v0.5.0 + github.com/prometheus/common v0.48.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/rs/xid v1.5.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/slackhq/nebula v1.6.1 // indirect + github.com/spf13/cast v1.7.0 // indirect + github.com/stoewer/go-strcase v1.2.0 // indirect + github.com/urfave/cli v1.22.14 // indirect + go.etcd.io/bbolt v1.3.9 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 // indirect + go.opentelemetry.io/otel/metric v1.31.0 // indirect + go.opentelemetry.io/otel/trace v1.31.0 + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.step.sm/cli-utils v0.9.0 // indirect + go.step.sm/crypto v0.45.0 + go.step.sm/linkedca v0.20.1 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/mod v0.18.0 // indirect + golang.org/x/sys v0.30.0 + golang.org/x/text v0.21.0 // indirect + golang.org/x/tools v0.22.0 // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.1 // indirect + howett.net/plist v1.0.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000000..27b97f31ed7 --- /dev/null +++ b/go.sum @@ -0,0 +1,779 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= +cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= +cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg= +cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro= +cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= +cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= +cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= +cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0= +cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE= +cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY= +cloud.google.com/go/kms v1.16.0/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc= +cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= +cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= +dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= +dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= +dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/KimMachineGun/automemlimit v0.7.0 h1:7G06p/dMSf7G8E6oq+f2uOPuVncFyIlDI/pBWK49u88= +github.com/KimMachineGun/automemlimit v0.7.0/go.mod h1:QZxpHaGOQoYvFhv/r4u3U0JTC2ZcOwbSr11UZF46UBM= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= +github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= +github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= +github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= +github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= +github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw= +github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= +github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= +github.com/aws/aws-sdk-go-v2/config v1.27.13 h1:WbKW8hOzrWoOA/+35S5okqO/2Ap8hkkFUzoW8Hzq24A= +github.com/aws/aws-sdk-go-v2/config v1.27.13/go.mod h1:XLiyiTMnguytjRER7u5RIkhIqS8Nyz41SwAWb4xEjxs= +github.com/aws/aws-sdk-go-v2/credentials v1.17.13 h1:XDCJDzk/u5cN7Aple7D/MiAhx1Rjo/0nueJ0La8mRuE= +github.com/aws/aws-sdk-go-v2/credentials v1.17.13/go.mod h1:FMNcjQrmuBYvOTZDtOLCIu0esmxjF7RuA/89iSXWzQI= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk= +github.com/aws/aws-sdk-go-v2/service/kms v1.31.1 h1:5wtyAwuUiJiM3DHYeGZmP5iMonM7DFBWAEaaVPHYZA0= +github.com/aws/aws-sdk-go-v2/service/kms v1.31.1/go.mod h1:2snWQJQUKsbN66vAawJuOGX7dr37pfOq9hb0tZDGIqQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.6 h1:o5cTaeunSpfXiLTIBx5xo2enQmiChtu1IBbzXnfU9Hs= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.6/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0 h1:Qe0r0lVURDDeBQJ4yP+BOrJkvkiCo/3FH/t+wY11dmw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.7 h1:et3Ta53gotFR4ERLXXHIHl/Uuk1qYpP5uU7cvNql8ns= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.7/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw= +github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= +github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/caddyserver/certmagic v0.21.7 h1:66KJioPFJwttL43KYSWk7ErSmE6LfaJgCQuhm8Sg6fg= +github.com/caddyserver/certmagic v0.21.7/go.mod h1:LCPG3WLxcnjVKl/xpjzM0gqh0knrKKKiO5WVttX2eEI= +github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= +github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= +github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= +github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= +github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= +github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE= +github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= +github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= +github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= +github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= +github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= +github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= +github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= +github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= +github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 h1:heyoXNxkRT155x4jTAiSv5BVSVkueifPUm+Q8LUXMRo= +github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745/go.mod h1:zN0wUQgV9LjwLZeFHnrAbQi8hzMVvEWePyk+MhPOk7k= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk= +github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= +github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98= +github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY= +github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus= +github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20231212022811-ec68065c825e h1:bwOy7hAFd0C91URzMIEBfr6BAz29yk7Qj0cy6S7DJlU= +github.com/google/pprof v0.0.0-20231212022811-ec68065c825e/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU= +github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= +github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg= +github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= +github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= +github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mholt/acmez/v3 v3.0.1 h1:4PcjKjaySlgXK857aTfDuRbmnM5gb3Ruz3tvoSJAUp8= +github.com/mholt/acmez/v3 v3.0.1/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= +github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= +github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= +github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs= +github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= +github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= +github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU= +github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o= +github.com/pires/go-proxyproto v0.7.1-0.20240628150027-b718e7ce4964 h1:ct/vxNBgHpASQ4sT8NaBX9LtsEtluZqaUJydLG50U3E= +github.com/pires/go-proxyproto v0.7.1-0.20240628150027-b718e7ce4964/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.50.0 h1:3H/ld1pa3CYhkcc20TPIyG1bNsdhn9qZBGN3b9/UyUo= +github.com/quic-go/quic-go v0.50.0/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n6E= +github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= +github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= +github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= +github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= +github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= +github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= +github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= +github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= +github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= +github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= +github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= +github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= +github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= +github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= +github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= +github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/slackhq/nebula v1.6.1 h1:/OCTR3abj0Sbf2nGoLUrdDXImrCv0ZVFpVPP5qa0DsM= +github.com/slackhq/nebula v1.6.1/go.mod h1:UmkqnXe4O53QwToSl/gG7sM4BroQwAB7dd4hUaT6MlI= +github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= +github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= +github.com/smallstep/certificates v0.26.1 h1:FIUliEBcExSfJJDhRFA/s8aZgMIFuorexnRSKQd884o= +github.com/smallstep/certificates v0.26.1/go.mod h1:OQMrW39IrGKDViKSHrKcgSQArMZ8c7EcjhYKK7mYqis= +github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 h1:kjYvkvS/Wdy0PVRDUAA0gGJIVSEZYhiAJtfwYgOYoGA= +github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4= +github.com/smallstep/nosql v0.6.1 h1:X8IBZFTRIp1gmuf23ne/jlD/BWKJtDQbtatxEn7Et1Y= +github.com/smallstep/nosql v0.6.1/go.mod h1:vrN+CftYYNnDM+DQqd863ATynvYFm/6FuY9D4TeAm2Y= +github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 h1:B6cED3iLJTgxpdh4tuqByDjRRKan2EvtnOfHr2zHJVg= +github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81/go.mod h1:SoUAr/4M46rZ3WaLstHxGhLEgoYIDRqxQEXLOmOEB0Y= +github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d h1:06LUHn4Ia2X6syjIaCMNaXXDNdU+1N/oOHynJbWgpXw= +github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d/go.mod h1:4d0ub42ut1mMtvGyMensjuHYEUpRrASvkzLEJvoRQcU= +github.com/smallstep/truststore v0.13.0 h1:90if9htAOblavbMeWlqNLnO9bsjjgVv2hQeQJCi/py4= +github.com/smallstep/truststore v0.13.0/go.mod h1:3tmMp2aLKZ/OA/jnFUB0cYPcho402UG2knuJoPh4j7A= +github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= +github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 h1:uxMgm0C+EjytfAqyfBG55ZONKQ7mvd7x4YYCWsf8QHQ= +github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= +github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= +github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= +github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= +github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= +github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= +github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= +github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= +github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= +github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= +github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= +go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= +go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= +go.opentelemetry.io/contrib/propagators/autoprop v0.42.0 h1:s2RzYOAqHVgG23q8fPWYChobUoZM6rJZ98EnylJr66w= +go.opentelemetry.io/contrib/propagators/autoprop v0.42.0/go.mod h1:Mv/tWNtZn+NbALDb2XcItP0OM3lWWZjAfSroINxfW+Y= +go.opentelemetry.io/contrib/propagators/aws v1.17.0 h1:IX8d7l2uRw61BlmZBOTQFaK+y22j6vytMVTs9wFrO+c= +go.opentelemetry.io/contrib/propagators/aws v1.17.0/go.mod h1:pAlCYRWff4uGqRXOVn3WP8pDZ5E0K56bEoG7a1VSL4k= +go.opentelemetry.io/contrib/propagators/b3 v1.17.0 h1:ImOVvHnku8jijXqkwCSyYKRDt2YrnGXD4BbhcpfbfJo= +go.opentelemetry.io/contrib/propagators/b3 v1.17.0/go.mod h1:IkfUfMpKWmynvvE0264trz0sf32NRTZL4nuAN9AbWRc= +go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 h1:Zbpbmwav32Ea5jSotpmkWEl3a6Xvd4tw/3xxGO1i05Y= +go.opentelemetry.io/contrib/propagators/jaeger v1.17.0/go.mod h1:tcTUAlmO8nuInPDSBVfG+CP6Mzjy5+gNV4mPxMbL0IA= +go.opentelemetry.io/contrib/propagators/ot v1.17.0 h1:ufo2Vsz8l76eI47jFjuVyjyB3Ae2DmfiCV/o6Vc8ii0= +go.opentelemetry.io/contrib/propagators/ot v1.17.0/go.mod h1:SbKPj5XGp8K/sGm05XblaIABgMgw2jDczP8gGeuaVLk= +go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 h1:FFeLy03iVTXP6ffeN2iXrxfGsZGCjVx0/4KlizjyBwU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0/go.mod h1:TMu73/k1CP8nBUpDLc71Wj/Kf7ZS9FK5b53VapRsP9o= +go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= +go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= +go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.step.sm/cli-utils v0.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ= +go.step.sm/cli-utils v0.9.0/go.mod h1:Y/CRoWl1FVR9j+7PnAewufAwKmBOTzR6l9+7EYGAnp8= +go.step.sm/crypto v0.45.0 h1:Z0WYAaaOYrJmKP9sJkPW+6wy3pgN3Ija8ek/D4serjc= +go.step.sm/crypto v0.45.0/go.mod h1:6IYlT0L2jfj81nVyCPpvA5cORy0EVHPhieSgQyuwHIY= +go.step.sm/linkedca v0.20.1 h1:bHDn1+UG1NgRrERkWbbCiAIvv4lD5NOFaswPDTyO5vU= +go.step.sm/linkedca v0.20.1/go.mod h1:Vaq4+Umtjh7DLFI1KuIxeo598vfBzgSYZUjgVJ7Syxw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= +go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= +go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto/x509roots/fallback v0.0.0-20241104001025-71ed71b4faf9 h1:4cEcP5+OjGppY79LCQ5Go2B1Boix2x0v6pvA01P3FoA= +golang.org/x/crypto/x509roots/fallback v0.0.0-20241104001025-71ed71b4faf9/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= +google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4= +google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw= +google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw= +google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= +google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= +howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= +sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/internal/filesystems/map.go b/internal/filesystems/map.go new file mode 100644 index 00000000000..e795ed1fec1 --- /dev/null +++ b/internal/filesystems/map.go @@ -0,0 +1,77 @@ +package filesystems + +import ( + "io/fs" + "strings" + "sync" +) + +const ( + DefaultFilesystemKey = "default" +) + +var DefaultFilesystem = &wrapperFs{key: DefaultFilesystemKey, FS: OsFS{}} + +// wrapperFs exists so can easily add to wrapperFs down the line +type wrapperFs struct { + key string + fs.FS +} + +// FilesystemMap stores a map of filesystems +// the empty key will be overwritten to be the default key +// it includes a default filesystem, based off the os fs +type FilesystemMap struct { + m sync.Map +} + +// note that the first invocation of key cannot be called in a racy context. +func (f *FilesystemMap) key(k string) string { + if k == "" { + k = DefaultFilesystemKey + } + return k +} + +// Register will add the filesystem with key to later be retrieved +// A call with a nil fs will call unregister, ensuring that a call to Default() will never be nil +func (f *FilesystemMap) Register(k string, v fs.FS) { + k = f.key(k) + if v == nil { + f.Unregister(k) + return + } + f.m.Store(k, &wrapperFs{key: k, FS: v}) +} + +// Unregister will remove the filesystem with key from the filesystem map +// if the key is the default key, it will set the default to the osFS instead of deleting it +// modules should call this on cleanup to be safe +func (f *FilesystemMap) Unregister(k string) { + k = f.key(k) + if k == DefaultFilesystemKey { + f.m.Store(k, DefaultFilesystem) + } else { + f.m.Delete(k) + } +} + +// Get will get a filesystem with a given key +func (f *FilesystemMap) Get(k string) (v fs.FS, ok bool) { + k = f.key(k) + c, ok := f.m.Load(strings.TrimSpace(k)) + if !ok { + if k == DefaultFilesystemKey { + f.m.Store(k, DefaultFilesystem) + return DefaultFilesystem, true + } + return nil, ok + } + return c.(fs.FS), true +} + +// Default will get the default filesystem in the filesystem map +func (f *FilesystemMap) Default() fs.FS { + val, _ := f.Get(DefaultFilesystemKey) + return val +} diff --git a/internal/filesystems/os.go b/internal/filesystems/os.go new file mode 100644 index 00000000000..04b4d5b4079 --- /dev/null +++ b/internal/filesystems/os.go @@ -0,0 +1,29 @@ +package filesystems + +import ( + "io/fs" + "os" + "path/filepath" +) + +// OsFS is a simple fs.FS implementation that uses the local +// file system. (We do not use os.DirFS because we do our own +// rooting or path prefixing without being constrained to a single +// root folder. The standard os.DirFS implementation is problematic +// since roots can be dynamic in our application.) +// +// OsFS also implements fs.StatFS, fs.GlobFS, fs.ReadDirFS, and fs.ReadFileFS. +type OsFS struct{} + +func (OsFS) Open(name string) (fs.File, error) { return os.Open(name) } +func (OsFS) Stat(name string) (fs.FileInfo, error) { return os.Stat(name) } +func (OsFS) Glob(pattern string) ([]string, error) { return filepath.Glob(pattern) } +func (OsFS) ReadDir(name string) ([]fs.DirEntry, error) { return os.ReadDir(name) } +func (OsFS) ReadFile(name string) ([]byte, error) { return os.ReadFile(name) } + +var ( + _ fs.StatFS = (*OsFS)(nil) + _ fs.GlobFS = (*OsFS)(nil) + _ fs.ReadDirFS = (*OsFS)(nil) + _ fs.ReadFileFS = (*OsFS)(nil) +) diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go new file mode 100644 index 00000000000..7ae09b60465 --- /dev/null +++ b/internal/metrics/metrics.go @@ -0,0 +1,39 @@ +package metrics + +import ( + "net/http" + "strconv" +) + +func SanitizeCode(s int) string { + switch s { + case 0, 200: + return "200" + default: + return strconv.Itoa(s) + } +} + +// Only support the list of "regular" HTTP methods, see +// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods +var methodMap = map[string]string{ + "GET": http.MethodGet, "get": http.MethodGet, + "HEAD": http.MethodHead, "head": http.MethodHead, + "PUT": http.MethodPut, "put": http.MethodPut, + "POST": http.MethodPost, "post": http.MethodPost, + "DELETE": http.MethodDelete, "delete": http.MethodDelete, + "CONNECT": http.MethodConnect, "connect": http.MethodConnect, + "OPTIONS": http.MethodOptions, "options": http.MethodOptions, + "TRACE": http.MethodTrace, "trace": http.MethodTrace, + "PATCH": http.MethodPatch, "patch": http.MethodPatch, +} + +// SanitizeMethod sanitizes the method for use as a metric label. This helps +// prevent high cardinality on the method label. The name is always upper case. +func SanitizeMethod(m string) string { + if m, ok := methodMap[m]; ok { + return m + } + + return "OTHER" +} diff --git a/internal/metrics/metrics_test.go b/internal/metrics/metrics_test.go new file mode 100644 index 00000000000..c3f5965b986 --- /dev/null +++ b/internal/metrics/metrics_test.go @@ -0,0 +1,28 @@ +package metrics + +import ( + "strings" + "testing" +) + +func TestSanitizeMethod(t *testing.T) { + tests := []struct { + method string + expected string + }{ + {method: "get", expected: "GET"}, + {method: "POST", expected: "POST"}, + {method: "OPTIONS", expected: "OPTIONS"}, + {method: "connect", expected: "CONNECT"}, + {method: "trace", expected: "TRACE"}, + {method: "UNKNOWN", expected: "OTHER"}, + {method: strings.Repeat("ohno", 9999), expected: "OTHER"}, + } + + for _, d := range tests { + actual := SanitizeMethod(d.method) + if actual != d.expected { + t.Errorf("Not same: expected %#v, but got %#v", d.expected, actual) + } + } +} diff --git a/internal/ranges.go b/internal/ranges.go new file mode 100644 index 00000000000..e9429e2635f --- /dev/null +++ b/internal/ranges.go @@ -0,0 +1,14 @@ +package internal + +// PrivateRangesCIDR returns a list of private CIDR range +// strings, which can be used as a configuration shortcut. +func PrivateRangesCIDR() []string { + return []string{ + "192.168.0.0/16", + "172.16.0.0/12", + "10.0.0.0/8", + "127.0.0.1/8", + "fd00::/8", + "::1", + } +} diff --git a/internal/sockets.go b/internal/sockets.go new file mode 100644 index 00000000000..56ae9f4e6fa --- /dev/null +++ b/internal/sockets.go @@ -0,0 +1,56 @@ +// 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 internal + +import ( + "fmt" + "io/fs" + "strconv" + "strings" +) + +// SplitUnixSocketPermissionsBits takes a unix socket address in the +// unusual "path|bits" format (e.g. /run/caddy.sock|0222) and tries +// to split it into socket path (host) and permissions bits (port). +// Colons (":") can't be used as separator, as socket paths on Windows +// may include a drive letter (e.g. `unix/c:\absolute\path.sock`). +// Permission bits will default to 0200 if none are specified. +// Throws an error, if the first carrying bit does not +// include write perms (e.g. `0422` or `022`). +// Symbolic permission representation (e.g. `u=w,g=w,o=w`) +// is not supported and will throw an error for now! +func SplitUnixSocketPermissionsBits(addr string) (path string, fileMode fs.FileMode, err error) { + addrSplit := strings.SplitN(addr, "|", 2) + + if len(addrSplit) == 2 { + // parse octal permission bit string as uint32 + fileModeUInt64, err := strconv.ParseUint(addrSplit[1], 8, 32) + if err != nil { + return "", 0, fmt.Errorf("could not parse octal permission bits in %s: %v", addr, err) + } + fileMode = fs.FileMode(fileModeUInt64) + + // FileMode.String() returns a string like `-rwxr-xr--` for `u=rwx,g=rx,o=r` (`0754`) + if string(fileMode.String()[2]) != "w" { + return "", 0, fmt.Errorf("owner of the socket requires '-w-' (write, octal: '2') permissions at least; got '%s' in %s", fileMode.String()[1:4], addr) + } + + return addrSplit[0], fileMode, nil + } + + // default to 0200 (symbolic: `u=w,g=,o=`) + // if no permission bits are specified + return addr, 0o200, nil +} diff --git a/internal/testmocks/dummyverifier.go b/internal/testmocks/dummyverifier.go new file mode 100644 index 00000000000..1fbef32bfe2 --- /dev/null +++ b/internal/testmocks/dummyverifier.go @@ -0,0 +1,41 @@ +package testmocks + +import ( + "crypto/x509" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/modules/caddytls" +) + +func init() { + caddy.RegisterModule(new(dummyVerifier)) +} + +type dummyVerifier struct{} + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. +func (dummyVerifier) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + return nil +} + +// CaddyModule implements caddy.Module. +func (dummyVerifier) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.client_auth.verifier.dummy", + New: func() caddy.Module { + return new(dummyVerifier) + }, + } +} + +// VerifyClientCertificate implements ClientCertificateVerifier. +func (dummyVerifier) VerifyClientCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + return nil +} + +var ( + _ caddy.Module = dummyVerifier{} + _ caddytls.ClientCertificateVerifier = dummyVerifier{} + _ caddyfile.Unmarshaler = dummyVerifier{} +) diff --git a/listen.go b/listen.go new file mode 100644 index 00000000000..1a7051bbfd4 --- /dev/null +++ b/listen.go @@ -0,0 +1,318 @@ +// 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 !unix || solaris + +package caddy + +import ( + "context" + "fmt" + "net" + "os" + "slices" + "strconv" + "sync" + "sync/atomic" + "time" + + "go.uber.org/zap" +) + +func reuseUnixSocket(_, _ string) (any, error) { + return nil, nil +} + +func listenReusable(ctx context.Context, lnKey string, network, address string, config net.ListenConfig) (any, error) { + var socketFile *os.File + + fd := slices.Contains([]string{"fd", "fdgram"}, network) + if fd { + socketFd, err := strconv.ParseUint(address, 0, strconv.IntSize) + if err != nil { + return nil, fmt.Errorf("invalid file descriptor: %v", err) + } + + func() { + socketFilesMu.Lock() + defer socketFilesMu.Unlock() + + socketFdWide := uintptr(socketFd) + var ok bool + + socketFile, ok = socketFiles[socketFdWide] + + if !ok { + socketFile = os.NewFile(socketFdWide, lnKey) + if socketFile != nil { + socketFiles[socketFdWide] = socketFile + } + } + }() + + if socketFile == nil { + return nil, fmt.Errorf("invalid socket file descriptor: %d", socketFd) + } + } + + datagram := slices.Contains([]string{"udp", "udp4", "udp6", "unixgram", "fdgram"}, network) + if datagram { + sharedPc, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) { + var ( + pc net.PacketConn + err error + ) + if fd { + pc, err = net.FilePacketConn(socketFile) + } else { + pc, err = config.ListenPacket(ctx, network, address) + } + if err != nil { + return nil, err + } + return &sharedPacketConn{PacketConn: pc, key: lnKey}, nil + }) + if err != nil { + return nil, err + } + return &fakeClosePacketConn{sharedPacketConn: sharedPc.(*sharedPacketConn)}, nil + } + + sharedLn, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) { + var ( + ln net.Listener + err error + ) + if fd { + ln, err = net.FileListener(socketFile) + } else { + ln, err = config.Listen(ctx, network, address) + } + if err != nil { + return nil, err + } + return &sharedListener{Listener: ln, key: lnKey}, nil + }) + if err != nil { + return nil, err + } + return &fakeCloseListener{sharedListener: sharedLn.(*sharedListener), keepAlivePeriod: config.KeepAlive}, nil +} + +// fakeCloseListener is a private wrapper over a listener that +// is shared. The state of fakeCloseListener is not shared. +// This allows one user of a socket to "close" the listener +// while in reality the socket stays open for other users of +// the listener. In this way, servers become hot-swappable +// while the listener remains running. Listeners should be +// re-wrapped in a new fakeCloseListener each time the listener +// is reused. This type is atomic and values must not be copied. +type fakeCloseListener struct { + closed int32 // accessed atomically; belongs to this struct only + *sharedListener // embedded, so we also become a net.Listener + keepAlivePeriod time.Duration +} + +type canSetKeepAlive interface { + SetKeepAlivePeriod(d time.Duration) error + SetKeepAlive(bool) error +} + +func (fcl *fakeCloseListener) Accept() (net.Conn, error) { + // if the listener is already "closed", return error + if atomic.LoadInt32(&fcl.closed) == 1 { + return nil, fakeClosedErr(fcl) + } + + // call underlying accept + conn, err := fcl.sharedListener.Accept() + if err == nil { + // if 0, do nothing, Go's default is already set + // and if the connection allows setting KeepAlive, set it + if tconn, ok := conn.(canSetKeepAlive); ok && fcl.keepAlivePeriod != 0 { + if fcl.keepAlivePeriod > 0 { + err = tconn.SetKeepAlivePeriod(fcl.keepAlivePeriod) + } else { // negative + err = tconn.SetKeepAlive(false) + } + if err != nil { + Log().With(zap.String("server", fcl.sharedListener.key)).Warn("unable to set keepalive for new connection:", zap.Error(err)) + } + } + return conn, nil + } + + // since Accept() returned an error, it may be because our reference to + // the listener (this fakeCloseListener) may have been closed, i.e. the + // server is shutting down; in that case, we need to clear the deadline + // that we set when Close() was called, and return a non-temporary and + // non-timeout error value to the caller, masking the "true" error, so + // that server loops / goroutines won't retry, linger, and leak + if atomic.LoadInt32(&fcl.closed) == 1 { + // we dereference the sharedListener explicitly even though it's embedded + // so that it's clear in the code that side-effects are shared with other + // users of this listener, not just our own reference to it; we also don't + // do anything with the error because all we could do is log it, but we + // explicitly assign it to nothing so we don't forget it's there if needed + _ = fcl.sharedListener.clearDeadline() + + if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + return nil, fakeClosedErr(fcl) + } + } + + return nil, err +} + +// Close stops accepting new connections without closing the +// underlying listener. The underlying listener is only closed +// if the caller is the last known user of the socket. +func (fcl *fakeCloseListener) Close() error { + if atomic.CompareAndSwapInt32(&fcl.closed, 0, 1) { + // There are two ways I know of to get an Accept() + // function to return to the server loop that called + // it: close the listener, or set a deadline in the + // past. Obviously, we can't close the socket yet + // since others may be using it (hence this whole + // file). But we can set the deadline in the past, + // and this is kind of cheating, but it works, and + // it apparently even works on Windows. + _ = fcl.sharedListener.setDeadline() + _, _ = listenerPool.Delete(fcl.sharedListener.key) + } + return nil +} + +// sharedListener is a wrapper over an underlying listener. The listener +// and the other fields on the struct are shared state that is synchronized, +// so sharedListener structs must never be copied (always use a pointer). +type sharedListener struct { + net.Listener + key string // uniquely identifies this listener + deadline bool // whether a deadline is currently set + deadlineMu sync.Mutex +} + +func (sl *sharedListener) clearDeadline() error { + var err error + sl.deadlineMu.Lock() + if sl.deadline { + switch ln := sl.Listener.(type) { + case *net.TCPListener: + err = ln.SetDeadline(time.Time{}) + } + sl.deadline = false + } + sl.deadlineMu.Unlock() + return err +} + +func (sl *sharedListener) setDeadline() error { + timeInPast := time.Now().Add(-1 * time.Minute) + var err error + sl.deadlineMu.Lock() + if !sl.deadline { + switch ln := sl.Listener.(type) { + case *net.TCPListener: + err = ln.SetDeadline(timeInPast) + } + sl.deadline = true + } + sl.deadlineMu.Unlock() + return err +} + +// Destruct is called by the UsagePool when the listener is +// finally not being used anymore. It closes the socket. +func (sl *sharedListener) Destruct() error { + return sl.Listener.Close() +} + +// fakeClosePacketConn is like fakeCloseListener, but for PacketConns, +// or more specifically, *net.UDPConn +type fakeClosePacketConn struct { + closed int32 // accessed atomically; belongs to this struct only + *sharedPacketConn // embedded, so we also become a net.PacketConn; its key is used in Close +} + +func (fcpc *fakeClosePacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { + // if the listener is already "closed", return error + if atomic.LoadInt32(&fcpc.closed) == 1 { + return 0, nil, &net.OpError{ + Op: "readfrom", + Net: fcpc.LocalAddr().Network(), + Addr: fcpc.LocalAddr(), + Err: errFakeClosed, + } + } + + // call underlying readfrom + n, addr, err = fcpc.sharedPacketConn.ReadFrom(p) + if err != nil { + // this server was stopped, so clear the deadline and let + // any new server continue reading; but we will exit + if atomic.LoadInt32(&fcpc.closed) == 1 { + if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + if err = fcpc.SetReadDeadline(time.Time{}); err != nil { + return + } + } + } + return + } + + return +} + +// Close won't close the underlying socket unless there is no more reference, then listenerPool will close it. +func (fcpc *fakeClosePacketConn) Close() error { + if atomic.CompareAndSwapInt32(&fcpc.closed, 0, 1) { + _ = fcpc.SetReadDeadline(time.Now()) // unblock ReadFrom() calls to kick old servers out of their loops + _, _ = listenerPool.Delete(fcpc.sharedPacketConn.key) + } + return nil +} + +func (fcpc *fakeClosePacketConn) Unwrap() net.PacketConn { + return fcpc.sharedPacketConn.PacketConn +} + +// sharedPacketConn is like sharedListener, but for net.PacketConns. +type sharedPacketConn struct { + net.PacketConn + key string +} + +// Destruct closes the underlying socket. +func (spc *sharedPacketConn) Destruct() error { + return spc.PacketConn.Close() +} + +// Unwrap returns the underlying socket +func (spc *sharedPacketConn) Unwrap() net.PacketConn { + return spc.PacketConn +} + +// Interface guards (see https://github.com/caddyserver/caddy/issues/3998) +var ( + _ (interface { + Unwrap() net.PacketConn + }) = (*fakeClosePacketConn)(nil) +) + +// socketFiles is a fd -> *os.File map used to make a FileListener/FilePacketConn from a socket file descriptor. +var socketFiles = map[uintptr]*os.File{} + +// socketFilesMu synchronizes socketFiles insertions +var socketFilesMu sync.Mutex diff --git a/listen_unix.go b/listen_unix.go new file mode 100644 index 00000000000..d6ae0cb8ebb --- /dev/null +++ b/listen_unix.go @@ -0,0 +1,312 @@ +// 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. + +// Even though the filename ends in _unix.go, we still have to specify the +// build constraint here, because the filename convention only works for +// literal GOOS values, and "unix" is a shortcut unique to build tags. +//go:build unix && !solaris + +package caddy + +import ( + "context" + "errors" + "fmt" + "io" + "io/fs" + "net" + "os" + "slices" + "strconv" + "sync" + "sync/atomic" + "syscall" + + "go.uber.org/zap" + "golang.org/x/sys/unix" +) + +// reuseUnixSocket copies and reuses the unix domain socket (UDS) if we already +// have it open; if not, unlink it so we can have it. +// No-op if not a unix network. +func reuseUnixSocket(network, addr string) (any, error) { + socketKey := listenerKey(network, addr) + + socket, exists := unixSockets[socketKey] + if exists { + // make copy of file descriptor + socketFile, err := socket.File() // does dup() deep down + if err != nil { + return nil, err + } + + // use copied fd to make new Listener or PacketConn, then replace + // it in the map so that future copies always come from the most + // recent fd (as the previous ones will be closed, and we'd get + // "use of closed network connection" errors) -- note that we + // preserve the *pointer* to the counter (not just the value) so + // that all socket wrappers will refer to the same value + switch unixSocket := socket.(type) { + case *unixListener: + ln, err := net.FileListener(socketFile) + if err != nil { + return nil, err + } + atomic.AddInt32(unixSocket.count, 1) + unixSockets[socketKey] = &unixListener{ln.(*net.UnixListener), socketKey, unixSocket.count} + + case *unixConn: + pc, err := net.FilePacketConn(socketFile) + if err != nil { + return nil, err + } + atomic.AddInt32(unixSocket.count, 1) + unixSockets[socketKey] = &unixConn{pc.(*net.UnixConn), socketKey, unixSocket.count} + } + + return unixSockets[socketKey], nil + } + + // from what I can tell after some quick research, it's quite common for programs to + // leave their socket file behind after they close, so the typical pattern is to + // unlink it before you bind to it -- this is often crucial if the last program using + // it was killed forcefully without a chance to clean up the socket, but there is a + // race, as the comment in net.UnixListener.close() explains... oh well, I guess? + if err := syscall.Unlink(addr); err != nil && !errors.Is(err, fs.ErrNotExist) { + return nil, err + } + + return nil, nil +} + +// listenReusable creates a new listener for the given network and address, and adds it to listenerPool. +func listenReusable(ctx context.Context, lnKey string, network, address string, config net.ListenConfig) (any, error) { + // even though SO_REUSEPORT lets us bind the socket multiple times, + // we still put it in the listenerPool so we can count how many + // configs are using this socket; necessary to ensure we can know + // whether to enforce shutdown delays, for example (see #5393). + var ( + ln io.Closer + err error + socketFile *os.File + ) + + fd := slices.Contains([]string{"fd", "fdgram"}, network) + if fd { + socketFd, err := strconv.ParseUint(address, 0, strconv.IntSize) + if err != nil { + return nil, fmt.Errorf("invalid file descriptor: %v", err) + } + + func() { + socketFilesMu.Lock() + defer socketFilesMu.Unlock() + + socketFdWide := uintptr(socketFd) + var ok bool + + socketFile, ok = socketFiles[socketFdWide] + + if !ok { + socketFile = os.NewFile(socketFdWide, lnKey) + if socketFile != nil { + socketFiles[socketFdWide] = socketFile + } + } + }() + + if socketFile == nil { + return nil, fmt.Errorf("invalid socket file descriptor: %d", socketFd) + } + } else { + // wrap any Control function set by the user so we can also add our reusePort control without clobbering theirs + oldControl := config.Control + config.Control = func(network, address string, c syscall.RawConn) error { + if oldControl != nil { + if err := oldControl(network, address, c); err != nil { + return err + } + } + return reusePort(network, address, c) + } + } + + datagram := slices.Contains([]string{"udp", "udp4", "udp6", "unixgram", "fdgram"}, network) + if datagram { + if fd { + ln, err = net.FilePacketConn(socketFile) + } else { + ln, err = config.ListenPacket(ctx, network, address) + } + } else { + if fd { + ln, err = net.FileListener(socketFile) + } else { + ln, err = config.Listen(ctx, network, address) + } + } + + if err == nil { + listenerPool.LoadOrStore(lnKey, nil) + } + + if datagram { + if !fd { + // TODO: Not 100% sure this is necessary, but we do this for net.UnixListener, so... + if unix, ok := ln.(*net.UnixConn); ok { + one := int32(1) + ln = &unixConn{unix, lnKey, &one} + unixSockets[lnKey] = ln.(*unixConn) + } + } + // lightly wrap the connection so that when it is closed, + // we can decrement the usage pool counter + if specificLn, ok := ln.(net.PacketConn); ok { + ln = deletePacketConn{specificLn, lnKey} + } + } else { + if !fd { + // if new listener is a unix socket, make sure we can reuse it later + // (we do our own "unlink on close" -- not required, but more tidy) + if unix, ok := ln.(*net.UnixListener); ok { + unix.SetUnlinkOnClose(false) + one := int32(1) + ln = &unixListener{unix, lnKey, &one} + unixSockets[lnKey] = ln.(*unixListener) + } + } + // lightly wrap the listener so that when it is closed, + // we can decrement the usage pool counter + if specificLn, ok := ln.(net.Listener); ok { + ln = deleteListener{specificLn, lnKey} + } + } + + // other types, I guess we just return them directly + return ln, err +} + +// reusePort sets SO_REUSEPORT. Ineffective for unix sockets. +func reusePort(network, address string, conn syscall.RawConn) error { + if IsUnixNetwork(network) { + return nil + } + return conn.Control(func(descriptor uintptr) { + if err := unix.SetsockoptInt(int(descriptor), unix.SOL_SOCKET, unixSOREUSEPORT, 1); err != nil { + Log().Error("setting SO_REUSEPORT", + zap.String("network", network), + zap.String("address", address), + zap.Uintptr("descriptor", descriptor), + zap.Error(err)) + } + }) +} + +type unixListener struct { + *net.UnixListener + mapKey string + count *int32 // accessed atomically +} + +func (uln *unixListener) Close() error { + newCount := atomic.AddInt32(uln.count, -1) + if newCount == 0 { + file, err := uln.File() + var name string + if err == nil { + name = file.Name() + } + defer func() { + unixSocketsMu.Lock() + delete(unixSockets, uln.mapKey) + unixSocketsMu.Unlock() + if err == nil { + _ = syscall.Unlink(name) + } + }() + } + return uln.UnixListener.Close() +} + +type unixConn struct { + *net.UnixConn + mapKey string + count *int32 // accessed atomically +} + +func (uc *unixConn) Close() error { + newCount := atomic.AddInt32(uc.count, -1) + if newCount == 0 { + file, err := uc.File() + var name string + if err == nil { + name = file.Name() + } + defer func() { + unixSocketsMu.Lock() + delete(unixSockets, uc.mapKey) + unixSocketsMu.Unlock() + if err == nil { + _ = syscall.Unlink(name) + } + }() + } + return uc.UnixConn.Close() +} + +func (uc *unixConn) Unwrap() net.PacketConn { + return uc.UnixConn +} + +// unixSockets keeps track of the currently-active unix sockets +// so we can transfer their FDs gracefully during reloads. +var unixSockets = make(map[string]interface { + File() (*os.File, error) +}) + +// socketFiles is a fd -> *os.File map used to make a FileListener/FilePacketConn from a socket file descriptor. +var socketFiles = map[uintptr]*os.File{} + +// socketFilesMu synchronizes socketFiles insertions +var socketFilesMu sync.Mutex + +// deleteListener is a type that simply deletes itself +// from the listenerPool when it closes. It is used +// solely for the purpose of reference counting (i.e. +// counting how many configs are using a given socket). +type deleteListener struct { + net.Listener + lnKey string +} + +func (dl deleteListener) Close() error { + _, _ = listenerPool.Delete(dl.lnKey) + return dl.Listener.Close() +} + +// deletePacketConn is like deleteListener, but +// for net.PacketConns. +type deletePacketConn struct { + net.PacketConn + lnKey string +} + +func (dl deletePacketConn) Close() error { + _, _ = listenerPool.Delete(dl.lnKey) + return dl.PacketConn.Close() +} + +func (dl deletePacketConn) Unwrap() net.PacketConn { + return dl.PacketConn +} diff --git a/listen_unix_setopt.go b/listen_unix_setopt.go new file mode 100644 index 00000000000..13ee7b83096 --- /dev/null +++ b/listen_unix_setopt.go @@ -0,0 +1,7 @@ +//go:build unix && !freebsd && !solaris + +package caddy + +import "golang.org/x/sys/unix" + +const unixSOREUSEPORT = unix.SO_REUSEPORT diff --git a/listen_unix_setopt_freebsd.go b/listen_unix_setopt_freebsd.go new file mode 100644 index 00000000000..06520540829 --- /dev/null +++ b/listen_unix_setopt_freebsd.go @@ -0,0 +1,7 @@ +//go:build freebsd + +package caddy + +import "golang.org/x/sys/unix" + +const unixSOREUSEPORT = unix.SO_REUSEPORT_LB diff --git a/listeners.go b/listeners.go new file mode 100644 index 00000000000..b22df77ba5a --- /dev/null +++ b/listeners.go @@ -0,0 +1,699 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddy + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "io" + "io/fs" + "net" + "net/netip" + "os" + "strconv" + "strings" + "sync" + "sync/atomic" + + "github.com/quic-go/quic-go" + "github.com/quic-go/quic-go/http3" + "github.com/quic-go/quic-go/qlog" + "go.uber.org/zap" + "golang.org/x/time/rate" + + "github.com/caddyserver/caddy/v2/internal" +) + +// NetworkAddress represents one or more network addresses. +// It contains the individual components for a parsed network +// address of the form accepted by ParseNetworkAddress(). +type NetworkAddress struct { + // Should be a network value accepted by Go's net package or + // by a plugin providing a listener for that network type. + Network string + + // The "main" part of the network address is the host, which + // often takes the form of a hostname, DNS name, IP address, + // or socket path. + Host string + + // For addresses that contain a port, ranges are given by + // [StartPort, EndPort]; i.e. for a single port, StartPort + // and EndPort are the same. For no port, they are 0. + StartPort uint + EndPort uint +} + +// ListenAll calls Listen for all addresses represented by this struct, i.e. all ports in the range. +// (If the address doesn't use ports or has 1 port only, then only 1 listener will be created.) +// It returns an error if any listener failed to bind, and closes any listeners opened up to that point. +func (na NetworkAddress) ListenAll(ctx context.Context, config net.ListenConfig) ([]any, error) { + var listeners []any + var err error + + // if one of the addresses has a failure, we need to close + // any that did open a socket to avoid leaking resources + defer func() { + if err == nil { + return + } + for _, ln := range listeners { + if cl, ok := ln.(io.Closer); ok { + cl.Close() + } + } + }() + + // an address can contain a port range, which represents multiple addresses; + // some addresses don't use ports at all and have a port range size of 1; + // whatever the case, iterate each address represented and bind a socket + for portOffset := uint(0); portOffset < na.PortRangeSize(); portOffset++ { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + // create (or reuse) the listener ourselves + var ln any + ln, err = na.Listen(ctx, portOffset, config) + if err != nil { + return nil, err + } + listeners = append(listeners, ln) + } + + return listeners, nil +} + +// Listen is similar to net.Listen, with a few differences: +// +// Listen announces on the network address using the port calculated by adding +// portOffset to the start port. (For network types that do not use ports, the +// portOffset is ignored.) +// +// First Listen checks if a plugin can provide a listener from this address. Otherwise, +// the provided ListenConfig is used to create the listener. Its Control function, +// if set, may be wrapped by an internally-used Control function. The provided +// context may be used to cancel long operations early. The context is not used +// to close the listener after it has been created. +// +// Caddy's listeners can overlap each other: multiple listeners may be created on +// the same socket at the same time. This is useful because during config changes, +// the new config is started while the old config is still running. How this is +// accomplished varies by platform and network type. For example, on Unix, SO_REUSEPORT +// is set except on Unix sockets, for which the file descriptor is duplicated and +// reused; on Windows, the close logic is virtualized using timeouts. Like normal +// listeners, be sure to Close() them when you are done. +// +// This method returns any type, as the implementations of listeners for various +// network types are not interchangeable. The type of listener returned is switched +// on the network type. Stream-based networks ("tcp", "unix", "unixpacket", etc.) +// return a net.Listener; datagram-based networks ("udp", "unixgram", etc.) return +// a net.PacketConn; and so forth. The actual concrete types are not guaranteed to +// be standard, exported types (wrapping is necessary to provide graceful reloads). +// +// Unix sockets will be unlinked before being created, to ensure we can bind to +// it even if the previous program using it exited uncleanly; it will also be +// unlinked upon a graceful exit (or when a new config does not use that socket). +// Listen synchronizes binds to unix domain sockets to avoid race conditions +// while an existing socket is unlinked. +func (na NetworkAddress) Listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) { + if na.IsUnixNetwork() { + unixSocketsMu.Lock() + defer unixSocketsMu.Unlock() + } + + // check to see if plugin provides listener + if ln, err := getListenerFromPlugin(ctx, na.Network, na.Host, na.port(), portOffset, config); ln != nil || err != nil { + return ln, err + } + + // create (or reuse) the listener ourselves + return na.listen(ctx, portOffset, config) +} + +func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) { + var ( + ln any + err error + address string + unixFileMode fs.FileMode + ) + + // split unix socket addr early so lnKey + // is independent of permissions bits + if na.IsUnixNetwork() { + address, unixFileMode, err = internal.SplitUnixSocketPermissionsBits(na.Host) + if err != nil { + return nil, err + } + } else if na.IsFdNetwork() { + address = na.Host + } else { + address = na.JoinHostPort(portOffset) + } + + if strings.HasPrefix(na.Network, "ip") { + ln, err = config.ListenPacket(ctx, na.Network, address) + } else { + if na.IsUnixNetwork() { + // if this is a unix socket, see if we already have it open + ln, err = reuseUnixSocket(na.Network, address) + } + + if ln == nil && err == nil { + // otherwise, create a new listener + lnKey := listenerKey(na.Network, address) + ln, err = listenReusable(ctx, lnKey, na.Network, address, config) + } + } + + if err != nil { + return nil, err + } + + if ln == nil { + return nil, fmt.Errorf("unsupported network type: %s", na.Network) + } + + if IsUnixNetwork(na.Network) { + isAbstractUnixSocket := strings.HasPrefix(address, "@") + if !isAbstractUnixSocket { + err = os.Chmod(address, unixFileMode) + if err != nil { + return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err) + } + } + } + + return ln, nil +} + +// IsUnixNetwork returns true if na.Network is +// unix, unixgram, or unixpacket. +func (na NetworkAddress) IsUnixNetwork() bool { + return IsUnixNetwork(na.Network) +} + +// IsUnixNetwork returns true if na.Network is +// fd or fdgram. +func (na NetworkAddress) IsFdNetwork() bool { + return IsFdNetwork(na.Network) +} + +// JoinHostPort is like net.JoinHostPort, but where the port +// is StartPort + offset. +func (na NetworkAddress) JoinHostPort(offset uint) string { + if na.IsUnixNetwork() || na.IsFdNetwork() { + return na.Host + } + return net.JoinHostPort(na.Host, strconv.FormatUint(uint64(na.StartPort+offset), 10)) +} + +// Expand returns one NetworkAddress for each port in the port range. +func (na NetworkAddress) Expand() []NetworkAddress { + size := na.PortRangeSize() + addrs := make([]NetworkAddress, size) + for portOffset := uint(0); portOffset < size; portOffset++ { + addrs[portOffset] = na.At(portOffset) + } + return addrs +} + +// At returns a NetworkAddress with a port range of just 1 +// at the given port offset; i.e. a NetworkAddress that +// represents precisely 1 address only. +func (na NetworkAddress) At(portOffset uint) NetworkAddress { + na2 := na + na2.StartPort, na2.EndPort = na.StartPort+portOffset, na.StartPort+portOffset + return na2 +} + +// PortRangeSize returns how many ports are in +// pa's port range. Port ranges are inclusive, +// so the size is the difference of start and +// end ports plus one. +func (na NetworkAddress) PortRangeSize() uint { + if na.EndPort < na.StartPort { + return 0 + } + return (na.EndPort - na.StartPort) + 1 +} + +func (na NetworkAddress) isLoopback() bool { + if na.IsUnixNetwork() || na.IsFdNetwork() { + return true + } + if na.Host == "localhost" { + return true + } + if ip, err := netip.ParseAddr(na.Host); err == nil { + return ip.IsLoopback() + } + return false +} + +func (na NetworkAddress) isWildcardInterface() bool { + if na.Host == "" { + return true + } + if ip, err := netip.ParseAddr(na.Host); err == nil { + return ip.IsUnspecified() + } + return false +} + +func (na NetworkAddress) port() string { + if na.StartPort == na.EndPort { + return strconv.FormatUint(uint64(na.StartPort), 10) + } + return fmt.Sprintf("%d-%d", na.StartPort, na.EndPort) +} + +// String reconstructs the address string for human display. +// The output can be parsed by ParseNetworkAddress(). If the +// address is a unix socket, any non-zero port will be dropped. +func (na NetworkAddress) String() string { + if na.Network == "tcp" && (na.Host != "" || na.port() != "") { + na.Network = "" // omit default network value for brevity + } + return JoinNetworkAddress(na.Network, na.Host, na.port()) +} + +// IsUnixNetwork returns true if the netw is a unix network. +func IsUnixNetwork(netw string) bool { + return strings.HasPrefix(netw, "unix") +} + +// IsFdNetwork returns true if the netw is a fd network. +func IsFdNetwork(netw string) bool { + return strings.HasPrefix(netw, "fd") +} + +// ParseNetworkAddress parses addr into its individual +// components. The input string is expected to be of +// the form "network/host:port-range" where any part is +// optional. The default network, if unspecified, is tcp. +// Port ranges are inclusive. +// +// Network addresses are distinct from URLs and do not +// use URL syntax. +func ParseNetworkAddress(addr string) (NetworkAddress, error) { + return ParseNetworkAddressWithDefaults(addr, "tcp", 0) +} + +// ParseNetworkAddressWithDefaults is like ParseNetworkAddress but allows +// the default network and port to be specified. +func ParseNetworkAddressWithDefaults(addr, defaultNetwork string, defaultPort uint) (NetworkAddress, error) { + var host, port string + network, host, port, err := SplitNetworkAddress(addr) + if err != nil { + return NetworkAddress{}, err + } + if network == "" { + network = defaultNetwork + } + if IsUnixNetwork(network) { + _, _, err := internal.SplitUnixSocketPermissionsBits(host) + return NetworkAddress{ + Network: network, + Host: host, + }, err + } + if IsFdNetwork(network) { + return NetworkAddress{ + Network: network, + Host: host, + }, nil + } + var start, end uint64 + if port == "" { + start = uint64(defaultPort) + end = uint64(defaultPort) + } else { + before, after, found := strings.Cut(port, "-") + if !found { + after = before + } + start, err = strconv.ParseUint(before, 10, 16) + if err != nil { + return NetworkAddress{}, fmt.Errorf("invalid start port: %v", err) + } + end, err = strconv.ParseUint(after, 10, 16) + if err != nil { + return NetworkAddress{}, fmt.Errorf("invalid end port: %v", err) + } + if end < start { + return NetworkAddress{}, fmt.Errorf("end port must not be less than start port") + } + if (end - start) > maxPortSpan { + return NetworkAddress{}, fmt.Errorf("port range exceeds %d ports", maxPortSpan) + } + } + return NetworkAddress{ + Network: network, + Host: host, + StartPort: uint(start), + EndPort: uint(end), + }, nil +} + +// SplitNetworkAddress splits a into its network, host, and port components. +// Note that port may be a port range (:X-Y), or omitted for unix sockets. +func SplitNetworkAddress(a string) (network, host, port string, err error) { + beforeSlash, afterSlash, slashFound := strings.Cut(a, "/") + if slashFound { + network = strings.ToLower(strings.TrimSpace(beforeSlash)) + a = afterSlash + if IsUnixNetwork(network) || IsFdNetwork(network) { + host = a + return + } + } + + host, port, err = net.SplitHostPort(a) + firstErr := err + + if err != nil { + // in general, if there was an error, it was likely "missing port", + // so try removing square brackets around an IPv6 host, adding a bogus + // port to take advantage of standard library's robust parser, then + // strip the artificial port. + host, _, err = net.SplitHostPort(net.JoinHostPort(strings.Trim(a, "[]"), "0")) + port = "" + } + + if err != nil { + err = errors.Join(firstErr, err) + } + + return +} + +// JoinNetworkAddress combines network, host, and port into a single +// address string of the form accepted by ParseNetworkAddress(). For +// unix sockets, the network should be "unix" (or "unixgram" or +// "unixpacket") and the path to the socket should be given as the +// host parameter. +func JoinNetworkAddress(network, host, port string) string { + var a string + if network != "" { + a = network + "/" + } + if (host != "" && port == "") || IsUnixNetwork(network) || IsFdNetwork(network) { + a += host + } else if port != "" { + a += net.JoinHostPort(host, port) + } + return a +} + +// ListenQUIC returns a http3.QUICEarlyListener suitable for use in a Caddy module. +// +// The network will be transformed into a QUIC-compatible type if the same address can be used with +// different networks. Currently this just means that for tcp, udp will be used with the same +// address instead. +// +// NOTE: This API is EXPERIMENTAL and may be changed or removed. +func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config net.ListenConfig, tlsConf *tls.Config) (http3.QUICEarlyListener, error) { + lnKey := listenerKey("quic"+na.Network, na.JoinHostPort(portOffset)) + + sharedEarlyListener, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) { + lnAny, err := na.Listen(ctx, portOffset, config) + if err != nil { + return nil, err + } + + ln := lnAny.(net.PacketConn) + + h3ln := ln + for { + // retrieve the underlying socket, so quic-go can optimize. + if unwrapper, ok := h3ln.(interface{ Unwrap() net.PacketConn }); ok { + h3ln = unwrapper.Unwrap() + } else { + break + } + } + + sqs := newSharedQUICState(tlsConf) + // http3.ConfigureTLSConfig only uses this field and tls App sets this field as well + //nolint:gosec + quicTlsConfig := &tls.Config{GetConfigForClient: sqs.getConfigForClient} + // Require clients to verify their source address when we're handling more than 1000 handshakes per second. + // TODO: make tunable? + limiter := rate.NewLimiter(1000, 1000) + tr := &quic.Transport{ + Conn: h3ln, + VerifySourceAddress: func(addr net.Addr) bool { return !limiter.Allow() }, + } + earlyLn, err := tr.ListenEarly( + http3.ConfigureTLSConfig(quicTlsConfig), + &quic.Config{ + Allow0RTT: true, + Tracer: qlog.DefaultConnectionTracer, + }, + ) + if err != nil { + return nil, err + } + // TODO: figure out when to close the listener and the transport + // using the original net.PacketConn to close them properly + return &sharedQuicListener{EarlyListener: earlyLn, packetConn: ln, sqs: sqs, key: lnKey}, nil + }) + if err != nil { + return nil, err + } + + sql := sharedEarlyListener.(*sharedQuicListener) + // add current tls.Config to sqs, so GetConfigForClient will always return the latest tls.Config in case of context cancellation + ctx, cancel := sql.sqs.addState(tlsConf) + + return &fakeCloseQuicListener{ + sharedQuicListener: sql, + context: ctx, + contextCancel: cancel, + }, nil +} + +// ListenerUsage returns the current usage count of the given listener address. +func ListenerUsage(network, addr string) int { + count, _ := listenerPool.References(listenerKey(network, addr)) + return count +} + +// contextAndCancelFunc groups context and its cancelFunc +type contextAndCancelFunc struct { + context.Context + context.CancelFunc +} + +// sharedQUICState manages GetConfigForClient +// see issue: https://github.com/caddyserver/caddy/pull/4849 +type sharedQUICState struct { + rmu sync.RWMutex + tlsConfs map[*tls.Config]contextAndCancelFunc + activeTlsConf *tls.Config +} + +// newSharedQUICState creates a new sharedQUICState +func newSharedQUICState(tlsConfig *tls.Config) *sharedQUICState { + sqtc := &sharedQUICState{ + tlsConfs: make(map[*tls.Config]contextAndCancelFunc), + activeTlsConf: tlsConfig, + } + sqtc.addState(tlsConfig) + return sqtc +} + +// getConfigForClient is used as tls.Config's GetConfigForClient field +func (sqs *sharedQUICState) getConfigForClient(ch *tls.ClientHelloInfo) (*tls.Config, error) { + sqs.rmu.RLock() + defer sqs.rmu.RUnlock() + return sqs.activeTlsConf.GetConfigForClient(ch) +} + +// addState adds tls.Config and activeRequests to the map if not present and returns the corresponding context and its cancelFunc +// so that when cancelled, the active tls.Config will change +func (sqs *sharedQUICState) addState(tlsConfig *tls.Config) (context.Context, context.CancelFunc) { + sqs.rmu.Lock() + defer sqs.rmu.Unlock() + + if cacc, ok := sqs.tlsConfs[tlsConfig]; ok { + return cacc.Context, cacc.CancelFunc + } + + ctx, cancel := context.WithCancel(context.Background()) + wrappedCancel := func() { + cancel() + + sqs.rmu.Lock() + defer sqs.rmu.Unlock() + + delete(sqs.tlsConfs, tlsConfig) + if sqs.activeTlsConf == tlsConfig { + // select another tls.Config, if there is none, + // related sharedQuicListener will be destroyed anyway + for tc := range sqs.tlsConfs { + sqs.activeTlsConf = tc + break + } + } + } + sqs.tlsConfs[tlsConfig] = contextAndCancelFunc{ctx, wrappedCancel} + // there should be at most 2 tls.Configs + if len(sqs.tlsConfs) > 2 { + Log().Warn("quic listener tls configs are more than 2", zap.Int("number of configs", len(sqs.tlsConfs))) + } + return ctx, wrappedCancel +} + +// sharedQuicListener is like sharedListener, but for quic.EarlyListeners. +type sharedQuicListener struct { + *quic.EarlyListener + packetConn net.PacketConn // we have to hold these because quic-go won't close listeners it didn't create + sqs *sharedQUICState + key string +} + +// Destruct closes the underlying QUIC listener and its associated net.PacketConn. +func (sql *sharedQuicListener) Destruct() error { + // close EarlyListener first to stop any operations being done to the net.PacketConn + _ = sql.EarlyListener.Close() + // then close the net.PacketConn + return sql.packetConn.Close() +} + +// fakeClosedErr returns an error value that is not temporary +// nor a timeout, suitable for making the caller think the +// listener is actually closed +func fakeClosedErr(l interface{ Addr() net.Addr }) error { + return &net.OpError{ + Op: "accept", + Net: l.Addr().Network(), + Addr: l.Addr(), + Err: errFakeClosed, + } +} + +// errFakeClosed is the underlying error value returned by +// fakeCloseListener.Accept() after Close() has been called, +// indicating that it is pretending to be closed so that the +// server using it can terminate, while the underlying +// socket is actually left open. +var errFakeClosed = fmt.Errorf("listener 'closed' 😉") + +type fakeCloseQuicListener struct { + closed int32 // accessed atomically; belongs to this struct only + *sharedQuicListener // embedded, so we also become a quic.EarlyListener + context context.Context + contextCancel context.CancelFunc +} + +// Currently Accept ignores the passed context, however a situation where +// someone would need a hotswappable QUIC-only (not http3, since it uses context.Background here) +// server on which Accept would be called with non-empty contexts +// (mind that the default net listeners' Accept doesn't take a context argument) +// sounds way too rare for us to sacrifice efficiency here. +func (fcql *fakeCloseQuicListener) Accept(_ context.Context) (quic.EarlyConnection, error) { + conn, err := fcql.sharedQuicListener.Accept(fcql.context) + if err == nil { + return conn, nil + } + + // if the listener is "closed", return a fake closed error instead + if atomic.LoadInt32(&fcql.closed) == 1 && errors.Is(err, context.Canceled) { + return nil, fakeClosedErr(fcql) + } + return nil, err +} + +func (fcql *fakeCloseQuicListener) Close() error { + if atomic.CompareAndSwapInt32(&fcql.closed, 0, 1) { + fcql.contextCancel() + _, _ = listenerPool.Delete(fcql.sharedQuicListener.key) + } + return nil +} + +// RegisterNetwork registers a network type with Caddy so that if a listener is +// created for that network type, getListener will be invoked to get the listener. +// This should be called during init() and will panic if the network type is standard +// or reserved, or if it is already registered. EXPERIMENTAL and subject to change. +func RegisterNetwork(network string, getListener ListenerFunc) { + network = strings.TrimSpace(strings.ToLower(network)) + + if network == "tcp" || network == "tcp4" || network == "tcp6" || + network == "udp" || network == "udp4" || network == "udp6" || + network == "unix" || network == "unixpacket" || network == "unixgram" || + strings.HasPrefix("ip:", network) || strings.HasPrefix("ip4:", network) || strings.HasPrefix("ip6:", network) || + network == "fd" || network == "fdgram" { + panic("network type " + network + " is reserved") + } + + if _, ok := networkTypes[strings.ToLower(network)]; ok { + panic("network type " + network + " is already registered") + } + + networkTypes[network] = getListener +} + +var unixSocketsMu sync.Mutex + +// getListenerFromPlugin returns a listener on the given network and address +// if a plugin has registered the network name. It may return (nil, nil) if +// no plugin can provide a listener. +func getListenerFromPlugin(ctx context.Context, network, host, port string, portOffset uint, config net.ListenConfig) (any, error) { + // get listener from plugin if network type is registered + if getListener, ok := networkTypes[network]; ok { + Log().Debug("getting listener from plugin", zap.String("network", network)) + return getListener(ctx, network, host, port, portOffset, config) + } + + return nil, nil +} + +func listenerKey(network, addr string) string { + return network + "/" + addr +} + +// ListenerFunc is a function that can return a listener given a network and address. +// The listeners must be capable of overlapping: with Caddy, new configs are loaded +// before old ones are unloaded, so listeners may overlap briefly if the configs +// both need the same listener. EXPERIMENTAL and subject to change. +type ListenerFunc func(ctx context.Context, network, host, portRange string, portOffset uint, cfg net.ListenConfig) (any, error) + +var networkTypes = map[string]ListenerFunc{} + +// ListenerWrapper is a type that wraps a listener +// so it can modify the input listener's methods. +// Modules that implement this interface are found +// in the caddy.listeners namespace. Usually, to +// wrap a listener, you will define your own struct +// type that embeds the input listener, then +// implement your own methods that you want to wrap, +// calling the underlying listener's methods where +// appropriate. +type ListenerWrapper interface { + WrapListener(net.Listener) net.Listener +} + +// listenerPool stores and allows reuse of active listeners. +var listenerPool = NewUsagePool() + +const maxPortSpan = 65535 diff --git a/listeners_fuzz.go b/listeners_fuzz.go new file mode 100644 index 00000000000..02b65ab2e19 --- /dev/null +++ b/listeners_fuzz.go @@ -0,0 +1,25 @@ +// 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 caddy + +func FuzzParseNetworkAddress(data []byte) int { + _, err := ParseNetworkAddress(string(data)) + if err != nil { + return 0 + } + return 1 +} diff --git a/listeners_test.go b/listeners_test.go new file mode 100644 index 00000000000..03945308e40 --- /dev/null +++ b/listeners_test.go @@ -0,0 +1,656 @@ +// 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 ( + "reflect" + "testing" + + "github.com/caddyserver/caddy/v2/internal" +) + +func TestSplitNetworkAddress(t *testing.T) { + for i, tc := range []struct { + input string + expectNetwork string + expectHost string + expectPort string + expectErr bool + }{ + { + input: "", + expectHost: "", + }, + { + input: "foo", + expectHost: "foo", + }, + { + input: ":", // empty host & empty port + }, + { + input: "::", + expectHost: "::", + }, + { + input: "[::]", + expectHost: "::", + }, + { + input: ":1234", + expectPort: "1234", + }, + { + input: "foo:1234", + expectHost: "foo", + expectPort: "1234", + }, + { + input: "foo:1234-5678", + expectHost: "foo", + expectPort: "1234-5678", + }, + { + input: "udp/foo:1234", + expectNetwork: "udp", + expectHost: "foo", + expectPort: "1234", + }, + { + input: "tcp6/foo:1234-5678", + expectNetwork: "tcp6", + expectHost: "foo", + expectPort: "1234-5678", + }, + { + input: "udp/", + expectNetwork: "udp", + expectHost: "", + }, + { + input: "unix//foo/bar", + expectNetwork: "unix", + expectHost: "/foo/bar", + }, + { + input: "unixgram//foo/bar", + expectNetwork: "unixgram", + expectHost: "/foo/bar", + }, + { + input: "unixpacket//foo/bar", + expectNetwork: "unixpacket", + expectHost: "/foo/bar", + }, + } { + actualNetwork, actualHost, actualPort, err := SplitNetworkAddress(tc.input) + if tc.expectErr && err == nil { + t.Errorf("Test %d: Expected error but got %v", i, err) + } + if !tc.expectErr && err != nil { + t.Errorf("Test %d: Expected no error but got %v", i, err) + } + if actualNetwork != tc.expectNetwork { + t.Errorf("Test %d: Expected network '%s' but got '%s'", i, tc.expectNetwork, actualNetwork) + } + if actualHost != tc.expectHost { + t.Errorf("Test %d: Expected host '%s' but got '%s'", i, tc.expectHost, actualHost) + } + if actualPort != tc.expectPort { + t.Errorf("Test %d: Expected port '%s' but got '%s'", i, tc.expectPort, actualPort) + } + } +} + +func TestJoinNetworkAddress(t *testing.T) { + for i, tc := range []struct { + network, host, port string + expect string + }{ + { + network: "", host: "", port: "", + expect: "", + }, + { + network: "tcp", host: "", port: "", + expect: "tcp/", + }, + { + network: "", host: "foo", port: "", + expect: "foo", + }, + { + network: "", host: "", port: "1234", + expect: ":1234", + }, + { + network: "", host: "", port: "1234-5678", + expect: ":1234-5678", + }, + { + network: "", host: "foo", port: "1234", + expect: "foo:1234", + }, + { + network: "udp", host: "foo", port: "1234", + expect: "udp/foo:1234", + }, + { + network: "udp", host: "", port: "1234", + expect: "udp/:1234", + }, + { + network: "unix", host: "/foo/bar", port: "", + expect: "unix//foo/bar", + }, + { + network: "unix", host: "/foo/bar", port: "0", + expect: "unix//foo/bar", + }, + { + network: "unix", host: "/foo/bar", port: "1234", + expect: "unix//foo/bar", + }, + { + network: "", host: "::1", port: "1234", + expect: "[::1]:1234", + }, + } { + actual := JoinNetworkAddress(tc.network, tc.host, tc.port) + if actual != tc.expect { + t.Errorf("Test %d: Expected '%s' but got '%s'", i, tc.expect, actual) + } + } +} + +func TestParseNetworkAddress(t *testing.T) { + for i, tc := range []struct { + input string + defaultNetwork string + defaultPort uint + expectAddr NetworkAddress + expectErr bool + }{ + { + input: "", + expectAddr: NetworkAddress{ + }, + }, + { + input: ":", + defaultNetwork: "udp", + expectAddr: NetworkAddress{ + Network: "udp", + }, + }, + { + input: "[::]", + defaultNetwork: "udp", + defaultPort: 53, + expectAddr: NetworkAddress{ + Network: "udp", + Host: "::", + StartPort: 53, + EndPort: 53, + }, + }, + { + input: ":1234", + defaultNetwork: "udp", + expectAddr: NetworkAddress{ + Network: "udp", + Host: "", + StartPort: 1234, + EndPort: 1234, + }, + }, + { + input: "udp/:1234", + defaultNetwork: "udp", + expectAddr: NetworkAddress{ + Network: "udp", + Host: "", + StartPort: 1234, + EndPort: 1234, + }, + }, + { + input: "tcp6/:1234", + defaultNetwork: "tcp", + expectAddr: NetworkAddress{ + Network: "tcp6", + Host: "", + StartPort: 1234, + EndPort: 1234, + }, + }, + { + input: "tcp4/localhost:1234", + defaultNetwork: "tcp", + expectAddr: NetworkAddress{ + Network: "tcp4", + Host: "localhost", + StartPort: 1234, + EndPort: 1234, + }, + }, + { + input: "unix//foo/bar", + defaultNetwork: "tcp", + expectAddr: NetworkAddress{ + Network: "unix", + Host: "/foo/bar", + }, + }, + { + input: "localhost:1234-1234", + defaultNetwork: "tcp", + expectAddr: NetworkAddress{ + Network: "tcp", + Host: "localhost", + StartPort: 1234, + EndPort: 1234, + }, + }, + { + input: "localhost:2-1", + defaultNetwork: "tcp", + expectErr: true, + }, + { + input: "localhost:0", + defaultNetwork: "tcp", + expectAddr: NetworkAddress{ + Network: "tcp", + Host: "localhost", + StartPort: 0, + EndPort: 0, + }, + }, + { + input: "localhost:1-999999999999", + defaultNetwork: "tcp", + expectErr: true, + }, + } { + actualAddr, err := ParseNetworkAddressWithDefaults(tc.input, tc.defaultNetwork, tc.defaultPort) + if tc.expectErr && err == nil { + t.Errorf("Test %d: Expected error but got: %v", i, err) + } + if !tc.expectErr && err != nil { + t.Errorf("Test %d: Expected no error but got: %v", i, err) + } + + if actualAddr.Network != tc.expectAddr.Network { + t.Errorf("Test %d: Expected network '%v' but got '%v'", i, tc.expectAddr, actualAddr) + } + if !reflect.DeepEqual(tc.expectAddr, actualAddr) { + t.Errorf("Test %d: Expected addresses %v but got %v", i, tc.expectAddr, actualAddr) + } + } +} + +func TestParseNetworkAddressWithDefaults(t *testing.T) { + for i, tc := range []struct { + input string + defaultNetwork string + defaultPort uint + expectAddr NetworkAddress + expectErr bool + }{ + { + input: "", + expectAddr: NetworkAddress{ + }, + }, + { + input: ":", + defaultNetwork: "udp", + expectAddr: NetworkAddress{ + Network: "udp", + }, + }, + { + input: "[::]", + defaultNetwork: "udp", + defaultPort: 53, + expectAddr: NetworkAddress{ + Network: "udp", + Host: "::", + StartPort: 53, + EndPort: 53, + }, + }, + { + input: ":1234", + defaultNetwork: "udp", + expectAddr: NetworkAddress{ + Network: "udp", + Host: "", + StartPort: 1234, + EndPort: 1234, + }, + }, + { + input: "udp/:1234", + defaultNetwork: "udp", + expectAddr: NetworkAddress{ + Network: "udp", + Host: "", + StartPort: 1234, + EndPort: 1234, + }, + }, + { + input: "tcp6/:1234", + defaultNetwork: "tcp", + expectAddr: NetworkAddress{ + Network: "tcp6", + Host: "", + StartPort: 1234, + EndPort: 1234, + }, + }, + { + input: "tcp4/localhost:1234", + defaultNetwork: "tcp", + expectAddr: NetworkAddress{ + Network: "tcp4", + Host: "localhost", + StartPort: 1234, + EndPort: 1234, + }, + }, + { + input: "unix//foo/bar", + defaultNetwork: "tcp", + expectAddr: NetworkAddress{ + Network: "unix", + Host: "/foo/bar", + }, + }, + { + input: "localhost:1234-1234", + defaultNetwork: "tcp", + expectAddr: NetworkAddress{ + Network: "tcp", + Host: "localhost", + StartPort: 1234, + EndPort: 1234, + }, + }, + { + input: "localhost:2-1", + defaultNetwork: "tcp", + expectErr: true, + }, + { + input: "localhost:0", + defaultNetwork: "tcp", + expectAddr: NetworkAddress{ + Network: "tcp", + Host: "localhost", + StartPort: 0, + EndPort: 0, + }, + }, + { + input: "localhost:1-999999999999", + defaultNetwork: "tcp", + expectErr: true, + }, + } { + actualAddr, err := ParseNetworkAddressWithDefaults(tc.input, tc.defaultNetwork, tc.defaultPort) + if tc.expectErr && err == nil { + t.Errorf("Test %d: Expected error but got: %v", i, err) + } + if !tc.expectErr && err != nil { + t.Errorf("Test %d: Expected no error but got: %v", i, err) + } + + if actualAddr.Network != tc.expectAddr.Network { + t.Errorf("Test %d: Expected network '%v' but got '%v'", i, tc.expectAddr, actualAddr) + } + if !reflect.DeepEqual(tc.expectAddr, actualAddr) { + t.Errorf("Test %d: Expected addresses %v but got %v", i, tc.expectAddr, actualAddr) + } + } +} + +func TestJoinHostPort(t *testing.T) { + for i, tc := range []struct { + pa NetworkAddress + offset uint + expect string + }{ + { + pa: NetworkAddress{ + Network: "tcp", + Host: "localhost", + StartPort: 1234, + EndPort: 1234, + }, + expect: "localhost:1234", + }, + { + pa: NetworkAddress{ + Network: "tcp", + Host: "localhost", + StartPort: 1234, + EndPort: 1235, + }, + expect: "localhost:1234", + }, + { + pa: NetworkAddress{ + Network: "tcp", + Host: "localhost", + StartPort: 1234, + EndPort: 1235, + }, + offset: 1, + expect: "localhost:1235", + }, + { + pa: NetworkAddress{ + Network: "unix", + Host: "/run/php/php7.3-fpm.sock", + }, + expect: "/run/php/php7.3-fpm.sock", + }, + } { + actual := tc.pa.JoinHostPort(tc.offset) + if actual != tc.expect { + t.Errorf("Test %d: Expected '%s' but got '%s'", i, tc.expect, actual) + } + } +} + +func TestExpand(t *testing.T) { + for i, tc := range []struct { + input NetworkAddress + expect []NetworkAddress + }{ + { + input: NetworkAddress{ + Network: "tcp", + Host: "localhost", + StartPort: 2000, + EndPort: 2000, + }, + expect: []NetworkAddress{ + { + Network: "tcp", + Host: "localhost", + StartPort: 2000, + EndPort: 2000, + }, + }, + }, + { + input: NetworkAddress{ + Network: "tcp", + Host: "localhost", + StartPort: 2000, + EndPort: 2002, + }, + expect: []NetworkAddress{ + { + Network: "tcp", + Host: "localhost", + StartPort: 2000, + EndPort: 2000, + }, + { + Network: "tcp", + Host: "localhost", + StartPort: 2001, + EndPort: 2001, + }, + { + Network: "tcp", + Host: "localhost", + StartPort: 2002, + EndPort: 2002, + }, + }, + }, + { + input: NetworkAddress{ + Network: "tcp", + Host: "localhost", + StartPort: 2000, + EndPort: 1999, + }, + expect: []NetworkAddress{}, + }, + { + input: NetworkAddress{ + Network: "unix", + Host: "/foo/bar", + StartPort: 0, + EndPort: 0, + }, + expect: []NetworkAddress{ + { + Network: "unix", + Host: "/foo/bar", + StartPort: 0, + EndPort: 0, + }, + }, + }, + } { + actual := tc.input.Expand() + if !reflect.DeepEqual(actual, tc.expect) { + t.Errorf("Test %d: Expected %+v but got %+v", i, tc.expect, actual) + } + } +} + +func TestSplitUnixSocketPermissionsBits(t *testing.T) { + for i, tc := range []struct { + input string + expectNetwork string + expectPath string + expectFileMode string + expectErr bool + }{ + { + input: "./foo.socket", + expectPath: "./foo.socket", + expectFileMode: "--w-------", + }, + { + input: `.\relative\path.socket`, + expectPath: `.\relative\path.socket`, + expectFileMode: "--w-------", + }, + { + // literal colon in resulting address + // and defaulting to 0200 bits + input: "./foo.socket:0666", + expectPath: "./foo.socket:0666", + expectFileMode: "--w-------", + }, + { + input: "./foo.socket|0220", + expectPath: "./foo.socket", + expectFileMode: "--w--w----", + }, + { + input: "/var/run/foo|222", + expectPath: "/var/run/foo", + expectFileMode: "--w--w--w-", + }, + { + input: "./foo.socket|0660", + expectPath: "./foo.socket", + expectFileMode: "-rw-rw----", + }, + { + input: "./foo.socket|0666", + expectPath: "./foo.socket", + expectFileMode: "-rw-rw-rw-", + }, + { + input: "/var/run/foo|666", + expectPath: "/var/run/foo", + expectFileMode: "-rw-rw-rw-", + }, + { + input: `c:\absolute\path.socket|220`, + expectPath: `c:\absolute\path.socket`, + expectFileMode: "--w--w----", + }, + { + // symbolic permission representation is not supported for now + input: "./foo.socket|u=rw,g=rw,o=rw", + expectErr: true, + }, + { + // octal (base-8) permission representation has to be between + // `0` for no read, no write, no exec (`---`) and + // `7` for read (4), write (2), exec (1) (`rwx` => `4+2+1 = 7`) + input: "./foo.socket|888", + expectErr: true, + }, + { + // too many colons in address + input: "./foo.socket|123456|0660", + expectErr: true, + }, + { + // owner is missing write perms + input: "./foo.socket|0522", + expectErr: true, + }, + } { + actualPath, actualFileMode, err := internal.SplitUnixSocketPermissionsBits(tc.input) + if tc.expectErr && err == nil { + t.Errorf("Test %d: Expected error but got: %v", i, err) + } + if !tc.expectErr && err != nil { + t.Errorf("Test %d: Expected no error but got: %v", i, err) + } + if actualPath != tc.expectPath { + t.Errorf("Test %d: Expected path '%s' but got '%s'", i, tc.expectPath, actualPath) + } + // fileMode.Perm().String() parses 0 to "----------" + if !tc.expectErr && actualFileMode.Perm().String() != tc.expectFileMode { + t.Errorf("Test %d: Expected perms '%s' but got '%s'", i, tc.expectFileMode, actualFileMode.Perm().String()) + } + } +} diff --git a/logging.go b/logging.go new file mode 100644 index 00000000000..ca10beeeddc --- /dev/null +++ b/logging.go @@ -0,0 +1,799 @@ +// 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" + "io" + "log" + "os" + "strings" + "sync" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "golang.org/x/term" +) + +func init() { + RegisterModule(StdoutWriter{}) + RegisterModule(StderrWriter{}) + RegisterModule(DiscardWriter{}) +} + +// Logging facilitates logging within Caddy. The default log is +// called "default" and you can customize it. You can also define +// additional logs. +// +// By default, all logs at INFO level and higher are written to +// standard error ("stderr" writer) in a human-readable format +// ("console" encoder if stdout is an interactive terminal, "json" +// encoder otherwise). +// +// All defined logs accept all log entries by default, but you +// can filter by level and module/logger names. A logger's name +// is the same as the module's name, but a module may append to +// logger names for more specificity. For example, you can +// filter logs emitted only by HTTP handlers using the name +// "http.handlers", because all HTTP handler module names have +// that prefix. +// +// Caddy logs (except the sink) are zero-allocation, so they are +// very high-performing in terms of memory and CPU time. Enabling +// sampling can further increase throughput on extremely high-load +// servers. +type Logging struct { + // Sink is the destination for all unstructured logs emitted + // from Go's standard library logger. These logs are common + // in dependencies that are not designed specifically for use + // in Caddy. Because it is global and unstructured, the sink + // lacks most advanced features and customizations. + Sink *SinkLog `json:"sink,omitempty"` + + // Logs are your logs, keyed by an arbitrary name of your + // choosing. The default log can be customized by defining + // a log called "default". You can further define other logs + // and filter what kinds of entries they accept. + Logs map[string]*CustomLog `json:"logs,omitempty"` + + // a list of all keys for open writers; all writers + // that are opened to provision this logging config + // must have their keys added to this list so they + // can be closed when cleaning up + writerKeys []string +} + +// openLogs sets up the config and opens all the configured writers. +// It closes its logs when ctx is canceled, so it should clean up +// after itself. +func (logging *Logging) openLogs(ctx Context) error { + // make sure to deallocate resources when context is done + ctx.OnCancel(func() { + err := logging.closeLogs() + if err != nil { + Log().Error("closing logs", zap.Error(err)) + } + }) + + // set up the "sink" log first (std lib's default global logger) + if logging.Sink != nil { + err := logging.Sink.provision(ctx, logging) + if err != nil { + return fmt.Errorf("setting up sink log: %v", err) + } + } + + // as a special case, set up the default structured Caddy log next + if err := logging.setupNewDefault(ctx); err != nil { + return err + } + + // then set up any other custom logs + for name, l := range logging.Logs { + // the default log is already set up + if name == DefaultLoggerName { + continue + } + + err := l.provision(ctx, logging) + if err != nil { + return fmt.Errorf("setting up custom log '%s': %v", name, err) + } + + // Any other logs that use the discard writer can be deleted + // entirely. This avoids encoding and processing of each + // log entry that would just be thrown away anyway. Notably, + // we do not reach this point for the default log, which MUST + // exist, otherwise core log emissions would panic because + // they use the Log() function directly which expects a non-nil + // logger. Even if we keep logs with a discard writer, they + // have a nop core, and keeping them at all seems unnecessary. + if _, ok := l.writerOpener.(*DiscardWriter); ok { + delete(logging.Logs, name) + continue + } + } + + return nil +} + +func (logging *Logging) setupNewDefault(ctx Context) error { + if logging.Logs == nil { + logging.Logs = make(map[string]*CustomLog) + } + + // extract the user-defined default log, if any + newDefault := new(defaultCustomLog) + if userDefault, ok := logging.Logs[DefaultLoggerName]; ok { + newDefault.CustomLog = userDefault + } else { + // if none, make one with our own default settings + var err error + newDefault, err = newDefaultProductionLog() + if err != nil { + return fmt.Errorf("setting up default Caddy log: %v", err) + } + logging.Logs[DefaultLoggerName] = newDefault.CustomLog + } + + // options for the default logger + options, err := newDefault.CustomLog.buildOptions() + if err != nil { + return fmt.Errorf("setting up default log: %v", err) + } + + // set up this new log + err = newDefault.CustomLog.provision(ctx, logging) + if err != nil { + return fmt.Errorf("setting up default log: %v", err) + } + newDefault.logger = zap.New(newDefault.CustomLog.core, options...) + + // redirect the default caddy logs + defaultLoggerMu.Lock() + oldDefault := defaultLogger + defaultLogger = newDefault + defaultLoggerMu.Unlock() + + // if the new writer is different, indicate it in the logs for convenience + var newDefaultLogWriterKey, currentDefaultLogWriterKey string + var newDefaultLogWriterStr, currentDefaultLogWriterStr string + if newDefault.writerOpener != nil { + newDefaultLogWriterKey = newDefault.writerOpener.WriterKey() + newDefaultLogWriterStr = newDefault.writerOpener.String() + } + if oldDefault.writerOpener != nil { + currentDefaultLogWriterKey = oldDefault.writerOpener.WriterKey() + currentDefaultLogWriterStr = oldDefault.writerOpener.String() + } + if newDefaultLogWriterKey != currentDefaultLogWriterKey { + oldDefault.logger.Info("redirected default logger", + zap.String("from", currentDefaultLogWriterStr), + zap.String("to", newDefaultLogWriterStr), + ) + } + + return nil +} + +// closeLogs cleans up resources allocated during openLogs. +// A successful call to openLogs calls this automatically +// when the context is canceled. +func (logging *Logging) closeLogs() error { + for _, key := range logging.writerKeys { + _, err := writers.Delete(key) + if err != nil { + log.Printf("[ERROR] Closing log writer %v: %v", key, err) + } + } + return nil +} + +// Logger returns a logger that is ready for the module to use. +func (logging *Logging) Logger(mod Module) *zap.Logger { + modID := string(mod.CaddyModule().ID) + var cores []zapcore.Core + var options []zap.Option + + if logging != nil { + for _, l := range logging.Logs { + if l.matchesModule(modID) { + if len(l.Include) == 0 && len(l.Exclude) == 0 { + cores = append(cores, l.core) + continue + } + if len(options) == 0 { + newOptions, err := l.buildOptions() + if err != nil { + Log().Error("building options for logger", zap.String("module", modID), zap.Error(err)) + } + options = newOptions + } + cores = append(cores, &filteringCore{Core: l.core, cl: l}) + } + } + } + + multiCore := zapcore.NewTee(cores...) + + return zap.New(multiCore, options...).Named(modID) +} + +// openWriter opens a writer using opener, and returns true if +// the writer is new, or false if the writer already exists. +func (logging *Logging) openWriter(opener WriterOpener) (io.WriteCloser, bool, error) { + key := opener.WriterKey() + writer, loaded, err := writers.LoadOrNew(key, func() (Destructor, error) { + w, err := opener.OpenWriter() + return writerDestructor{w}, err + }) + if err != nil { + return nil, false, err + } + logging.writerKeys = append(logging.writerKeys, key) + return writer.(io.WriteCloser), !loaded, nil +} + +// WriterOpener is a module that can open a log writer. +// It can return a human-readable string representation +// of itself so that operators can understand where +// the logs are going. +type WriterOpener interface { + fmt.Stringer + + // WriterKey is a string that uniquely identifies this + // writer configuration. It is not shown to humans. + WriterKey() string + + // OpenWriter opens a log for writing. The writer + // should be safe for concurrent use but need not + // be synchronous. + OpenWriter() (io.WriteCloser, error) +} + +// IsWriterStandardStream returns true if the input is a +// writer-opener to a standard stream (stdout, stderr). +func IsWriterStandardStream(wo WriterOpener) bool { + switch wo.(type) { + case StdoutWriter, StderrWriter, + *StdoutWriter, *StderrWriter: + return true + } + return false +} + +type writerDestructor struct { + io.WriteCloser +} + +func (wdest writerDestructor) Destruct() error { + return wdest.Close() +} + +// BaseLog contains the common logging parameters for logging. +type BaseLog struct { + // The module that writes out log entries for the sink. + WriterRaw json.RawMessage `json:"writer,omitempty" caddy:"namespace=caddy.logging.writers inline_key=output"` + + // The encoder is how the log entries are formatted or encoded. + EncoderRaw json.RawMessage `json:"encoder,omitempty" caddy:"namespace=caddy.logging.encoders inline_key=format"` + + // Tees entries through a zap.Core module which can extract + // log entry metadata and fields for further processing. + CoreRaw json.RawMessage `json:"core,omitempty" caddy:"namespace=caddy.logging.cores inline_key=module"` + + // Level is the minimum level to emit, and is inclusive. + // Possible levels: DEBUG, INFO, WARN, ERROR, PANIC, and FATAL + Level string `json:"level,omitempty"` + + // Sampling configures log entry sampling. If enabled, + // only some log entries will be emitted. This is useful + // for improving performance on extremely high-pressure + // servers. + Sampling *LogSampling `json:"sampling,omitempty"` + + // If true, the log entry will include the caller's + // file name and line number. Default off. + WithCaller bool `json:"with_caller,omitempty"` + + // If non-zero, and `with_caller` is true, this many + // stack frames will be skipped when determining the + // caller. Default 0. + WithCallerSkip int `json:"with_caller_skip,omitempty"` + + // If not empty, the log entry will include a stack trace + // for all logs at the given level or higher. See `level` + // for possible values. Default off. + WithStacktrace string `json:"with_stacktrace,omitempty"` + + writerOpener WriterOpener + writer io.WriteCloser + encoder zapcore.Encoder + levelEnabler zapcore.LevelEnabler + core zapcore.Core +} + +func (cl *BaseLog) provisionCommon(ctx Context, logging *Logging) error { + if cl.WriterRaw != nil { + mod, err := ctx.LoadModule(cl, "WriterRaw") + if err != nil { + return fmt.Errorf("loading log writer module: %v", err) + } + cl.writerOpener = mod.(WriterOpener) + } + if cl.writerOpener == nil { + cl.writerOpener = StderrWriter{} + } + var err error + cl.writer, _, err = logging.openWriter(cl.writerOpener) + if err != nil { + return fmt.Errorf("opening log writer using %#v: %v", cl.writerOpener, err) + } + + // set up the log level + cl.levelEnabler, err = parseLevel(cl.Level) + if err != nil { + return err + } + + if cl.EncoderRaw != nil { + mod, err := ctx.LoadModule(cl, "EncoderRaw") + if err != nil { + return fmt.Errorf("loading log encoder module: %v", err) + } + cl.encoder = mod.(zapcore.Encoder) + + // if the encoder module needs the writer to determine + // the correct default to use for a nested encoder, we + // pass it down as a secondary provisioning step + if cfd, ok := mod.(ConfiguresFormatterDefault); ok { + if err := cfd.ConfigureDefaultFormat(cl.writerOpener); err != nil { + return fmt.Errorf("configuring default format for encoder module: %v", err) + } + } + } + if cl.encoder == nil { + cl.encoder = newDefaultProductionLogEncoder(cl.writerOpener) + } + cl.buildCore() + if cl.CoreRaw != nil { + mod, err := ctx.LoadModule(cl, "CoreRaw") + if err != nil { + return fmt.Errorf("loading log core module: %v", err) + } + core := mod.(zapcore.Core) + cl.core = zapcore.NewTee(cl.core, core) + } + return nil +} + +func (cl *BaseLog) buildCore() { + // logs which only discard their output don't need + // to perform encoding or any other processing steps + // at all, so just shortcut to a nop core instead + if _, ok := cl.writerOpener.(*DiscardWriter); ok { + cl.core = zapcore.NewNopCore() + return + } + c := zapcore.NewCore( + cl.encoder, + zapcore.AddSync(cl.writer), + cl.levelEnabler, + ) + if cl.Sampling != nil { + if cl.Sampling.Interval == 0 { + cl.Sampling.Interval = 1 * time.Second + } + if cl.Sampling.First == 0 { + cl.Sampling.First = 100 + } + if cl.Sampling.Thereafter == 0 { + cl.Sampling.Thereafter = 100 + } + c = zapcore.NewSamplerWithOptions(c, cl.Sampling.Interval, + cl.Sampling.First, cl.Sampling.Thereafter) + } + cl.core = c +} + +func (cl *BaseLog) buildOptions() ([]zap.Option, error) { + var options []zap.Option + if cl.WithCaller { + options = append(options, zap.AddCaller()) + if cl.WithCallerSkip != 0 { + options = append(options, zap.AddCallerSkip(cl.WithCallerSkip)) + } + } + if cl.WithStacktrace != "" { + levelEnabler, err := parseLevel(cl.WithStacktrace) + if err != nil { + return options, fmt.Errorf("setting up default Caddy log: %v", err) + } + options = append(options, zap.AddStacktrace(levelEnabler)) + } + return options, nil +} + +// SinkLog configures the default Go standard library +// global logger in the log package. This is necessary because +// module dependencies which are not built specifically for +// Caddy will use the standard logger. This is also known as +// the "sink" logger. +type SinkLog struct { + BaseLog +} + +func (sll *SinkLog) provision(ctx Context, logging *Logging) error { + if err := sll.provisionCommon(ctx, logging); err != nil { + return err + } + + options, err := sll.buildOptions() + if err != nil { + return err + } + + logger := zap.New(sll.core, options...) + ctx.cleanupFuncs = append(ctx.cleanupFuncs, zap.RedirectStdLog(logger)) + return nil +} + +// CustomLog represents a custom logger configuration. +// +// By default, a log will emit all log entries. Some entries +// will be skipped if sampling is enabled. Further, the Include +// and Exclude parameters define which loggers (by name) are +// allowed or rejected from emitting in this log. If both Include +// and Exclude are populated, their values must be mutually +// exclusive, and longer namespaces have priority. If neither +// are populated, all logs are emitted. +type CustomLog struct { + BaseLog + + // Include defines the names of loggers to emit in this + // log. For example, to include only logs emitted by the + // admin API, you would include "admin.api". + Include []string `json:"include,omitempty"` + + // Exclude defines the names of loggers that should be + // skipped by this log. For example, to exclude only + // HTTP access logs, you would exclude "http.log.access". + Exclude []string `json:"exclude,omitempty"` +} + +func (cl *CustomLog) provision(ctx Context, logging *Logging) error { + if err := cl.provisionCommon(ctx, logging); err != nil { + return err + } + + // If both Include and Exclude lists are populated, then each item must + // be a superspace or subspace of an item in the other list, because + // populating both lists means that any given item is either a rule + // or an exception to another rule. But if the item is not a super- + // or sub-space of any item in the other list, it is neither a rule + // nor an exception, and is a contradiction. Ensure, too, that the + // sets do not intersect, which is also a contradiction. + if len(cl.Include) > 0 && len(cl.Exclude) > 0 { + // prevent intersections + for _, allow := range cl.Include { + for _, deny := range cl.Exclude { + if allow == deny { + return fmt.Errorf("include and exclude must not intersect, but found %s in both lists", allow) + } + } + } + + // ensure namespaces are nested + outer: + for _, allow := range cl.Include { + for _, deny := range cl.Exclude { + if strings.HasPrefix(allow+".", deny+".") || + strings.HasPrefix(deny+".", allow+".") { + continue outer + } + } + return fmt.Errorf("when both include and exclude are populated, each element must be a superspace or subspace of one in the other list; check '%s' in include", allow) + } + } + return nil +} + +func (cl *CustomLog) matchesModule(moduleID string) bool { + return cl.loggerAllowed(moduleID, true) +} + +// loggerAllowed returns true if name is allowed to emit +// to cl. isModule should be true if name is the name of +// a module and you want to see if ANY of that module's +// logs would be permitted. +func (cl *CustomLog) loggerAllowed(name string, isModule bool) bool { + // accept all loggers by default + if len(cl.Include) == 0 && len(cl.Exclude) == 0 { + return true + } + + // append a dot so that partial names don't match + // (i.e. we don't want "foo.b" to match "foo.bar"); we + // will also have to append a dot when we do HasPrefix + // below to compensate for when namespaces are equal + if name != "" && name != "*" && name != "." { + name += "." + } + + var longestAccept, longestReject int + + if len(cl.Include) > 0 { + for _, namespace := range cl.Include { + var hasPrefix bool + if isModule { + hasPrefix = strings.HasPrefix(namespace+".", name) + } else { + hasPrefix = strings.HasPrefix(name, namespace+".") + } + if hasPrefix && len(namespace) > longestAccept { + longestAccept = len(namespace) + } + } + // the include list was populated, meaning that + // a match in this list is absolutely required + // if we are to accept the entry + if longestAccept == 0 { + return false + } + } + + if len(cl.Exclude) > 0 { + for _, namespace := range cl.Exclude { + // * == all logs emitted by modules + // . == all logs emitted by core + if (namespace == "*" && name != ".") || + (namespace == "." && name == ".") { + return false + } + if strings.HasPrefix(name, namespace+".") && + len(namespace) > longestReject { + longestReject = len(namespace) + } + } + // the reject list is populated, so we have to + // reject this entry if its match is better + // than the best from the accept list + if longestReject > longestAccept { + return false + } + } + + return (longestAccept > longestReject) || + (len(cl.Include) == 0 && longestReject == 0) +} + +// filteringCore filters log entries based on logger name, +// according to the rules of a CustomLog. +type filteringCore struct { + zapcore.Core + cl *CustomLog +} + +// With properly wraps With. +func (fc *filteringCore) With(fields []zapcore.Field) zapcore.Core { + return &filteringCore{ + Core: fc.Core.With(fields), + cl: fc.cl, + } +} + +// Check only allows the log entry if its logger name +// is allowed from the include/exclude rules of fc.cl. +func (fc *filteringCore) Check(e zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { + if fc.cl.loggerAllowed(e.LoggerName, false) { + return fc.Core.Check(e, ce) + } + return ce +} + +// LogSampling configures log entry sampling. +type LogSampling struct { + // The window over which to conduct sampling. + Interval time.Duration `json:"interval,omitempty"` + + // Log this many entries within a given level and + // message for each interval. + First int `json:"first,omitempty"` + + // If more entries with the same level and message + // are seen during the same interval, keep one in + // this many entries until the end of the interval. + Thereafter int `json:"thereafter,omitempty"` +} + +type ( + // StdoutWriter writes logs to standard out. + StdoutWriter struct{} + + // StderrWriter writes logs to standard error. + StderrWriter struct{} + + // DiscardWriter discards all writes. + DiscardWriter struct{} +) + +// CaddyModule returns the Caddy module information. +func (StdoutWriter) CaddyModule() ModuleInfo { + return ModuleInfo{ + ID: "caddy.logging.writers.stdout", + New: func() Module { return new(StdoutWriter) }, + } +} + +// CaddyModule returns the Caddy module information. +func (StderrWriter) CaddyModule() ModuleInfo { + return ModuleInfo{ + ID: "caddy.logging.writers.stderr", + New: func() Module { return new(StderrWriter) }, + } +} + +// CaddyModule returns the Caddy module information. +func (DiscardWriter) CaddyModule() ModuleInfo { + return ModuleInfo{ + ID: "caddy.logging.writers.discard", + New: func() Module { return new(DiscardWriter) }, + } +} + +func (StdoutWriter) String() string { return "stdout" } +func (StderrWriter) String() string { return "stderr" } +func (DiscardWriter) String() string { return "discard" } + +// WriterKey returns a unique key representing stdout. +func (StdoutWriter) WriterKey() string { return "std:out" } + +// WriterKey returns a unique key representing stderr. +func (StderrWriter) WriterKey() string { return "std:err" } + +// WriterKey returns a unique key representing discard. +func (DiscardWriter) WriterKey() string { return "discard" } + +// OpenWriter returns os.Stdout that can't be closed. +func (StdoutWriter) OpenWriter() (io.WriteCloser, error) { + return notClosable{os.Stdout}, nil +} + +// OpenWriter returns os.Stderr that can't be closed. +func (StderrWriter) OpenWriter() (io.WriteCloser, error) { + return notClosable{os.Stderr}, nil +} + +// OpenWriter returns io.Discard that can't be closed. +func (DiscardWriter) OpenWriter() (io.WriteCloser, error) { + return notClosable{io.Discard}, nil +} + +// notClosable is an io.WriteCloser that can't be closed. +type notClosable struct{ io.Writer } + +func (fc notClosable) Close() error { return nil } + +type defaultCustomLog struct { + *CustomLog + logger *zap.Logger +} + +// newDefaultProductionLog configures a custom log that is +// intended for use by default if no other log is specified +// in a config. It writes to stderr, uses the console encoder, +// and enables INFO-level logs and higher. +func newDefaultProductionLog() (*defaultCustomLog, error) { + cl := new(CustomLog) + cl.writerOpener = StderrWriter{} + var err error + cl.writer, err = cl.writerOpener.OpenWriter() + if err != nil { + return nil, err + } + cl.encoder = newDefaultProductionLogEncoder(cl.writerOpener) + cl.levelEnabler = zapcore.InfoLevel + + cl.buildCore() + + logger := zap.New(cl.core) + + // capture logs from other libraries which + // may not be using zap logging directly + _ = zap.RedirectStdLog(logger) + + return &defaultCustomLog{ + CustomLog: cl, + logger: logger, + }, nil +} + +func newDefaultProductionLogEncoder(wo WriterOpener) zapcore.Encoder { + encCfg := zap.NewProductionEncoderConfig() + if IsWriterStandardStream(wo) && term.IsTerminal(int(os.Stderr.Fd())) { + // if interactive terminal, make output more human-readable by default + encCfg.EncodeTime = func(ts time.Time, encoder zapcore.PrimitiveArrayEncoder) { + encoder.AppendString(ts.UTC().Format("2006/01/02 15:04:05.000")) + } + if coloringEnabled { + encCfg.EncodeLevel = zapcore.CapitalColorLevelEncoder + } + + return zapcore.NewConsoleEncoder(encCfg) + } + return zapcore.NewJSONEncoder(encCfg) +} + +func parseLevel(levelInput string) (zapcore.LevelEnabler, error) { + repl := NewReplacer() + level, err := repl.ReplaceOrErr(levelInput, true, true) + if err != nil { + return nil, fmt.Errorf("invalid log level: %v", err) + } + level = strings.ToLower(level) + + // set up the log level + switch level { + case "debug": + return zapcore.DebugLevel, nil + case "", "info": + return zapcore.InfoLevel, nil + case "warn": + return zapcore.WarnLevel, nil + case "error": + return zapcore.ErrorLevel, nil + case "panic": + return zapcore.PanicLevel, nil + case "fatal": + return zapcore.FatalLevel, nil + default: + return nil, fmt.Errorf("unrecognized log level: %s", level) + } +} + +// Log returns the current default logger. +func Log() *zap.Logger { + defaultLoggerMu.RLock() + defer defaultLoggerMu.RUnlock() + return defaultLogger.logger +} + +var ( + coloringEnabled = os.Getenv("NO_COLOR") == "" && os.Getenv("TERM") != "xterm-mono" + defaultLogger, _ = newDefaultProductionLog() + defaultLoggerMu sync.RWMutex +) + +var writers = NewUsagePool() + +// ConfiguresFormatterDefault is an optional interface that +// encoder modules can implement to configure the default +// format of their encoder. This is useful for encoders +// which nest an encoder, that needs to know the writer +// in order to determine the correct default. +type ConfiguresFormatterDefault interface { + ConfigureDefaultFormat(WriterOpener) error +} + +const DefaultLoggerName = "default" + +// Interface guards +var ( + _ io.WriteCloser = (*notClosable)(nil) + _ WriterOpener = (*StdoutWriter)(nil) + _ WriterOpener = (*StderrWriter)(nil) +) diff --git a/metrics.go b/metrics.go new file mode 100644 index 00000000000..0ee3853eb85 --- /dev/null +++ b/metrics.go @@ -0,0 +1,84 @@ +package caddy + +import ( + "net/http" + + "github.com/prometheus/client_golang/prometheus" + + "github.com/caddyserver/caddy/v2/internal/metrics" +) + +// define and register the metrics used in this package. +func init() { + const ns, sub = "caddy", "admin" + adminMetrics.requestCount = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: ns, + Subsystem: sub, + Name: "http_requests_total", + Help: "Counter of requests made to the Admin API's HTTP endpoints.", + }, []string{"handler", "path", "code", "method"}) + adminMetrics.requestErrors = prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: ns, + Subsystem: sub, + Name: "http_request_errors_total", + Help: "Number of requests resulting in middleware errors.", + }, []string{"handler", "path", "method"}) + globalMetrics.configSuccess = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "caddy_config_last_reload_successful", + Help: "Whether the last configuration reload attempt was successful.", + }) + globalMetrics.configSuccessTime = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "caddy_config_last_reload_success_timestamp_seconds", + Help: "Timestamp of the last successful configuration reload.", + }) +} + +// adminMetrics is a collection of metrics that can be tracked for the admin API. +var adminMetrics = struct { + requestCount *prometheus.CounterVec + requestErrors *prometheus.CounterVec +}{} + +// globalMetrics is a collection of metrics that can be tracked for Caddy global state +var globalMetrics = struct { + configSuccess prometheus.Gauge + configSuccessTime prometheus.Gauge +}{} + +// Similar to promhttp.InstrumentHandlerCounter, but upper-cases method names +// instead of lower-casing them. +// +// Unlike promhttp.InstrumentHandlerCounter, this assumes a "code" and "method" +// label is present, and will panic otherwise. +func instrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + d := newDelegator(w) + next.ServeHTTP(d, r) + counter.With(prometheus.Labels{ + "code": metrics.SanitizeCode(d.status), + "method": metrics.SanitizeMethod(r.Method), + }).Inc() + }) +} + +func newDelegator(w http.ResponseWriter) *delegator { + return &delegator{ + ResponseWriter: w, + } +} + +type delegator struct { + http.ResponseWriter + status int +} + +func (d *delegator) WriteHeader(code int) { + d.status = code + d.ResponseWriter.WriteHeader(code) +} + +// Unwrap returns the underlying ResponseWriter, necessary for +// http.ResponseController to work correctly. +func (d *delegator) Unwrap() http.ResponseWriter { + return d.ResponseWriter +} diff --git a/modules.go b/modules.go new file mode 100644 index 00000000000..470c25e3725 --- /dev/null +++ b/modules.go @@ -0,0 +1,366 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddy + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "sort" + "strings" + "sync" +) + +// Module is a type that is used as a Caddy module. In +// addition to this interface, most modules will implement +// some interface expected by their host module in order +// to be useful. To learn which interface(s) to implement, +// see the documentation for the host module. At a bare +// minimum, this interface, when implemented, only provides +// the module's ID and constructor function. +// +// Modules will often implement additional interfaces +// including Provisioner, Validator, and CleanerUpper. +// If a module implements these interfaces, their +// methods are called during the module's lifespan. +// +// When a module is loaded by a host module, the following +// happens: 1) ModuleInfo.New() is called to get a new +// instance of the module. 2) The module's configuration is +// unmarshaled into that instance. 3) If the module is a +// Provisioner, the Provision() method is called. 4) If the +// module is a Validator, the Validate() method is called. +// 5) The module will probably be type-asserted from +// 'any' to some other, more useful interface expected +// by the host module. For example, HTTP handler modules are +// type-asserted as caddyhttp.MiddlewareHandler values. +// 6) When a module's containing Context is canceled, if it is +// a CleanerUpper, its Cleanup() method is called. +type Module interface { + // This method indicates that the type is a Caddy + // module. The returned ModuleInfo must have both + // a name and a constructor function. This method + // must not have any side-effects. + CaddyModule() ModuleInfo +} + +// ModuleInfo represents a registered Caddy module. +type ModuleInfo struct { + // ID is the "full name" of the module. It + // must be unique and properly namespaced. + ID ModuleID + + // New returns a pointer to a new, empty + // instance of the module's type. This + // method must not have any side-effects, + // and no other initialization should + // occur within it. Any initialization + // of the returned value should be done + // in a Provision() method (see the + // Provisioner interface). + New func() Module +} + +// ModuleID is a string that uniquely identifies a Caddy module. A +// module ID is lightly structured. It consists of dot-separated +// labels which form a simple hierarchy from left to right. The last +// label is the module name, and the labels before that constitute +// the namespace (or scope). +// +// Thus, a module ID has the form: . +// +// An ID with no dot has the empty namespace, which is appropriate +// for app modules (these are "top-level" modules that Caddy core +// loads and runs). +// +// Module IDs should be lowercase and use underscores (_) instead of +// spaces. +// +// Examples of valid IDs: +// - http +// - http.handlers.file_server +// - caddy.logging.encoders.json +type ModuleID string + +// Namespace returns the namespace (or scope) portion of a module ID, +// which is all but the last label of the ID. If the ID has only one +// label, then the namespace is empty. +func (id ModuleID) Namespace() string { + lastDot := strings.LastIndex(string(id), ".") + if lastDot < 0 { + return "" + } + return string(id)[:lastDot] +} + +// Name returns the Name (last element) of a module ID. +func (id ModuleID) Name() string { + if id == "" { + return "" + } + parts := strings.Split(string(id), ".") + return parts[len(parts)-1] +} + +func (mi ModuleInfo) String() string { return string(mi.ID) } + +// ModuleMap is a map that can contain multiple modules, +// where the map key is the module's name. (The namespace +// is usually read from an associated field's struct tag.) +// Because the module's name is given as the key in a +// module map, the name does not have to be given in the +// json.RawMessage. +type ModuleMap map[string]json.RawMessage + +// RegisterModule registers a module by receiving a +// plain/empty value of the module. For registration to +// be properly recorded, this should be called in the +// init phase of runtime. Typically, the module package +// will do this as a side-effect of being imported. +// This function panics if the module's info is +// incomplete or invalid, or if the module is already +// registered. +func RegisterModule(instance Module) { + mod := instance.CaddyModule() + + if mod.ID == "" { + panic("module ID missing") + } + if mod.ID == "caddy" || mod.ID == "admin" { + panic(fmt.Sprintf("module ID '%s' is reserved", mod.ID)) + } + if mod.New == nil { + panic("missing ModuleInfo.New") + } + if val := mod.New(); val == nil { + panic("ModuleInfo.New must return a non-nil module instance") + } + + modulesMu.Lock() + defer modulesMu.Unlock() + + if _, ok := modules[string(mod.ID)]; ok { + panic(fmt.Sprintf("module already registered: %s", mod.ID)) + } + modules[string(mod.ID)] = mod +} + +// GetModule returns module information from its ID (full name). +func GetModule(name string) (ModuleInfo, error) { + modulesMu.RLock() + defer modulesMu.RUnlock() + m, ok := modules[name] + if !ok { + return ModuleInfo{}, fmt.Errorf("module not registered: %s", name) + } + return m, nil +} + +// GetModuleName returns a module's name (the last label of its ID) +// from an instance of its value. If the value is not a module, an +// empty string will be returned. +func GetModuleName(instance any) string { + var name string + if mod, ok := instance.(Module); ok { + name = mod.CaddyModule().ID.Name() + } + return name +} + +// GetModuleID returns a module's ID from an instance of its value. +// If the value is not a module, an empty string will be returned. +func GetModuleID(instance any) string { + var id string + if mod, ok := instance.(Module); ok { + id = string(mod.CaddyModule().ID) + } + return id +} + +// GetModules returns all modules in the given scope/namespace. +// For example, a scope of "foo" returns modules named "foo.bar", +// "foo.loo", but not "bar", "foo.bar.loo", etc. An empty scope +// returns top-level modules, for example "foo" or "bar". Partial +// scopes are not matched (i.e. scope "foo.ba" does not match +// name "foo.bar"). +// +// Because modules are registered to a map under the hood, the +// returned slice will be sorted to keep it deterministic. +func GetModules(scope string) []ModuleInfo { + modulesMu.RLock() + defer modulesMu.RUnlock() + + scopeParts := strings.Split(scope, ".") + + // handle the special case of an empty scope, which + // should match only the top-level modules + if scope == "" { + scopeParts = []string{} + } + + var mods []ModuleInfo +iterateModules: + for id, m := range modules { + modParts := strings.Split(id, ".") + + // match only the next level of nesting + if len(modParts) != len(scopeParts)+1 { + continue + } + + // specified parts must be exact matches + for i := range scopeParts { + if modParts[i] != scopeParts[i] { + continue iterateModules + } + } + + mods = append(mods, m) + } + + // make return value deterministic + sort.Slice(mods, func(i, j int) bool { + return mods[i].ID < mods[j].ID + }) + + return mods +} + +// Modules returns the names of all registered modules +// in ascending lexicographical order. +func Modules() []string { + modulesMu.RLock() + defer modulesMu.RUnlock() + + names := make([]string, 0, len(modules)) + for name := range modules { + names = append(names, name) + } + + sort.Strings(names) + + return names +} + +// getModuleNameInline loads the string value from raw of moduleNameKey, +// where raw must be a JSON encoding of a map. It returns that value, +// along with the result of removing that key from raw. +func getModuleNameInline(moduleNameKey string, raw json.RawMessage) (string, json.RawMessage, error) { + var tmp map[string]any + err := json.Unmarshal(raw, &tmp) + if err != nil { + return "", nil, err + } + + moduleName, ok := tmp[moduleNameKey].(string) + if !ok || moduleName == "" { + return "", nil, fmt.Errorf("module name not specified with key '%s' in %+v", moduleNameKey, tmp) + } + + // remove key from the object, otherwise decoding it later + // will yield an error because the struct won't recognize it + // (this is only needed because we strictly enforce that + // all keys are recognized when loading modules) + delete(tmp, moduleNameKey) + result, err := json.Marshal(tmp) + if err != nil { + return "", nil, fmt.Errorf("re-encoding module configuration: %v", err) + } + + return moduleName, result, nil +} + +// Provisioner is implemented by modules which may need to perform +// some additional "setup" steps immediately after being loaded. +// Provisioning should be fast (imperceptible running time). If +// any side-effects result in the execution of this function (e.g. +// creating global state, any other allocations which require +// garbage collection, opening files, starting goroutines etc.), +// be sure to clean up properly by implementing the CleanerUpper +// interface to avoid leaking resources. +type Provisioner interface { + Provision(Context) error +} + +// Validator is implemented by modules which can verify that their +// configurations are valid. This method will be called after +// Provision() (if implemented). Validation should always be fast +// (imperceptible running time) and an error must be returned if +// the module's configuration is invalid. +type Validator interface { + Validate() error +} + +// CleanerUpper is implemented by modules which may have side-effects +// such as opened files, spawned goroutines, or allocated some sort +// of non-stack state when they were provisioned. This method should +// deallocate/cleanup those resources to prevent memory leaks. Cleanup +// should be fast and efficient. Cleanup should work even if Provision +// returns an error, to allow cleaning up from partial provisionings. +type CleanerUpper interface { + Cleanup() error +} + +// ParseStructTag parses a caddy struct tag into its keys and values. +// It is very simple. The expected syntax is: +// `caddy:"key1=val1 key2=val2 ..."` +func ParseStructTag(tag string) (map[string]string, error) { + results := make(map[string]string) + pairs := strings.Split(tag, " ") + for i, pair := range pairs { + if pair == "" { + continue + } + before, after, isCut := strings.Cut(pair, "=") + if !isCut { + return nil, fmt.Errorf("missing key in '%s' (pair %d)", pair, i) + } + results[before] = after + } + return results, nil +} + +// StrictUnmarshalJSON is like json.Unmarshal but returns an error +// if any of the fields are unrecognized. Useful when decoding +// module configurations, where you want to be more sure they're +// correct. +func StrictUnmarshalJSON(data []byte, v any) error { + dec := json.NewDecoder(bytes.NewReader(data)) + dec.DisallowUnknownFields() + return dec.Decode(v) +} + +// isJSONRawMessage returns true if the type is encoding/json.RawMessage. +func isJSONRawMessage(typ reflect.Type) bool { + return typ.PkgPath() == "encoding/json" && typ.Name() == "RawMessage" +} + +// isModuleMapType returns true if the type is map[string]json.RawMessage. +// It assumes that the string key is the module name, but this is not +// always the case. To know for sure, this function must return true, but +// also the struct tag where this type appears must NOT define an inline_key +// attribute, which would mean that the module names appear inline with the +// values, not in the key. +func isModuleMapType(typ reflect.Type) bool { + return typ.Kind() == reflect.Map && + typ.Key().Kind() == reflect.String && + isJSONRawMessage(typ.Elem()) +} + +var ( + modules = make(map[string]ModuleInfo) + modulesMu sync.RWMutex +) diff --git a/modules/caddyevents/app.go b/modules/caddyevents/app.go new file mode 100644 index 00000000000..e78b00f8cfe --- /dev/null +++ b/modules/caddyevents/app.go @@ -0,0 +1,405 @@ +// 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 caddyevents + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strings" + "time" + + "github.com/google/uuid" + "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2" +) + +func init() { + caddy.RegisterModule(App{}) +} + +// App implements a global eventing system within Caddy. +// Modules can emit and subscribe to events, providing +// hooks into deep parts of the code base that aren't +// otherwise accessible. Events provide information about +// what and when things are happening, and this facility +// allows handlers to take action when events occur, +// add information to the event's metadata, and even +// control program flow in some cases. +// +// Events are propagated in a DOM-like fashion. An event +// emitted from module `a.b.c` (the "origin") will first +// invoke handlers listening to `a.b.c`, then `a.b`, +// then `a`, then those listening regardless of origin. +// If a handler returns the special error Aborted, then +// propagation immediately stops and the event is marked +// as aborted. Emitters may optionally choose to adjust +// program flow based on an abort. +// +// Modules can subscribe to events by origin and/or name. +// A handler is invoked only if it is subscribed to the +// event by name and origin. Subscriptions should be +// registered during the provisioning phase, before apps +// are started. +// +// Event handlers are fired synchronously as part of the +// regular flow of the program. This allows event handlers +// to control the flow of the program if the origin permits +// it and also allows handlers to convey new information +// back into the origin module before it continues. +// In essence, event handlers are similar to HTTP +// middleware handlers. +// +// Event bindings/subscribers are unordered; i.e. +// event handlers are invoked in an arbitrary order. +// Event handlers should not rely on the logic of other +// handlers to succeed. +// +// The entirety of this app module is EXPERIMENTAL and +// subject to change. Pay attention to release notes. +type App struct { + // Subscriptions bind handlers to one or more events + // either globally or scoped to specific modules or module + // namespaces. + Subscriptions []*Subscription `json:"subscriptions,omitempty"` + + // Map of event name to map of module ID/namespace to handlers + subscriptions map[string]map[caddy.ModuleID][]Handler + + logger *zap.Logger + started bool +} + +// Subscription represents binding of one or more handlers to +// one or more events. +type Subscription struct { + // The name(s) of the event(s) to bind to. Default: all events. + Events []string `json:"events,omitempty"` + + // The ID or namespace of the module(s) from which events + // originate to listen to for events. Default: all modules. + // + // Events propagate up, so events emitted by module "a.b.c" + // will also trigger the event for "a.b" and "a". Thus, to + // receive all events from "a.b.c" and "a.b.d", for example, + // one can subscribe to either "a.b" or all of "a" entirely. + Modules []caddy.ModuleID `json:"modules,omitempty"` + + // The event handler modules. These implement the actual + // behavior to invoke when an event occurs. At least one + // handler is required. + HandlersRaw []json.RawMessage `json:"handlers,omitempty" caddy:"namespace=events.handlers inline_key=handler"` + + // The decoded handlers; Go code that is subscribing to + // an event should set this field directly; HandlersRaw + // is meant for JSON configuration to fill out this field. + Handlers []Handler `json:"-"` +} + +// CaddyModule returns the Caddy module information. +func (App) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "events", + New: func() caddy.Module { return new(App) }, + } +} + +// Provision sets up the app. +func (app *App) Provision(ctx caddy.Context) error { + app.logger = ctx.Logger() + app.subscriptions = make(map[string]map[caddy.ModuleID][]Handler) + + for _, sub := range app.Subscriptions { + if sub.HandlersRaw == nil { + continue + } + handlersIface, err := ctx.LoadModule(sub, "HandlersRaw") + if err != nil { + return fmt.Errorf("loading event subscriber modules: %v", err) + } + for _, h := range handlersIface.([]any) { + sub.Handlers = append(sub.Handlers, h.(Handler)) + } + if len(sub.Handlers) == 0 { + // pointless to bind without any handlers + return fmt.Errorf("no handlers defined") + } + } + + return nil +} + +// Start runs the app. +func (app *App) Start() error { + for _, sub := range app.Subscriptions { + if err := app.Subscribe(sub); err != nil { + return err + } + } + + app.started = true + + return nil +} + +// Stop gracefully shuts down the app. +func (app *App) Stop() error { + return nil +} + +// Subscribe binds one or more event handlers to one or more events +// according to the subscription s. For now, subscriptions can only +// be created during the provision phase; new bindings cannot be +// created after the events app has started. +func (app *App) Subscribe(s *Subscription) error { + if app.started { + return fmt.Errorf("events already started; new subscriptions closed") + } + + // handle special case of catch-alls (omission of event name or module space implies all) + if len(s.Events) == 0 { + s.Events = []string{""} + } + if len(s.Modules) == 0 { + s.Modules = []caddy.ModuleID{""} + } + + for _, eventName := range s.Events { + if app.subscriptions[eventName] == nil { + app.subscriptions[eventName] = make(map[caddy.ModuleID][]Handler) + } + for _, originModule := range s.Modules { + app.subscriptions[eventName][originModule] = append(app.subscriptions[eventName][originModule], s.Handlers...) + } + } + + return nil +} + +// On is syntactic sugar for Subscribe() that binds a single handler +// to a single event from any module. If the eventName is empty string, +// it counts for all events. +func (app *App) On(eventName string, handler Handler) error { + return app.Subscribe(&Subscription{ + Events: []string{eventName}, + Handlers: []Handler{handler}, + }) +} + +// Emit creates and dispatches an event named eventName to all relevant handlers with +// the metadata data. Events are emitted and propagated synchronously. The returned Event +// value will have any additional information from the invoked handlers. +// +// Note that the data map is not copied, for efficiency. After Emit() is called, the +// data passed in should not be changed in other goroutines. +func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) Event { + logger := app.logger.With(zap.String("name", eventName)) + + id, err := uuid.NewRandom() + if err != nil { + logger.Error("failed generating new event ID", zap.Error(err)) + } + + eventName = strings.ToLower(eventName) + + e := Event{ + Data: data, + id: id, + ts: time.Now(), + name: eventName, + origin: ctx.Module(), + } + + logger = logger.With( + zap.String("id", e.id.String()), + zap.String("origin", e.origin.CaddyModule().String())) + + // add event info to replacer, make sure it's in the context + repl, ok := ctx.Context.Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + if !ok { + repl = caddy.NewReplacer() + ctx.Context = context.WithValue(ctx.Context, caddy.ReplacerCtxKey, repl) + } + repl.Map(func(key string) (any, bool) { + switch key { + case "event": + return e, true + case "event.id": + return e.id, true + case "event.name": + return e.name, true + case "event.time": + return e.ts, true + case "event.time_unix": + return e.ts.UnixMilli(), true + case "event.module": + return e.origin.CaddyModule().ID, true + case "event.data": + return e.Data, true + } + + if strings.HasPrefix(key, "event.data.") { + key = strings.TrimPrefix(key, "event.data.") + if val, ok := e.Data[key]; ok { + return val, true + } + } + + return nil, false + }) + + logger = logger.WithLazy(zap.Any("data", e.Data)) + + logger.Debug("event") + + // invoke handlers bound to the event by name and also all events; this for loop + // iterates twice at most: once for the event name, once for "" (all events) + for { + moduleID := e.origin.CaddyModule().ID + + // implement propagation up the module tree (i.e. start with "a.b.c" then "a.b" then "a" then "") + for { + if app.subscriptions[eventName] == nil { + break // shortcut if event not bound at all + } + + for _, handler := range app.subscriptions[eventName][moduleID] { + select { + case <-ctx.Done(): + logger.Error("context canceled; event handling stopped") + return e + default: + } + + // this log can be a useful sanity check to ensure your handlers are in fact being invoked + // (see https://github.com/mholt/caddy-events-exec/issues/6) + logger.Debug("invoking subscribed handler", + zap.String("subscribed_to", eventName), + zap.Any("handler", handler)) + + if err := handler.Handle(ctx, e); err != nil { + aborted := errors.Is(err, ErrAborted) + + logger.Error("handler error", + zap.Error(err), + zap.Bool("aborted", aborted)) + + if aborted { + e.Aborted = err + return e + } + } + } + + if moduleID == "" { + break + } + lastDot := strings.LastIndex(string(moduleID), ".") + if lastDot < 0 { + moduleID = "" // include handlers bound to events regardless of module + } else { + moduleID = moduleID[:lastDot] + } + } + + // include handlers listening to all events + if eventName == "" { + break + } + eventName = "" + } + + return e +} + +// Event represents something that has happened or is happening. +// An Event value is not synchronized, so it should be copied if +// being used in goroutines. +// +// EXPERIMENTAL: As with the rest of this package, events are +// subject to change. +type Event struct { + // If non-nil, the event has been aborted, meaning + // propagation has stopped to other handlers and + // the code should stop what it was doing. Emitters + // may choose to use this as a signal to adjust their + // code path appropriately. + Aborted error + + // The data associated with the event. Usually the + // original emitter will be the only one to set or + // change these values, but the field is exported + // so handlers can have full access if needed. + // However, this map is not synchronized, so + // handlers must not use this map directly in new + // goroutines; instead, copy the map to use it in a + // goroutine. + Data map[string]any + + id uuid.UUID + ts time.Time + name string + origin caddy.Module +} + +func (e Event) ID() uuid.UUID { return e.id } +func (e Event) Timestamp() time.Time { return e.ts } +func (e Event) Name() string { return e.name } +func (e Event) Origin() caddy.Module { return e.origin } + +// CloudEvent exports event e as a structure that, when +// serialized as JSON, is compatible with the +// CloudEvents spec. +func (e Event) CloudEvent() CloudEvent { + dataJSON, _ := json.Marshal(e.Data) + return CloudEvent{ + ID: e.id.String(), + Source: e.origin.CaddyModule().String(), + SpecVersion: "1.0", + Type: e.name, + Time: e.ts, + DataContentType: "application/json", + Data: dataJSON, + } +} + +// CloudEvent is a JSON-serializable structure that +// is compatible with the CloudEvents specification. +// See https://cloudevents.io. +type CloudEvent struct { + ID string `json:"id"` + Source string `json:"source"` + SpecVersion string `json:"specversion"` + Type string `json:"type"` + Time time.Time `json:"time"` + DataContentType string `json:"datacontenttype,omitempty"` + Data json.RawMessage `json:"data,omitempty"` +} + +// ErrAborted cancels an event. +var ErrAborted = errors.New("event aborted") + +// Handler is a type that can handle events. +type Handler interface { + Handle(context.Context, Event) error +} + +// Interface guards +var ( + _ caddy.App = (*App)(nil) + _ caddy.Provisioner = (*App)(nil) +) diff --git a/modules/caddyevents/eventsconfig/caddyfile.go b/modules/caddyevents/eventsconfig/caddyfile.go new file mode 100644 index 00000000000..93a4c3d388f --- /dev/null +++ b/modules/caddyevents/eventsconfig/caddyfile.go @@ -0,0 +1,82 @@ +// 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 eventsconfig is for configuring caddyevents.App with the +// Caddyfile. This code can't be in the caddyevents package because +// the httpcaddyfile package imports caddyhttp, which imports +// caddyevents: hence, it creates an import cycle. +package eventsconfig + +import ( + "encoding/json" + + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyevents" +) + +func init() { + httpcaddyfile.RegisterGlobalOption("events", parseApp) +} + +// parseApp configures the "events" global option from Caddyfile to set up the events app. +// Syntax: +// +// events { +// on +// } +// +// If is *, then it will bind to all events. +func parseApp(d *caddyfile.Dispenser, _ any) (any, error) { + d.Next() // consume option name + app := new(caddyevents.App) + for d.NextBlock(0) { + switch d.Val() { + case "on": + if !d.NextArg() { + return nil, d.ArgErr() + } + eventName := d.Val() + if eventName == "*" { + eventName = "" + } + + if !d.NextArg() { + return nil, d.ArgErr() + } + handlerName := d.Val() + modID := "events.handlers." + handlerName + unm, err := caddyfile.UnmarshalModule(d, modID) + if err != nil { + return nil, err + } + + app.Subscriptions = append(app.Subscriptions, &caddyevents.Subscription{ + Events: []string{eventName}, + HandlersRaw: []json.RawMessage{ + caddyconfig.JSONModuleObject(unm, "handler", handlerName, nil), + }, + }) + + default: + return nil, d.ArgErr() + } + } + + return httpcaddyfile.App{ + Name: "events", + Value: caddyconfig.JSON(app, nil), + }, nil +} diff --git a/modules/caddyfs/filesystem.go b/modules/caddyfs/filesystem.go new file mode 100644 index 00000000000..b2fdcf7a274 --- /dev/null +++ b/modules/caddyfs/filesystem.go @@ -0,0 +1,112 @@ +package caddyfs + +import ( + "encoding/json" + "fmt" + "io/fs" + + "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" +) + +func init() { + caddy.RegisterModule(Filesystems{}) + httpcaddyfile.RegisterGlobalOption("filesystem", parseFilesystems) +} + +type moduleEntry struct { + Key string `json:"name,omitempty"` + FileSystemRaw json.RawMessage `json:"file_system,omitempty" caddy:"namespace=caddy.fs inline_key=backend"` + fileSystem fs.FS +} + +// Filesystems loads caddy.fs modules into the global filesystem map +type Filesystems struct { + Filesystems []*moduleEntry `json:"filesystems"` + + defers []func() +} + +func parseFilesystems(d *caddyfile.Dispenser, existingVal any) (any, error) { + p := &Filesystems{} + current, ok := existingVal.(*Filesystems) + if ok { + p = current + } + x := &moduleEntry{} + err := x.UnmarshalCaddyfile(d) + if err != nil { + return nil, err + } + p.Filesystems = append(p.Filesystems, x) + return p, nil +} + +// CaddyModule returns the Caddy module information. +func (Filesystems) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.filesystems", + New: func() caddy.Module { return new(Filesystems) }, + } +} + +func (xs *Filesystems) Start() error { return nil } +func (xs *Filesystems) Stop() error { return nil } + +func (xs *Filesystems) Provision(ctx caddy.Context) error { + // load the filesystem module + for _, f := range xs.Filesystems { + if len(f.FileSystemRaw) > 0 { + mod, err := ctx.LoadModule(f, "FileSystemRaw") + if err != nil { + return fmt.Errorf("loading file system module: %v", err) + } + f.fileSystem = mod.(fs.FS) + } + // register that module + ctx.Logger().Debug("registering fs", zap.String("fs", f.Key)) + ctx.Filesystems().Register(f.Key, f.fileSystem) + // remember to unregister the module when we are done + xs.defers = append(xs.defers, func() { + ctx.Logger().Debug("unregistering fs", zap.String("fs", f.Key)) + ctx.Filesystems().Unregister(f.Key) + }) + } + return nil +} + +func (f *Filesystems) Cleanup() error { + for _, v := range f.defers { + v() + } + return nil +} + +func (f *moduleEntry) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + for d.Next() { + // key required for now + if !d.Args(&f.Key) { + return d.ArgErr() + } + // get the module json + if !d.NextArg() { + return d.ArgErr() + } + name := d.Val() + modID := "caddy.fs." + name + unm, err := caddyfile.UnmarshalModule(d, modID) + if err != nil { + return err + } + fsys, ok := unm.(fs.FS) + if !ok { + return d.Errf("module %s (%T) is not a supported file system implementation (requires fs.FS)", modID, unm) + } + f.FileSystemRaw = caddyconfig.JSONModuleObject(fsys, "backend", name, nil) + } + return nil +} diff --git a/modules/caddyhttp/app.go b/modules/caddyhttp/app.go new file mode 100644 index 00000000000..5477ed8fec7 --- /dev/null +++ b/modules/caddyhttp/app.go @@ -0,0 +1,808 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "cmp" + "context" + "crypto/tls" + "fmt" + "maps" + "net" + "net/http" + "strconv" + "sync" + "time" + + "go.uber.org/zap" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyevents" + "github.com/caddyserver/caddy/v2/modules/caddytls" +) + +func init() { + caddy.RegisterModule(App{}) +} + +// App is a robust, production-ready HTTP server. +// +// HTTPS is enabled by default if host matchers with qualifying names are used +// in any of routes; certificates are automatically provisioned and renewed. +// Additionally, automatic HTTPS will also enable HTTPS for servers that listen +// only on the HTTPS port but which do not have any TLS connection policies +// defined by adding a good, default TLS connection policy. +// +// In HTTP routes, additional placeholders are available (replace any `*`): +// +// Placeholder | Description +// ------------|--------------- +// `{http.request.body}` | The request body (⚠️ inefficient; use only for debugging) +// `{http.request.cookie.*}` | HTTP request cookie +// `{http.request.duration}` | Time up to now spent handling the request (after decoding headers from client) +// `{http.request.duration_ms}` | Same as 'duration', but in milliseconds. +// `{http.request.uuid}` | The request unique identifier +// `{http.request.header.*}` | Specific request header field +// `{http.request.host}` | The host part of the request's Host header +// `{http.request.host.labels.*}` | Request host labels (0-based from right); e.g. for foo.example.com: 0=com, 1=example, 2=foo +// `{http.request.hostport}` | The host and port from the request's Host header +// `{http.request.method}` | The request method +// `{http.request.orig_method}` | The request's original method +// `{http.request.orig_uri}` | The request's original URI +// `{http.request.orig_uri.path}` | The request's original path +// `{http.request.orig_uri.path.*}` | Parts of the original path, split by `/` (0-based from left) +// `{http.request.orig_uri.path.dir}` | The request's original directory +// `{http.request.orig_uri.path.file}` | The request's original filename +// `{http.request.orig_uri.query}` | The request's original query string (without `?`) +// `{http.request.port}` | The port part of the request's Host header +// `{http.request.proto}` | The protocol of the request +// `{http.request.local.host}` | The host (IP) part of the local address the connection arrived on +// `{http.request.local.port}` | The port part of the local address the connection arrived on +// `{http.request.local}` | The local address the connection arrived on +// `{http.request.remote.host}` | The host (IP) part of the remote client's address +// `{http.request.remote.port}` | The port part of the remote client's address +// `{http.request.remote}` | The address of the remote client +// `{http.request.scheme}` | The request scheme, typically `http` or `https` +// `{http.request.tls.version}` | The TLS version name +// `{http.request.tls.cipher_suite}` | The TLS cipher suite +// `{http.request.tls.resumed}` | The TLS connection resumed a previous connection +// `{http.request.tls.proto}` | The negotiated next protocol +// `{http.request.tls.proto_mutual}` | The negotiated next protocol was advertised by the server +// `{http.request.tls.server_name}` | The server name requested by the client, if any +// `{http.request.tls.client.fingerprint}` | The SHA256 checksum of the client certificate +// `{http.request.tls.client.public_key}` | The public key of the client certificate. +// `{http.request.tls.client.public_key_sha256}` | The SHA256 checksum of the client's public key. +// `{http.request.tls.client.certificate_pem}` | The PEM-encoded value of the certificate. +// `{http.request.tls.client.certificate_der_base64}` | The base64-encoded value of the certificate. +// `{http.request.tls.client.issuer}` | The issuer DN of the client certificate +// `{http.request.tls.client.serial}` | The serial number of the client certificate +// `{http.request.tls.client.subject}` | The subject DN of the client certificate +// `{http.request.tls.client.san.dns_names.*}` | SAN DNS names(index optional) +// `{http.request.tls.client.san.emails.*}` | SAN email addresses (index optional) +// `{http.request.tls.client.san.ips.*}` | SAN IP addresses (index optional) +// `{http.request.tls.client.san.uris.*}` | SAN URIs (index optional) +// `{http.request.uri}` | The full request URI +// `{http.request.uri.path}` | The path component of the request URI +// `{http.request.uri.path.*}` | Parts of the path, split by `/` (0-based from left) +// `{http.request.uri.path.dir}` | The directory, excluding leaf filename +// `{http.request.uri.path.file}` | The filename of the path, excluding directory +// `{http.request.uri.query}` | The query string (without `?`) +// `{http.request.uri.query.*}` | Individual query string value +// `{http.response.header.*}` | Specific response header field +// `{http.vars.*}` | Custom variables in the HTTP handler chain +// `{http.shutting_down}` | True if the HTTP app is shutting down +// `{http.time_until_shutdown}` | Time until HTTP server shutdown, if scheduled +type App struct { + // HTTPPort specifies the port to use for HTTP (as opposed to HTTPS), + // which is used when setting up HTTP->HTTPS redirects or ACME HTTP + // challenge solvers. Default: 80. + HTTPPort int `json:"http_port,omitempty"` + + // HTTPSPort specifies the port to use for HTTPS, which is used when + // solving the ACME TLS-ALPN challenges, or whenever HTTPS is needed + // but no specific port number is given. Default: 443. + HTTPSPort int `json:"https_port,omitempty"` + + // GracePeriod is how long to wait for active connections when shutting + // down the servers. During the grace period, no new connections are + // accepted, idle connections are closed, and active connections will + // be given the full length of time to become idle and close. + // Once the grace period is over, connections will be forcefully closed. + // If zero, the grace period is eternal. Default: 0. + GracePeriod caddy.Duration `json:"grace_period,omitempty"` + + // ShutdownDelay is how long to wait before initiating the grace + // period. When this app is stopping (e.g. during a config reload or + // process exit), all servers will be shut down. Normally this immediately + // initiates the grace period. However, if this delay is configured, servers + // will not be shut down until the delay is over. During this time, servers + // continue to function normally and allow new connections. At the end, the + // grace period will begin. This can be useful to allow downstream load + // balancers time to move this instance out of the rotation without hiccups. + // + // When shutdown has been scheduled, placeholders {http.shutting_down} (bool) + // and {http.time_until_shutdown} (duration) may be useful for health checks. + ShutdownDelay caddy.Duration `json:"shutdown_delay,omitempty"` + + // Servers is the list of servers, keyed by arbitrary names chosen + // at your discretion for your own convenience; the keys do not + // affect functionality. + Servers map[string]*Server `json:"servers,omitempty"` + + // If set, metrics observations will be enabled. + // This setting is EXPERIMENTAL and subject to change. + Metrics *Metrics `json:"metrics,omitempty"` + + ctx caddy.Context + logger *zap.Logger + tlsApp *caddytls.TLS + + // used temporarily between phases 1 and 2 of auto HTTPS + allCertDomains []string +} + +// CaddyModule returns the Caddy module information. +func (App) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http", + New: func() caddy.Module { return new(App) }, + } +} + +// Provision sets up the app. +func (app *App) Provision(ctx caddy.Context) error { + // store some references + tlsAppIface, err := ctx.App("tls") + if err != nil { + return fmt.Errorf("getting tls app: %v", err) + } + app.tlsApp = tlsAppIface.(*caddytls.TLS) + app.ctx = ctx + app.logger = ctx.Logger() + + eventsAppIface, err := ctx.App("events") + if err != nil { + return fmt.Errorf("getting events app: %v", err) + } + + repl := caddy.NewReplacer() + + // this provisions the matchers for each route, + // and prepares auto HTTP->HTTPS redirects, and + // is required before we provision each server + err = app.automaticHTTPSPhase1(ctx, repl) + if err != nil { + return err + } + + if app.Metrics != nil { + app.Metrics.init = sync.Once{} + app.Metrics.httpMetrics = &httpMetrics{} + } + // prepare each server + oldContext := ctx.Context + for srvName, srv := range app.Servers { + ctx.Context = context.WithValue(oldContext, ServerCtxKey, srv) + srv.name = srvName + srv.tlsApp = app.tlsApp + srv.events = eventsAppIface.(*caddyevents.App) + srv.ctx = ctx + srv.logger = app.logger.Named("log") + srv.errorLogger = app.logger.Named("log.error") + srv.shutdownAtMu = new(sync.RWMutex) + + if srv.Metrics != nil { + srv.logger.Warn("per-server 'metrics' is deprecated; use 'metrics' in the root 'http' app instead") + app.Metrics = cmp.Or[*Metrics](app.Metrics, &Metrics{ + init: sync.Once{}, + httpMetrics: &httpMetrics{}, + }) + app.Metrics.PerHost = app.Metrics.PerHost || srv.Metrics.PerHost + } + + // only enable access logs if configured + if srv.Logs != nil { + srv.accessLogger = app.logger.Named("log.access") + if srv.Logs.Trace { + srv.traceLogger = app.logger.Named("log.trace") + } + } + + // if no protocols configured explicitly, enable all except h2c + if len(srv.Protocols) == 0 { + srv.Protocols = []string{"h1", "h2", "h3"} + } + + srvProtocolsUnique := map[string]struct{}{} + for _, srvProtocol := range srv.Protocols { + srvProtocolsUnique[srvProtocol] = struct{}{} + } + _, h1ok := srvProtocolsUnique["h1"] + _, h2ok := srvProtocolsUnique["h2"] + _, h2cok := srvProtocolsUnique["h2c"] + + // the Go standard library does not let us serve only HTTP/2 using + // http.Server; we would probably need to write our own server + if !h1ok && (h2ok || h2cok) { + return fmt.Errorf("server %s: cannot enable HTTP/2 or H2C without enabling HTTP/1.1; add h1 to protocols or remove h2/h2c", srvName) + } + + if srv.ListenProtocols != nil { + if len(srv.ListenProtocols) != len(srv.Listen) { + return fmt.Errorf("server %s: listener protocols count does not match address count: %d != %d", + srvName, len(srv.ListenProtocols), len(srv.Listen)) + } + + for i, lnProtocols := range srv.ListenProtocols { + if lnProtocols != nil { + // populate empty listen protocols with server protocols + lnProtocolsDefault := false + var lnProtocolsInclude []string + srvProtocolsInclude := maps.Clone(srvProtocolsUnique) + + // keep existing listener protocols unless they are empty + for _, lnProtocol := range lnProtocols { + if lnProtocol == "" { + lnProtocolsDefault = true + } else { + lnProtocolsInclude = append(lnProtocolsInclude, lnProtocol) + delete(srvProtocolsInclude, lnProtocol) + } + } + + // append server protocols to listener protocols if any listener protocols were empty + if lnProtocolsDefault { + for _, srvProtocol := range srv.Protocols { + if _, ok := srvProtocolsInclude[srvProtocol]; ok { + lnProtocolsInclude = append(lnProtocolsInclude, srvProtocol) + } + } + } + + lnProtocolsIncludeUnique := map[string]struct{}{} + for _, lnProtocol := range lnProtocolsInclude { + lnProtocolsIncludeUnique[lnProtocol] = struct{}{} + } + _, h1ok := lnProtocolsIncludeUnique["h1"] + _, h2ok := lnProtocolsIncludeUnique["h2"] + _, h2cok := lnProtocolsIncludeUnique["h2c"] + + // check if any listener protocols contain h2 or h2c without h1 + if !h1ok && (h2ok || h2cok) { + return fmt.Errorf("server %s, listener %d: cannot enable HTTP/2 or H2C without enabling HTTP/1.1; add h1 to protocols or remove h2/h2c", srvName, i) + } + + srv.ListenProtocols[i] = lnProtocolsInclude + } + } + } + + // if not explicitly configured by the user, disallow TLS + // client auth bypass (domain fronting) which could + // otherwise be exploited by sending an unprotected SNI + // value during a TLS handshake, then putting a protected + // domain in the Host header after establishing connection; + // this is a safe default, but we allow users to override + // it for example in the case of running a proxy where + // domain fronting is desired and access is not restricted + // based on hostname + if srv.StrictSNIHost == nil && srv.hasTLSClientAuth() { + app.logger.Warn("enabling strict SNI-Host enforcement because TLS client auth is configured", + zap.String("server_id", srvName)) + trueBool := true + srv.StrictSNIHost = &trueBool + } + + // set up the trusted proxies source + for srv.TrustedProxiesRaw != nil { + val, err := ctx.LoadModule(srv, "TrustedProxiesRaw") + if err != nil { + return fmt.Errorf("loading trusted proxies modules: %v", err) + } + srv.trustedProxies = val.(IPRangeSource) + } + + // set the default client IP header to read from + if srv.ClientIPHeaders == nil { + srv.ClientIPHeaders = []string{"X-Forwarded-For"} + } + + // process each listener address + for i := range srv.Listen { + lnOut, err := repl.ReplaceOrErr(srv.Listen[i], true, true) + if err != nil { + return fmt.Errorf("server %s, listener %d: %v", srvName, i, err) + } + srv.Listen[i] = lnOut + } + + // set up each listener modifier + if srv.ListenerWrappersRaw != nil { + vals, err := ctx.LoadModule(srv, "ListenerWrappersRaw") + if err != nil { + return fmt.Errorf("loading listener wrapper modules: %v", err) + } + var hasTLSPlaceholder bool + for i, val := range vals.([]any) { + if _, ok := val.(*tlsPlaceholderWrapper); ok { + if i == 0 { + // putting the tls placeholder wrapper first is nonsensical because + // that is the default, implicit setting: without it, all wrappers + // will go after the TLS listener anyway + return fmt.Errorf("it is unnecessary to specify the TLS listener wrapper in the first position because that is the default") + } + if hasTLSPlaceholder { + return fmt.Errorf("TLS listener wrapper can only be specified once") + } + hasTLSPlaceholder = true + } + srv.listenerWrappers = append(srv.listenerWrappers, val.(caddy.ListenerWrapper)) + } + // if any wrappers were configured but the TLS placeholder wrapper is + // absent, prepend it so all defined wrappers come after the TLS + // handshake; this simplifies logic when starting the server, since we + // can simply assume the TLS placeholder will always be there + if !hasTLSPlaceholder && len(srv.listenerWrappers) > 0 { + srv.listenerWrappers = append([]caddy.ListenerWrapper{new(tlsPlaceholderWrapper)}, srv.listenerWrappers...) + } + } + // pre-compile the primary handler chain, and be sure to wrap it in our + // route handler so that important security checks are done, etc. + primaryRoute := emptyHandler + if srv.Routes != nil { + err := srv.Routes.ProvisionHandlers(ctx, app.Metrics) + if err != nil { + return fmt.Errorf("server %s: setting up route handlers: %v", srvName, err) + } + primaryRoute = srv.Routes.Compile(emptyHandler) + } + srv.primaryHandlerChain = srv.wrapPrimaryRoute(primaryRoute) + + // pre-compile the error handler chain + if srv.Errors != nil { + err := srv.Errors.Routes.Provision(ctx) + if err != nil { + return fmt.Errorf("server %s: setting up error handling routes: %v", srvName, err) + } + srv.errorHandlerChain = srv.Errors.Routes.Compile(errorEmptyHandler) + } + + // provision the named routes (they get compiled at runtime) + for name, route := range srv.NamedRoutes { + err := route.Provision(ctx, app.Metrics) + if err != nil { + return fmt.Errorf("server %s: setting up named route '%s' handlers: %v", name, srvName, err) + } + } + + // prepare the TLS connection policies + err = srv.TLSConnPolicies.Provision(ctx) + if err != nil { + return fmt.Errorf("server %s: setting up TLS connection policies: %v", srvName, err) + } + + // if there is no idle timeout, set a sane default; users have complained + // before that aggressive CDNs leave connections open until the server + // closes them, so if we don't close them it leads to resource exhaustion + if srv.IdleTimeout == 0 { + srv.IdleTimeout = defaultIdleTimeout + } + if srv.ReadHeaderTimeout == 0 { + srv.ReadHeaderTimeout = defaultReadHeaderTimeout // see #6663 + } + } + ctx.Context = oldContext + return nil +} + +// Validate ensures the app's configuration is valid. +func (app *App) Validate() error { + lnAddrs := make(map[string]string) + + for srvName, srv := range app.Servers { + // each server must use distinct listener addresses + for _, addr := range srv.Listen { + listenAddr, err := caddy.ParseNetworkAddress(addr) + if err != nil { + return fmt.Errorf("invalid listener address '%s': %v", addr, err) + } + // check that every address in the port range is unique to this server; + // we do not use <= here because PortRangeSize() adds 1 to EndPort for us + for i := uint(0); i < listenAddr.PortRangeSize(); i++ { + addr := caddy.JoinNetworkAddress(listenAddr.Network, listenAddr.Host, strconv.FormatUint(uint64(listenAddr.StartPort+i), 10)) + if sn, ok := lnAddrs[addr]; ok { + return fmt.Errorf("server %s: listener address repeated: %s (already claimed by server '%s')", srvName, addr, sn) + } + lnAddrs[addr] = srvName + } + } + + // logger names must not have ports + if srv.Logs != nil { + for host := range srv.Logs.LoggerNames { + if _, _, err := net.SplitHostPort(host); err == nil { + return fmt.Errorf("server %s: logger name must not have a port: %s", srvName, host) + } + } + } + } + return nil +} + +// Start runs the app. It finishes automatic HTTPS if enabled, +// including management of certificates. +func (app *App) Start() error { + // get a logger compatible with http.Server + serverLogger, err := zap.NewStdLogAt(app.logger.Named("stdlib"), zap.DebugLevel) + if err != nil { + return fmt.Errorf("failed to set up server logger: %v", err) + } + + for srvName, srv := range app.Servers { + srv.server = &http.Server{ + ReadTimeout: time.Duration(srv.ReadTimeout), + ReadHeaderTimeout: time.Duration(srv.ReadHeaderTimeout), + WriteTimeout: time.Duration(srv.WriteTimeout), + IdleTimeout: time.Duration(srv.IdleTimeout), + MaxHeaderBytes: srv.MaxHeaderBytes, + Handler: srv, + ErrorLog: serverLogger, + ConnContext: func(ctx context.Context, c net.Conn) context.Context { + return context.WithValue(ctx, ConnCtxKey, c) + }, + } + h2server := new(http2.Server) + + // disable HTTP/2, which we enabled by default during provisioning + if !srv.protocol("h2") { + srv.server.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler)) + for _, cp := range srv.TLSConnPolicies { + // the TLSConfig was already provisioned, so... manually remove it + for i, np := range cp.TLSConfig.NextProtos { + if np == "h2" { + cp.TLSConfig.NextProtos = append(cp.TLSConfig.NextProtos[:i], cp.TLSConfig.NextProtos[i+1:]...) + break + } + } + // remove it from the parent connection policy too, just to keep things tidy + for i, alpn := range cp.ALPN { + if alpn == "h2" { + cp.ALPN = append(cp.ALPN[:i], cp.ALPN[i+1:]...) + break + } + } + } + } else { + //nolint:errcheck + http2.ConfigureServer(srv.server, h2server) + } + + // this TLS config is used by the std lib to choose the actual TLS config for connections + // by looking through the connection policies to find the first one that matches + tlsCfg := srv.TLSConnPolicies.TLSConfig(app.ctx) + srv.configureServer(srv.server) + + // enable H2C if configured + if srv.protocol("h2c") { + srv.server.Handler = h2c.NewHandler(srv, h2server) + } + + for lnIndex, lnAddr := range srv.Listen { + listenAddr, err := caddy.ParseNetworkAddress(lnAddr) + if err != nil { + return fmt.Errorf("%s: parsing listen address '%s': %v", srvName, lnAddr, err) + } + + srv.addresses = append(srv.addresses, listenAddr) + + protocols := srv.Protocols + if srv.ListenProtocols != nil && srv.ListenProtocols[lnIndex] != nil { + protocols = srv.ListenProtocols[lnIndex] + } + + protocolsUnique := map[string]struct{}{} + for _, protocol := range protocols { + protocolsUnique[protocol] = struct{}{} + } + _, h1ok := protocolsUnique["h1"] + _, h2ok := protocolsUnique["h2"] + _, h2cok := protocolsUnique["h2c"] + _, h3ok := protocolsUnique["h3"] + + for portOffset := uint(0); portOffset < listenAddr.PortRangeSize(); portOffset++ { + hostport := listenAddr.JoinHostPort(portOffset) + + // enable TLS if there is a policy and if this is not the HTTP port + useTLS := len(srv.TLSConnPolicies) > 0 && int(listenAddr.StartPort+portOffset) != app.httpPort() + + if h1ok || h2ok && useTLS || h2cok { + // create the listener for this socket + lnAny, err := listenAddr.Listen(app.ctx, portOffset, net.ListenConfig{KeepAlive: time.Duration(srv.KeepAliveInterval)}) + if err != nil { + return fmt.Errorf("listening on %s: %v", listenAddr.At(portOffset), err) + } + ln, ok := lnAny.(net.Listener) + if !ok { + return fmt.Errorf("network '%s' cannot handle HTTP/1 or HTTP/2 connections", listenAddr.Network) + } + + // wrap listener before TLS (up to the TLS placeholder wrapper) + var lnWrapperIdx int + for i, lnWrapper := range srv.listenerWrappers { + if _, ok := lnWrapper.(*tlsPlaceholderWrapper); ok { + lnWrapperIdx = i + 1 // mark the next wrapper's spot + break + } + ln = lnWrapper.WrapListener(ln) + } + + if useTLS { + // create TLS listener - this enables and terminates TLS + ln = tls.NewListener(ln, tlsCfg) + } + + // finish wrapping listener where we left off before TLS + for i := lnWrapperIdx; i < len(srv.listenerWrappers); i++ { + ln = srv.listenerWrappers[i].WrapListener(ln) + } + + // handle http2 if use tls listener wrapper + if h2ok { + http2lnWrapper := &http2Listener{ + Listener: ln, + server: srv.server, + h2server: h2server, + } + srv.h2listeners = append(srv.h2listeners, http2lnWrapper) + ln = http2lnWrapper + } + + // if binding to port 0, the OS chooses a port for us; + // but the user won't know the port unless we print it + if !listenAddr.IsUnixNetwork() && !listenAddr.IsFdNetwork() && listenAddr.StartPort == 0 && listenAddr.EndPort == 0 { + app.logger.Info("port 0 listener", + zap.String("input_address", lnAddr), + zap.String("actual_address", ln.Addr().String())) + } + + app.logger.Debug("starting server loop", + zap.String("address", ln.Addr().String()), + zap.Bool("tls", useTLS), + zap.Bool("http3", srv.h3server != nil)) + + srv.listeners = append(srv.listeners, ln) + + // enable HTTP/1 if configured + if h1ok { + //nolint:errcheck + go srv.server.Serve(ln) + } + } + + if h2ok && !useTLS { + // Can only serve h2 with TLS enabled + app.logger.Warn("HTTP/2 skipped because it requires TLS", + zap.String("network", listenAddr.Network), + zap.String("addr", hostport)) + } + + if h3ok { + // Can't serve HTTP/3 on the same socket as HTTP/1 and 2 because it uses + // a different transport mechanism... which is fine, but the OS doesn't + // differentiate between a SOCK_STREAM file and a SOCK_DGRAM file; they + // are still one file on the system. So even though "unixpacket" and + // "unixgram" are different network types just as "tcp" and "udp" are, + // the OS will not let us use the same file as both STREAM and DGRAM. + if listenAddr.IsUnixNetwork() { + app.logger.Warn("HTTP/3 disabled because Unix can't multiplex STREAM and DGRAM on same socket", + zap.String("file", hostport)) + continue + } + + if useTLS { + // enable HTTP/3 if configured + app.logger.Info("enabling HTTP/3 listener", zap.String("addr", hostport)) + if err := srv.serveHTTP3(listenAddr.At(portOffset), tlsCfg); err != nil { + return err + } + } else { + // Can only serve h3 with TLS enabled + app.logger.Warn("HTTP/3 skipped because it requires TLS", + zap.String("network", listenAddr.Network), + zap.String("addr", hostport)) + } + } + } + } + + srv.logger.Info("server running", + zap.String("name", srvName), + zap.Strings("protocols", srv.Protocols)) + } + + // finish automatic HTTPS by finally beginning + // certificate management + err = app.automaticHTTPSPhase2() + if err != nil { + return fmt.Errorf("finalizing automatic HTTPS: %v", err) + } + + return nil +} + +// Stop gracefully shuts down the HTTP server. +func (app *App) Stop() error { + ctx := context.Background() + + // see if any listeners in our config will be closing or if they are continuing + // through a reload; because if any are closing, we will enforce shutdown delay + var delay bool + scheduledTime := time.Now().Add(time.Duration(app.ShutdownDelay)) + if app.ShutdownDelay > 0 { + for _, server := range app.Servers { + for _, na := range server.addresses { + for _, addr := range na.Expand() { + if caddy.ListenerUsage(addr.Network, addr.JoinHostPort(0)) < 2 { + app.logger.Debug("listener closing and shutdown delay is configured", zap.String("address", addr.String())) + server.shutdownAtMu.Lock() + server.shutdownAt = scheduledTime + server.shutdownAtMu.Unlock() + delay = true + } else { + app.logger.Debug("shutdown delay configured but listener will remain open", zap.String("address", addr.String())) + } + } + } + } + } + + // honor scheduled/delayed shutdown time + if delay { + app.logger.Info("shutdown scheduled", + zap.Duration("delay_duration", time.Duration(app.ShutdownDelay)), + zap.Time("time", scheduledTime)) + time.Sleep(time.Duration(app.ShutdownDelay)) + } + + // enforce grace period if configured + if app.GracePeriod > 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, time.Duration(app.GracePeriod)) + defer cancel() + app.logger.Info("servers shutting down; grace period initiated", zap.Duration("duration", time.Duration(app.GracePeriod))) + } else { + app.logger.Info("servers shutting down with eternal grace period") + } + + // goroutines aren't guaranteed to be scheduled right away, + // so we'll use one WaitGroup to wait for all the goroutines + // to start their server shutdowns, and another to wait for + // them to finish; we'll always block for them to start so + // that when we return the caller can be confident* that the + // old servers are no longer accepting new connections + // (* the scheduler might still pause them right before + // calling Shutdown(), but it's unlikely) + var startedShutdown, finishedShutdown sync.WaitGroup + + // these will run in goroutines + stopServer := func(server *Server) { + defer finishedShutdown.Done() + startedShutdown.Done() + + if err := server.server.Shutdown(ctx); err != nil { + app.logger.Error("server shutdown", + zap.Error(err), + zap.Strings("addresses", server.Listen)) + } + } + stopH3Server := func(server *Server) { + defer finishedShutdown.Done() + startedShutdown.Done() + + if server.h3server == nil { + return + } + + if err := server.h3server.Shutdown(ctx); err != nil { + app.logger.Error("HTTP/3 server shutdown", + zap.Error(err), + zap.Strings("addresses", server.Listen)) + } + } + stopH2Listener := func(server *Server) { + defer finishedShutdown.Done() + startedShutdown.Done() + + for i, s := range server.h2listeners { + if err := s.Shutdown(ctx); err != nil { + app.logger.Error("http2 listener shutdown", + zap.Error(err), + zap.Int("index", i)) + } + } + } + + for _, server := range app.Servers { + startedShutdown.Add(3) + finishedShutdown.Add(3) + go stopServer(server) + go stopH3Server(server) + go stopH2Listener(server) + } + + // block until all the goroutines have been run by the scheduler; + // this means that they have likely called Shutdown() by now + startedShutdown.Wait() + + // if the process is exiting, we need to block here and wait + // for the grace periods to complete, otherwise the process will + // terminate before the servers are finished shutting down; but + // we don't really need to wait for the grace period to finish + // if the process isn't exiting (but note that frequent config + // reloads with long grace periods for a sustained length of time + // may deplete resources) + if caddy.Exiting() { + finishedShutdown.Wait() + } + + // run stop callbacks now that the server shutdowns are complete + for name, s := range app.Servers { + for _, stopHook := range s.onStopFuncs { + if err := stopHook(ctx); err != nil { + app.logger.Error("server stop hook", zap.String("server", name), zap.Error(err)) + } + } + } + + return nil +} + +func (app *App) httpPort() int { + if app.HTTPPort == 0 { + return DefaultHTTPPort + } + return app.HTTPPort +} + +func (app *App) httpsPort() int { + if app.HTTPSPort == 0 { + return DefaultHTTPSPort + } + return app.HTTPSPort +} + +const ( + // defaultIdleTimeout is the default HTTP server timeout + // for closing idle connections; useful to avoid resource + // exhaustion behind hungry CDNs, for example (we've had + // several complaints without this). + defaultIdleTimeout = caddy.Duration(5 * time.Minute) + + // defaultReadHeaderTimeout is the default timeout for + // reading HTTP headers from clients. Headers are generally + // small, often less than 1 KB, so it shouldn't take a + // long time even on legitimately slow connections or + // busy servers to read it. + defaultReadHeaderTimeout = caddy.Duration(time.Minute) +) + +// Interface guards +var ( + _ caddy.App = (*App)(nil) + _ caddy.Provisioner = (*App)(nil) + _ caddy.Validator = (*App)(nil) +) diff --git a/modules/caddyhttp/autohttps.go b/modules/caddyhttp/autohttps.go new file mode 100644 index 00000000000..4449e1f4db6 --- /dev/null +++ b/modules/caddyhttp/autohttps.go @@ -0,0 +1,782 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "fmt" + "net/http" + "slices" + "strconv" + "strings" + + "github.com/caddyserver/certmagic" + "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddytls" +) + +// AutoHTTPSConfig is used to disable automatic HTTPS +// or certain aspects of it for a specific server. +// HTTPS is enabled automatically and by default when +// qualifying hostnames are available from the config. +type AutoHTTPSConfig struct { + // If true, automatic HTTPS will be entirely disabled, + // including certificate management and redirects. + Disabled bool `json:"disable,omitempty"` + + // If true, only automatic HTTP->HTTPS redirects will + // be disabled, but other auto-HTTPS features will + // remain enabled. + DisableRedir bool `json:"disable_redirects,omitempty"` + + // If true, automatic certificate management will be + // disabled, but other auto-HTTPS features will + // remain enabled. + DisableCerts bool `json:"disable_certificates,omitempty"` + + // Hosts/domain names listed here will not be included + // in automatic HTTPS (they will not have certificates + // loaded nor redirects applied). + Skip []string `json:"skip,omitempty"` + + // Hosts/domain names listed here will still be enabled + // for automatic HTTPS (unless in the Skip list), except + // that certificates will not be provisioned and managed + // for these names. + SkipCerts []string `json:"skip_certificates,omitempty"` + + // By default, automatic HTTPS will obtain and renew + // certificates for qualifying hostnames. However, if + // a certificate with a matching SAN is already loaded + // into the cache, certificate management will not be + // enabled. To force automated certificate management + // regardless of loaded certificates, set this to true. + IgnoreLoadedCerts bool `json:"ignore_loaded_certificates,omitempty"` + + // If true, automatic HTTPS will prefer wildcard names + // and ignore non-wildcard names if both are available. + // This allows for writing a config with top-level host + // matchers without having those names produce certificates. + PreferWildcard bool `json:"prefer_wildcard,omitempty"` +} + +// automaticHTTPSPhase1 provisions all route matchers, determines +// which domain names found in the routes qualify for automatic +// HTTPS, and sets up HTTP->HTTPS redirects. This phase must occur +// at the beginning of provisioning, because it may add routes and +// even servers to the app, which still need to be set up with the +// rest of them during provisioning. +func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) error { + logger := app.logger.Named("auto_https") + + // this map acts as a set to store the domain names + // for which we will manage certificates automatically + uniqueDomainsForCerts := make(map[string]struct{}) + + // this maps domain names for automatic HTTP->HTTPS + // redirects to their destination server addresses + // (there might be more than 1 if bind is used; see + // https://github.com/caddyserver/caddy/issues/3443) + redirDomains := make(map[string][]caddy.NetworkAddress) + + // the log configuration for an HTTPS enabled server + var logCfg *ServerLogConfig + + for srvName, srv := range app.Servers { + // as a prerequisite, provision route matchers; this is + // required for all routes on all servers, and must be + // done before we attempt to do phase 1 of auto HTTPS, + // since we have to access the decoded host matchers the + // handlers will be provisioned later + if srv.Routes != nil { + err := srv.Routes.ProvisionMatchers(ctx) + if err != nil { + return fmt.Errorf("server %s: setting up route matchers: %v", srvName, err) + } + } + + // prepare for automatic HTTPS + if srv.AutoHTTPS == nil { + srv.AutoHTTPS = new(AutoHTTPSConfig) + } + if srv.AutoHTTPS.Disabled { + logger.Info("automatic HTTPS is completely disabled for server", zap.String("server_name", srvName)) + continue + } + + // skip if all listeners use the HTTP port + if !srv.listenersUseAnyPortOtherThan(app.httpPort()) { + logger.Warn("server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server", + zap.String("server_name", srvName), + zap.Int("http_port", app.httpPort()), + ) + srv.AutoHTTPS.Disabled = true + continue + } + + // if all listeners are on the HTTPS port, make sure + // there is at least one TLS connection policy; it + // should be obvious that they want to use TLS without + // needing to specify one empty policy to enable it + if srv.TLSConnPolicies == nil && + !srv.listenersUseAnyPortOtherThan(app.httpsPort()) { + logger.Info("server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS", + zap.String("server_name", srvName), + zap.Int("https_port", app.httpsPort()), + ) + srv.TLSConnPolicies = caddytls.ConnectionPolicies{new(caddytls.ConnectionPolicy)} + } + + // find all qualifying domain names (deduplicated) in this server + // (this is where we need the provisioned, decoded request matchers) + serverDomainSet := make(map[string]struct{}) + for routeIdx, route := range srv.Routes { + for matcherSetIdx, matcherSet := range route.MatcherSets { + for matcherIdx, m := range matcherSet { + if hm, ok := m.(*MatchHost); ok { + for hostMatcherIdx, d := range *hm { + var err error + d, err = repl.ReplaceOrErr(d, true, false) + if err != nil { + return fmt.Errorf("%s: route %d, matcher set %d, matcher %d, host matcher %d: %v", + srvName, routeIdx, matcherSetIdx, matcherIdx, hostMatcherIdx, err) + } + if !slices.Contains(srv.AutoHTTPS.Skip, d) { + serverDomainSet[d] = struct{}{} + } + } + } + } + } + } + + if srv.AutoHTTPS.PreferWildcard { + wildcards := make(map[string]struct{}) + for d := range serverDomainSet { + if strings.HasPrefix(d, "*.") { + wildcards[d[2:]] = struct{}{} + } + } + for d := range serverDomainSet { + if strings.HasPrefix(d, "*.") { + continue + } + base := d + if idx := strings.Index(d, "."); idx != -1 { + base = d[idx+1:] + } + if _, ok := wildcards[base]; ok { + delete(serverDomainSet, d) + } + } + } + + // nothing more to do here if there are no domains that qualify for + // automatic HTTPS and there are no explicit TLS connection policies: + // if there is at least one domain but no TLS conn policy (F&&T), we'll + // add one below; if there are no domains but at least one TLS conn + // policy (meaning TLS is enabled) (T&&F), it could be a catch-all with + // on-demand TLS -- and in that case we would still need HTTP->HTTPS + // redirects, which we set up below; hence these two conditions + if len(serverDomainSet) == 0 && len(srv.TLSConnPolicies) == 0 { + continue + } + + // clone the logger so we can apply it to the HTTP server + // (not sure if necessary to clone it; but probably safer) + // (we choose one log cfg arbitrarily; not sure which is best) + if srv.Logs != nil { + logCfg = srv.Logs.clone() + } + + // for all the hostnames we found, filter them so we have + // a deduplicated list of names for which to obtain certs + // (only if cert management not disabled for this server) + if srv.AutoHTTPS.DisableCerts { + logger.Warn("skipping automated certificate management for server because it is disabled", zap.String("server_name", srvName)) + } else { + for d := range serverDomainSet { + if certmagic.SubjectQualifiesForCert(d) && + !slices.Contains(srv.AutoHTTPS.SkipCerts, d) { + // if a certificate for this name is already loaded, + // don't obtain another one for it, unless we are + // supposed to ignore loaded certificates + if !srv.AutoHTTPS.IgnoreLoadedCerts && app.tlsApp.HasCertificateForSubject(d) { + logger.Info("skipping automatic certificate management because one or more matching certificates are already loaded", + zap.String("domain", d), + zap.String("server_name", srvName), + ) + continue + } + + // most clients don't accept wildcards like *.tld... we + // can handle that, but as a courtesy, warn the user + if strings.Contains(d, "*") && + strings.Count(strings.Trim(d, "."), ".") == 1 { + logger.Warn("most clients do not trust second-level wildcard certificates (*.tld)", + zap.String("domain", d)) + } + + uniqueDomainsForCerts[d] = struct{}{} + } + } + } + + // tell the server to use TLS if it is not already doing so + if srv.TLSConnPolicies == nil { + srv.TLSConnPolicies = caddytls.ConnectionPolicies{new(caddytls.ConnectionPolicy)} + } + + // nothing left to do if auto redirects are disabled + if srv.AutoHTTPS.DisableRedir { + logger.Info("automatic HTTP->HTTPS redirects are disabled", zap.String("server_name", srvName)) + continue + } + + logger.Info("enabling automatic HTTP->HTTPS redirects", zap.String("server_name", srvName)) + + // create HTTP->HTTPS redirects + for _, listenAddr := range srv.Listen { + // figure out the address we will redirect to... + addr, err := caddy.ParseNetworkAddress(listenAddr) + if err != nil { + msg := "%s: invalid listener address: %v" + if strings.Count(listenAddr, ":") > 1 { + msg = msg + ", there are too many colons, so the port is ambiguous. Did you mean to wrap the IPv6 address with [] brackets?" + } + return fmt.Errorf(msg, srvName, listenAddr) + } + + // this address might not have a hostname, i.e. might be a + // catch-all address for a particular port; we need to keep + // track if it is, so we can set up redirects for it anyway + // (e.g. the user might have enabled on-demand TLS); we use + // an empty string to indicate a catch-all, which we have to + // treat special later + if len(serverDomainSet) == 0 { + redirDomains[""] = append(redirDomains[""], addr) + continue + } + + // ...and associate it with each domain in this server + for d := range serverDomainSet { + // if this domain is used on more than one HTTPS-enabled + // port, we'll have to choose one, so prefer the HTTPS port + if _, ok := redirDomains[d]; !ok || + addr.StartPort == uint(app.httpsPort()) { + redirDomains[d] = append(redirDomains[d], addr) + } + } + } + } + + // we now have a list of all the unique names for which we need certs; + // turn the set into a slice so that phase 2 can use it + app.allCertDomains = make([]string, 0, len(uniqueDomainsForCerts)) + var internal, tailscale []string +uniqueDomainsLoop: + for d := range uniqueDomainsForCerts { + if !isTailscaleDomain(d) { + // whether or not there is already an automation policy for this + // name, we should add it to the list to manage a cert for it, + // unless it's a Tailscale domain, because we don't manage those + app.allCertDomains = append(app.allCertDomains, d) + } + + // some names we've found might already have automation policies + // explicitly specified for them; we should exclude those from + // our hidden/implicit policy, since applying a name to more than + // one automation policy would be confusing and an error + if app.tlsApp.Automation != nil { + for _, ap := range app.tlsApp.Automation.Policies { + for _, apHost := range ap.Subjects() { + if apHost == d { + // if the automation policy has all internal subjects but no issuers, + // it will default to CertMagic's issuers which are public CAs; use + // our internal issuer instead + if len(ap.Issuers) == 0 && ap.AllInternalSubjects() { + iss := new(caddytls.InternalIssuer) + if err := iss.Provision(ctx); err != nil { + return err + } + ap.Issuers = append(ap.Issuers, iss) + } + continue uniqueDomainsLoop + } + } + } + } + + // if no automation policy exists for the name yet, we will associate it with an implicit one; + // we handle tailscale domains specially, and we also separate out identifiers that need the + // internal issuer (self-signed certs); certmagic does not consider public IP addresses to be + // disqualified for public certs, because there are public CAs that will issue certs for IPs. + // However, with auto-HTTPS, many times there is no issuer explicitly defined, and the default + // issuers do not (currently, as of 2024) issue IP certificates; so assign all IP subjects to + // the internal issuer when there are no explicit automation policies + shouldUseInternal := func(ident string) bool { + usingDefaultIssuersAndIsIP := certmagic.SubjectIsIP(ident) && + (app.tlsApp == nil || app.tlsApp.Automation == nil || len(app.tlsApp.Automation.Policies) == 0) + return !certmagic.SubjectQualifiesForPublicCert(d) || usingDefaultIssuersAndIsIP + } + if isTailscaleDomain(d) { + tailscale = append(tailscale, d) + } else if shouldUseInternal(d) { + internal = append(internal, d) + } + } + + // ensure there is an automation policy to handle these certs + err := app.createAutomationPolicies(ctx, internal, tailscale) + if err != nil { + return err + } + + // we need to reduce the mapping, i.e. group domains by address + // since new routes are appended to servers by their address + domainsByAddr := make(map[string][]string) + for domain, addrs := range redirDomains { + for _, addr := range addrs { + addrStr := addr.String() + domainsByAddr[addrStr] = append(domainsByAddr[addrStr], domain) + } + } + + // these keep track of the redirect server address(es) + // and the routes for those servers which actually + // respond with the redirects + redirServerAddrs := make(map[string]struct{}) + redirServers := make(map[string][]Route) + var redirRoutes RouteList + + for addrStr, domains := range domainsByAddr { + // build the matcher set for this redirect route; (note that we happen + // to bypass Provision and Validate steps for these matcher modules) + matcherSet := MatcherSet{MatchProtocol("http")} + // match on known domain names, unless it's our special case of a + // catch-all which is an empty string (common among catch-all sites + // that enable on-demand TLS for yet-unknown domain names) + if !(len(domains) == 1 && domains[0] == "") { + matcherSet = append(matcherSet, MatchHost(domains)) + } + + addr, err := caddy.ParseNetworkAddress(addrStr) + if err != nil { + return err + } + redirRoute := app.makeRedirRoute(addr.StartPort, matcherSet) + + // use the network/host information from the address, + // but change the port to the HTTP port then rebuild + redirAddr := addr + redirAddr.StartPort = uint(app.httpPort()) + redirAddr.EndPort = redirAddr.StartPort + redirAddrStr := redirAddr.String() + + redirServers[redirAddrStr] = append(redirServers[redirAddrStr], redirRoute) + } + + // on-demand TLS means that hostnames may be used which are not + // explicitly defined in the config, and we still need to redirect + // those; so we can append a single catch-all route (notice there + // is no Host matcher) after the other redirect routes which will + // allow us to handle unexpected/new hostnames... however, it's + // not entirely clear what the redirect destination should be, + // so I'm going to just hard-code the app's HTTPS port and call + // it good for now... + // TODO: This implies that all plaintext requests will be blindly + // redirected to their HTTPS equivalent, even if this server + // doesn't handle that hostname at all; I don't think this is a + // bad thing, and it also obscures the actual hostnames that this + // server is configured to match on, which may be desirable, but + // it's not something that should be relied on. We can change this + // if we want to. + appendCatchAll := func(routes []Route) []Route { + return append(routes, app.makeRedirRoute(uint(app.httpsPort()), MatcherSet{MatchProtocol("http")})) + } + +redirServersLoop: + for redirServerAddr, routes := range redirServers { + // for each redirect listener, see if there's already a + // server configured to listen on that exact address; if so, + // insert the redirect route to the end of its route list + // after any other routes with host matchers; otherwise, + // we'll create a new server for all the listener addresses + // that are unused and serve the remaining redirects from it + for _, srv := range app.Servers { + // only look at servers which listen on an address which + // we want to add redirects to + if !srv.hasListenerAddress(redirServerAddr) { + continue + } + + // find the index of the route after the last route with a host + // matcher, then insert the redirects there, but before any + // user-defined catch-all routes + // see https://github.com/caddyserver/caddy/issues/3212 + insertIndex := srv.findLastRouteWithHostMatcher() + + // add the redirects at the insert index, except for when + // we have a catch-all for HTTPS, in which case the user's + // defined catch-all should take precedence. See #4829 + if len(uniqueDomainsForCerts) != 0 { + srv.Routes = append(srv.Routes[:insertIndex], append(routes, srv.Routes[insertIndex:]...)...) + } + + // append our catch-all route in case the user didn't define their own + srv.Routes = appendCatchAll(srv.Routes) + + continue redirServersLoop + } + + // no server with this listener address exists; + // save this address and route for custom server + redirServerAddrs[redirServerAddr] = struct{}{} + redirRoutes = append(redirRoutes, routes...) + } + + // if there are routes remaining which do not belong + // in any existing server, make our own to serve the + // rest of the redirects + if len(redirServerAddrs) > 0 { + redirServerAddrsList := make([]string, 0, len(redirServerAddrs)) + for a := range redirServerAddrs { + redirServerAddrsList = append(redirServerAddrsList, a) + } + app.Servers["remaining_auto_https_redirects"] = &Server{ + Listen: redirServerAddrsList, + Routes: appendCatchAll(redirRoutes), + Logs: logCfg, + } + } + + logger.Debug("adjusted config", + zap.Reflect("tls", app.tlsApp), + zap.Reflect("http", app)) + + return nil +} + +func (app *App) makeRedirRoute(redirToPort uint, matcherSet MatcherSet) Route { + redirTo := "https://{http.request.host}" + + // since this is an external redirect, we should only append an explicit + // port if we know it is not the officially standardized HTTPS port, and, + // notably, also not the port that Caddy thinks is the HTTPS port (the + // configurable HTTPSPort parameter) - we can't change the standard HTTPS + // port externally, so that config parameter is for internal use only; + // we also do not append the port if it happens to be the HTTP port as + // well, obviously (for example, user defines the HTTP port explicitly + // in the list of listen addresses for a server) + if redirToPort != uint(app.httpPort()) && + redirToPort != uint(app.httpsPort()) && + redirToPort != DefaultHTTPPort && + redirToPort != DefaultHTTPSPort { + redirTo += ":" + strconv.Itoa(int(redirToPort)) + } + + redirTo += "{http.request.uri}" + return Route{ + MatcherSets: []MatcherSet{matcherSet}, + Handlers: []MiddlewareHandler{ + StaticResponse{ + StatusCode: WeakString(strconv.Itoa(http.StatusPermanentRedirect)), + Headers: http.Header{ + "Location": []string{redirTo}, + }, + Close: true, + }, + }, + } +} + +// createAutomationPolicies ensures that automated certificates for this +// app are managed properly. This adds up to two automation policies: +// one for the public names, and one for the internal names. If a catch-all +// automation policy exists, it will be shallow-copied and used as the +// base for the new ones (this is important for preserving behavior the +// user intends to be "defaults"). +func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames, tailscaleNames []string) error { + // before we begin, loop through the existing automation policies + // and, for any ACMEIssuers we find, make sure they're filled in + // with default values that might be specified in our HTTP app; also + // look for a base (or "catch-all" / default) automation policy, + // which we're going to essentially require, to make sure it has + // those defaults, too + var basePolicy *caddytls.AutomationPolicy + var foundBasePolicy bool + if app.tlsApp.Automation == nil { + // we will expect this to not be nil from now on + app.tlsApp.Automation = new(caddytls.AutomationConfig) + } + for _, ap := range app.tlsApp.Automation.Policies { + // on-demand policies can have the tailscale manager added implicitly + // if there's no explicit manager configured -- for convenience + if ap.OnDemand && len(ap.Managers) == 0 { + var ts caddytls.Tailscale + if err := ts.Provision(ctx); err != nil { + return err + } + ap.Managers = []certmagic.Manager{ts} + + // must reprovision the automation policy so that the underlying + // CertMagic config knows about the updated Managers + if err := ap.Provision(app.tlsApp); err != nil { + return fmt.Errorf("re-provisioning automation policy: %v", err) + } + } + + // set up default issuer -- honestly, this is only + // really necessary because the HTTP app is opinionated + // and has settings which could be inferred as new + // defaults for the ACMEIssuer in the TLS app (such as + // what the HTTP and HTTPS ports are) + if ap.Issuers == nil { + var err error + ap.Issuers, err = caddytls.DefaultIssuersProvisioned(ctx) + if err != nil { + return err + } + } + for _, iss := range ap.Issuers { + if acmeIssuer, ok := iss.(acmeCapable); ok { + err := app.fillInACMEIssuer(acmeIssuer.GetACMEIssuer()) + if err != nil { + return err + } + } + } + + // while we're here, is this the catch-all/base policy? + if !foundBasePolicy && len(ap.SubjectsRaw) == 0 { + basePolicy = ap + foundBasePolicy = true + } + } + + if basePolicy == nil { + // no base policy found; we will make one + basePolicy = new(caddytls.AutomationPolicy) + } + + // if the basePolicy has an existing ACMEIssuer (particularly to + // include any type that embeds/wraps an ACMEIssuer), let's use it + // (I guess we just use the first one?), otherwise we'll make one + var baseACMEIssuer *caddytls.ACMEIssuer + for _, iss := range basePolicy.Issuers { + if acmeWrapper, ok := iss.(acmeCapable); ok { + baseACMEIssuer = acmeWrapper.GetACMEIssuer() + break + } + } + if baseACMEIssuer == nil { + // note that this happens if basePolicy.Issuers is empty + // OR if it is not empty but does not have not an ACMEIssuer + baseACMEIssuer = new(caddytls.ACMEIssuer) + } + + // if there was a base policy to begin with, we already + // filled in its issuer's defaults; if there wasn't, we + // still need to do that + if !foundBasePolicy { + err := app.fillInACMEIssuer(baseACMEIssuer) + if err != nil { + return err + } + } + + // never overwrite any other issuer that might already be configured + if basePolicy.Issuers == nil { + var err error + basePolicy.Issuers, err = caddytls.DefaultIssuersProvisioned(ctx) + if err != nil { + return err + } + for _, iss := range basePolicy.Issuers { + if acmeIssuer, ok := iss.(acmeCapable); ok { + err := app.fillInACMEIssuer(acmeIssuer.GetACMEIssuer()) + if err != nil { + return err + } + } + } + } + + if !foundBasePolicy { + // there was no base policy to begin with, so add + // our base/catch-all policy - this will serve the + // public-looking names as well as any other names + // that don't match any other policy + err := app.tlsApp.AddAutomationPolicy(basePolicy) + if err != nil { + return err + } + } else { + // a base policy already existed; we might have + // changed it, so re-provision it + err := basePolicy.Provision(app.tlsApp) + if err != nil { + return err + } + } + + // public names will be taken care of by the base (catch-all) + // policy, which we've ensured exists if not already specified; + // internal names, however, need to be handled by an internal + // issuer, which we need to make a new policy for, scoped to + // just those names (yes, this logic is a bit asymmetric, but + // it works, because our assumed/natural default issuer is an + // ACME issuer) + if len(internalNames) > 0 { + internalIssuer := new(caddytls.InternalIssuer) + + // shallow-copy the base policy; we want to inherit + // from it, not replace it... this takes two lines to + // overrule compiler optimizations + policyCopy := *basePolicy + newPolicy := &policyCopy + + // very important to provision the issuer, since we + // are bypassing the JSON-unmarshaling step + if err := internalIssuer.Provision(ctx); err != nil { + return err + } + + // this policy should apply only to the given names + // and should use our issuer -- yes, this overrides + // any issuer that may have been set in the base + // policy, but we do this because these names do not + // already have a policy associated with them, which + // is easy to do; consider the case of a Caddyfile + // that has only "localhost" as a name, but sets the + // default/global ACME CA to the Let's Encrypt staging + // endpoint... they probably don't intend to change the + // fundamental set of names that setting applies to, + // rather they just want to change the CA for the set + // of names that would normally use the production API; + // anyway, that gets into the weeds a bit... + newPolicy.SubjectsRaw = internalNames + newPolicy.Issuers = []certmagic.Issuer{internalIssuer} + err := app.tlsApp.AddAutomationPolicy(newPolicy) + if err != nil { + return err + } + } + + // tailscale names go in their own automation policies because + // they require on-demand TLS to be enabled, which we obviously + // can't enable for everything + if len(tailscaleNames) > 0 { + policyCopy := *basePolicy + newPolicy := &policyCopy + + var ts caddytls.Tailscale + if err := ts.Provision(ctx); err != nil { + return err + } + + newPolicy.SubjectsRaw = tailscaleNames + newPolicy.Issuers = nil + newPolicy.Managers = append(newPolicy.Managers, ts) + err := app.tlsApp.AddAutomationPolicy(newPolicy) + if err != nil { + return err + } + } + + // we just changed a lot of stuff, so double-check that it's all good + err := app.tlsApp.Validate() + if err != nil { + return err + } + + return nil +} + +// fillInACMEIssuer fills in default values into acmeIssuer that +// are defined in app; these values at time of writing are just +// app.HTTPPort and app.HTTPSPort, which are used by ACMEIssuer. +// Sure, we could just use the global/CertMagic defaults, but if +// a user has configured those ports in the HTTP app, it makes +// sense to use them in the TLS app too, even if they forgot (or +// were too lazy, like me) to set it in each automation policy +// that uses it -- this just makes things a little less tedious +// for the user, so they don't have to repeat those ports in +// potentially many places. This function never steps on existing +// config values. If any changes are made, acmeIssuer is +// reprovisioned. acmeIssuer must not be nil. +func (app *App) fillInACMEIssuer(acmeIssuer *caddytls.ACMEIssuer) error { + if app.HTTPPort > 0 || app.HTTPSPort > 0 { + if acmeIssuer.Challenges == nil { + acmeIssuer.Challenges = new(caddytls.ChallengesConfig) + } + } + if app.HTTPPort > 0 { + if acmeIssuer.Challenges.HTTP == nil { + acmeIssuer.Challenges.HTTP = new(caddytls.HTTPChallengeConfig) + } + // don't overwrite existing explicit config + if acmeIssuer.Challenges.HTTP.AlternatePort == 0 { + acmeIssuer.Challenges.HTTP.AlternatePort = app.HTTPPort + } + } + if app.HTTPSPort > 0 { + if acmeIssuer.Challenges.TLSALPN == nil { + acmeIssuer.Challenges.TLSALPN = new(caddytls.TLSALPNChallengeConfig) + } + // don't overwrite existing explicit config + if acmeIssuer.Challenges.TLSALPN.AlternatePort == 0 { + acmeIssuer.Challenges.TLSALPN.AlternatePort = app.HTTPSPort + } + } + // we must provision all ACME issuers, even if nothing + // was changed, because we don't know if they are new + // and haven't been provisioned yet; if an ACME issuer + // never gets provisioned, its Agree field stays false, + // which leads to, um, problems later on + return acmeIssuer.Provision(app.ctx) +} + +// automaticHTTPSPhase2 begins certificate management for +// all names in the qualifying domain set for each server. +// This phase must occur after provisioning and at the end +// of app start, after all the servers have been started. +// Doing this last ensures that there won't be any race +// for listeners on the HTTP or HTTPS ports when management +// is async (if CertMagic's solvers bind to those ports +// first, then our servers would fail to bind to them, +// which would be bad, since CertMagic's bindings are +// temporary and don't serve the user's sites!). +func (app *App) automaticHTTPSPhase2() error { + if len(app.allCertDomains) == 0 { + return nil + } + app.logger.Info("enabling automatic TLS certificate management", + zap.Strings("domains", app.allCertDomains), + ) + err := app.tlsApp.Manage(app.allCertDomains) + if err != nil { + return fmt.Errorf("managing certificates for %d domains: %s", len(app.allCertDomains), err) + } + app.allCertDomains = nil // no longer needed; allow GC to deallocate + return nil +} + +func isTailscaleDomain(name string) bool { + return strings.HasSuffix(strings.ToLower(name), ".ts.net") +} + +type acmeCapable interface{ GetACMEIssuer() *caddytls.ACMEIssuer } diff --git a/modules/caddyhttp/caddyauth/basicauth.go b/modules/caddyhttp/caddyauth/basicauth.go new file mode 100644 index 00000000000..52a5a08c187 --- /dev/null +++ b/modules/caddyhttp/caddyauth/basicauth.go @@ -0,0 +1,302 @@ +// 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 caddyauth + +import ( + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + weakrand "math/rand" + "net/http" + "strings" + "sync" + + "golang.org/x/sync/singleflight" + + "github.com/caddyserver/caddy/v2" +) + +func init() { + caddy.RegisterModule(HTTPBasicAuth{}) +} + +// HTTPBasicAuth facilitates HTTP basic authentication. +type HTTPBasicAuth struct { + // The algorithm with which the passwords are hashed. Default: bcrypt + HashRaw json.RawMessage `json:"hash,omitempty" caddy:"namespace=http.authentication.hashes inline_key=algorithm"` + + // The list of accounts to authenticate. + AccountList []Account `json:"accounts,omitempty"` + + // The name of the realm. Default: restricted + Realm string `json:"realm,omitempty"` + + // If non-nil, a mapping of plaintext passwords to their + // hashes will be cached in memory (with random eviction). + // This can greatly improve the performance of traffic-heavy + // servers that use secure password hashing algorithms, with + // the downside that plaintext passwords will be stored in + // memory for a longer time (this should not be a problem + // as long as your machine is not compromised, at which point + // all bets are off, since basicauth necessitates plaintext + // passwords being received over the wire anyway). Note that + // a cache hit does not mean it is a valid password. + HashCache *Cache `json:"hash_cache,omitempty"` + + Accounts map[string]Account `json:"-"` + Hash Comparer `json:"-"` + + // fakePassword is used when a given user is not found, + // so that timing side-channels can be mitigated: it gives + // us something to hash and compare even if the user does + // not exist, which should have similar timing as a user + // account that does exist. + fakePassword []byte +} + +// CaddyModule returns the Caddy module information. +func (HTTPBasicAuth) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.authentication.providers.http_basic", + New: func() caddy.Module { return new(HTTPBasicAuth) }, + } +} + +// Provision provisions the HTTP basic auth provider. +func (hba *HTTPBasicAuth) Provision(ctx caddy.Context) error { + if hba.HashRaw == nil { + hba.HashRaw = json.RawMessage(`{"algorithm": "bcrypt"}`) + } + + // load password hasher + hasherIface, err := ctx.LoadModule(hba, "HashRaw") + if err != nil { + return fmt.Errorf("loading password hasher module: %v", err) + } + hba.Hash = hasherIface.(Comparer) + + if hba.Hash == nil { + return fmt.Errorf("hash is required") + } + + // if supported, generate a fake password we can compare against if needed + if hasher, ok := hba.Hash.(Hasher); ok { + hba.fakePassword = hasher.FakeHash() + } + + repl := caddy.NewReplacer() + + // load account list + hba.Accounts = make(map[string]Account) + for i, acct := range hba.AccountList { + if _, ok := hba.Accounts[acct.Username]; ok { + return fmt.Errorf("account %d: username is not unique: %s", i, acct.Username) + } + + acct.Username = repl.ReplaceAll(acct.Username, "") + acct.Password = repl.ReplaceAll(acct.Password, "") + + if acct.Username == "" || acct.Password == "" { + return fmt.Errorf("account %d: username and password are required", i) + } + + // TODO: Remove support for redundantly-encoded b64-encoded hashes + // Passwords starting with '$' are likely in Modular Crypt Format, + // so we don't need to base64 decode them. But historically, we + // required redundant base64, so we try to decode it otherwise. + if strings.HasPrefix(acct.Password, "$") { + acct.password = []byte(acct.Password) + } else { + acct.password, err = base64.StdEncoding.DecodeString(acct.Password) + if err != nil { + return fmt.Errorf("base64-decoding password: %v", err) + } + } + + hba.Accounts[acct.Username] = acct + } + hba.AccountList = nil // allow GC to deallocate + + if hba.HashCache != nil { + hba.HashCache.cache = make(map[string]bool) + hba.HashCache.mu = new(sync.RWMutex) + hba.HashCache.g = new(singleflight.Group) + } + + return nil +} + +// Authenticate validates the user credentials in req and returns the user, if valid. +func (hba HTTPBasicAuth) Authenticate(w http.ResponseWriter, req *http.Request) (User, bool, error) { + username, plaintextPasswordStr, ok := req.BasicAuth() + if !ok { + return hba.promptForCredentials(w, nil) + } + + account, accountExists := hba.Accounts[username] + if !accountExists { + // don't return early if account does not exist; we want + // to try to avoid side-channels that leak existence, so + // we use a fake password to simulate realistic CPU cycles + account.password = hba.fakePassword + } + + same, err := hba.correctPassword(account, []byte(plaintextPasswordStr)) + if err != nil || !same || !accountExists { + return hba.promptForCredentials(w, err) + } + + return User{ID: username}, true, nil +} + +func (hba HTTPBasicAuth) correctPassword(account Account, plaintextPassword []byte) (bool, error) { + compare := func() (bool, error) { + return hba.Hash.Compare(account.password, plaintextPassword) + } + + // if no caching is enabled, simply return the result of hashing + comparing + if hba.HashCache == nil { + return compare() + } + + // compute a cache key that is unique for these input parameters + cacheKey := hex.EncodeToString(append(account.password, plaintextPassword...)) + + // fast track: if the result of the input is already cached, use it + hba.HashCache.mu.RLock() + same, ok := hba.HashCache.cache[cacheKey] + hba.HashCache.mu.RUnlock() + if ok { + return same, nil + } + // slow track: do the expensive op, then add it to the cache + // but perform it in a singleflight group so that multiple + // parallel requests using the same password don't cause a + // thundering herd problem by all performing the same hashing + // operation before the first one finishes and caches it. + v, err, _ := hba.HashCache.g.Do(cacheKey, func() (any, error) { + return compare() + }) + if err != nil { + return false, err + } + same = v.(bool) + hba.HashCache.mu.Lock() + if len(hba.HashCache.cache) >= 1000 { + hba.HashCache.makeRoom() // keep cache size under control + } + hba.HashCache.cache[cacheKey] = same + hba.HashCache.mu.Unlock() + + return same, nil +} + +func (hba HTTPBasicAuth) promptForCredentials(w http.ResponseWriter, err error) (User, bool, error) { + // browsers show a message that says something like: + // "The website says: " + // which is kinda dumb, but whatever. + realm := hba.Realm + if realm == "" { + realm = "restricted" + } + w.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm="%s"`, realm)) + return User{}, false, err +} + +// Cache enables caching of basic auth results. This is especially +// helpful for secure password hashes which can be expensive to +// compute on every HTTP request. +type Cache struct { + mu *sync.RWMutex + g *singleflight.Group + + // map of concatenated hashed password + plaintext password, to result + cache map[string]bool +} + +// makeRoom deletes about 1/10 of the items in the cache +// in order to keep its size under control. It must not be +// called without a lock on c.mu. +func (c *Cache) makeRoom() { + // we delete more than just 1 entry so that we don't have + // to do this on every request; assuming the capacity of + // the cache is on a long tail, we can save a lot of CPU + // time by doing a whole bunch of deletions now and then + // we won't have to do them again for a while + numToDelete := len(c.cache) / 10 + if numToDelete < 1 { + numToDelete = 1 + } + for deleted := 0; deleted <= numToDelete; deleted++ { + // Go maps are "nondeterministic" not actually random, + // so although we could just chop off the "front" of the + // map with less code, this is a heavily skewed eviction + // strategy; generating random numbers is cheap and + // ensures a much better distribution. + //nolint:gosec + rnd := weakrand.Intn(len(c.cache)) + i := 0 + for key := range c.cache { + if i == rnd { + delete(c.cache, key) + break + } + i++ + } + } +} + +// Comparer is a type that can securely compare +// a plaintext password with a hashed password +// in constant-time. Comparers should hash the +// plaintext password and then use constant-time +// comparison. +type Comparer interface { + // Compare returns true if the result of hashing + // plaintextPassword is hashedPassword, false + // otherwise. An error is returned only if + // there is a technical/configuration error. + Compare(hashedPassword, plaintextPassword []byte) (bool, error) +} + +// Hasher is a type that can generate a secure hash +// given a plaintext. Hashing modules which implement +// this interface can be used with the hash-password +// subcommand as well as benefitting from anti-timing +// features. A hasher also returns a fake hash which +// can be used for timing side-channel mitigation. +type Hasher interface { + Hash(plaintext []byte) ([]byte, error) + FakeHash() []byte +} + +// Account contains a username and password. +type Account struct { + // A user's username. + Username string `json:"username"` + + // The user's hashed password, in Modular Crypt Format (with `$` prefix) + // or base64-encoded. + Password string `json:"password"` + + password []byte +} + +// Interface guards +var ( + _ caddy.Provisioner = (*HTTPBasicAuth)(nil) + _ Authenticator = (*HTTPBasicAuth)(nil) +) diff --git a/modules/caddyhttp/caddyauth/caddyauth.go b/modules/caddyhttp/caddyauth/caddyauth.go new file mode 100644 index 00000000000..f799d7a0cb0 --- /dev/null +++ b/modules/caddyhttp/caddyauth/caddyauth.go @@ -0,0 +1,127 @@ +// 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 caddyauth + +import ( + "fmt" + "net/http" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func init() { + caddy.RegisterModule(Authentication{}) +} + +// Authentication is a middleware which provides user authentication. +// Rejects requests with HTTP 401 if the request is not authenticated. +// +// After a successful authentication, the placeholder +// `{http.auth.user.id}` will be set to the username, and also +// `{http.auth.user.*}` placeholders may be set for any authentication +// modules that provide user metadata. +// +// Its API is still experimental and may be subject to change. +type Authentication struct { + // A set of authentication providers. If none are specified, + // all requests will always be unauthenticated. + ProvidersRaw caddy.ModuleMap `json:"providers,omitempty" caddy:"namespace=http.authentication.providers"` + + Providers map[string]Authenticator `json:"-"` + + logger *zap.Logger +} + +// CaddyModule returns the Caddy module information. +func (Authentication) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.authentication", + New: func() caddy.Module { return new(Authentication) }, + } +} + +// Provision sets up a. +func (a *Authentication) Provision(ctx caddy.Context) error { + a.logger = ctx.Logger() + a.Providers = make(map[string]Authenticator) + mods, err := ctx.LoadModule(a, "ProvidersRaw") + if err != nil { + return fmt.Errorf("loading authentication providers: %v", err) + } + for modName, modIface := range mods.(map[string]any) { + a.Providers[modName] = modIface.(Authenticator) + } + return nil +} + +func (a Authentication) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + var user User + var authed bool + var err error + for provName, prov := range a.Providers { + user, authed, err = prov.Authenticate(w, r) + if err != nil { + if c := a.logger.Check(zapcore.ErrorLevel, "auth provider returned error"); c != nil { + c.Write(zap.String("provider", provName), zap.Error(err)) + } + continue + } + if authed { + break + } + } + if !authed { + return caddyhttp.Error(http.StatusUnauthorized, fmt.Errorf("not authenticated")) + } + + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + repl.Set("http.auth.user.id", user.ID) + for k, v := range user.Metadata { + repl.Set("http.auth.user."+k, v) + } + + return next.ServeHTTP(w, r) +} + +// Authenticator is a type which can authenticate a request. +// If a request was not authenticated, it returns false. An +// error is only returned if authenticating the request fails +// for a technical reason (not for bad/missing credentials). +type Authenticator interface { + Authenticate(http.ResponseWriter, *http.Request) (User, bool, error) +} + +// User represents an authenticated user. +type User struct { + // The ID of the authenticated user. + ID string + + // Any other relevant data about this + // user. Keys should be adhere to Caddy + // conventions (snake_casing), as all + // keys will be made available as + // placeholders. + Metadata map[string]string +} + +// Interface guards +var ( + _ caddy.Provisioner = (*Authentication)(nil) + _ caddyhttp.MiddlewareHandler = (*Authentication)(nil) +) diff --git a/modules/caddyhttp/caddyauth/caddyfile.go b/modules/caddyhttp/caddyauth/caddyfile.go new file mode 100644 index 00000000000..cc92477e5ab --- /dev/null +++ b/modules/caddyhttp/caddyauth/caddyfile.go @@ -0,0 +1,97 @@ +// 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 caddyauth + +import ( + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func init() { + httpcaddyfile.RegisterHandlerDirective("basicauth", parseCaddyfile) // deprecated + httpcaddyfile.RegisterHandlerDirective("basic_auth", parseCaddyfile) +} + +// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax: +// +// basic_auth [] [ []] { +// +// ... +// } +// +// If no hash algorithm is supplied, bcrypt will be assumed. +func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + h.Next() // consume directive name + + // "basicauth" is deprecated, replaced by "basic_auth" + if h.Val() == "basicauth" { + caddy.Log().Named("config.adapter.caddyfile").Warn("the 'basicauth' directive is deprecated, please use 'basic_auth' instead!") + } + + var ba HTTPBasicAuth + ba.HashCache = new(Cache) + + var cmp Comparer + args := h.RemainingArgs() + + var hashName string + switch len(args) { + case 0: + hashName = "bcrypt" + case 1: + hashName = args[0] + case 2: + hashName = args[0] + ba.Realm = args[1] + default: + return nil, h.ArgErr() + } + + switch hashName { + case "bcrypt": + cmp = BcryptHash{} + default: + return nil, h.Errf("unrecognized hash algorithm: %s", hashName) + } + + ba.HashRaw = caddyconfig.JSONModuleObject(cmp, "algorithm", hashName, nil) + + for h.NextBlock(0) { + username := h.Val() + + var b64Pwd string + h.Args(&b64Pwd) + if h.NextArg() { + return nil, h.ArgErr() + } + + if username == "" || b64Pwd == "" { + return nil, h.Err("username and password cannot be empty or missing") + } + + ba.AccountList = append(ba.AccountList, Account{ + Username: username, + Password: b64Pwd, + }) + } + + return Authentication{ + ProvidersRaw: caddy.ModuleMap{ + "http_basic": caddyconfig.JSON(ba, nil), + }, + }, nil +} diff --git a/modules/caddyhttp/caddyauth/command.go b/modules/caddyhttp/caddyauth/command.go new file mode 100644 index 00000000000..c9f44006037 --- /dev/null +++ b/modules/caddyhttp/caddyauth/command.go @@ -0,0 +1,123 @@ +// 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 caddyauth + +import ( + "bufio" + "bytes" + "fmt" + "os" + "os/signal" + + "github.com/spf13/cobra" + "golang.org/x/term" + + caddycmd "github.com/caddyserver/caddy/v2/cmd" + + "github.com/caddyserver/caddy/v2" +) + +func init() { + caddycmd.RegisterCommand(caddycmd.Command{ + Name: "hash-password", + Usage: "[--plaintext ] [--algorithm ]", + Short: "Hashes a password and writes base64", + Long: ` +Convenient way to hash a plaintext password. The resulting +hash is written to stdout as a base64 string. + +--plaintext, when omitted, will be read from stdin. If +Caddy is attached to a controlling tty, the plaintext will +not be echoed. + +--algorithm currently only supports 'bcrypt', and is the default. +`, + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("plaintext", "p", "", "The plaintext password") + cmd.Flags().StringP("algorithm", "a", "bcrypt", "Name of the hash algorithm") + cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdHashPassword) + }, + }) +} + +func cmdHashPassword(fs caddycmd.Flags) (int, error) { + var err error + + algorithm := fs.String("algorithm") + plaintext := []byte(fs.String("plaintext")) + + if len(plaintext) == 0 { + fd := int(os.Stdin.Fd()) + if term.IsTerminal(fd) { + // ensure the terminal state is restored on SIGINT + state, _ := term.GetState(fd) + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + go func() { + <-c + _ = term.Restore(fd, state) + os.Exit(caddy.ExitCodeFailedStartup) + }() + defer signal.Stop(c) + + fmt.Fprint(os.Stderr, "Enter password: ") + plaintext, err = term.ReadPassword(fd) + fmt.Fprintln(os.Stderr) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + fmt.Fprint(os.Stderr, "Confirm password: ") + confirmation, err := term.ReadPassword(fd) + fmt.Fprintln(os.Stderr) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + if !bytes.Equal(plaintext, confirmation) { + return caddy.ExitCodeFailedStartup, fmt.Errorf("password does not match") + } + } else { + rd := bufio.NewReader(os.Stdin) + plaintext, err = rd.ReadBytes('\n') + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + plaintext = plaintext[:len(plaintext)-1] // Trailing newline + } + + if len(plaintext) == 0 { + return caddy.ExitCodeFailedStartup, fmt.Errorf("plaintext is required") + } + } + + var hash []byte + var hashString string + switch algorithm { + case "bcrypt": + hash, err = BcryptHash{}.Hash(plaintext) + hashString = string(hash) + default: + return caddy.ExitCodeFailedStartup, fmt.Errorf("unrecognized hash algorithm: %s", algorithm) + } + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + fmt.Println(hashString) + + return 0, nil +} diff --git a/modules/caddyhttp/caddyauth/hashes.go b/modules/caddyhttp/caddyauth/hashes.go new file mode 100644 index 00000000000..ce3df901e68 --- /dev/null +++ b/modules/caddyhttp/caddyauth/hashes.go @@ -0,0 +1,66 @@ +// 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 caddyauth + +import ( + "golang.org/x/crypto/bcrypt" + + "github.com/caddyserver/caddy/v2" +) + +func init() { + caddy.RegisterModule(BcryptHash{}) +} + +// BcryptHash implements the bcrypt hash. +type BcryptHash struct{} + +// CaddyModule returns the Caddy module information. +func (BcryptHash) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.authentication.hashes.bcrypt", + New: func() caddy.Module { return new(BcryptHash) }, + } +} + +// Compare compares passwords. +func (BcryptHash) Compare(hashed, plaintext []byte) (bool, error) { + err := bcrypt.CompareHashAndPassword(hashed, plaintext) + if err == bcrypt.ErrMismatchedHashAndPassword { + return false, nil + } + if err != nil { + return false, err + } + return true, nil +} + +// Hash hashes plaintext using a random salt. +func (BcryptHash) Hash(plaintext []byte) ([]byte, error) { + return bcrypt.GenerateFromPassword(plaintext, 14) +} + +// FakeHash returns a fake hash. +func (BcryptHash) FakeHash() []byte { + // hashed with the following command: + // caddy hash-password --plaintext "antitiming" --algorithm "bcrypt" + return []byte("$2a$14$X3ulqf/iGxnf1k6oMZ.RZeJUoqI9PX2PM4rS5lkIKJXduLGXGPrt6") +} + +// Interface guards +var ( + _ Comparer = (*BcryptHash)(nil) + _ Hasher = (*BcryptHash)(nil) +) diff --git a/modules/caddyhttp/caddyhttp.go b/modules/caddyhttp/caddyhttp.go new file mode 100644 index 00000000000..aacafc92eba --- /dev/null +++ b/modules/caddyhttp/caddyhttp.go @@ -0,0 +1,341 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "bytes" + "encoding/json" + "io" + "net" + "net/http" + "path" + "path/filepath" + "strconv" + "strings" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func init() { + caddy.RegisterModule(tlsPlaceholderWrapper{}) +} + +// RequestMatcher is a type that can match to a request. +// A route matcher MUST NOT modify the request, with the +// only exception being its context. +// +// Deprecated: Matchers should now implement RequestMatcherWithError. +// You may remove any interface guards for RequestMatcher +// but keep your Match() methods for backwards compatibility. +type RequestMatcher interface { + Match(*http.Request) bool +} + +// RequestMatcherWithError is like RequestMatcher but can return an error. +// An error during matching will abort the request middleware chain and +// invoke the error middleware chain. +// +// This will eventually replace RequestMatcher. Matcher modules +// should implement both interfaces, and once all modules have +// been updated to use RequestMatcherWithError, the RequestMatcher +// interface may eventually be dropped. +type RequestMatcherWithError interface { + MatchWithError(*http.Request) (bool, error) +} + +// Handler is like http.Handler except ServeHTTP may return an error. +// +// If any handler encounters an error, it should be returned for proper +// handling. Return values should be propagated down the middleware chain +// by returning it unchanged. Returned errors should not be re-wrapped +// if they are already HandlerError values. +type Handler interface { + ServeHTTP(http.ResponseWriter, *http.Request) error +} + +// HandlerFunc is a convenience type like http.HandlerFunc. +type HandlerFunc func(http.ResponseWriter, *http.Request) error + +// ServeHTTP implements the Handler interface. +func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) error { + return f(w, r) +} + +// Middleware chains one Handler to the next by being passed +// the next Handler in the chain. +type Middleware func(Handler) Handler + +// MiddlewareHandler is like Handler except it takes as a third +// argument the next handler in the chain. The next handler will +// never be nil, but may be a no-op handler if this is the last +// handler in the chain. Handlers which act as middleware should +// call the next handler's ServeHTTP method so as to propagate +// the request down the chain properly. Handlers which act as +// responders (content origins) need not invoke the next handler, +// since the last handler in the chain should be the first to +// write the response. +type MiddlewareHandler interface { + ServeHTTP(http.ResponseWriter, *http.Request, Handler) error +} + +// emptyHandler is used as a no-op handler. +var emptyHandler Handler = HandlerFunc(func(_ http.ResponseWriter, req *http.Request) error { + SetVar(req.Context(), "unhandled", true) + return nil +}) + +// An implicit suffix middleware that, if reached, sets the StatusCode to the +// error stored in the ErrorCtxKey. This is to prevent situations where the +// Error chain does not actually handle the error (for instance, it matches only +// on some errors). See #3053 +var errorEmptyHandler Handler = HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { + httpError := r.Context().Value(ErrorCtxKey) + if handlerError, ok := httpError.(HandlerError); ok { + w.WriteHeader(handlerError.StatusCode) + } else { + w.WriteHeader(http.StatusInternalServerError) + } + return nil +}) + +// ResponseHandler pairs a response matcher with custom handling +// logic. Either the status code can be changed to something else +// while using the original response body, or, if a status code +// is not set, it can execute a custom route list; this is useful +// for executing handler routes based on the properties of an HTTP +// response that has not been written out to the client yet. +// +// To use this type, provision it at module load time, then when +// ready to use, match the response against its matcher; if it +// matches (or doesn't have a matcher), change the status code on +// the response if configured; otherwise invoke the routes by +// calling `rh.Routes.Compile(next).ServeHTTP(rw, req)` (or similar). +type ResponseHandler struct { + // The response matcher for this handler. If empty/nil, + // it always matches. + Match *ResponseMatcher `json:"match,omitempty"` + + // To write the original response body but with a different + // status code, set this field to the desired status code. + // If set, this takes priority over routes. + StatusCode WeakString `json:"status_code,omitempty"` + + // The list of HTTP routes to execute if no status code is + // specified. If evaluated, the original response body + // will not be written. + Routes RouteList `json:"routes,omitempty"` +} + +// Provision sets up the routes in rh. +func (rh *ResponseHandler) Provision(ctx caddy.Context) error { + if rh.Routes != nil { + err := rh.Routes.Provision(ctx) + if err != nil { + return err + } + } + return nil +} + +// WeakString is a type that unmarshals any JSON value +// as a string literal, with the following exceptions: +// +// 1. actual string values are decoded as strings; and +// 2. null is decoded as empty string; +// +// and provides methods for getting the value as various +// primitive types. However, using this type removes any +// type safety as far as deserializing JSON is concerned. +type WeakString string + +// UnmarshalJSON satisfies json.Unmarshaler according to +// this type's documentation. +func (ws *WeakString) UnmarshalJSON(b []byte) error { + if len(b) == 0 { + return io.EOF + } + if b[0] == byte('"') && b[len(b)-1] == byte('"') { + var s string + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *ws = WeakString(s) + return nil + } + if bytes.Equal(b, []byte("null")) { + return nil + } + *ws = WeakString(b) + return nil +} + +// MarshalJSON marshals was a boolean if true or false, +// a number if an integer, or a string otherwise. +func (ws WeakString) MarshalJSON() ([]byte, error) { + if ws == "true" { + return []byte("true"), nil + } + if ws == "false" { + return []byte("false"), nil + } + if num, err := strconv.Atoi(string(ws)); err == nil { + return json.Marshal(num) + } + return json.Marshal(string(ws)) +} + +// Int returns ws as an integer. If ws is not an +// integer, 0 is returned. +func (ws WeakString) Int() int { + num, _ := strconv.Atoi(string(ws)) + return num +} + +// Float64 returns ws as a float64. If ws is not a +// float value, the zero value is returned. +func (ws WeakString) Float64() float64 { + num, _ := strconv.ParseFloat(string(ws), 64) + return num +} + +// Bool returns ws as a boolean. If ws is not a +// boolean, false is returned. +func (ws WeakString) Bool() bool { + return string(ws) == "true" +} + +// String returns ws as a string. +func (ws WeakString) String() string { + return string(ws) +} + +// StatusCodeMatches returns true if a real HTTP status code matches +// the configured status code, which may be either a real HTTP status +// code or an integer representing a class of codes (e.g. 4 for all +// 4xx statuses). +func StatusCodeMatches(actual, configured int) bool { + if actual == configured { + return true + } + if configured < 100 && + actual >= configured*100 && + actual < (configured+1)*100 { + return true + } + return false +} + +// SanitizedPathJoin performs filepath.Join(root, reqPath) that +// is safe against directory traversal attacks. It uses logic +// similar to that in the Go standard library, specifically +// in the implementation of http.Dir. The root is assumed to +// be a trusted path, but reqPath is not; and the output will +// never be outside of root. The resulting path can be used +// with the local file system. If root is empty, the current +// directory is assumed. If the cleaned request path is deemed +// not local according to lexical processing (i.e. ignoring links), +// it will be rejected as unsafe and only the root will be returned. +func SanitizedPathJoin(root, reqPath string) string { + if root == "" { + root = "." + } + + relPath := path.Clean("/" + reqPath)[1:] // clean path and trim the leading / + if relPath != "" && !filepath.IsLocal(relPath) { + // path is unsafe (see https://github.com/golang/go/issues/56336#issuecomment-1416214885) + return root + } + + path := filepath.Join(root, filepath.FromSlash(relPath)) + + // filepath.Join also cleans the path, and cleaning strips + // the trailing slash, so we need to re-add it afterwards. + // if the length is 1, then it's a path to the root, + // and that should return ".", so we don't append the separator. + if strings.HasSuffix(reqPath, "/") && len(reqPath) > 1 { + path += separator + } + + return path +} + +// CleanPath cleans path p according to path.Clean(), but only +// merges repeated slashes if collapseSlashes is true, and always +// preserves trailing slashes. +func CleanPath(p string, collapseSlashes bool) string { + if collapseSlashes { + return cleanPath(p) + } + + // insert an invalid/impossible URI character into each two consecutive + // slashes to expand empty path segments; then clean the path as usual, + // and then remove the remaining temporary characters. + const tmpCh = 0xff + var sb strings.Builder + for i, ch := range p { + if ch == '/' && i > 0 && p[i-1] == '/' { + sb.WriteByte(tmpCh) + } + sb.WriteRune(ch) + } + halfCleaned := cleanPath(sb.String()) + halfCleaned = strings.ReplaceAll(halfCleaned, string([]byte{tmpCh}), "") + + return halfCleaned +} + +// cleanPath does path.Clean(p) but preserves any trailing slash. +func cleanPath(p string) string { + cleaned := path.Clean(p) + if cleaned != "/" && strings.HasSuffix(p, "/") { + cleaned = cleaned + "/" + } + return cleaned +} + +// tlsPlaceholderWrapper is a no-op listener wrapper that marks +// where the TLS listener should be in a chain of listener wrappers. +// It should only be used if another listener wrapper must be placed +// in front of the TLS handshake. +type tlsPlaceholderWrapper struct{} + +func (tlsPlaceholderWrapper) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.listeners.tls", + New: func() caddy.Module { return new(tlsPlaceholderWrapper) }, + } +} + +func (tlsPlaceholderWrapper) WrapListener(ln net.Listener) net.Listener { return ln } + +func (tlsPlaceholderWrapper) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { return nil } + +const ( + // DefaultHTTPPort is the default port for HTTP. + DefaultHTTPPort = 80 + + // DefaultHTTPSPort is the default port for HTTPS. + DefaultHTTPSPort = 443 +) + +const separator = string(filepath.Separator) + +// Interface guard +var ( + _ caddy.ListenerWrapper = (*tlsPlaceholderWrapper)(nil) + _ caddyfile.Unmarshaler = (*tlsPlaceholderWrapper)(nil) +) diff --git a/modules/caddyhttp/caddyhttp_test.go b/modules/caddyhttp/caddyhttp_test.go new file mode 100644 index 00000000000..aeed0135cc8 --- /dev/null +++ b/modules/caddyhttp/caddyhttp_test.go @@ -0,0 +1,205 @@ +package caddyhttp + +import ( + "net/url" + "path/filepath" + "runtime" + "testing" +) + +func TestSanitizedPathJoin(t *testing.T) { + // For reference: + // %2e = . + // %2f = / + // %5c = \ + for i, tc := range []struct { + inputRoot string + inputPath string + expect string + expectWindows string + }{ + { + inputPath: "", + expect: ".", + }, + { + inputPath: "/", + expect: ".", + }, + { + // fileserver.MatchFile passes an inputPath of "//" for some try_files values. + // See https://github.com/caddyserver/caddy/issues/6352 + inputPath: "//", + expect: filepath.FromSlash("./"), + }, + { + inputPath: "/foo", + expect: "foo", + }, + { + inputPath: "/foo/", + expect: filepath.FromSlash("foo/"), + }, + { + inputPath: "/foo/bar", + expect: filepath.FromSlash("foo/bar"), + }, + { + inputRoot: "/a", + inputPath: "/foo/bar", + expect: filepath.FromSlash("/a/foo/bar"), + }, + { + inputPath: "/foo/../bar", + expect: "bar", + }, + { + inputRoot: "/a/b", + inputPath: "/foo/../bar", + expect: filepath.FromSlash("/a/b/bar"), + }, + { + inputRoot: "/a/b", + inputPath: "/..%2fbar", + expect: filepath.FromSlash("/a/b/bar"), + }, + { + inputRoot: "/a/b", + inputPath: "/%2e%2e%2fbar", + expect: filepath.FromSlash("/a/b/bar"), + }, + { + // inputPath fails the IsLocal test so only the root is returned, + // but with a trailing slash since one was included in inputPath + inputRoot: "/a/b", + inputPath: "/%2e%2e%2f%2e%2e%2f", + expect: filepath.FromSlash("/a/b/"), + }, + { + inputRoot: "/a/b", + inputPath: "/foo%2fbar", + expect: filepath.FromSlash("/a/b/foo/bar"), + }, + { + inputRoot: "/a/b", + inputPath: "/foo%252fbar", + expect: filepath.FromSlash("/a/b/foo%2fbar"), + }, + { + inputRoot: "C:\\www", + inputPath: "/foo/bar", + expect: filepath.Join("C:\\www", "foo", "bar"), + }, + { + inputRoot: "C:\\www", + inputPath: "/D:\\foo\\bar", + expect: filepath.Join("C:\\www", "D:\\foo\\bar"), + expectWindows: "C:\\www", // inputPath fails IsLocal on Windows + }, + { + inputRoot: `C:\www`, + inputPath: `/..\windows\win.ini`, + expect: `C:\www/..\windows\win.ini`, + expectWindows: `C:\www`, + }, + { + inputRoot: `C:\www`, + inputPath: `/..\..\..\..\..\..\..\..\..\..\windows\win.ini`, + expect: `C:\www/..\..\..\..\..\..\..\..\..\..\windows\win.ini`, + expectWindows: `C:\www`, + }, + { + inputRoot: `C:\www`, + inputPath: `/..%5cwindows%5cwin.ini`, + expect: `C:\www/..\windows\win.ini`, + expectWindows: `C:\www`, + }, + { + inputRoot: `C:\www`, + inputPath: `/..%5c..%5c..%5c..%5c..%5c..%5c..%5c..%5c..%5c..%5cwindows%5cwin.ini`, + expect: `C:\www/..\..\..\..\..\..\..\..\..\..\windows\win.ini`, + expectWindows: `C:\www`, + }, + { + // https://github.com/golang/go/issues/56336#issuecomment-1416214885 + inputRoot: "root", + inputPath: "/a/b/../../c", + expect: filepath.FromSlash("root/c"), + }, + } { + // we don't *need* to use an actual parsed URL, but it + // adds some authenticity to the tests since real-world + // values will be coming in from URLs; thus, the test + // corpus can contain paths as encoded by clients, which + // more closely emulates the actual attack vector + u, err := url.Parse("http://test:9999" + tc.inputPath) + if err != nil { + t.Fatalf("Test %d: invalid URL: %v", i, err) + } + actual := SanitizedPathJoin(tc.inputRoot, u.Path) + if runtime.GOOS == "windows" && tc.expectWindows != "" { + tc.expect = tc.expectWindows + } + if actual != tc.expect { + t.Errorf("Test %d: SanitizedPathJoin('%s', '%s') => '%s' (expected '%s')", + i, tc.inputRoot, tc.inputPath, actual, tc.expect) + } + } +} + +func TestCleanPath(t *testing.T) { + for i, tc := range []struct { + input string + mergeSlashes bool + expect string + }{ + { + input: "/foo", + expect: "/foo", + }, + { + input: "/foo/", + expect: "/foo/", + }, + { + input: "//foo", + expect: "//foo", + }, + { + input: "//foo", + mergeSlashes: true, + expect: "/foo", + }, + { + input: "/foo//bar/", + mergeSlashes: true, + expect: "/foo/bar/", + }, + { + input: "/foo/./.././bar", + expect: "/bar", + }, + { + input: "/foo//./..//./bar", + expect: "/foo//bar", + }, + { + input: "/foo///./..//./bar", + expect: "/foo///bar", + }, + { + input: "/foo///./..//.", + expect: "/foo//", + }, + { + input: "/foo//./bar", + expect: "/foo//bar", + }, + } { + actual := CleanPath(tc.input, tc.mergeSlashes) + if actual != tc.expect { + t.Errorf("Test %d [input='%s' mergeSlashes=%t]: Got '%s', expected '%s'", + i, tc.input, tc.mergeSlashes, actual, tc.expect) + } + } +} diff --git a/modules/caddyhttp/celmatcher.go b/modules/caddyhttp/celmatcher.go new file mode 100644 index 00000000000..3d118ea798e --- /dev/null +++ b/modules/caddyhttp/celmatcher.go @@ -0,0 +1,810 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "crypto/x509/pkix" + "encoding/json" + "errors" + "fmt" + "net/http" + "reflect" + "regexp" + "strings" + "time" + + "github.com/google/cel-go/cel" + "github.com/google/cel-go/common" + "github.com/google/cel-go/common/ast" + "github.com/google/cel-go/common/operators" + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" + "github.com/google/cel-go/common/types/traits" + "github.com/google/cel-go/ext" + "github.com/google/cel-go/interpreter" + "github.com/google/cel-go/interpreter/functions" + "github.com/google/cel-go/parser" + "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func init() { + caddy.RegisterModule(MatchExpression{}) +} + +// MatchExpression matches requests by evaluating a +// [CEL](https://github.com/google/cel-spec) expression. +// This enables complex logic to be expressed using a comfortable, +// familiar syntax. Please refer to +// [the standard definitions of CEL functions and operators](https://github.com/google/cel-spec/blob/master/doc/langdef.md#standard-definitions). +// +// This matcher's JSON interface is actually a string, not a struct. +// The generated docs are not correct because this type has custom +// marshaling logic. +// +// COMPATIBILITY NOTE: This module is still experimental and is not +// subject to Caddy's compatibility guarantee. +type MatchExpression struct { + // The CEL expression to evaluate. Any Caddy placeholders + // will be expanded and situated into proper CEL function + // calls before evaluating. + Expr string `json:"expr,omitempty"` + + // Name is an optional name for this matcher. + // This is used to populate the name for regexp + // matchers that appear in the expression. + Name string `json:"name,omitempty"` + + expandedExpr string + prg cel.Program + ta types.Adapter + + log *zap.Logger +} + +// CaddyModule returns the Caddy module information. +func (MatchExpression) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.matchers.expression", + New: func() caddy.Module { return new(MatchExpression) }, + } +} + +// MarshalJSON marshals m's expression. +func (m MatchExpression) MarshalJSON() ([]byte, error) { + // if the name is empty, then we can marshal just the expression string + if m.Name == "" { + return json.Marshal(m.Expr) + } + // otherwise, we need to marshal the full object, using an + // anonymous struct to avoid infinite recursion + return json.Marshal(struct { + Expr string `json:"expr"` + Name string `json:"name"` + }{ + Expr: m.Expr, + Name: m.Name, + }) +} + +// UnmarshalJSON unmarshals m's expression. +func (m *MatchExpression) UnmarshalJSON(data []byte) error { + // if the data is a string, then it's just the expression + if data[0] == '"' { + return json.Unmarshal(data, &m.Expr) + } + // otherwise, it's a full object, so unmarshal it, + // using an temp map to avoid infinite recursion + var tmpJson map[string]any + err := json.Unmarshal(data, &tmpJson) + *m = MatchExpression{ + Expr: tmpJson["expr"].(string), + Name: tmpJson["name"].(string), + } + return err +} + +// Provision sets ups m. +func (m *MatchExpression) Provision(ctx caddy.Context) error { + m.log = ctx.Logger() + + // replace placeholders with a function call - this is just some + // light (and possibly naïve) syntactic sugar + m.expandedExpr = placeholderRegexp.ReplaceAllString(m.Expr, placeholderExpansion) + + // as a second pass, we'll strip the escape character from an escaped + // placeholder, so that it can be used as an input to other CEL functions + m.expandedExpr = escapedPlaceholderRegexp.ReplaceAllString(m.expandedExpr, escapedPlaceholderExpansion) + + // our type adapter expands CEL's standard type support + m.ta = celTypeAdapter{} + + // initialize the CEL libraries from the Matcher implementations which + // have been configured to support CEL. + matcherLibProducers := []CELLibraryProducer{} + for _, info := range caddy.GetModules("http.matchers") { + p, ok := info.New().(CELLibraryProducer) + if ok { + matcherLibProducers = append(matcherLibProducers, p) + } + } + + // add the matcher name to the context so that the matcher name + // can be used by regexp matchers being provisioned + ctx = ctx.WithValue(MatcherNameCtxKey, m.Name) + + // Assemble the compilation and program options from the different library + // producers into a single cel.Library implementation. + matcherEnvOpts := []cel.EnvOption{} + matcherProgramOpts := []cel.ProgramOption{} + for _, producer := range matcherLibProducers { + l, err := producer.CELLibrary(ctx) + if err != nil { + return fmt.Errorf("error initializing CEL library for %T: %v", producer, err) + } + matcherEnvOpts = append(matcherEnvOpts, l.CompileOptions()...) + matcherProgramOpts = append(matcherProgramOpts, l.ProgramOptions()...) + } + matcherLib := cel.Lib(NewMatcherCELLibrary(matcherEnvOpts, matcherProgramOpts)) + + // create the CEL environment + env, err := cel.NewEnv( + cel.Function(CELPlaceholderFuncName, cel.SingletonBinaryBinding(m.caddyPlaceholderFunc), cel.Overload( + CELPlaceholderFuncName+"_httpRequest_string", + []*cel.Type{httpRequestObjectType, cel.StringType}, + cel.AnyType, + )), + cel.Variable(CELRequestVarName, httpRequestObjectType), + cel.CustomTypeAdapter(m.ta), + ext.Strings(), + ext.Bindings(), + ext.Lists(), + ext.Math(), + matcherLib, + ) + if err != nil { + return fmt.Errorf("setting up CEL environment: %v", err) + } + + // parse and type-check the expression + checked, issues := env.Compile(m.expandedExpr) + if issues.Err() != nil { + return fmt.Errorf("compiling CEL program: %s", issues.Err()) + } + + // request matching is a boolean operation, so we don't really know + // what to do if the expression returns a non-boolean type + if checked.OutputType() != cel.BoolType { + return fmt.Errorf("CEL request matcher expects return type of bool, not %s", checked.OutputType()) + } + + // compile the "program" + m.prg, err = env.Program(checked, cel.EvalOptions(cel.OptOptimize)) + if err != nil { + return fmt.Errorf("compiling CEL program: %s", err) + } + return nil +} + +// Match returns true if r matches m. +func (m MatchExpression) Match(r *http.Request) bool { + match, err := m.MatchWithError(r) + if err != nil { + SetVar(r.Context(), MatcherErrorVarKey, err) + } + return match +} + +// MatchWithError returns true if r matches m. +func (m MatchExpression) MatchWithError(r *http.Request) (bool, error) { + celReq := celHTTPRequest{r} + out, _, err := m.prg.Eval(celReq) + if err != nil { + m.log.Error("evaluating expression", zap.Error(err)) + return false, err + } + if outBool, ok := out.Value().(bool); ok { + return outBool, nil + } + return false, nil +} + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. +func (m *MatchExpression) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume matcher name + + // if there's multiple args, then we need to keep the raw + // tokens because the user may have used quotes within their + // CEL expression (e.g. strings) and we should retain that + if d.CountRemainingArgs() > 1 { + m.Expr = strings.Join(d.RemainingArgsRaw(), " ") + return nil + } + + // there should at least be one arg + if !d.NextArg() { + return d.ArgErr() + } + + // if there's only one token, then we can safely grab the + // cleaned token (no quotes) and use that as the expression + // because there's no valid CEL expression that is only a + // quoted string; commonly quotes are used in Caddyfile to + // define the expression + m.Expr = d.Val() + + // use the named matcher's name, to fill regexp + // matchers names by default + m.Name = d.GetContextString(caddyfile.MatcherNameCtxKey) + + return nil +} + +// caddyPlaceholderFunc implements the custom CEL function that accesses the +// Replacer on a request and gets values from it. +func (m MatchExpression) caddyPlaceholderFunc(lhs, rhs ref.Val) ref.Val { + celReq, ok := lhs.(celHTTPRequest) + if !ok { + return types.NewErr( + "invalid request of type '%v' to %s(request, placeholderVarName)", + lhs.Type(), + CELPlaceholderFuncName, + ) + } + phStr, ok := rhs.(types.String) + if !ok { + return types.NewErr( + "invalid placeholder variable name of type '%v' to %s(request, placeholderVarName)", + rhs.Type(), + CELPlaceholderFuncName, + ) + } + + repl := celReq.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + val, _ := repl.Get(string(phStr)) + + return m.ta.NativeToValue(val) +} + +// httpRequestCELType is the type representation of a native HTTP request. +var httpRequestCELType = cel.ObjectType("http.Request", traits.ReceiverType) + +// celHTTPRequest wraps an http.Request with ref.Val interface methods. +// +// This type also implements the interpreter.Activation interface which +// drops allocation costs for CEL expression evaluations by roughly half. +type celHTTPRequest struct{ *http.Request } + +func (cr celHTTPRequest) ResolveName(name string) (any, bool) { + if name == CELRequestVarName { + return cr, true + } + return nil, false +} + +func (cr celHTTPRequest) Parent() interpreter.Activation { + return nil +} + +func (cr celHTTPRequest) ConvertToNative(typeDesc reflect.Type) (any, error) { + return cr.Request, nil +} + +func (celHTTPRequest) ConvertToType(typeVal ref.Type) ref.Val { + panic("not implemented") +} + +func (cr celHTTPRequest) Equal(other ref.Val) ref.Val { + if o, ok := other.Value().(celHTTPRequest); ok { + return types.Bool(o.Request == cr.Request) + } + return types.ValOrErr(other, "%v is not comparable type", other) +} +func (celHTTPRequest) Type() ref.Type { return httpRequestCELType } +func (cr celHTTPRequest) Value() any { return cr } + +var pkixNameCELType = cel.ObjectType("pkix.Name", traits.ReceiverType) + +// celPkixName wraps an pkix.Name with +// methods to satisfy the ref.Val interface. +type celPkixName struct{ *pkix.Name } + +func (pn celPkixName) ConvertToNative(typeDesc reflect.Type) (any, error) { + return pn.Name, nil +} + +func (pn celPkixName) ConvertToType(typeVal ref.Type) ref.Val { + if typeVal.TypeName() == "string" { + return types.String(pn.Name.String()) + } + panic("not implemented") +} + +func (pn celPkixName) Equal(other ref.Val) ref.Val { + if o, ok := other.Value().(string); ok { + return types.Bool(pn.Name.String() == o) + } + return types.ValOrErr(other, "%v is not comparable type", other) +} +func (celPkixName) Type() ref.Type { return pkixNameCELType } +func (pn celPkixName) Value() any { return pn } + +// celTypeAdapter can adapt our custom types to a CEL value. +type celTypeAdapter struct{} + +func (celTypeAdapter) NativeToValue(value any) ref.Val { + switch v := value.(type) { + case celHTTPRequest: + return v + case pkix.Name: + return celPkixName{&v} + case time.Time: + return types.Timestamp{Time: v} + case error: + return types.WrapErr(v) + } + return types.DefaultTypeAdapter.NativeToValue(value) +} + +// CELLibraryProducer provide CEL libraries that expose a Matcher +// implementation as a first class function within the CEL expression +// matcher. +type CELLibraryProducer interface { + // CELLibrary creates a cel.Library which makes it possible to use the + // target object within CEL expression matchers. + CELLibrary(caddy.Context) (cel.Library, error) +} + +// CELMatcherImpl creates a new cel.Library based on the following pieces of +// data: +// +// - macroName: the function name to be used within CEL. This will be a macro +// and not a function proper. +// - funcName: the function overload name generated by the CEL macro used to +// represent the matcher. +// - matcherDataTypes: the argument types to the macro. +// - fac: a matcherFactory implementation which converts from CEL constant +// values to a Matcher instance. +// +// Note, macro names and function names must not collide with other macros or +// functions exposed within CEL expressions, or an error will be produced +// during the expression matcher plan time. +// +// The existing CELMatcherImpl support methods are configured to support a +// limited set of function signatures. For strong type validation you may need +// to provide a custom macro which does a more detailed analysis of the CEL +// literal provided to the macro as an argument. +func CELMatcherImpl(macroName, funcName string, matcherDataTypes []*cel.Type, fac any) (cel.Library, error) { + requestType := cel.ObjectType("http.Request") + var macro parser.Macro + switch len(matcherDataTypes) { + case 1: + matcherDataType := matcherDataTypes[0] + switch matcherDataType.String() { + case "list(string)": + macro = parser.NewGlobalVarArgMacro(macroName, celMatcherStringListMacroExpander(funcName)) + case cel.StringType.String(): + macro = parser.NewGlobalMacro(macroName, 1, celMatcherStringMacroExpander(funcName)) + case CELTypeJSON.String(): + macro = parser.NewGlobalMacro(macroName, 1, celMatcherJSONMacroExpander(funcName)) + default: + return nil, fmt.Errorf("unsupported matcher data type: %s", matcherDataType) + } + case 2: + if matcherDataTypes[0] == cel.StringType && matcherDataTypes[1] == cel.StringType { + macro = parser.NewGlobalMacro(macroName, 2, celMatcherStringListMacroExpander(funcName)) + matcherDataTypes = []*cel.Type{cel.ListType(cel.StringType)} + } else { + return nil, fmt.Errorf("unsupported matcher data type: %s, %s", matcherDataTypes[0], matcherDataTypes[1]) + } + case 3: + if matcherDataTypes[0] == cel.StringType && matcherDataTypes[1] == cel.StringType && matcherDataTypes[2] == cel.StringType { + macro = parser.NewGlobalMacro(macroName, 3, celMatcherStringListMacroExpander(funcName)) + matcherDataTypes = []*cel.Type{cel.ListType(cel.StringType)} + } else { + return nil, fmt.Errorf("unsupported matcher data type: %s, %s, %s", matcherDataTypes[0], matcherDataTypes[1], matcherDataTypes[2]) + } + } + envOptions := []cel.EnvOption{ + cel.Macros(macro), + cel.Function(funcName, + cel.Overload(funcName, append([]*cel.Type{requestType}, matcherDataTypes...), cel.BoolType), + cel.SingletonBinaryBinding(CELMatcherRuntimeFunction(funcName, fac))), + } + programOptions := []cel.ProgramOption{ + cel.CustomDecorator(CELMatcherDecorator(funcName, fac)), + } + return NewMatcherCELLibrary(envOptions, programOptions), nil +} + +// CELMatcherFactory converts a constant CEL value into a RequestMatcher. +// Deprecated: Use CELMatcherWithErrorFactory instead. +type CELMatcherFactory = func(data ref.Val) (RequestMatcher, error) + +// CELMatcherWithErrorFactory converts a constant CEL value into a RequestMatcherWithError. +type CELMatcherWithErrorFactory = func(data ref.Val) (RequestMatcherWithError, error) + +// matcherCELLibrary is a simplistic configurable cel.Library implementation. +type matcherCELLibrary struct { + envOptions []cel.EnvOption + programOptions []cel.ProgramOption +} + +// NewMatcherCELLibrary creates a matcherLibrary from option setes. +func NewMatcherCELLibrary(envOptions []cel.EnvOption, programOptions []cel.ProgramOption) cel.Library { + return &matcherCELLibrary{ + envOptions: envOptions, + programOptions: programOptions, + } +} + +func (lib *matcherCELLibrary) CompileOptions() []cel.EnvOption { + return lib.envOptions +} + +func (lib *matcherCELLibrary) ProgramOptions() []cel.ProgramOption { + return lib.programOptions +} + +// CELMatcherDecorator matches a call overload generated by a CEL macro +// that takes a single argument, and optimizes the implementation to precompile +// the matcher and return a function that references the precompiled and +// provisioned matcher. +func CELMatcherDecorator(funcName string, fac any) interpreter.InterpretableDecorator { + return func(i interpreter.Interpretable) (interpreter.Interpretable, error) { + call, ok := i.(interpreter.InterpretableCall) + if !ok { + return i, nil + } + if call.OverloadID() != funcName { + return i, nil + } + callArgs := call.Args() + reqAttr, ok := callArgs[0].(interpreter.InterpretableAttribute) + if !ok { + return nil, errors.New("missing 'req' argument") + } + nsAttr, ok := reqAttr.Attr().(interpreter.NamespacedAttribute) + if !ok { + return nil, errors.New("missing 'req' argument") + } + varNames := nsAttr.CandidateVariableNames() + if len(varNames) != 1 || len(varNames) == 1 && varNames[0] != CELRequestVarName { + return nil, errors.New("missing 'req' argument") + } + matcherData, ok := callArgs[1].(interpreter.InterpretableConst) + if !ok { + // If the matcher arguments are not constant, then this means + // they contain a Caddy placeholder reference and the evaluation + // and matcher provisioning should be handled at dynamically. + return i, nil + } + + if factory, ok := fac.(CELMatcherWithErrorFactory); ok { + matcher, err := factory(matcherData.Value()) + if err != nil { + return nil, err + } + return interpreter.NewCall( + i.ID(), funcName, funcName+"_opt", + []interpreter.Interpretable{reqAttr}, + func(args ...ref.Val) ref.Val { + // The request value, guaranteed to be of type celHTTPRequest + celReq := args[0] + // If needed this call could be changed to convert the value + // to a *http.Request using CEL's ConvertToNative method. + httpReq := celReq.Value().(celHTTPRequest) + match, err := matcher.MatchWithError(httpReq.Request) + if err != nil { + return types.WrapErr(err) + } + return types.Bool(match) + }, + ), nil + } + + if factory, ok := fac.(CELMatcherFactory); ok { + matcher, err := factory(matcherData.Value()) + if err != nil { + return nil, err + } + return interpreter.NewCall( + i.ID(), funcName, funcName+"_opt", + []interpreter.Interpretable{reqAttr}, + func(args ...ref.Val) ref.Val { + // The request value, guaranteed to be of type celHTTPRequest + celReq := args[0] + // If needed this call could be changed to convert the value + // to a *http.Request using CEL's ConvertToNative method. + httpReq := celReq.Value().(celHTTPRequest) + if m, ok := matcher.(RequestMatcherWithError); ok { + match, err := m.MatchWithError(httpReq.Request) + if err != nil { + return types.WrapErr(err) + } + return types.Bool(match) + } + return types.Bool(matcher.Match(httpReq.Request)) + }, + ), nil + } + + return nil, fmt.Errorf("invalid matcher factory, must be CELMatcherFactory or CELMatcherWithErrorFactory: %T", fac) + } +} + +// CELMatcherRuntimeFunction creates a function binding for when the input to the matcher +// is dynamically resolved rather than a set of static constant values. +func CELMatcherRuntimeFunction(funcName string, fac any) functions.BinaryOp { + return func(celReq, matcherData ref.Val) ref.Val { + if factory, ok := fac.(CELMatcherWithErrorFactory); ok { + matcher, err := factory(matcherData) + if err != nil { + return types.WrapErr(err) + } + httpReq := celReq.Value().(celHTTPRequest) + match, err := matcher.MatchWithError(httpReq.Request) + if err != nil { + return types.WrapErr(err) + } + return types.Bool(match) + } + if factory, ok := fac.(CELMatcherFactory); ok { + matcher, err := factory(matcherData) + if err != nil { + return types.WrapErr(err) + } + httpReq := celReq.Value().(celHTTPRequest) + if m, ok := matcher.(RequestMatcherWithError); ok { + match, err := m.MatchWithError(httpReq.Request) + if err != nil { + return types.WrapErr(err) + } + return types.Bool(match) + } + return types.Bool(matcher.Match(httpReq.Request)) + } + return types.NewErr("CELMatcherRuntimeFunction invalid matcher factory: %T", fac) + } +} + +// celMatcherStringListMacroExpander validates that the macro is called +// with a variable number of string arguments (at least one). +// +// The arguments are collected into a single list argument the following +// function call returned: (request, [args]) +func celMatcherStringListMacroExpander(funcName string) cel.MacroFactory { + return func(eh cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *common.Error) { + matchArgs := []ast.Expr{} + if len(args) == 0 { + return nil, eh.NewError(0, "matcher requires at least one argument") + } + for _, arg := range args { + if isCELStringExpr(arg) { + matchArgs = append(matchArgs, arg) + } else { + return nil, eh.NewError(arg.ID(), "matcher arguments must be string constants") + } + } + return eh.NewCall(funcName, eh.NewIdent(CELRequestVarName), eh.NewList(matchArgs...)), nil + } +} + +// celMatcherStringMacroExpander validates that the macro is called a single +// string argument. +// +// The following function call is returned: (request, arg) +func celMatcherStringMacroExpander(funcName string) parser.MacroExpander { + return func(eh cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *common.Error) { + if len(args) != 1 { + return nil, eh.NewError(0, "matcher requires one argument") + } + if isCELStringExpr(args[0]) { + return eh.NewCall(funcName, eh.NewIdent(CELRequestVarName), args[0]), nil + } + return nil, eh.NewError(args[0].ID(), "matcher argument must be a string literal") + } +} + +// celMatcherJSONMacroExpander validates that the macro is called a single +// map literal argument. +// +// The following function call is returned: (request, arg) +func celMatcherJSONMacroExpander(funcName string) parser.MacroExpander { + return func(eh cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *common.Error) { + if len(args) != 1 { + return nil, eh.NewError(0, "matcher requires a map literal argument") + } + arg := args[0] + + switch arg.Kind() { + case ast.StructKind: + return nil, eh.NewError(arg.ID(), + fmt.Sprintf("matcher input must be a map literal, not a %s", arg.AsStruct().TypeName())) + case ast.MapKind: + mapExpr := arg.AsMap() + for _, entry := range mapExpr.Entries() { + isStringPlaceholder := isCELStringExpr(entry.AsMapEntry().Key()) + if !isStringPlaceholder { + return nil, eh.NewError(entry.ID(), "matcher map keys must be string literals") + } + isStringListPlaceholder := isCELStringExpr(entry.AsMapEntry().Value()) || + isCELStringListLiteral(entry.AsMapEntry().Value()) + if !isStringListPlaceholder { + return nil, eh.NewError(entry.AsMapEntry().Value().ID(), "matcher map values must be string or list literals") + } + } + return eh.NewCall(funcName, eh.NewIdent(CELRequestVarName), arg), nil + case ast.UnspecifiedExprKind, ast.CallKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.SelectKind: + // appeasing the linter :) + } + + return nil, eh.NewError(arg.ID(), "matcher requires a map literal argument") + } +} + +// CELValueToMapStrList converts a CEL value to a map[string][]string +// +// Earlier validation stages should guarantee that the value has this type +// at compile time, and that the runtime value type is map[string]any. +// The reason for the slight difference in value type is that CEL allows for +// map literals containing heterogeneous values, in this case string and list +// of string. +func CELValueToMapStrList(data ref.Val) (map[string][]string, error) { + mapStrType := reflect.TypeOf(map[string]any{}) + mapStrRaw, err := data.ConvertToNative(mapStrType) + if err != nil { + return nil, err + } + mapStrIface := mapStrRaw.(map[string]any) + mapStrListStr := make(map[string][]string, len(mapStrIface)) + for k, v := range mapStrIface { + switch val := v.(type) { + case string: + mapStrListStr[k] = []string{val} + case types.String: + mapStrListStr[k] = []string{string(val)} + case []string: + mapStrListStr[k] = val + case []ref.Val: + convVals := make([]string, len(val)) + for i, elem := range val { + strVal, ok := elem.(types.String) + if !ok { + return nil, fmt.Errorf("unsupported value type in header match: %T", val) + } + convVals[i] = string(strVal) + } + mapStrListStr[k] = convVals + default: + return nil, fmt.Errorf("unsupported value type in header match: %T", val) + } + } + return mapStrListStr, nil +} + +// isCELStringExpr indicates whether the expression is a supported string expression +func isCELStringExpr(e ast.Expr) bool { + return isCELStringLiteral(e) || isCELCaddyPlaceholderCall(e) || isCELConcatCall(e) +} + +// isCELStringLiteral returns whether the expression is a CEL string literal. +func isCELStringLiteral(e ast.Expr) bool { + switch e.Kind() { + case ast.LiteralKind: + constant := e.AsLiteral() + switch constant.Type() { + case types.StringType: + return true + } + case ast.UnspecifiedExprKind, ast.CallKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.MapKind, ast.SelectKind, ast.StructKind: + // appeasing the linter :) + } + return false +} + +// isCELCaddyPlaceholderCall returns whether the expression is a caddy placeholder call. +func isCELCaddyPlaceholderCall(e ast.Expr) bool { + switch e.Kind() { + case ast.CallKind: + call := e.AsCall() + if call.FunctionName() == CELPlaceholderFuncName { + return true + } + case ast.UnspecifiedExprKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.MapKind, ast.SelectKind, ast.StructKind: + // appeasing the linter :) + } + return false +} + +// isCELConcatCall tests whether the expression is a concat function (+) with string, placeholder, or +// other concat call arguments. +func isCELConcatCall(e ast.Expr) bool { + switch e.Kind() { + case ast.CallKind: + call := e.AsCall() + if call.Target().Kind() != ast.UnspecifiedExprKind { + return false + } + if call.FunctionName() != operators.Add { + return false + } + for _, arg := range call.Args() { + if !isCELStringExpr(arg) { + return false + } + } + return true + case ast.UnspecifiedExprKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.MapKind, ast.SelectKind, ast.StructKind: + // appeasing the linter :) + } + return false +} + +// isCELStringListLiteral returns whether the expression resolves to a list literal +// containing only string constants or a placeholder call. +func isCELStringListLiteral(e ast.Expr) bool { + switch e.Kind() { + case ast.ListKind: + list := e.AsList() + for _, elem := range list.Elements() { + if !isCELStringExpr(elem) { + return false + } + } + return true + case ast.UnspecifiedExprKind, ast.CallKind, ast.ComprehensionKind, ast.IdentKind, ast.LiteralKind, ast.MapKind, ast.SelectKind, ast.StructKind: + // appeasing the linter :) + } + return false +} + +// Variables used for replacing Caddy placeholders in CEL +// expressions with a proper CEL function call; this is +// just for syntactic sugar. +var ( + // The placeholder may not be preceded by a backslash; the expansion + // will include the preceding character if it is not a backslash. + placeholderRegexp = regexp.MustCompile(`([^\\]|^){([a-zA-Z][\w.-]+)}`) + placeholderExpansion = `${1}ph(req, "${2}")` + + // As a second pass, we need to strip the escape character in front of + // the placeholder, if it exists. + escapedPlaceholderRegexp = regexp.MustCompile(`\\{([a-zA-Z][\w.-]+)}`) + escapedPlaceholderExpansion = `{${1}}` + + CELTypeJSON = cel.MapType(cel.StringType, cel.DynType) +) + +var httpRequestObjectType = cel.ObjectType("http.Request") + +// The name of the CEL function which accesses Replacer values. +const CELPlaceholderFuncName = "ph" + +// The name of the CEL request variable. +const CELRequestVarName = "req" + +const MatcherNameCtxKey = "matcher_name" + +// Interface guards +var ( + _ caddy.Provisioner = (*MatchExpression)(nil) + _ RequestMatcherWithError = (*MatchExpression)(nil) + _ caddyfile.Unmarshaler = (*MatchExpression)(nil) + _ json.Marshaler = (*MatchExpression)(nil) + _ json.Unmarshaler = (*MatchExpression)(nil) +) diff --git a/modules/caddyhttp/celmatcher_test.go b/modules/caddyhttp/celmatcher_test.go new file mode 100644 index 00000000000..a7e91529c34 --- /dev/null +++ b/modules/caddyhttp/celmatcher_test.go @@ -0,0 +1,575 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "context" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "net/http" + "net/http/httptest" + "testing" + + "github.com/caddyserver/caddy/v2" +) + +var ( + clientCert = []byte(`-----BEGIN CERTIFICATE----- +MIIB9jCCAV+gAwIBAgIBAjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1DYWRk +eSBUZXN0IENBMB4XDTE4MDcyNDIxMzUwNVoXDTI4MDcyMTIxMzUwNVowHTEbMBkG +A1UEAwwSY2xpZW50LmxvY2FsZG9tYWluMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQDFDEpzF0ew68teT3xDzcUxVFaTII+jXH1ftHXxxP4BEYBU4q90qzeKFneF +z83I0nC0WAQ45ZwHfhLMYHFzHPdxr6+jkvKPASf0J2v2HDJuTM1bHBbik5Ls5eq+ +fVZDP8o/VHKSBKxNs8Goc2NTsr5b07QTIpkRStQK+RJALk4x9QIDAQABo0swSTAJ +BgNVHRMEAjAAMAsGA1UdDwQEAwIHgDAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8A +AAEwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADgYEANSjz2Sk+ +eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV +3Q9fgDkiUod+uIK0IynzIKvw+Cjg+3nx6NQ0IM0zo8c7v398RzB4apbXKZyeeqUH +9fNwfEi+OoXR6s+upSKobCmLGLGi9Na5s5g= +-----END CERTIFICATE-----`) + + matcherTests = []struct { + name string + expression *MatchExpression + urlTarget string + httpMethod string + httpHeader *http.Header + wantErr bool + wantResult bool + clientCertificate []byte + }{ + { + name: "boolean matches succeed for placeholder http.request.tls.client.subject", + expression: &MatchExpression{ + Expr: "{http.request.tls.client.subject} == 'CN=client.localdomain'", + }, + clientCertificate: clientCert, + urlTarget: "https://example.com/foo", + wantResult: true, + }, + { + name: "header matches (MatchHeader)", + expression: &MatchExpression{ + Expr: `header({'Field': 'foo'})`, + }, + urlTarget: "https://example.com/foo", + httpHeader: &http.Header{"Field": []string{"foo", "bar"}}, + wantResult: true, + }, + { + name: "header matches an escaped placeholder value (MatchHeader)", + expression: &MatchExpression{ + Expr: `header({'Field': '\\\{foobar}'})`, + }, + urlTarget: "https://example.com/foo", + httpHeader: &http.Header{"Field": []string{"{foobar}"}}, + wantResult: true, + }, + { + name: "header matches an placeholder replaced during the header matcher (MatchHeader)", + expression: &MatchExpression{ + Expr: `header({'Field': '\{http.request.uri.path}'})`, + }, + urlTarget: "https://example.com/foo", + httpHeader: &http.Header{"Field": []string{"/foo"}}, + wantResult: true, + }, + { + name: "header error, invalid escape sequence (MatchHeader)", + expression: &MatchExpression{ + Expr: `header({'Field': '\\{foobar}'})`, + }, + wantErr: true, + }, + { + name: "header error, needs to be JSON syntax with field as key (MatchHeader)", + expression: &MatchExpression{ + Expr: `header('foo')`, + }, + wantErr: true, + }, + { + name: "header_regexp matches (MatchHeaderRE)", + expression: &MatchExpression{ + Expr: `header_regexp('Field', 'fo{2}')`, + }, + urlTarget: "https://example.com/foo", + httpHeader: &http.Header{"Field": []string{"foo", "bar"}}, + wantResult: true, + }, + { + name: "header_regexp matches with name (MatchHeaderRE)", + expression: &MatchExpression{ + Expr: `header_regexp('foo', 'Field', 'fo{2}')`, + }, + urlTarget: "https://example.com/foo", + httpHeader: &http.Header{"Field": []string{"foo", "bar"}}, + wantResult: true, + }, + { + name: "header_regexp does not match (MatchHeaderRE)", + expression: &MatchExpression{ + Expr: `header_regexp('foo', 'Nope', 'fo{2}')`, + }, + urlTarget: "https://example.com/foo", + httpHeader: &http.Header{"Field": []string{"foo", "bar"}}, + wantResult: false, + }, + { + name: "header_regexp error (MatchHeaderRE)", + expression: &MatchExpression{ + Expr: `header_regexp('foo')`, + }, + wantErr: true, + }, + { + name: "host matches localhost (MatchHost)", + expression: &MatchExpression{ + Expr: `host('localhost')`, + }, + urlTarget: "http://localhost", + wantResult: true, + }, + { + name: "host matches (MatchHost)", + expression: &MatchExpression{ + Expr: `host('*.example.com')`, + }, + urlTarget: "https://foo.example.com", + wantResult: true, + }, + { + name: "host does not match (MatchHost)", + expression: &MatchExpression{ + Expr: `host('example.net', '*.example.com')`, + }, + urlTarget: "https://foo.example.org", + wantResult: false, + }, + { + name: "host error (MatchHost)", + expression: &MatchExpression{ + Expr: `host(80)`, + }, + wantErr: true, + }, + { + name: "method does not match (MatchMethod)", + expression: &MatchExpression{ + Expr: `method('PUT')`, + }, + urlTarget: "https://foo.example.com", + httpMethod: "GET", + wantResult: false, + }, + { + name: "method matches (MatchMethod)", + expression: &MatchExpression{ + Expr: `method('DELETE', 'PUT', 'POST')`, + }, + urlTarget: "https://foo.example.com", + httpMethod: "PUT", + wantResult: true, + }, + { + name: "method error not enough arguments (MatchMethod)", + expression: &MatchExpression{ + Expr: `method()`, + }, + wantErr: true, + }, + { + name: "path matches substring (MatchPath)", + expression: &MatchExpression{ + Expr: `path('*substring*')`, + }, + urlTarget: "https://example.com/foo/substring/bar.txt", + wantResult: true, + }, + { + name: "path does not match (MatchPath)", + expression: &MatchExpression{ + Expr: `path('/foo')`, + }, + urlTarget: "https://example.com/foo/bar", + wantResult: false, + }, + { + name: "path matches end url fragment (MatchPath)", + expression: &MatchExpression{ + Expr: `path('/foo')`, + }, + urlTarget: "https://example.com/FOO", + wantResult: true, + }, + { + name: "path matches end fragment with substring prefix (MatchPath)", + expression: &MatchExpression{ + Expr: `path('/foo*')`, + }, + urlTarget: "https://example.com/FOOOOO", + wantResult: true, + }, + { + name: "path matches one of multiple (MatchPath)", + expression: &MatchExpression{ + Expr: `path('/foo', '/foo/*', '/bar', '/bar/*', '/baz', '/baz*')`, + }, + urlTarget: "https://example.com/foo", + wantResult: true, + }, + { + name: "path_regexp with empty regex matches empty path (MatchPathRE)", + expression: &MatchExpression{ + Expr: `path_regexp('')`, + }, + urlTarget: "https://example.com/", + wantResult: true, + }, + { + name: "path_regexp with slash regex matches empty path (MatchPathRE)", + expression: &MatchExpression{ + Expr: `path_regexp('/')`, + }, + urlTarget: "https://example.com/", + wantResult: true, + }, + { + name: "path_regexp matches end url fragment (MatchPathRE)", + expression: &MatchExpression{ + Expr: `path_regexp('^/foo')`, + }, + urlTarget: "https://example.com/foo/", + wantResult: true, + }, + { + name: "path_regexp does not match fragment at end (MatchPathRE)", + expression: &MatchExpression{ + Expr: `path_regexp('bar_at_start', '^/bar')`, + }, + urlTarget: "https://example.com/foo/bar", + wantResult: false, + }, + { + name: "protocol matches (MatchProtocol)", + expression: &MatchExpression{ + Expr: `protocol('HTTPs')`, + }, + urlTarget: "https://example.com", + wantResult: true, + }, + { + name: "protocol does not match (MatchProtocol)", + expression: &MatchExpression{ + Expr: `protocol('grpc')`, + }, + urlTarget: "https://example.com", + wantResult: false, + }, + { + name: "protocol invocation error no args (MatchProtocol)", + expression: &MatchExpression{ + Expr: `protocol()`, + }, + wantErr: true, + }, + { + name: "protocol invocation error too many args (MatchProtocol)", + expression: &MatchExpression{ + Expr: `protocol('grpc', 'https')`, + }, + wantErr: true, + }, + { + name: "protocol invocation error wrong arg type (MatchProtocol)", + expression: &MatchExpression{ + Expr: `protocol(true)`, + }, + wantErr: true, + }, + { + name: "query does not match against a specific value (MatchQuery)", + expression: &MatchExpression{ + Expr: `query({"debug": "1"})`, + }, + urlTarget: "https://example.com/foo", + wantResult: false, + }, + { + name: "query matches against a specific value (MatchQuery)", + expression: &MatchExpression{ + Expr: `query({"debug": "1"})`, + }, + urlTarget: "https://example.com/foo/?debug=1", + wantResult: true, + }, + { + name: "query matches against multiple values (MatchQuery)", + expression: &MatchExpression{ + Expr: `query({"debug": ["0", "1", {http.request.uri.query.debug}+"1"]})`, + }, + urlTarget: "https://example.com/foo/?debug=1", + wantResult: true, + }, + { + name: "query matches against a wildcard (MatchQuery)", + expression: &MatchExpression{ + Expr: `query({"debug": ["*"]})`, + }, + urlTarget: "https://example.com/foo/?debug=something", + wantResult: true, + }, + { + name: "query matches against a placeholder value (MatchQuery)", + expression: &MatchExpression{ + Expr: `query({"debug": {http.request.uri.query.debug}})`, + }, + urlTarget: "https://example.com/foo/?debug=1", + wantResult: true, + }, + { + name: "query error bad map key type (MatchQuery)", + expression: &MatchExpression{ + Expr: `query({1: "1"})`, + }, + wantErr: true, + }, + { + name: "query error typed struct instead of map (MatchQuery)", + expression: &MatchExpression{ + Expr: `query(Message{field: "1"})`, + }, + wantErr: true, + }, + { + name: "query error bad map value type (MatchQuery)", + expression: &MatchExpression{ + Expr: `query({"debug": 1})`, + }, + wantErr: true, + }, + { + name: "query error no args (MatchQuery)", + expression: &MatchExpression{ + Expr: `query()`, + }, + wantErr: true, + }, + { + name: "remote_ip error no args (MatchRemoteIP)", + expression: &MatchExpression{ + Expr: `remote_ip()`, + }, + wantErr: true, + }, + { + name: "remote_ip single IP match (MatchRemoteIP)", + expression: &MatchExpression{ + Expr: `remote_ip('192.0.2.1')`, + }, + urlTarget: "https://example.com/foo", + wantResult: true, + }, + { + name: "vars value (VarsMatcher)", + expression: &MatchExpression{ + Expr: `vars({'foo': 'bar'})`, + }, + urlTarget: "https://example.com/foo", + wantResult: true, + }, + { + name: "vars matches placeholder, needs escape (VarsMatcher)", + expression: &MatchExpression{ + Expr: `vars({'\{http.request.uri.path}': '/foo'})`, + }, + urlTarget: "https://example.com/foo", + wantResult: true, + }, + { + name: "vars error wrong syntax (VarsMatcher)", + expression: &MatchExpression{ + Expr: `vars('foo', 'bar')`, + }, + wantErr: true, + }, + { + name: "vars error no args (VarsMatcher)", + expression: &MatchExpression{ + Expr: `vars()`, + }, + wantErr: true, + }, + { + name: "vars_regexp value (MatchVarsRE)", + expression: &MatchExpression{ + Expr: `vars_regexp('foo', 'ba?r')`, + }, + urlTarget: "https://example.com/foo", + wantResult: true, + }, + { + name: "vars_regexp value with name (MatchVarsRE)", + expression: &MatchExpression{ + Expr: `vars_regexp('name', 'foo', 'ba?r')`, + }, + urlTarget: "https://example.com/foo", + wantResult: true, + }, + { + name: "vars_regexp matches placeholder, needs escape (MatchVarsRE)", + expression: &MatchExpression{ + Expr: `vars_regexp('\{http.request.uri.path}', '/fo?o')`, + }, + urlTarget: "https://example.com/foo", + wantResult: true, + }, + { + name: "vars_regexp error no args (MatchVarsRE)", + expression: &MatchExpression{ + Expr: `vars_regexp()`, + }, + wantErr: true, + }, + } +) + +func TestMatchExpressionMatch(t *testing.T) { + for _, tst := range matcherTests { + tc := tst + t.Run(tc.name, func(t *testing.T) { + caddyCtx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + err := tc.expression.Provision(caddyCtx) + if err != nil { + if !tc.wantErr { + t.Errorf("MatchExpression.Provision() error = %v, wantErr %v", err, tc.wantErr) + } + return + } + + req := httptest.NewRequest(tc.httpMethod, tc.urlTarget, nil) + if tc.httpHeader != nil { + req.Header = *tc.httpHeader + } + repl := caddy.NewReplacer() + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + ctx = context.WithValue(ctx, VarsCtxKey, map[string]any{ + "foo": "bar", + }) + req = req.WithContext(ctx) + addHTTPVarsToReplacer(repl, req, httptest.NewRecorder()) + + if tc.clientCertificate != nil { + block, _ := pem.Decode(clientCert) + if block == nil { + t.Fatalf("failed to decode PEM certificate") + } + + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + t.Fatalf("failed to decode PEM certificate: %v", err) + } + + req.TLS = &tls.ConnectionState{ + PeerCertificates: []*x509.Certificate{cert}, + } + } + + matches, err := tc.expression.MatchWithError(req) + if err != nil { + t.Errorf("MatchExpression.Match() error = %v", err) + } + if matches != tc.wantResult { + t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tc.wantResult, tc.expression.Expr) + } + }) + } +} + +func BenchmarkMatchExpressionMatch(b *testing.B) { + for _, tst := range matcherTests { + tc := tst + if tc.wantErr { + continue + } + b.Run(tst.name, func(b *testing.B) { + tc.expression.Provision(caddy.Context{}) + req := httptest.NewRequest(tc.httpMethod, tc.urlTarget, nil) + if tc.httpHeader != nil { + req.Header = *tc.httpHeader + } + repl := caddy.NewReplacer() + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + ctx = context.WithValue(ctx, VarsCtxKey, map[string]any{ + "foo": "bar", + }) + req = req.WithContext(ctx) + addHTTPVarsToReplacer(repl, req, httptest.NewRecorder()) + if tc.clientCertificate != nil { + block, _ := pem.Decode(clientCert) + if block == nil { + b.Fatalf("failed to decode PEM certificate") + } + + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + b.Fatalf("failed to decode PEM certificate: %v", err) + } + + req.TLS = &tls.ConnectionState{ + PeerCertificates: []*x509.Certificate{cert}, + } + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + tc.expression.MatchWithError(req) + } + }) + } +} + +func TestMatchExpressionProvision(t *testing.T) { + tests := []struct { + name string + expression *MatchExpression + wantErr bool + }{ + { + name: "boolean matches succeed", + expression: &MatchExpression{ + Expr: "{http.request.uri.query} != ''", + }, + wantErr: false, + }, + { + name: "reject expressions with non-boolean results", + expression: &MatchExpression{ + Expr: "{http.request.uri.query}", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + if err := tt.expression.Provision(ctx); (err != nil) != tt.wantErr { + t.Errorf("MatchExpression.Provision() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/modules/caddyhttp/encode/brotli/brotli_precompressed.go b/modules/caddyhttp/encode/brotli/brotli_precompressed.go new file mode 100644 index 00000000000..fbd04418e45 --- /dev/null +++ b/modules/caddyhttp/encode/brotli/brotli_precompressed.go @@ -0,0 +1,31 @@ +package caddybrotli + +import ( + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode" +) + +func init() { + caddy.RegisterModule(BrotliPrecompressed{}) +} + +// BrotliPrecompressed provides the file extension for files precompressed with brotli encoding. +type BrotliPrecompressed struct{} + +// CaddyModule returns the Caddy module information. +func (BrotliPrecompressed) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.precompressed.br", + New: func() caddy.Module { return new(BrotliPrecompressed) }, + } +} + +// AcceptEncoding returns the name of the encoding as +// used in the Accept-Encoding request headers. +func (BrotliPrecompressed) AcceptEncoding() string { return "br" } + +// Suffix returns the filename suffix of precompressed files. +func (BrotliPrecompressed) Suffix() string { return ".br" } + +// Interface guards +var _ encode.Precompressed = (*BrotliPrecompressed)(nil) diff --git a/modules/caddyhttp/encode/caddyfile.go b/modules/caddyhttp/encode/caddyfile.go new file mode 100644 index 00000000000..8b865708082 --- /dev/null +++ b/modules/caddyhttp/encode/caddyfile.go @@ -0,0 +1,127 @@ +// 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 encode + +import ( + "strconv" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func init() { + httpcaddyfile.RegisterHandlerDirective("encode", parseCaddyfile) +} + +func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + enc := new(Encode) + err := enc.UnmarshalCaddyfile(h.Dispenser) + if err != nil { + return nil, err + } + return enc, nil +} + +// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax: +// +// encode [] { +// gzip [] +// zstd +// minimum_length +// # response matcher block +// match { +// status +// header [] +// } +// # or response matcher single line syntax +// match [header []] | [status ] +// } +// +// Specifying the formats on the first line will use those formats' defaults. +func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume directive name + + prefer := []string{} + remainingArgs := d.RemainingArgs() + + responseMatchers := make(map[string]caddyhttp.ResponseMatcher) + for d.NextBlock(0) { + switch d.Val() { + case "minimum_length": + if !d.NextArg() { + return d.ArgErr() + } + minLength, err := strconv.Atoi(d.Val()) + if err != nil { + return err + } + enc.MinLength = minLength + case "match": + err := caddyhttp.ParseNamedResponseMatcher(d.NewFromNextSegment(), responseMatchers) + if err != nil { + return err + } + matcher := responseMatchers["match"] + enc.Matcher = &matcher + default: + name := d.Val() + modID := "http.encoders." + name + unm, err := caddyfile.UnmarshalModule(d, modID) + if err != nil { + return err + } + encoding, ok := unm.(Encoding) + if !ok { + return d.Errf("module %s is not an HTTP encoding; is %T", modID, unm) + } + if enc.EncodingsRaw == nil { + enc.EncodingsRaw = make(caddy.ModuleMap) + } + enc.EncodingsRaw[name] = caddyconfig.JSON(encoding, nil) + prefer = append(prefer, name) + } + } + + if len(prefer) == 0 && len(remainingArgs) == 0 { + remainingArgs = []string{"zstd", "gzip"} + } + + for _, arg := range remainingArgs { + mod, err := caddy.GetModule("http.encoders." + arg) + if err != nil { + return d.Errf("finding encoder module '%s': %v", mod, err) + } + encoding, ok := mod.New().(Encoding) + if !ok { + return d.Errf("module %s is not an HTTP encoding", mod) + } + if enc.EncodingsRaw == nil { + enc.EncodingsRaw = make(caddy.ModuleMap) + } + enc.EncodingsRaw[arg] = caddyconfig.JSON(encoding, nil) + prefer = append(prefer, arg) + } + + // use the order in which the encoders were defined. + enc.Prefer = prefer + + return nil +} + +// Interface guard +var _ caddyfile.Unmarshaler = (*Encode)(nil) diff --git a/modules/caddyhttp/encode/encode.go b/modules/caddyhttp/encode/encode.go new file mode 100644 index 00000000000..bea86083a67 --- /dev/null +++ b/modules/caddyhttp/encode/encode.go @@ -0,0 +1,574 @@ +// 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 encode implements an encoder middleware for Caddy. The initial +// enhancements related to Accept-Encoding, minimum content length, and +// buffer/writer pools were adapted from https://github.com/xi2/httpgzip +// then modified heavily to accommodate modular encoders and fix bugs. +// Code borrowed from that repository is Copyright (c) 2015 The Httpgzip Authors. +package encode + +import ( + "fmt" + "io" + "math" + "net/http" + "slices" + "sort" + "strconv" + "strings" + "sync" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func init() { + caddy.RegisterModule(Encode{}) +} + +// Encode is a middleware which can encode responses. +type Encode struct { + // Selection of compression algorithms to choose from. The best one + // will be chosen based on the client's Accept-Encoding header. + EncodingsRaw caddy.ModuleMap `json:"encodings,omitempty" caddy:"namespace=http.encoders"` + + // If the client has no strong preference, choose these encodings in order. + Prefer []string `json:"prefer,omitempty"` + + // Only encode responses that are at least this many bytes long. + MinLength int `json:"minimum_length,omitempty"` + + // Only encode responses that match against this ResponseMmatcher. + // The default is a collection of text-based Content-Type headers. + Matcher *caddyhttp.ResponseMatcher `json:"match,omitempty"` + + writerPools map[string]*sync.Pool // TODO: these pools do not get reused through config reloads... +} + +// CaddyModule returns the Caddy module information. +func (Encode) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.encode", + New: func() caddy.Module { return new(Encode) }, + } +} + +// Provision provisions enc. +func (enc *Encode) Provision(ctx caddy.Context) error { + mods, err := ctx.LoadModule(enc, "EncodingsRaw") + if err != nil { + return fmt.Errorf("loading encoder modules: %v", err) + } + for modName, modIface := range mods.(map[string]any) { + err = enc.addEncoding(modIface.(Encoding)) + if err != nil { + return fmt.Errorf("adding encoding %s: %v", modName, err) + } + } + if enc.MinLength == 0 { + enc.MinLength = defaultMinLength + } + + if enc.Matcher == nil { + // common text-based content types + // list based on https://developers.cloudflare.com/speed/optimization/content/brotli/content-compression/#compression-between-cloudflare-and-website-visitors + enc.Matcher = &caddyhttp.ResponseMatcher{ + Headers: http.Header{ + "Content-Type": []string{ + "application/atom+xml*", + "application/eot*", + "application/font*", + "application/geo+json*", + "application/graphql+json*", + "application/javascript*", + "application/json*", + "application/ld+json*", + "application/manifest+json*", + "application/opentype*", + "application/otf*", + "application/rss+xml*", + "application/truetype*", + "application/ttf*", + "application/vnd.api+json*", + "application/vnd.ms-fontobject*", + "application/wasm*", + "application/x-httpd-cgi*", + "application/x-javascript*", + "application/x-opentype*", + "application/x-otf*", + "application/x-perl*", + "application/x-protobuf*", + "application/x-ttf*", + "application/xhtml+xml*", + "application/xml*", + "font/ttf*", + "font/otf*", + "image/svg+xml*", + "image/vnd.microsoft.icon*", + "image/x-icon*", + "multipart/bag*", + "multipart/mixed*", + "text/*", + }, + }, + } + } + + return nil +} + +// Validate ensures that enc's configuration is valid. +func (enc *Encode) Validate() error { + check := make(map[string]bool) + for _, encName := range enc.Prefer { + if _, ok := enc.writerPools[encName]; !ok { + return fmt.Errorf("encoding %s not enabled", encName) + } + + if _, ok := check[encName]; ok { + return fmt.Errorf("encoding %s is duplicated in prefer", encName) + } + check[encName] = true + } + + return nil +} + +func isEncodeAllowed(h http.Header) bool { + return !strings.Contains(h.Get("Cache-Control"), "no-transform") +} + +func (enc *Encode) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + if isEncodeAllowed(r.Header) { + for _, encName := range AcceptedEncodings(r, enc.Prefer) { + if _, ok := enc.writerPools[encName]; !ok { + continue // encoding not offered + } + w = enc.openResponseWriter(encName, w, r.Method == http.MethodConnect) + defer w.(*responseWriter).Close() + + // to comply with RFC 9110 section 8.8.3(.3), we modify the Etag when encoding + // by appending a hyphen and the encoder name; the problem is, the client will + // send back that Etag in a If-None-Match header, but upstream handlers that set + // the Etag in the first place don't know that we appended to their Etag! so here + // we have to strip our addition so the upstream handlers can still honor client + // caches without knowing about our changes... + if etag := r.Header.Get("If-None-Match"); etag != "" && !strings.HasPrefix(etag, "W/") { + ourSuffix := "-" + encName + `"` + if strings.HasSuffix(etag, ourSuffix) { + etag = strings.TrimSuffix(etag, ourSuffix) + `"` + r.Header.Set("If-None-Match", etag) + } + } + + break + } + } + return next.ServeHTTP(w, r) +} + +func (enc *Encode) addEncoding(e Encoding) error { + ae := e.AcceptEncoding() + if ae == "" { + return fmt.Errorf("encoder does not specify an Accept-Encoding value") + } + if _, ok := enc.writerPools[ae]; ok { + return fmt.Errorf("encoder already added: %s", ae) + } + if enc.writerPools == nil { + enc.writerPools = make(map[string]*sync.Pool) + } + enc.writerPools[ae] = &sync.Pool{ + New: func() any { + return e.NewEncoder() + }, + } + return nil +} + +// openResponseWriter creates a new response writer that may (or may not) +// encode the response with encodingName. The returned response writer MUST +// be closed after the handler completes. +func (enc *Encode) openResponseWriter(encodingName string, w http.ResponseWriter, isConnect bool) *responseWriter { + var rw responseWriter + return enc.initResponseWriter(&rw, encodingName, w, isConnect) +} + +// initResponseWriter initializes the responseWriter instance +// allocated in openResponseWriter, enabling mid-stack inlining. +func (enc *Encode) initResponseWriter(rw *responseWriter, encodingName string, wrappedRW http.ResponseWriter, isConnect bool) *responseWriter { + if rww, ok := wrappedRW.(*caddyhttp.ResponseWriterWrapper); ok { + rw.ResponseWriter = rww + } else { + rw.ResponseWriter = &caddyhttp.ResponseWriterWrapper{ResponseWriter: wrappedRW} + } + rw.encodingName = encodingName + rw.config = enc + rw.isConnect = isConnect + + return rw +} + +// responseWriter writes to an underlying response writer +// using the encoding represented by encodingName and +// configured by config. +type responseWriter struct { + http.ResponseWriter + encodingName string + w Encoder + config *Encode + statusCode int + wroteHeader bool + isConnect bool +} + +// WriteHeader stores the status to write when the time comes +// to actually write the header. +func (rw *responseWriter) WriteHeader(status int) { + rw.statusCode = status + + // See #5849 and RFC 9110 section 15.4.5 (https://www.rfc-editor.org/rfc/rfc9110.html#section-15.4.5) - 304 + // Not Modified must have certain headers set as if it was a 200 response, and according to the issue + // we would miss the Vary header in this case when compression was also enabled; note that we set this + // header in the responseWriter.init() method but that is only called if we are writing a response body + if status == http.StatusNotModified && !hasVaryValue(rw.Header(), "Accept-Encoding") { + rw.Header().Add("Vary", "Accept-Encoding") + } + + // write status immediately if status is 2xx and the request is CONNECT + // since it means the response is successful. + // see: https://github.com/caddyserver/caddy/issues/6733#issuecomment-2525058845 + if rw.isConnect && 200 <= status && status <= 299 { + rw.ResponseWriter.WriteHeader(status) + rw.wroteHeader = true + } + + // write status immediately when status code is informational + // see: https://caddy.community/t/disappear-103-early-hints-response-with-encode-enable-caddy-v2-7-6/23081/5 + if 100 <= status && status <= 199 { + rw.ResponseWriter.WriteHeader(status) + } +} + +// Match determines, if encoding should be done based on the ResponseMatcher. +func (enc *Encode) Match(rw *responseWriter) bool { + return enc.Matcher.Match(rw.statusCode, rw.Header()) +} + +// FlushError is an alternative Flush returning an error. It delays the actual Flush of the underlying +// ResponseWriterWrapper until headers were written. +func (rw *responseWriter) FlushError() error { + // WriteHeader wasn't called and is a CONNECT request, treat it as a success. + // otherwise, wait until header is written. + if rw.isConnect && !rw.wroteHeader && rw.statusCode == 0 { + rw.WriteHeader(http.StatusOK) + } + + if !rw.wroteHeader { + // flushing the underlying ResponseWriter will write header and status code, + // but we need to delay that until we can determine if we must encode and + // therefore add the Content-Encoding header; this happens in the first call + // to rw.Write (see bug in #4314) + return nil + } + // also flushes the encoder, if any + // see: https://github.com/jjiang-stripe/caddy-slow-gzip + if rw.w != nil { + err := rw.w.Flush() + if err != nil { + return err + } + } + //nolint:bodyclose + return http.NewResponseController(rw.ResponseWriter).Flush() +} + +// Write writes to the response. If the response qualifies, +// it is encoded using the encoder, which is initialized +// if not done so already. +func (rw *responseWriter) Write(p []byte) (int, error) { + // ignore zero data writes, probably head request + if len(p) == 0 { + return 0, nil + } + + // WriteHeader wasn't called and is a CONNECT request, treat it as a success. + // otherwise, determine if the response should be compressed. + if rw.isConnect && !rw.wroteHeader && rw.statusCode == 0 { + rw.WriteHeader(http.StatusOK) + } + + // sniff content-type and determine content-length + if !rw.wroteHeader && rw.config.MinLength > 0 { + var gtMinLength bool + if len(p) > rw.config.MinLength { + gtMinLength = true + } else if cl, err := strconv.Atoi(rw.Header().Get("Content-Length")); err == nil && cl > rw.config.MinLength { + gtMinLength = true + } + + if gtMinLength { + if rw.Header().Get("Content-Type") == "" { + rw.Header().Set("Content-Type", http.DetectContentType(p)) + } + rw.init() + } + } + + // before we write to the response, we need to make + // sure the header is written exactly once; we do + // that by checking if a status code has been set, + // and if so, that means we haven't written the + // header OR the default status code will be written + // by the standard library + if !rw.wroteHeader { + if rw.statusCode != 0 { + rw.ResponseWriter.WriteHeader(rw.statusCode) + } + rw.wroteHeader = true + } + + if rw.w != nil { + return rw.w.Write(p) + } else { + return rw.ResponseWriter.Write(p) + } +} + +// used to mask ReadFrom method +type writerOnly struct { + io.Writer +} + +// copied from stdlib +const sniffLen = 512 + +// ReadFrom will try to use sendfile to copy from the reader to the response writer. +// It's only used if the response writer implements io.ReaderFrom and the data can't be compressed. +// It's based on stdlin http1.1 response writer implementation. +// https://github.com/golang/go/blob/f4e3ec3dbe3b8e04a058d266adf8e048bab563f2/src/net/http/server.go#L586 +func (rw *responseWriter) ReadFrom(r io.Reader) (int64, error) { + rf, ok := rw.ResponseWriter.(io.ReaderFrom) + // sendfile can't be used anyway + if !ok { + // mask ReadFrom to avoid infinite recursion + return io.Copy(writerOnly{rw}, r) + } + + var ns int64 + // try to sniff the content type and determine if the response should be compressed + if !rw.wroteHeader && rw.config.MinLength > 0 { + var ( + err error + buf [sniffLen]byte + ) + // mask ReadFrom to let Write determine if the response should be compressed + ns, err = io.CopyBuffer(writerOnly{rw}, io.LimitReader(r, sniffLen), buf[:]) + if err != nil || ns < sniffLen { + return ns, err + } + } + + // the response will be compressed, no sendfile support + if rw.w != nil { + nr, err := io.Copy(rw.w, r) + return nr + ns, err + } + nr, err := rf.ReadFrom(r) + return nr + ns, err +} + +// Close writes any remaining buffered response and +// deallocates any active resources. +func (rw *responseWriter) Close() error { + // didn't write, probably head request + if !rw.wroteHeader { + cl, err := strconv.Atoi(rw.Header().Get("Content-Length")) + if err == nil && cl > rw.config.MinLength { + rw.init() + } + + // issue #5059, don't write status code if not set explicitly. + if rw.statusCode != 0 { + rw.ResponseWriter.WriteHeader(rw.statusCode) + } + rw.wroteHeader = true + } + + var err error + if rw.w != nil { + err = rw.w.Close() + rw.w.Reset(nil) + rw.config.writerPools[rw.encodingName].Put(rw.w) + rw.w = nil + } + return err +} + +// Unwrap returns the underlying ResponseWriter. +func (rw *responseWriter) Unwrap() http.ResponseWriter { + return rw.ResponseWriter +} + +// init should be called before we write a response, if rw.buf has contents. +func (rw *responseWriter) init() { + hdr := rw.Header() + if hdr.Get("Content-Encoding") == "" && isEncodeAllowed(hdr) && + rw.config.Match(rw) { + rw.w = rw.config.writerPools[rw.encodingName].Get().(Encoder) + rw.w.Reset(rw.ResponseWriter) + hdr.Del("Content-Length") // https://github.com/golang/go/issues/14975 + hdr.Set("Content-Encoding", rw.encodingName) + if !hasVaryValue(hdr, "Accept-Encoding") { + hdr.Add("Vary", "Accept-Encoding") + } + hdr.Del("Accept-Ranges") // we don't know ranges for dynamically-encoded content + + // strong ETags need to be distinct depending on the encoding ("selected representation") + // see RFC 9110 section 8.8.3.3: + // https://www.rfc-editor.org/rfc/rfc9110.html#name-example-entity-tags-varying + // I don't know a great way to do this... how about appending? That's a neat trick! + // (We have to strip the value we append from If-None-Match headers before + // sending subsequent requests back upstream, however, since upstream handlers + // don't know about our appending to their Etag since they've already done their work) + if etag := hdr.Get("Etag"); etag != "" && !strings.HasPrefix(etag, "W/") { + etag = fmt.Sprintf(`%s-%s"`, strings.TrimSuffix(etag, `"`), rw.encodingName) + hdr.Set("Etag", etag) + } + } +} + +func hasVaryValue(hdr http.Header, target string) bool { + for _, vary := range hdr.Values("Vary") { + vals := strings.Split(vary, ",") + for _, val := range vals { + if strings.EqualFold(strings.TrimSpace(val), target) { + return true + } + } + } + return false +} + +// AcceptedEncodings returns the list of encodings that the +// client supports, in descending order of preference. +// The client preference via q-factor and the server +// preference via Prefer setting are taken into account. If +// the Sec-WebSocket-Key header is present then non-identity +// encodings are not considered. See +// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html. +func AcceptedEncodings(r *http.Request, preferredOrder []string) []string { + acceptEncHeader := r.Header.Get("Accept-Encoding") + websocketKey := r.Header.Get("Sec-WebSocket-Key") + if acceptEncHeader == "" { + return []string{} + } + + prefs := []encodingPreference{} + + for _, accepted := range strings.Split(acceptEncHeader, ",") { + parts := strings.Split(accepted, ";") + encName := strings.ToLower(strings.TrimSpace(parts[0])) + + // determine q-factor + qFactor := 1.0 + if len(parts) > 1 { + qFactorStr := strings.ToLower(strings.TrimSpace(parts[1])) + if strings.HasPrefix(qFactorStr, "q=") { + if qFactorFloat, err := strconv.ParseFloat(qFactorStr[2:], 32); err == nil { + if qFactorFloat >= 0 && qFactorFloat <= 1 { + qFactor = qFactorFloat + } + } + } + } + + // encodings with q-factor of 0 are not accepted; + // use a small threshold to account for float precision + if qFactor < 0.00001 { + continue + } + + // don't encode WebSocket handshakes + if websocketKey != "" && encName != "identity" { + continue + } + + // set server preference + prefOrder := slices.Index(preferredOrder, encName) + if prefOrder > -1 { + prefOrder = len(preferredOrder) - prefOrder + } + + prefs = append(prefs, encodingPreference{ + encoding: encName, + q: qFactor, + preferOrder: prefOrder, + }) + } + + // sort preferences by descending q-factor first, then by preferOrder + sort.Slice(prefs, func(i, j int) bool { + if math.Abs(prefs[i].q-prefs[j].q) < 0.00001 { + return prefs[i].preferOrder > prefs[j].preferOrder + } + return prefs[i].q > prefs[j].q + }) + + prefEncNames := make([]string, len(prefs)) + for i := range prefs { + prefEncNames[i] = prefs[i].encoding + } + + return prefEncNames +} + +// encodingPreference pairs an encoding with its q-factor. +type encodingPreference struct { + encoding string + q float64 + preferOrder int +} + +// Encoder is a type which can encode a stream of data. +type Encoder interface { + io.WriteCloser + Reset(io.Writer) + Flush() error // encoder by default buffers data to maximize compressing rate +} + +// Encoding is a type which can create encoders of its kind +// and return the name used in the Accept-Encoding header. +type Encoding interface { + AcceptEncoding() string + NewEncoder() Encoder +} + +// Precompressed is a type which returns filename suffix of precompressed +// file and Accept-Encoding header to use when serving this file. +type Precompressed interface { + AcceptEncoding() string + Suffix() string +} + +// defaultMinLength is the minimum length at which to compress content. +const defaultMinLength = 512 + +// Interface guards +var ( + _ caddy.Provisioner = (*Encode)(nil) + _ caddy.Validator = (*Encode)(nil) + _ caddyhttp.MiddlewareHandler = (*Encode)(nil) +) diff --git a/modules/caddyhttp/encode/encode_test.go b/modules/caddyhttp/encode/encode_test.go new file mode 100644 index 00000000000..83effa58c19 --- /dev/null +++ b/modules/caddyhttp/encode/encode_test.go @@ -0,0 +1,308 @@ +package encode + +import ( + "net/http" + "sync" + "testing" +) + +func BenchmarkOpenResponseWriter(b *testing.B) { + enc := new(Encode) + for n := 0; n < b.N; n++ { + enc.openResponseWriter("test", nil, false) + } +} + +func TestPreferOrder(t *testing.T) { + testCases := []struct { + name string + accept string + prefer []string + expected []string + }{ + { + name: "PreferOrder(): 4 accept, 3 prefer", + accept: "deflate, gzip, br, zstd", + prefer: []string{"zstd", "br", "gzip"}, + expected: []string{"zstd", "br", "gzip", "deflate"}, + }, + { + name: "PreferOrder(): 2 accept, 3 prefer", + accept: "deflate, zstd", + prefer: []string{"zstd", "br", "gzip"}, + expected: []string{"zstd", "deflate"}, + }, + { + name: "PreferOrder(): 2 accept (1 empty), 3 prefer", + accept: "gzip,,zstd", + prefer: []string{"zstd", "br", "gzip"}, + expected: []string{"zstd", "gzip", ""}, + }, + { + name: "PreferOrder(): 1 accept, 2 prefer", + accept: "gzip", + prefer: []string{"zstd", "gzip"}, + expected: []string{"gzip"}, + }, + { + name: "PreferOrder(): 4 accept (1 duplicate), 1 prefer", + accept: "deflate, gzip, br, br", + prefer: []string{"br"}, + expected: []string{"br", "br", "deflate", "gzip"}, + }, + { + name: "PreferOrder(): empty accept, 0 prefer", + accept: "", + prefer: []string{}, + expected: []string{}, + }, + { + name: "PreferOrder(): empty accept, 1 prefer", + accept: "", + prefer: []string{"gzip"}, + expected: []string{}, + }, + { + name: "PreferOrder(): with q-factor", + accept: "deflate;q=0.8, gzip;q=0.4, br;q=0.2, zstd", + prefer: []string{"gzip"}, + expected: []string{"zstd", "deflate", "gzip", "br"}, + }, + { + name: "PreferOrder(): with q-factor, no prefer", + accept: "deflate;q=0.8, gzip;q=0.4, br;q=0.2, zstd", + prefer: []string{}, + expected: []string{"zstd", "deflate", "gzip", "br"}, + }, + { + name: "PreferOrder(): q-factor=0 filtered out", + accept: "deflate;q=0.1, gzip;q=0.4, br;q=0.5, zstd;q=0", + prefer: []string{"gzip"}, + expected: []string{"br", "gzip", "deflate"}, + }, + { + name: "PreferOrder(): q-factor=0 filtered out, no prefer", + accept: "deflate;q=0.1, gzip;q=0.4, br;q=0.5, zstd;q=0", + prefer: []string{}, + expected: []string{"br", "gzip", "deflate"}, + }, + { + name: "PreferOrder(): with invalid q-factor", + accept: "br, deflate, gzip;q=2, zstd;q=0.1", + prefer: []string{"zstd", "gzip"}, + expected: []string{"gzip", "br", "deflate", "zstd"}, + }, + { + name: "PreferOrder(): with invalid q-factor, no prefer", + accept: "br, deflate, gzip;q=2, zstd;q=0.1", + prefer: []string{}, + expected: []string{"br", "deflate", "gzip", "zstd"}, + }, + } + + enc := new(Encode) + r, _ := http.NewRequest("", "", nil) + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + if test.accept == "" { + r.Header.Del("Accept-Encoding") + } else { + r.Header.Set("Accept-Encoding", test.accept) + } + enc.Prefer = test.prefer + result := AcceptedEncodings(r, enc.Prefer) + if !sliceEqual(result, test.expected) { + t.Errorf("AcceptedEncodings() actual: %s expected: %s", + result, + test.expected) + } + }) + } +} + +func sliceEqual(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + +func TestValidate(t *testing.T) { + type testCase struct { + name string + prefer []string + wantErr bool + } + + var err error + var testCases []testCase + enc := new(Encode) + + enc.writerPools = map[string]*sync.Pool{ + "zstd": nil, + "gzip": nil, + "br": nil, + } + testCases = []testCase{ + { + name: "ValidatePrefer (zstd, gzip & br enabled): valid order with all encoder", + prefer: []string{"zstd", "br", "gzip"}, + wantErr: false, + }, + { + name: "ValidatePrefer (zstd, gzip & br enabled): valid order with 2 out of 3 encoders", + prefer: []string{"br", "gzip"}, + wantErr: false, + }, + { + name: "ValidatePrefer (zstd, gzip & br enabled): valid order with 1 out of 3 encoders", + prefer: []string{"gzip"}, + wantErr: false, + }, + { + name: "ValidatePrefer (zstd, gzip & br enabled): 1 duplicated (once) encoder", + prefer: []string{"gzip", "zstd", "gzip"}, + wantErr: true, + }, + { + name: "ValidatePrefer (zstd, gzip & br enabled): 1 not enabled encoder in prefer list", + prefer: []string{"br", "zstd", "gzip", "deflate"}, + wantErr: true, + }, + { + name: "ValidatePrefer (zstd, gzip & br enabled): no prefer list", + prefer: []string{}, + wantErr: false, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + enc.Prefer = test.prefer + err = enc.Validate() + if (err != nil) != test.wantErr { + t.Errorf("Validate() error = %v, wantErr = %v", err, test.wantErr) + } + }) + } + + enc.writerPools = map[string]*sync.Pool{ + "zstd": nil, + "gzip": nil, + } + testCases = []testCase{ + { + name: "ValidatePrefer (zstd & gzip enabled): 1 not enabled encoder in prefer list", + prefer: []string{"zstd", "br", "gzip"}, + wantErr: true, + }, + { + name: "ValidatePrefer (zstd & gzip enabled): 2 not enabled encoder in prefer list", + prefer: []string{"br", "zstd", "gzip", "deflate"}, + wantErr: true, + }, + { + name: "ValidatePrefer (zstd & gzip enabled): only not enabled encoder in prefer list", + prefer: []string{"deflate", "br", "gzip"}, + wantErr: true, + }, + { + name: "ValidatePrefer (zstd & gzip enabled): 1 duplicated (once) encoder in prefer list", + prefer: []string{"gzip", "zstd", "gzip"}, + wantErr: true, + }, + { + name: "ValidatePrefer (zstd & gzip enabled): 1 duplicated (twice) encoder in prefer list", + prefer: []string{"gzip", "zstd", "gzip", "gzip"}, + wantErr: true, + }, + { + name: "ValidatePrefer (zstd & gzip enabled): 1 duplicated encoder in prefer list", + prefer: []string{"zstd", "zstd", "gzip", "gzip"}, + wantErr: true, + }, + { + name: "ValidatePrefer (zstd & gzip enabled): 1 duplicated not enabled encoder in prefer list", + prefer: []string{"br", "br", "gzip"}, + wantErr: true, + }, + { + name: "ValidatePrefer (zstd & gzip enabled): 2 duplicated not enabled encoder in prefer list", + prefer: []string{"br", "deflate", "br", "deflate"}, + wantErr: true, + }, + { + name: "ValidatePrefer (zstd & gzip enabled): valid order zstd first", + prefer: []string{"zstd", "gzip"}, + wantErr: false, + }, + { + name: "ValidatePrefer (zstd & gzip enabled): valid order gzip first", + prefer: []string{"gzip", "zstd"}, + wantErr: false, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + enc.Prefer = test.prefer + err = enc.Validate() + if (err != nil) != test.wantErr { + t.Errorf("Validate() error = %v, wantErr = %v", err, test.wantErr) + } + }) + } +} + +func TestIsEncodeAllowed(t *testing.T) { + testCases := []struct { + name string + headers http.Header + expected bool + }{ + { + name: "Without any headers", + headers: http.Header{}, + expected: true, + }, + { + name: "Without Cache-Control HTTP header", + headers: http.Header{ + "Accept-Encoding": {"gzip"}, + }, + expected: true, + }, + { + name: "Cache-Control HTTP header ending with no-transform directive", + headers: http.Header{ + "Accept-Encoding": {"gzip"}, + "Cache-Control": {"no-cache; no-transform"}, + }, + expected: false, + }, + { + name: "With Cache-Control HTTP header no-transform as Cache-Extension value", + headers: http.Header{ + "Accept-Encoding": {"gzip"}, + "Cache-Control": {`no-store; no-cache; community="no-transform"`}, + }, + expected: false, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + if result := isEncodeAllowed(test.headers); result != test.expected { + t.Errorf("The headers given to the isEncodeAllowed should return %t, %t given.", + result, + test.expected) + } + }) + } +} diff --git a/modules/caddyhttp/encode/gzip/gzip.go b/modules/caddyhttp/encode/gzip/gzip.go new file mode 100644 index 00000000000..40f37ab8e35 --- /dev/null +++ b/modules/caddyhttp/encode/gzip/gzip.go @@ -0,0 +1,98 @@ +// 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 caddygzip + +import ( + "fmt" + "strconv" + + "github.com/klauspost/compress/gzip" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode" +) + +func init() { + caddy.RegisterModule(Gzip{}) +} + +// Gzip can create gzip encoders. +type Gzip struct { + Level int `json:"level,omitempty"` +} + +// CaddyModule returns the Caddy module information. +func (Gzip) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.encoders.gzip", + New: func() caddy.Module { return new(Gzip) }, + } +} + +// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. +func (g *Gzip) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume option name + if !d.NextArg() { + return nil + } + levelStr := d.Val() + level, err := strconv.Atoi(levelStr) + if err != nil { + return err + } + g.Level = level + return nil +} + +// Provision provisions g's configuration. +func (g *Gzip) Provision(ctx caddy.Context) error { + if g.Level == 0 { + g.Level = defaultGzipLevel + } + return nil +} + +// Validate validates g's configuration. +func (g Gzip) Validate() error { + if g.Level < gzip.StatelessCompression { + return fmt.Errorf("quality too low; must be >= %d", gzip.StatelessCompression) + } + if g.Level > gzip.BestCompression { + return fmt.Errorf("quality too high; must be <= %d", gzip.BestCompression) + } + return nil +} + +// AcceptEncoding returns the name of the encoding as +// used in the Accept-Encoding request headers. +func (Gzip) AcceptEncoding() string { return "gzip" } + +// NewEncoder returns a new gzip writer. +func (g Gzip) NewEncoder() encode.Encoder { + writer, _ := gzip.NewWriterLevel(nil, g.Level) + return writer +} + +// Informed from http://blog.klauspost.com/gzip-performance-for-go-webservers/ +var defaultGzipLevel = 5 + +// Interface guards +var ( + _ encode.Encoding = (*Gzip)(nil) + _ caddy.Provisioner = (*Gzip)(nil) + _ caddy.Validator = (*Gzip)(nil) + _ caddyfile.Unmarshaler = (*Gzip)(nil) +) diff --git a/modules/caddyhttp/encode/gzip/gzip_precompressed.go b/modules/caddyhttp/encode/gzip/gzip_precompressed.go new file mode 100644 index 00000000000..7103cc8d913 --- /dev/null +++ b/modules/caddyhttp/encode/gzip/gzip_precompressed.go @@ -0,0 +1,28 @@ +package caddygzip + +import ( + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode" +) + +func init() { + caddy.RegisterModule(GzipPrecompressed{}) +} + +// GzipPrecompressed provides the file extension for files precompressed with gzip encoding. +type GzipPrecompressed struct { + Gzip +} + +// CaddyModule returns the Caddy module information. +func (GzipPrecompressed) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.precompressed.gzip", + New: func() caddy.Module { return new(GzipPrecompressed) }, + } +} + +// Suffix returns the filename suffix of precompressed files. +func (GzipPrecompressed) Suffix() string { return ".gz" } + +var _ encode.Precompressed = (*GzipPrecompressed)(nil) diff --git a/modules/caddyhttp/encode/zstd/zstd.go b/modules/caddyhttp/encode/zstd/zstd.go new file mode 100644 index 00000000000..1706de89db7 --- /dev/null +++ b/modules/caddyhttp/encode/zstd/zstd.go @@ -0,0 +1,108 @@ +// 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 caddyzstd + +import ( + "fmt" + + "github.com/klauspost/compress/zstd" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode" +) + +func init() { + caddy.RegisterModule(Zstd{}) +} + +// Zstd can create Zstandard encoders. +type Zstd struct { + // The compression level. Accepted values: fastest, better, best, default. + Level string `json:"level,omitempty"` + + // Compression level refer to type constants value from zstd.SpeedFastest to zstd.SpeedBestCompression + level zstd.EncoderLevel +} + +// CaddyModule returns the Caddy module information. +func (Zstd) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.encoders.zstd", + New: func() caddy.Module { return new(Zstd) }, + } +} + +// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. +func (z *Zstd) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume option name + if !d.NextArg() { + return nil + } + levelStr := d.Val() + if ok, _ := zstd.EncoderLevelFromString(levelStr); !ok { + return d.Errf("unexpected compression level, use one of '%s', '%s', '%s', '%s'", + zstd.SpeedFastest, + zstd.SpeedBetterCompression, + zstd.SpeedBestCompression, + zstd.SpeedDefault, + ) + } + z.Level = levelStr + return nil +} + +// Provision provisions z's configuration. +func (z *Zstd) Provision(ctx caddy.Context) error { + if z.Level == "" { + z.Level = zstd.SpeedDefault.String() + } + var ok bool + if ok, z.level = zstd.EncoderLevelFromString(z.Level); !ok { + return fmt.Errorf("unexpected compression level, use one of '%s', '%s', '%s', '%s'", + zstd.SpeedFastest, + zstd.SpeedDefault, + zstd.SpeedBetterCompression, + zstd.SpeedBestCompression, + ) + } + return nil +} + +// AcceptEncoding returns the name of the encoding as +// used in the Accept-Encoding request headers. +func (Zstd) AcceptEncoding() string { return "zstd" } + +// NewEncoder returns a new Zstandard writer. +func (z Zstd) NewEncoder() encode.Encoder { + // The default of 8MB for the window is + // too large for many clients, so we limit + // it to 128K to lighten their load. + writer, _ := zstd.NewWriter( + nil, + zstd.WithWindowSize(128<<10), + zstd.WithEncoderConcurrency(1), + zstd.WithZeroFrames(true), + zstd.WithEncoderLevel(z.level), + ) + return writer +} + +// Interface guards +var ( + _ encode.Encoding = (*Zstd)(nil) + _ caddyfile.Unmarshaler = (*Zstd)(nil) + _ caddy.Provisioner = (*Zstd)(nil) +) diff --git a/modules/caddyhttp/encode/zstd/zstd_precompressed.go b/modules/caddyhttp/encode/zstd/zstd_precompressed.go new file mode 100644 index 00000000000..522f41735ab --- /dev/null +++ b/modules/caddyhttp/encode/zstd/zstd_precompressed.go @@ -0,0 +1,28 @@ +package caddyzstd + +import ( + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode" +) + +func init() { + caddy.RegisterModule(ZstdPrecompressed{}) +} + +// ZstdPrecompressed provides the file extension for files precompressed with zstandard encoding. +type ZstdPrecompressed struct { + Zstd +} + +// CaddyModule returns the Caddy module information. +func (ZstdPrecompressed) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.precompressed.zstd", + New: func() caddy.Module { return new(ZstdPrecompressed) }, + } +} + +// Suffix returns the filename suffix of precompressed files. +func (ZstdPrecompressed) Suffix() string { return ".zst" } + +var _ encode.Precompressed = (*ZstdPrecompressed)(nil) diff --git a/modules/caddyhttp/errors.go b/modules/caddyhttp/errors.go new file mode 100644 index 00000000000..fc8ffbfaa70 --- /dev/null +++ b/modules/caddyhttp/errors.go @@ -0,0 +1,117 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "errors" + "fmt" + weakrand "math/rand" + "path" + "runtime" + "strings" + + "github.com/caddyserver/caddy/v2" +) + +// Error is a convenient way for a Handler to populate the +// essential fields of a HandlerError. If err is itself a +// HandlerError, then any essential fields that are not +// set will be populated. +func Error(statusCode int, err error) HandlerError { + const idLen = 9 + var he HandlerError + if errors.As(err, &he) { + if he.ID == "" { + he.ID = randString(idLen, true) + } + if he.Trace == "" { + he.Trace = trace() + } + if he.StatusCode == 0 { + he.StatusCode = statusCode + } + return he + } + return HandlerError{ + ID: randString(idLen, true), + StatusCode: statusCode, + Err: err, + Trace: trace(), + } +} + +// HandlerError is a serializable representation of +// an error from within an HTTP handler. +type HandlerError struct { + Err error // the original error value and message + StatusCode int // the HTTP status code to associate with this error + + ID string // generated; for identifying this error in logs + Trace string // produced from call stack +} + +func (e HandlerError) Error() string { + var s string + if e.ID != "" { + s += fmt.Sprintf("{id=%s}", e.ID) + } + if e.Trace != "" { + s += " " + e.Trace + } + if e.StatusCode != 0 { + s += fmt.Sprintf(": HTTP %d", e.StatusCode) + } + if e.Err != nil { + s += ": " + e.Err.Error() + } + return strings.TrimSpace(s) +} + +// Unwrap returns the underlying error value. See the `errors` package for info. +func (e HandlerError) Unwrap() error { return e.Err } + +// randString returns a string of n random characters. +// It is not even remotely secure OR a proper distribution. +// But it's good enough for some things. It excludes certain +// confusing characters like I, l, 1, 0, O, etc. If sameCase +// is true, then uppercase letters are excluded. +func randString(n int, sameCase bool) string { + if n <= 0 { + return "" + } + dict := []byte("abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY23456789") + if sameCase { + dict = []byte("abcdefghijkmnpqrstuvwxyz0123456789") + } + b := make([]byte, n) + for i := range b { + //nolint:gosec + b[i] = dict[weakrand.Int63()%int64(len(dict))] + } + return string(b) +} + +func trace() string { + if pc, file, line, ok := runtime.Caller(2); ok { + filename := path.Base(file) + pkgAndFuncName := path.Base(runtime.FuncForPC(pc).Name()) + return fmt.Sprintf("%s (%s:%d)", pkgAndFuncName, filename, line) + } + return "" +} + +// ErrorCtxKey is the context key to use when storing +// an error (for use with context.Context). +const ErrorCtxKey = caddy.CtxKey("handler_chain_error") diff --git a/modules/caddyhttp/fileserver/browse.go b/modules/caddyhttp/fileserver/browse.go new file mode 100644 index 00000000000..52aa7a9f8e5 --- /dev/null +++ b/modules/caddyhttp/fileserver/browse.go @@ -0,0 +1,355 @@ +// 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 fileserver + +import ( + "bytes" + "context" + _ "embed" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "net/http" + "os" + "path" + "strings" + "sync" + "text/tabwriter" + "text/template" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/templates" +) + +// BrowseTemplate is the default template document to use for +// file listings. By default, its default value is an embedded +// document. You can override this value at program start, or +// if you are running Caddy via config, you can specify a +// custom template_file in the browse configuration. +// +//go:embed browse.html +var BrowseTemplate string + +// Browse configures directory browsing. +type Browse struct { + // Filename of the template to use instead of the embedded browse template. + TemplateFile string `json:"template_file,omitempty"` + + // Determines whether or not targets of symlinks should be revealed. + RevealSymlinks bool `json:"reveal_symlinks,omitempty"` + + // Override the default sort. + // It includes the following options: + // - sort_by: name(default), namedirfirst, size, time + // - order: asc(default), desc + // eg.: + // - `sort time desc` will sort by time in descending order + // - `sort size` will sort by size in ascending order + // The first option must be `sort_by` and the second option must be `order` (if exists). + SortOptions []string `json:"sort,omitempty"` + + // FileLimit limits the number of up to n DirEntry values in directory order. + FileLimit int `json:"file_limit,omitempty"` +} + +const ( + defaultDirEntryLimit = 10000 +) + +func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + if c := fsrv.logger.Check(zapcore.DebugLevel, "browse enabled; listing directory contents"); c != nil { + c.Write(zap.String("path", dirPath), zap.String("root", root)) + } + + // Navigation on the client-side gets messed up if the + // URL doesn't end in a trailing slash because hrefs to + // "b/c" at path "/a" end up going to "/b/c" instead + // of "/a/b/c" - so we have to redirect in this case + // so that the path is "/a/" and the client constructs + // relative hrefs "b/c" to be "/a/b/c". + // + // Only redirect if the last element of the path (the filename) was not + // rewritten; if the admin wanted to rewrite to the canonical path, they + // would have, and we have to be very careful not to introduce unwanted + // redirects and especially redirect loops! (Redirecting using the + // original URI is necessary because that's the URI the browser knows, + // we don't want to redirect from internally-rewritten URIs.) + // See https://github.com/caddyserver/caddy/issues/4205. + // We also redirect if the path is empty, because this implies the path + // prefix was fully stripped away by a `handle_path` handler for example. + // See https://github.com/caddyserver/caddy/issues/4466. + origReq := r.Context().Value(caddyhttp.OriginalRequestCtxKey).(http.Request) + if r.URL.Path == "" || path.Base(origReq.URL.Path) == path.Base(r.URL.Path) { + if !strings.HasSuffix(origReq.URL.Path, "/") { + if c := fsrv.logger.Check(zapcore.DebugLevel, "redirecting to trailing slash to preserve hrefs"); c != nil { + c.Write(zap.String("request_path", r.URL.Path)) + } + return redirect(w, r, origReq.URL.Path+"/") + } + } + + dir, err := fsrv.openFile(fileSystem, dirPath, w) + if err != nil { + return err + } + defer dir.Close() + + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + + // TODO: not entirely sure if path.Clean() is necessary here but seems like a safe plan (i.e. /%2e%2e%2f) - someone could verify this + listing, err := fsrv.loadDirectoryContents(r.Context(), fileSystem, dir.(fs.ReadDirFile), root, path.Clean(r.URL.EscapedPath()), repl) + switch { + case errors.Is(err, fs.ErrPermission): + return caddyhttp.Error(http.StatusForbidden, err) + case errors.Is(err, fs.ErrNotExist): + return fsrv.notFound(w, r, next) + case err != nil: + return caddyhttp.Error(http.StatusInternalServerError, err) + } + + w.Header().Add("Vary", "Accept, Accept-Encoding") + + // speed up browser/client experience and caching by supporting If-Modified-Since + if ifModSinceStr := r.Header.Get("If-Modified-Since"); ifModSinceStr != "" { + // basically a copy of stdlib file server's handling of If-Modified-Since + ifModSince, err := http.ParseTime(ifModSinceStr) + if err == nil && listing.lastModified.Truncate(time.Second).Compare(ifModSince) <= 0 { + w.WriteHeader(http.StatusNotModified) + return nil + } + } + + fsrv.browseApplyQueryParams(w, r, listing) + + buf := bufPool.Get().(*bytes.Buffer) + buf.Reset() + defer bufPool.Put(buf) + + acceptHeader := strings.ToLower(strings.Join(r.Header["Accept"], ",")) + w.Header().Set("Last-Modified", listing.lastModified.Format(http.TimeFormat)) + + switch { + case strings.Contains(acceptHeader, "application/json"): + if err := json.NewEncoder(buf).Encode(listing.Items); err != nil { + return caddyhttp.Error(http.StatusInternalServerError, err) + } + w.Header().Set("Content-Type", "application/json; charset=utf-8") + + case strings.Contains(acceptHeader, "text/plain"): + writer := tabwriter.NewWriter(buf, 0, 8, 1, '\t', tabwriter.AlignRight) + + // Header on top + if _, err := fmt.Fprintln(writer, "Name\tSize\tModified"); err != nil { + return caddyhttp.Error(http.StatusInternalServerError, err) + } + + // Lines to separate the header + if _, err := fmt.Fprintln(writer, "----\t----\t--------"); err != nil { + return caddyhttp.Error(http.StatusInternalServerError, err) + } + + // Actual files + for _, item := range listing.Items { + if _, err := fmt.Fprintf(writer, "%s\t%s\t%s\n", + item.Name, item.HumanSize(), item.HumanModTime("January 2, 2006 at 15:04:05"), + ); err != nil { + return caddyhttp.Error(http.StatusInternalServerError, err) + } + } + + if err := writer.Flush(); err != nil { + return caddyhttp.Error(http.StatusInternalServerError, err) + } + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + + default: + var fs http.FileSystem + if fsrv.Root != "" { + fs = http.Dir(repl.ReplaceAll(fsrv.Root, ".")) + } + + tplCtx := &templateContext{ + TemplateContext: templates.TemplateContext{ + Root: fs, + Req: r, + RespHeader: templates.WrappedHeader{Header: w.Header()}, + }, + browseTemplateContext: listing, + } + + tpl, err := fsrv.makeBrowseTemplate(tplCtx) + if err != nil { + return fmt.Errorf("parsing browse template: %v", err) + } + if err := tpl.Execute(buf, tplCtx); err != nil { + return caddyhttp.Error(http.StatusInternalServerError, err) + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + } + + _, _ = buf.WriteTo(w) + + return nil +} + +func (fsrv *FileServer) loadDirectoryContents(ctx context.Context, fileSystem fs.FS, dir fs.ReadDirFile, root, urlPath string, repl *caddy.Replacer) (*browseTemplateContext, error) { + // modTime for the directory itself + stat, err := dir.Stat() + if err != nil { + return nil, err + } + dirLimit := defaultDirEntryLimit + if fsrv.Browse.FileLimit != 0 { + dirLimit = fsrv.Browse.FileLimit + } + files, err := dir.ReadDir(dirLimit) + if err != nil && err != io.EOF { + return nil, err + } + + // user can presumably browse "up" to parent folder if path is longer than "/" + canGoUp := len(urlPath) > 1 + + return fsrv.directoryListing(ctx, fileSystem, stat.ModTime(), files, canGoUp, root, urlPath, repl), nil +} + +// browseApplyQueryParams applies query parameters to the listing. +// It mutates the listing and may set cookies. +func (fsrv *FileServer) browseApplyQueryParams(w http.ResponseWriter, r *http.Request, listing *browseTemplateContext) { + var orderParam, sortParam string + + // The configs in Caddyfile have lower priority than Query params, + // so put it at first. + for idx, item := range fsrv.Browse.SortOptions { + // Only `sort` & `order`, 2 params are allowed + if idx >= 2 { + break + } + switch item { + case sortByName, sortByNameDirFirst, sortBySize, sortByTime: + sortParam = item + case sortOrderAsc, sortOrderDesc: + orderParam = item + } + } + + layoutParam := r.URL.Query().Get("layout") + limitParam := r.URL.Query().Get("limit") + offsetParam := r.URL.Query().Get("offset") + sortParamTmp := r.URL.Query().Get("sort") + if sortParamTmp != "" { + sortParam = sortParamTmp + } + orderParamTmp := r.URL.Query().Get("order") + if orderParamTmp != "" { + orderParam = orderParamTmp + } + + switch layoutParam { + case "list", "grid", "": + listing.Layout = layoutParam + default: + listing.Layout = "list" + } + + // figure out what to sort by + switch sortParam { + case "": + sortParam = sortByNameDirFirst + if sortCookie, sortErr := r.Cookie("sort"); sortErr == nil { + sortParam = sortCookie.Value + } + case sortByName, sortByNameDirFirst, sortBySize, sortByTime: + http.SetCookie(w, &http.Cookie{Name: "sort", Value: sortParam, Secure: r.TLS != nil}) + } + + // then figure out the order + switch orderParam { + case "": + orderParam = sortOrderAsc + if orderCookie, orderErr := r.Cookie("order"); orderErr == nil { + orderParam = orderCookie.Value + } + case sortOrderAsc, sortOrderDesc: + http.SetCookie(w, &http.Cookie{Name: "order", Value: orderParam, Secure: r.TLS != nil}) + } + + // finally, apply the sorting and limiting + listing.applySortAndLimit(sortParam, orderParam, limitParam, offsetParam) +} + +// makeBrowseTemplate creates the template to be used for directory listings. +func (fsrv *FileServer) makeBrowseTemplate(tplCtx *templateContext) (*template.Template, error) { + var tpl *template.Template + var err error + + if fsrv.Browse.TemplateFile != "" { + tpl = tplCtx.NewTemplate(path.Base(fsrv.Browse.TemplateFile)) + tpl, err = tpl.ParseFiles(fsrv.Browse.TemplateFile) + if err != nil { + return nil, fmt.Errorf("parsing browse template file: %v", err) + } + } else { + tpl = tplCtx.NewTemplate("default_listing") + tpl, err = tpl.Parse(BrowseTemplate) + if err != nil { + return nil, fmt.Errorf("parsing default browse template: %v", err) + } + } + + return tpl, nil +} + +// isSymlinkTargetDir returns true if f's symbolic link target +// is a directory. +func (fsrv *FileServer) isSymlinkTargetDir(fileSystem fs.FS, f fs.FileInfo, root, urlPath string) bool { + if !isSymlink(f) { + return false + } + target := caddyhttp.SanitizedPathJoin(root, path.Join(urlPath, f.Name())) + targetInfo, err := fs.Stat(fileSystem, target) + if err != nil { + return false + } + return targetInfo.IsDir() +} + +// isSymlink return true if f is a symbolic link. +func isSymlink(f fs.FileInfo) bool { + return f.Mode()&os.ModeSymlink != 0 +} + +// templateContext powers the context used when evaluating the browse template. +// It combines browse-specific features with the standard templates handler +// features. +type templateContext struct { + templates.TemplateContext + *browseTemplateContext +} + +// bufPool is used to increase the efficiency of file listings. +var bufPool = sync.Pool{ + New: func() any { + return new(bytes.Buffer) + }, +} diff --git a/modules/caddyhttp/fileserver/browse.html b/modules/caddyhttp/fileserver/browse.html new file mode 100644 index 00000000000..d2d698197e6 --- /dev/null +++ b/modules/caddyhttp/fileserver/browse.html @@ -0,0 +1,1180 @@ +{{ $nonce := uuidv4 -}} +{{ $nonceAttribute := print "nonce=" (quote $nonce) -}} +{{ $csp := printf "default-src 'none'; img-src 'self'; object-src 'none'; base-uri 'none'; script-src 'nonce-%s'; style-src 'nonce-%s'; frame-ancestors 'self'; form-action 'self';" $nonce $nonce -}} +{{/* To disable the Content-Security-Policy, set this to false */}}{{ $enableCsp := true -}} +{{ if $enableCsp -}} + {{- .RespHeader.Set "Content-Security-Policy" $csp -}} +{{ end -}} +{{- define "icon"}} + {{- if .IsDir}} + {{- if .IsSymlink}} + + + + + + {{- else}} + + + + + {{- end}} + {{- else if or (eq .Name "LICENSE") (eq .Name "README")}} + + + + + + + {{- else if .HasExt ".jpg" ".jpeg" ".png" ".gif" ".webp" ".tiff" ".bmp" ".heif" ".heic" ".svg"}} + {{- if eq .Tpl.Layout "grid"}} + + {{- else}} + + + + + + + + {{- end}} + {{- else if .HasExt ".mp4" ".mov" ".m4v" ".mpeg" ".mpg" ".avi" ".ogg" ".webm" ".mkv" ".vob" ".gifv" ".3gp"}} + + + + + + + + + + + + {{- else if .HasExt ".mp3" ".m4a" ".aac" ".ogg" ".flac" ".wav" ".wma" ".midi" ".cda"}} + + + + + + + + {{- else if .HasExt ".pdf"}} + + + + + + + + + + {{- else if .HasExt ".csv" ".tsv"}} + + + + + + + + + {{- else if .HasExt ".txt" ".doc" ".docx" ".odt" ".fodt" ".rtf"}} + + + + + + + + + {{- else if .HasExt ".xls" ".xlsx" ".ods" ".fods"}} + + + + + + + + + {{- else if .HasExt ".ppt" ".pptx" ".odp" ".fodp"}} + + + + + + + + + + + {{- else if .HasExt ".zip" ".gz" ".xz" ".tar" ".7z" ".rar" ".xz" ".zst"}} + + + + + + + + + + + + {{- else if .HasExt ".deb" ".dpkg"}} + + + + + + {{- else if .HasExt ".rpm" ".exe" ".flatpak" ".appimage" ".jar" ".msi" ".apk"}} + + + + + + + + + {{- else if .HasExt ".ps1"}} + + + + + + + {{- else if .HasExt ".py" ".pyc" ".pyo"}} + + + + + + + + + {{- else if .HasExt ".bash" ".sh" ".com" ".bat" ".dll" ".so"}} + + + + + {{- else if .HasExt ".dmg"}} + + + + + + + + + {{- else if .HasExt ".iso" ".img"}} + + + + + + + + {{- else if .HasExt ".md" ".mdown" ".markdown"}} + + + + + + + {{- else if .HasExt ".ttf" ".otf" ".woff" ".woff2" ".eof"}} + + + + + + + + + {{- else if .HasExt ".go"}} + + + + + + + + + {{- else if .HasExt ".html" ".htm"}} + + + + + + + + + + + + + {{- else if .HasExt ".js"}} + + + + + + + + {{- else if .HasExt ".css"}} + + + + + + + + + {{- else if .HasExt ".json" ".json5" ".jsonc"}} + + + + + + + + {{- else if .HasExt ".ts"}} + + + + + + + + + + {{- else if .HasExt ".sql"}} + + + + + + + + + + + {{- else if .HasExt ".db" ".sqlite" ".bak" ".mdb"}} + + + + + + + {{- else if .HasExt ".eml" ".email" ".mailbox" ".mbox" ".msg"}} + + + + + + {{- else if .HasExt ".crt" ".pem" ".x509" ".cer" ".ca-bundle"}} + + + + + + + + + + {{- else if .HasExt ".key" ".keystore" ".jks" ".p12" ".pfx" ".pub"}} + + + + + + {{- else}} + {{- if .IsSymlink}} + + + + + + + + {{- else}} + + + + + + {{- end}} + {{- end}} +{{- end}} + + + + {{html .Name}} + + + + + +{{- if eq .Layout "grid"}} + +{{- end}} + + +
    +
    + +

    + {{range $i, $crumb := .Breadcrumbs}}{{html $crumb.Text}}{{if ne $i 0}}/{{end}}{{end}} +

    +
    +
    +
    +
    +
    +
    + + {{.NumDirs}} director{{if eq 1 .NumDirs}}y{{else}}ies{{end}} + + + {{.NumFiles}} file{{if ne 1 .NumFiles}}s{{end}} + + + {{.HumanTotalFileSize}} total + + {{- if ne 0 .Limit}} + + (of which only {{.Limit}} are displayed) + + {{- end}} +
    + + + + + + + List + + + + + + + + + + Grid + +
    +
    + {{- if eq .Layout "grid"}} + {{- range .Items}} +
    + + {{template "icon" .}} +
    {{html .Name}}
    +
    {{.HumanSize}}
    +
    +
    + {{- end}} + {{- else}} + + + + + + + + + + + + {{- if .CanGoUp}} + + + + + + + + {{- end}} + {{- range .Items}} + + + + {{- if .IsDir}} + + {{- else}} + + {{- end}} + + + + {{- end}} + +
    + {{- if and (eq .Sort "namedirfirst") (ne .Order "desc")}} + + + + + + + {{- else if and (eq .Sort "namedirfirst") (ne .Order "asc")}} + + + + + + + {{- else}} + + + + + + + {{- end}} + + {{- if and (eq .Sort "name") (ne .Order "desc")}} + + Name + + + + + + {{- else if and (eq .Sort "name") (ne .Order "asc")}} + + Name + + + + + + {{- else}} + + Name + + {{- end}} + +
    + + + + + + +
    +
    + {{- if and (eq .Sort "size") (ne .Order "desc")}} + + Size + + + + + + {{- else if and (eq .Sort "size") (ne .Order "asc")}} + + Size + + + + + + {{- else}} + + Size + + {{- end}} + + {{- if and (eq .Sort "time") (ne .Order "desc")}} + + Modified + + + + + + {{- else if and (eq .Sort "time") (ne .Order "asc")}} + + Modified + + + + + + {{- else}} + + Modified + + {{- end}} +
    + + + + + + Up + +
    + + {{template "icon" .}} + {{- if not .SymlinkPath}} + {{html .Name}} + {{- else}} + {{html .Name}} + + + + {{html .SymlinkPath}} + {{- end}} + + +
    +
    +
    + {{if .IsSymlink}}↱ {{end}}{{.HumanSize}} +
    +
    +
    + +
    + {{- end}} +
    +
    +
    + + + + + diff --git a/modules/caddyhttp/fileserver/browsetplcontext.go b/modules/caddyhttp/fileserver/browsetplcontext.go new file mode 100644 index 00000000000..b9489c6a6dc --- /dev/null +++ b/modules/caddyhttp/fileserver/browsetplcontext.go @@ -0,0 +1,383 @@ +// 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 fileserver + +import ( + "context" + "io/fs" + "net/url" + "os" + "path" + "path/filepath" + "slices" + "sort" + "strconv" + "strings" + "time" + + "github.com/dustin/go-humanize" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func (fsrv *FileServer) directoryListing(ctx context.Context, fileSystem fs.FS, parentModTime time.Time, entries []fs.DirEntry, canGoUp bool, root, urlPath string, repl *caddy.Replacer) *browseTemplateContext { + filesToHide := fsrv.transformHidePaths(repl) + + name, _ := url.PathUnescape(urlPath) + + tplCtx := &browseTemplateContext{ + Name: path.Base(name), + Path: urlPath, + CanGoUp: canGoUp, + lastModified: parentModTime, + } + + for _, entry := range entries { + if err := ctx.Err(); err != nil { + break + } + + name := entry.Name() + + if fileHidden(name, filesToHide) { + continue + } + + info, err := entry.Info() + if err != nil { + if c := fsrv.logger.Check(zapcore.ErrorLevel, "could not get info about directory entry"); c != nil { + c.Write(zap.String("name", entry.Name()), zap.String("root", root)) + } + continue + } + + // keep track of the most recently modified item in the listing + modTime := info.ModTime() + if tplCtx.lastModified.IsZero() || modTime.After(tplCtx.lastModified) { + tplCtx.lastModified = modTime + } + + isDir := entry.IsDir() || fsrv.isSymlinkTargetDir(fileSystem, info, root, urlPath) + + // add the slash after the escape of path to avoid escaping the slash as well + if isDir { + name += "/" + tplCtx.NumDirs++ + } else { + tplCtx.NumFiles++ + } + + size := info.Size() + + if !isDir { + // increase the total by the symlink's size, not the target's size, + // by incrementing before we follow the symlink + tplCtx.TotalFileSize += size + } + + fileIsSymlink := isSymlink(info) + symlinkPath := "" + if fileIsSymlink { + path := caddyhttp.SanitizedPathJoin(root, path.Join(urlPath, info.Name())) + fileInfo, err := fs.Stat(fileSystem, path) + if err == nil { + size = fileInfo.Size() + } + + if fsrv.Browse.RevealSymlinks { + symLinkTarget, err := filepath.EvalSymlinks(path) + if err == nil { + symlinkPath = symLinkTarget + } + } + + // An error most likely means the symlink target doesn't exist, + // which isn't entirely unusual and shouldn't fail the listing. + // In this case, just use the size of the symlink itself, which + // was already set above. + } + + if !isDir { + // increase the total including the symlink target's size + tplCtx.TotalFileSizeFollowingSymlinks += size + } + + u := url.URL{Path: "./" + name} // prepend with "./" to fix paths with ':' in the name + + tplCtx.Items = append(tplCtx.Items, fileInfo{ + IsDir: isDir, + IsSymlink: fileIsSymlink, + Name: name, + Size: size, + URL: u.String(), + ModTime: modTime.UTC(), + Mode: info.Mode(), + Tpl: tplCtx, // a reference up to the template context is useful + SymlinkPath: symlinkPath, + }) + } + + // this time is used for the Last-Modified header and comparing If-Modified-Since from client + // both are expected to be in UTC, so we convert to UTC here + // see: https://github.com/caddyserver/caddy/issues/6828 + tplCtx.lastModified = tplCtx.lastModified.UTC() + return tplCtx +} + +// browseTemplateContext provides the template context for directory listings. +type browseTemplateContext struct { + // The name of the directory (the last element of the path). + Name string `json:"name"` + + // The full path of the request. + Path string `json:"path"` + + // Whether the parent directory is browsable. + CanGoUp bool `json:"can_go_up"` + + // The items (files and folders) in the path. + Items []fileInfo `json:"items,omitempty"` + + // If ≠0 then Items starting from that many elements. + Offset int `json:"offset,omitempty"` + + // If ≠0 then Items have been limited to that many elements. + Limit int `json:"limit,omitempty"` + + // The number of directories in the listing. + NumDirs int `json:"num_dirs"` + + // The number of files (items that aren't directories) in the listing. + NumFiles int `json:"num_files"` + + // The total size of all files in the listing. Only includes the + // size of the files themselves, not the size of symlink targets + // (i.e. the calculation of this value does not follow symlinks). + TotalFileSize int64 `json:"total_file_size"` + + // The total size of all files in the listing, including the + // size of the files targeted by symlinks. + TotalFileSizeFollowingSymlinks int64 `json:"total_file_size_following_symlinks"` + + // Sort column used + Sort string `json:"sort,omitempty"` + + // Sorting order + Order string `json:"order,omitempty"` + + // Display format (list or grid) + Layout string `json:"layout,omitempty"` + + // The most recent file modification date in the listing. + // Used for HTTP header purposes. + lastModified time.Time +} + +// Breadcrumbs returns l.Path where every element maps +// the link to the text to display. +func (l browseTemplateContext) Breadcrumbs() []crumb { + if len(l.Path) == 0 { + return []crumb{} + } + + // skip trailing slash + lpath := l.Path + if lpath[len(lpath)-1] == '/' { + lpath = lpath[:len(lpath)-1] + } + parts := strings.Split(lpath, "/") + result := make([]crumb, len(parts)) + for i, p := range parts { + if i == 0 && p == "" { + p = "/" + } + // the directory name could include an encoded slash in its path, + // so the item name should be unescaped in the loop rather than unescaping the + // entire path outside the loop. + p, _ = url.PathUnescape(p) + lnk := strings.Repeat("../", len(parts)-i-1) + result[i] = crumb{Link: lnk, Text: p} + } + + return result +} + +func (l *browseTemplateContext) applySortAndLimit(sortParam, orderParam, limitParam string, offsetParam string) { + l.Sort = sortParam + l.Order = orderParam + + 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))) + } + } else { + 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)) + } + } + + if offsetParam != "" { + offset, _ := strconv.Atoi(offsetParam) + if offset > 0 && offset <= len(l.Items) { + l.Items = l.Items[offset:] + l.Offset = offset + } + } + + if limitParam != "" { + limit, _ := strconv.Atoi(limitParam) + + if limit > 0 && limit <= len(l.Items) { + l.Items = l.Items[:limit] + l.Limit = limit + } + } +} + +// crumb represents part of a breadcrumb menu, +// pairing a link with the text to display. +type crumb struct { + Link, Text string +} + +// fileInfo contains serializable information +// about a file or directory. +type fileInfo struct { + Name string `json:"name"` + Size int64 `json:"size"` + URL string `json:"url"` + ModTime time.Time `json:"mod_time"` + Mode os.FileMode `json:"mode"` + IsDir bool `json:"is_dir"` + IsSymlink bool `json:"is_symlink"` + SymlinkPath string `json:"symlink_path,omitempty"` + + // a pointer to the template context is useful inside nested templates + Tpl *browseTemplateContext `json:"-"` +} + +// HasExt returns true if the filename has any of the given suffixes, case-insensitive. +func (fi fileInfo) HasExt(exts ...string) bool { + return slices.ContainsFunc(exts, func(ext string) bool { + return strings.HasSuffix(strings.ToLower(fi.Name), strings.ToLower(ext)) + }) +} + +// 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)) +} + +// HumanTotalFileSize returns the total size of all files +// in the listing as a human-readable string in IEC format +// (i.e. power of 2 or base 1024). +func (btc browseTemplateContext) HumanTotalFileSize() string { + return humanize.IBytes(uint64(btc.TotalFileSize)) +} + +// HumanTotalFileSizeFollowingSymlinks is the same as HumanTotalFileSize +// except the returned value reflects the size of symlink targets. +func (btc browseTemplateContext) HumanTotalFileSizeFollowingSymlinks() string { + return humanize.IBytes(uint64(btc.TotalFileSizeFollowingSymlinks)) +} + +// HumanModTime returns the modified time of the file +// as a human-readable string given by format. +func (fi fileInfo) HumanModTime(format string) string { + return fi.ModTime.Format(format) +} + +type ( + byName browseTemplateContext + byNameDirFirst browseTemplateContext + bySize browseTemplateContext + byTime browseTemplateContext +) + +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] } + +func (l byName) Less(i, j int) bool { + return strings.ToLower(l.Items[i].Name) < strings.ToLower(l.Items[j].Name) +} + +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] } + +func (l byNameDirFirst) Less(i, j int) bool { + // sort by name if both are dir or file + if l.Items[i].IsDir == l.Items[j].IsDir { + return strings.ToLower(l.Items[i].Name) < strings.ToLower(l.Items[j].Name) + } + // sort dir ahead of file + return l.Items[i].IsDir +} + +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] } + +func (l bySize) Less(i, j int) bool { + const directoryOffset = -1 << 31 // = -math.MinInt32 + + iSize, jSize := l.Items[i].Size, l.Items[j].Size + + // directory sizes depend on the file system; to + // provide a consistent experience, put them up front + // and sort them by name + if l.Items[i].IsDir { + iSize = directoryOffset + } + if l.Items[j].IsDir { + jSize = directoryOffset + } + 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 +} + +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) } + +const ( + sortByName = "name" + sortByNameDirFirst = "namedirfirst" + sortBySize = "size" + sortByTime = "time" + + sortOrderAsc = "asc" + sortOrderDesc = "desc" +) diff --git a/modules/caddyhttp/fileserver/browsetplcontext_test.go b/modules/caddyhttp/fileserver/browsetplcontext_test.go new file mode 100644 index 00000000000..184196fa8c4 --- /dev/null +++ b/modules/caddyhttp/fileserver/browsetplcontext_test.go @@ -0,0 +1,106 @@ +// 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 fileserver + +import ( + "testing" +) + +func TestBreadcrumbs(t *testing.T) { + testdata := []struct { + path string + expected []crumb + }{ + {"", []crumb{}}, + {"/", []crumb{{Text: "/"}}}, + {"/foo/", []crumb{ + {Link: "../", Text: "/"}, + {Link: "", Text: "foo"}, + }}, + {"/foo/bar/", []crumb{ + {Link: "../../", Text: "/"}, + {Link: "../", Text: "foo"}, + {Link: "", Text: "bar"}, + }}, + {"/foo bar/", []crumb{ + {Link: "../", Text: "/"}, + {Link: "", Text: "foo bar"}, + }}, + {"/foo bar/baz/", []crumb{ + {Link: "../../", Text: "/"}, + {Link: "../", Text: "foo bar"}, + {Link: "", Text: "baz"}, + }}, + {"/100%25 test coverage/is a lie/", []crumb{ + {Link: "../../", Text: "/"}, + {Link: "../", Text: "100% test coverage"}, + {Link: "", Text: "is a lie"}, + }}, + {"/AC%2FDC/", []crumb{ + {Link: "../", Text: "/"}, + {Link: "", Text: "AC/DC"}, + }}, + {"/foo/%2e%2e%2f/bar", []crumb{ + {Link: "../../../", Text: "/"}, + {Link: "../../", Text: "foo"}, + {Link: "../", Text: "../"}, + {Link: "", Text: "bar"}, + }}, + {"/foo/../bar", []crumb{ + {Link: "../../../", Text: "/"}, + {Link: "../../", Text: "foo"}, + {Link: "../", Text: ".."}, + {Link: "", Text: "bar"}, + }}, + {"foo/bar/baz", []crumb{ + {Link: "../../", Text: "foo"}, + {Link: "../", Text: "bar"}, + {Link: "", Text: "baz"}, + }}, + {"/qux/quux/corge/", []crumb{ + {Link: "../../../", Text: "/"}, + {Link: "../../", Text: "qux"}, + {Link: "../", Text: "quux"}, + {Link: "", Text: "corge"}, + }}, + {"/مجلد/", []crumb{ + {Link: "../", Text: "/"}, + {Link: "", Text: "مجلد"}, + }}, + {"/مجلد-1/مجلد-2", []crumb{ + {Link: "../../", Text: "/"}, + {Link: "../", Text: "مجلد-1"}, + {Link: "", Text: "مجلد-2"}, + }}, + {"/مجلد%2F1", []crumb{ + {Link: "../", Text: "/"}, + {Link: "", Text: "مجلد/1"}, + }}, + } + + for testNum, d := range testdata { + l := browseTemplateContext{Path: d.path} + actual := l.Breadcrumbs() + if len(actual) != len(d.expected) { + t.Errorf("Test %d: Got %d components but expected %d; got: %+v", testNum, len(actual), len(d.expected), actual) + continue + } + for i, c := range actual { + if c != d.expected[i] { + t.Errorf("Test %d crumb %d: got %#v but expected %#v at index %d", testNum, i, c, d.expected[i], i) + } + } + } +} diff --git a/modules/caddyhttp/fileserver/caddyfile.go b/modules/caddyhttp/fileserver/caddyfile.go new file mode 100644 index 00000000000..80a37322bca --- /dev/null +++ b/modules/caddyhttp/fileserver/caddyfile.go @@ -0,0 +1,329 @@ +// 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 fileserver + +import ( + "path/filepath" + "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/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite" +) + +func init() { + httpcaddyfile.RegisterHandlerDirective("file_server", parseCaddyfile) + httpcaddyfile.RegisterDirective("try_files", parseTryFiles) +} + +// parseCaddyfile parses the file_server directive. +// See UnmarshalCaddyfile for the syntax. +func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + fsrv := new(FileServer) + err := fsrv.UnmarshalCaddyfile(h.Dispenser) + if err != nil { + return fsrv, err + } + err = fsrv.FinalizeUnmarshalCaddyfile(h) + if err != nil { + return nil, err + } + return fsrv, err +} + +// UnmarshalCaddyfile parses the file_server directive. It enables +// the static file server and configures it with this syntax: +// +// file_server [] [browse] { +// fs +// root +// hide +// index +// browse [] +// precompressed +// status +// disable_canonical_uris +// } +// +// The FinalizeUnmarshalCaddyfile method should be called after this +// to finalize setup of hidden Caddyfiles. +func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume directive name + + args := d.RemainingArgs() + switch len(args) { + case 0: + case 1: + if args[0] != "browse" { + return d.ArgErr() + } + fsrv.Browse = new(Browse) + default: + return d.ArgErr() + } + + for nesting := d.Nesting(); d.NextBlock(nesting); { + switch d.Val() { + case "fs": + if !d.NextArg() { + return d.ArgErr() + } + if fsrv.FileSystem != "" { + return d.Err("file system already specified") + } + fsrv.FileSystem = d.Val() + + case "hide": + fsrv.Hide = d.RemainingArgs() + if len(fsrv.Hide) == 0 { + return d.ArgErr() + } + + case "index": + fsrv.IndexNames = d.RemainingArgs() + if len(fsrv.IndexNames) == 0 { + return d.ArgErr() + } + + case "root": + if !d.Args(&fsrv.Root) { + return d.ArgErr() + } + + case "browse": + if fsrv.Browse != nil { + return d.Err("browsing is already configured") + } + fsrv.Browse = new(Browse) + d.Args(&fsrv.Browse.TemplateFile) + for nesting := d.Nesting(); d.NextBlock(nesting); { + switch d.Val() { + case "reveal_symlinks": + if fsrv.Browse.RevealSymlinks { + return d.Err("Symlinks path reveal is already enabled") + } + fsrv.Browse.RevealSymlinks = true + case "sort": + for d.NextArg() { + dVal := d.Val() + switch dVal { + case sortByName, sortByNameDirFirst, sortBySize, sortByTime, sortOrderAsc, sortOrderDesc: + fsrv.Browse.SortOptions = append(fsrv.Browse.SortOptions, dVal) + default: + return d.Errf("unknown sort option '%s'", dVal) + } + } + case "file_limit": + fileLimit := d.RemainingArgs() + if len(fileLimit) != 1 { + return d.Err("file_limit should have an integer value") + } + val, _ := strconv.Atoi(fileLimit[0]) + if fsrv.Browse.FileLimit != 0 { + return d.Err("file_limit is already enabled") + } + fsrv.Browse.FileLimit = val + default: + return d.Errf("unknown subdirective '%s'", d.Val()) + } + } + + case "precompressed": + fsrv.PrecompressedOrder = d.RemainingArgs() + if len(fsrv.PrecompressedOrder) == 0 { + fsrv.PrecompressedOrder = []string{"br", "zstd", "gzip"} + } + + for _, format := range fsrv.PrecompressedOrder { + modID := "http.precompressed." + format + mod, err := caddy.GetModule(modID) + if err != nil { + return d.Errf("getting module named '%s': %v", modID, err) + } + inst := mod.New() + precompress, ok := inst.(encode.Precompressed) + if !ok { + return d.Errf("module %s is not a precompressor; is %T", modID, inst) + } + if fsrv.PrecompressedRaw == nil { + fsrv.PrecompressedRaw = make(caddy.ModuleMap) + } + fsrv.PrecompressedRaw[format] = caddyconfig.JSON(precompress, nil) + } + + case "status": + if !d.NextArg() { + return d.ArgErr() + } + fsrv.StatusCode = caddyhttp.WeakString(d.Val()) + + case "disable_canonical_uris": + if d.NextArg() { + return d.ArgErr() + } + falseBool := false + fsrv.CanonicalURIs = &falseBool + + case "pass_thru": + if d.NextArg() { + return d.ArgErr() + } + fsrv.PassThru = true + + case "etag_file_extensions": + etagFileExtensions := d.RemainingArgs() + if len(etagFileExtensions) == 0 { + return d.ArgErr() + } + fsrv.EtagFileExtensions = etagFileExtensions + + default: + return d.Errf("unknown subdirective '%s'", d.Val()) + } + } + + return nil +} + +// FinalizeUnmarshalCaddyfile finalizes the Caddyfile parsing which +// requires having an httpcaddyfile.Helper to function, to setup hidden Caddyfiles. +func (fsrv *FileServer) FinalizeUnmarshalCaddyfile(h httpcaddyfile.Helper) error { + // Hide the Caddyfile (and any imported Caddyfiles). + // This needs to be done in here instead of UnmarshalCaddyfile + // because UnmarshalCaddyfile only has access to the dispenser + // and not the helper, and only the helper has access to the + // Caddyfiles function. + if configFiles := h.Caddyfiles(); len(configFiles) > 0 { + for _, file := range configFiles { + file = filepath.Clean(file) + if !fileHidden(file, fsrv.Hide) { + // if there's no path separator, the file server module will hide all + // files by that name, rather than a specific one; but we want to hide + // only this specific file, so ensure there's always a path separator + if !strings.Contains(file, separator) { + file = "." + separator + file + } + fsrv.Hide = append(fsrv.Hide, file) + } + } + } + return nil +} + +// parseTryFiles parses the try_files directive. It combines a file matcher +// with a rewrite directive, so this is not a standard handler directive. +// A try_files directive has this syntax (notice no matcher tokens accepted): +// +// try_files { +// policy first_exist|smallest_size|largest_size|most_recently_modified +// } +// +// and is basically shorthand for: +// +// @try_files file { +// try_files +// policy first_exist|smallest_size|largest_size|most_recently_modified +// } +// rewrite @try_files {http.matchers.file.relative} +// +// This directive rewrites request paths only, preserving any other part +// of the URI, unless the part is explicitly given in the file list. For +// example, if any of the files in the list have a query string: +// +// try_files {path} index.php?{query}&p={path} +// +// then the query string will not be treated as part of the file name; and +// if that file matches, the given query string will replace any query string +// that already exists on the request URI. +func parseTryFiles(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) { + if !h.Next() { + return nil, h.ArgErr() + } + + tryFiles := h.RemainingArgs() + if len(tryFiles) == 0 { + return nil, h.ArgErr() + } + + // parse out the optional try policy + var tryPolicy string + for h.NextBlock(0) { + switch h.Val() { + case "policy": + if tryPolicy != "" { + return nil, h.Err("try policy already configured") + } + if !h.NextArg() { + return nil, h.ArgErr() + } + tryPolicy = h.Val() + + switch tryPolicy { + case tryPolicyFirstExist, tryPolicyFirstExistFallback, tryPolicyLargestSize, tryPolicySmallestSize, tryPolicyMostRecentlyMod: + default: + return nil, h.Errf("unrecognized try policy: %s", tryPolicy) + } + } + } + + // makeRoute returns a route that tries the files listed in try + // and then rewrites to the matched file; userQueryString is + // appended to the rewrite rule. + makeRoute := func(try []string, userQueryString string) []httpcaddyfile.ConfigValue { + handler := rewrite.Rewrite{ + URI: "{http.matchers.file.relative}" + userQueryString, + } + matcherSet := caddy.ModuleMap{ + "file": h.JSON(MatchFile{TryFiles: try, TryPolicy: tryPolicy}), + } + return h.NewRoute(matcherSet, handler) + } + + var result []httpcaddyfile.ConfigValue + + // if there are query strings in the list, we have to split into + // a separate route for each item with a query string, because + // the rewrite is different for that item + try := make([]string, 0, len(tryFiles)) + for _, item := range tryFiles { + if idx := strings.Index(item, "?"); idx >= 0 { + if len(try) > 0 { + result = append(result, makeRoute(try, "")...) + try = []string{} + } + result = append(result, makeRoute([]string{item[:idx]}, item[idx:])...) + continue + } + // accumulate consecutive non-query-string parameters + try = append(try, item) + } + if len(try) > 0 { + result = append(result, makeRoute(try, "")...) + } + + // ensure that multiple routes (possible if rewrite targets + // have query strings, for example) are grouped together + // so only the first matching rewrite is performed (#2891) + h.GroupRoutes(result) + + return result, nil +} + +var _ caddyfile.Unmarshaler = (*FileServer)(nil) diff --git a/modules/caddyhttp/fileserver/command.go b/modules/caddyhttp/fileserver/command.go new file mode 100644 index 00000000000..a04d7cade07 --- /dev/null +++ b/modules/caddyhttp/fileserver/command.go @@ -0,0 +1,224 @@ +// 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 fileserver + +import ( + "encoding/json" + "fmt" + "io" + "log" + "os" + "strconv" + "time" + + "github.com/caddyserver/certmagic" + "github.com/spf13/cobra" + "go.uber.org/zap" + + caddycmd "github.com/caddyserver/caddy/v2/cmd" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode" + caddytpl "github.com/caddyserver/caddy/v2/modules/caddyhttp/templates" +) + +func init() { + caddycmd.RegisterCommand(caddycmd.Command{ + Name: "file-server", + Usage: "[--domain ] [--root ] [--listen ] [--browse] [--reveal-symlinks] [--access-log] [--precompressed]", + Short: "Spins up a production-ready file server", + Long: ` +A simple but production-ready file server. Useful for quick deployments, +demos, and development. + +The listener's socket address can be customized with the --listen flag. + +If a domain name is specified with --domain, the default listener address +will be changed to the HTTPS port and the server will use HTTPS. If using +a public domain, ensure A/AAAA records are properly configured before +using this option. + +By default, Zstandard and Gzip compression are enabled. Use --no-compress +to disable compression. + +If --browse is enabled, requests for folders without an index file will +respond with a file listing.`, + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("domain", "d", "", "Domain name at which to serve the files") + cmd.Flags().StringP("root", "r", "", "The path to the root of the site") + cmd.Flags().StringP("listen", "l", "", "The address to which to bind the listener") + cmd.Flags().BoolP("browse", "b", false, "Enable directory browsing") + cmd.Flags().BoolP("reveal-symlinks", "", false, "Show symlink paths when browse is enabled.") + cmd.Flags().BoolP("templates", "t", false, "Enable template rendering") + cmd.Flags().BoolP("access-log", "a", false, "Enable the access log") + cmd.Flags().BoolP("debug", "v", false, "Enable verbose debug logs") + cmd.Flags().IntP("file-limit", "f", defaultDirEntryLimit, "Max directories to read") + cmd.Flags().BoolP("no-compress", "", false, "Disable Zstandard and Gzip compression") + cmd.Flags().StringSliceP("precompressed", "p", []string{}, "Specify precompression file extensions. Compression preference implied from flag order.") + cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdFileServer) + cmd.AddCommand(&cobra.Command{ + Use: "export-template", + Short: "Exports the default file browser template", + Example: "caddy file-server export-template > browse.html", + RunE: func(cmd *cobra.Command, args []string) error { + _, err := io.WriteString(os.Stdout, BrowseTemplate) + return err + }, + }) + }, + }) +} + +func cmdFileServer(fs caddycmd.Flags) (int, error) { + caddy.TrapSignals() + + domain := fs.String("domain") + root := fs.String("root") + listen := fs.String("listen") + browse := fs.Bool("browse") + templates := fs.Bool("templates") + accessLog := fs.Bool("access-log") + fileLimit := fs.Int("file-limit") + debug := fs.Bool("debug") + revealSymlinks := fs.Bool("reveal-symlinks") + compress := !fs.Bool("no-compress") + precompressed, err := fs.GetStringSlice("precompressed") + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid precompressed flag: %v", err) + } + var handlers []json.RawMessage + + if compress { + zstd, err := caddy.GetModule("http.encoders.zstd") + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + gzip, err := caddy.GetModule("http.encoders.gzip") + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + handlers = append(handlers, caddyconfig.JSONModuleObject(encode.Encode{ + EncodingsRaw: caddy.ModuleMap{ + "zstd": caddyconfig.JSON(zstd.New(), nil), + "gzip": caddyconfig.JSON(gzip.New(), nil), + }, + Prefer: []string{"zstd", "gzip"}, + }, "handler", "encode", nil)) + } + + if templates { + handler := caddytpl.Templates{FileRoot: root} + handlers = append(handlers, caddyconfig.JSONModuleObject(handler, "handler", "templates", nil)) + } + + handler := FileServer{Root: root} + + if len(precompressed) != 0 { + // logic mirrors modules/caddyhttp/fileserver/caddyfile.go case "precompressed" + var order []string + for _, compression := range precompressed { + modID := "http.precompressed." + compression + mod, err := caddy.GetModule(modID) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("getting module named '%s': %v", modID, err) + } + inst := mod.New() + precompress, ok := inst.(encode.Precompressed) + if !ok { + return caddy.ExitCodeFailedStartup, fmt.Errorf("module %s is not a precompressor; is %T", modID, inst) + } + if handler.PrecompressedRaw == nil { + handler.PrecompressedRaw = make(caddy.ModuleMap) + } + handler.PrecompressedRaw[compression] = caddyconfig.JSON(precompress, nil) + order = append(order, compression) + } + handler.PrecompressedOrder = order + } + + if browse { + handler.Browse = &Browse{RevealSymlinks: revealSymlinks, FileLimit: fileLimit} + } + + handlers = append(handlers, caddyconfig.JSONModuleObject(handler, "handler", "file_server", nil)) + + route := caddyhttp.Route{HandlersRaw: handlers} + + if domain != "" { + route.MatcherSetsRaw = []caddy.ModuleMap{ + { + "host": caddyconfig.JSON(caddyhttp.MatchHost{domain}, nil), + }, + } + } + + server := &caddyhttp.Server{ + ReadHeaderTimeout: caddy.Duration(10 * time.Second), + IdleTimeout: caddy.Duration(30 * time.Second), + MaxHeaderBytes: 1024 * 10, + Routes: caddyhttp.RouteList{route}, + } + if listen == "" { + if domain == "" { + listen = ":80" + } else { + listen = ":" + strconv.Itoa(certmagic.HTTPSPort) + } + } + server.Listen = []string{listen} + if accessLog { + server.Logs = &caddyhttp.ServerLogConfig{} + } + + httpApp := caddyhttp.App{ + Servers: map[string]*caddyhttp.Server{"static": server}, + } + + var false bool + cfg := &caddy.Config{ + Admin: &caddy.AdminConfig{ + Disabled: true, + Config: &caddy.ConfigSettings{ + Persist: &false, + }, + }, + AppsRaw: caddy.ModuleMap{ + "http": caddyconfig.JSON(httpApp, nil), + }, + } + + if debug { + cfg.Logging = &caddy.Logging{ + Logs: map[string]*caddy.CustomLog{ + "default": { + BaseLog: caddy.BaseLog{Level: zap.DebugLevel.CapitalString()}, + }, + }, + } + } + + err = caddy.Run(cfg) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + log.Printf("Caddy serving static files on %s", listen) + + select {} +} diff --git a/modules/caddyhttp/fileserver/matcher.go b/modules/caddyhttp/fileserver/matcher.go new file mode 100644 index 00000000000..2bc665d4f92 --- /dev/null +++ b/modules/caddyhttp/fileserver/matcher.go @@ -0,0 +1,737 @@ +// 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 fileserver + +import ( + "fmt" + "io/fs" + "net/http" + "os" + "path" + "path/filepath" + "runtime" + "strconv" + "strings" + + "github.com/google/cel-go/cel" + "github.com/google/cel-go/common" + "github.com/google/cel-go/common/ast" + "github.com/google/cel-go/common/operators" + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" + "github.com/google/cel-go/parser" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func init() { + caddy.RegisterModule(MatchFile{}) +} + +// MatchFile is an HTTP request matcher that can match +// requests based upon file existence. +// +// Upon matching, three new placeholders will be made +// available: +// +// - `{http.matchers.file.relative}` The root-relative +// path of the file. This is often useful when rewriting +// requests. +// - `{http.matchers.file.absolute}` The absolute path +// of the matched file. +// - `{http.matchers.file.type}` Set to "directory" if +// the matched file is a directory, "file" otherwise. +// - `{http.matchers.file.remainder}` Set to the remainder +// of the path if the path was split by `split_path`. +// +// Even though file matching may depend on the OS path +// separator, the placeholder values always use /. +type MatchFile struct { + // The file system implementation to use. By default, the + // local disk file system will be used. + FileSystem string `json:"fs,omitempty"` + + // The root directory, used for creating absolute + // file paths, and required when working with + // relative paths; if not specified, `{http.vars.root}` + // will be used, if set; otherwise, the current + // directory is assumed. Accepts placeholders. + Root string `json:"root,omitempty"` + + // The list of files to try. Each path here is + // considered related to Root. If nil, the request + // URL's path will be assumed. Files and + // directories are treated distinctly, so to match + // a directory, the filepath MUST end in a forward + // slash `/`. To match a regular file, there must + // be no trailing slash. Accepts placeholders. If + // the policy is "first_exist", then an error may + // be triggered as a fallback by configuring "=" + // followed by a status code number, + // for example "=404". + TryFiles []string `json:"try_files,omitempty"` + + // How to choose a file in TryFiles. Can be: + // + // - first_exist + // - first_exist_fallback + // - smallest_size + // - largest_size + // - most_recently_modified + // + // Default is first_exist. + TryPolicy string `json:"try_policy,omitempty"` + + // A list of delimiters to use to split the path in two + // when trying files. If empty, no splitting will + // occur, and the path will be tried as-is. For each + // split value, the left-hand side of the split, + // including the split value, will be the path tried. + // For example, the path `/remote.php/dav/` using the + // split value `.php` would try the file `/remote.php`. + // Each delimiter must appear at the end of a URI path + // component in order to be used as a split delimiter. + SplitPath []string `json:"split_path,omitempty"` + + fsmap caddy.FileSystems + + logger *zap.Logger +} + +// CaddyModule returns the Caddy module information. +func (MatchFile) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.matchers.file", + New: func() caddy.Module { return new(MatchFile) }, + } +} + +// UnmarshalCaddyfile sets up the matcher from Caddyfile tokens. Syntax: +// +// file { +// root +// try_files +// try_policy first_exist|smallest_size|largest_size|most_recently_modified +// } +func (m *MatchFile) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + // iterate to merge multiple matchers into one + for d.Next() { + m.TryFiles = append(m.TryFiles, d.RemainingArgs()...) + for d.NextBlock(0) { + switch d.Val() { + case "root": + if !d.NextArg() { + return d.ArgErr() + } + m.Root = d.Val() + case "try_files": + m.TryFiles = append(m.TryFiles, d.RemainingArgs()...) + if len(m.TryFiles) == 0 { + return d.ArgErr() + } + case "try_policy": + if !d.NextArg() { + return d.ArgErr() + } + m.TryPolicy = d.Val() + case "split_path": + m.SplitPath = d.RemainingArgs() + if len(m.SplitPath) == 0 { + return d.ArgErr() + } + default: + return d.Errf("unrecognized subdirective: %s", d.Val()) + } + } + } + return nil +} + +// CELLibrary produces options that expose this matcher for use in CEL +// expression matchers. +// +// Example: +// +// expression file() +// expression file({http.request.uri.path}, '/index.php') +// expression file({'root': '/srv', 'try_files': [{http.request.uri.path}, '/index.php'], 'try_policy': 'first_exist', 'split_path': ['.php']}) +func (MatchFile) CELLibrary(ctx caddy.Context) (cel.Library, error) { + requestType := cel.ObjectType("http.Request") + + matcherFactory := func(data ref.Val) (caddyhttp.RequestMatcherWithError, error) { + values, err := caddyhttp.CELValueToMapStrList(data) + if err != nil { + return nil, err + } + + var root string + if len(values["root"]) > 0 { + root = values["root"][0] + } + + var fsName string + if len(values["fs"]) > 0 { + fsName = values["fs"][0] + } + + var try_policy string + if len(values["try_policy"]) > 0 { + try_policy = values["try_policy"][0] + } + + m := MatchFile{ + Root: root, + TryFiles: values["try_files"], + TryPolicy: try_policy, + SplitPath: values["split_path"], + FileSystem: fsName, + } + + err = m.Provision(ctx) + return m, err + } + + envOptions := []cel.EnvOption{ + cel.Macros(parser.NewGlobalVarArgMacro("file", celFileMatcherMacroExpander())), + cel.Function("file", cel.Overload("file_request_map", []*cel.Type{requestType, caddyhttp.CELTypeJSON}, cel.BoolType)), + cel.Function("file_request_map", + cel.Overload("file_request_map", []*cel.Type{requestType, caddyhttp.CELTypeJSON}, cel.BoolType), + cel.SingletonBinaryBinding(caddyhttp.CELMatcherRuntimeFunction("file_request_map", matcherFactory))), + } + + programOptions := []cel.ProgramOption{ + cel.CustomDecorator(caddyhttp.CELMatcherDecorator("file_request_map", matcherFactory)), + } + + return caddyhttp.NewMatcherCELLibrary(envOptions, programOptions), nil +} + +func celFileMatcherMacroExpander() parser.MacroExpander { + return func(eh parser.ExprHelper, target ast.Expr, args []ast.Expr) (ast.Expr, *common.Error) { + if len(args) == 0 { + return eh.NewCall("file", + eh.NewIdent(caddyhttp.CELRequestVarName), + eh.NewMap(), + ), nil + } + if len(args) == 1 { + arg := args[0] + if isCELStringLiteral(arg) || isCELCaddyPlaceholderCall(arg) { + return eh.NewCall("file", + eh.NewIdent(caddyhttp.CELRequestVarName), + eh.NewMap(eh.NewMapEntry( + eh.NewLiteral(types.String("try_files")), + eh.NewList(arg), + false, + )), + ), nil + } + if isCELTryFilesLiteral(arg) { + return eh.NewCall("file", eh.NewIdent(caddyhttp.CELRequestVarName), arg), nil + } + return nil, &common.Error{ + Location: eh.OffsetLocation(arg.ID()), + Message: "matcher requires either a map or string literal argument", + } + } + + for _, arg := range args { + if !(isCELStringLiteral(arg) || isCELCaddyPlaceholderCall(arg)) { + return nil, &common.Error{ + Location: eh.OffsetLocation(arg.ID()), + Message: "matcher only supports repeated string literal arguments", + } + } + } + return eh.NewCall("file", + eh.NewIdent(caddyhttp.CELRequestVarName), + eh.NewMap(eh.NewMapEntry( + eh.NewLiteral(types.String("try_files")), + eh.NewList(args...), + false, + )), + ), nil + } +} + +// Provision sets up m's defaults. +func (m *MatchFile) Provision(ctx caddy.Context) error { + m.logger = ctx.Logger() + + m.fsmap = ctx.Filesystems() + + if m.Root == "" { + m.Root = "{http.vars.root}" + } + + if m.FileSystem == "" { + m.FileSystem = "{http.vars.fs}" + } + + // if list of files to try was omitted entirely, assume URL path + // (use placeholder instead of r.URL.Path; see issue #4146) + if m.TryFiles == nil { + m.TryFiles = []string{"{http.request.uri.path}"} + } + return nil +} + +// Validate ensures m has a valid configuration. +func (m MatchFile) Validate() error { + switch m.TryPolicy { + case "", + tryPolicyFirstExist, + tryPolicyFirstExistFallback, + tryPolicyLargestSize, + tryPolicySmallestSize, + tryPolicyMostRecentlyMod: + default: + return fmt.Errorf("unknown try policy %s", m.TryPolicy) + } + return nil +} + +// Match returns true if r matches m. Returns true +// if a file was matched. If so, four placeholders +// will be available: +// - http.matchers.file.relative: Path to file relative to site root +// - http.matchers.file.absolute: Path to file including site root +// - http.matchers.file.type: file or directory +// - http.matchers.file.remainder: Portion remaining after splitting file path (if configured) +func (m MatchFile) Match(r *http.Request) bool { + match, err := m.selectFile(r) + if err != nil { + // nolint:staticcheck + caddyhttp.SetVar(r.Context(), caddyhttp.MatcherErrorVarKey, err) + } + return match +} + +// MatchWithError returns true if r matches m. +func (m MatchFile) MatchWithError(r *http.Request) (bool, error) { + return m.selectFile(r) +} + +// selectFile chooses a file according to m.TryPolicy by appending +// the paths in m.TryFiles to m.Root, with placeholder replacements. +func (m MatchFile) selectFile(r *http.Request) (bool, error) { + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + + root := filepath.Clean(repl.ReplaceAll(m.Root, ".")) + + fsName := repl.ReplaceAll(m.FileSystem, "") + + fileSystem, ok := m.fsmap.Get(fsName) + if !ok { + if c := m.logger.Check(zapcore.ErrorLevel, "use of unregistered filesystem"); c != nil { + c.Write(zap.String("fs", fsName)) + } + return false, nil + } + type matchCandidate struct { + fullpath, relative, splitRemainder string + } + + // makeCandidates evaluates placeholders in file and expands any glob expressions + // to build a list of file candidates. Special glob characters are escaped in + // placeholder replacements so globs cannot be expanded from placeholders, and + // globs are not evaluated on Windows because of its path separator character: + // escaping is not supported so we can't safely glob on Windows, or we can't + // support placeholders on Windows (pick one). (Actually, evaluating untrusted + // globs is not the end of the world since the file server will still hide any + // hidden files, it just might lead to unexpected behavior.) + makeCandidates := func(file string) []matchCandidate { + // first, evaluate placeholders in the file pattern + expandedFile, err := repl.ReplaceFunc(file, func(variable string, val any) (any, error) { + if runtime.GOOS == "windows" { + return val, nil + } + switch v := val.(type) { + case string: + return globSafeRepl.Replace(v), nil + case fmt.Stringer: + return globSafeRepl.Replace(v.String()), nil + } + return val, nil + }) + if err != nil { + if c := m.logger.Check(zapcore.ErrorLevel, "evaluating placeholders"); c != nil { + c.Write(zap.Error(err)) + } + + expandedFile = file // "oh well," I guess? + } + + // clean the path and split, if configured -- we must split before + // globbing so that the file system doesn't include the remainder + // ("afterSplit") in the filename; be sure to restore trailing slash + beforeSplit, afterSplit := m.firstSplit(path.Clean(expandedFile)) + if strings.HasSuffix(file, "/") { + beforeSplit += "/" + } + + // create the full path to the file by prepending the site root + fullPattern := caddyhttp.SanitizedPathJoin(root, beforeSplit) + + // expand glob expressions, but not on Windows because Glob() doesn't + // support escaping on Windows due to path separator) + var globResults []string + if runtime.GOOS == "windows" { + globResults = []string{fullPattern} // precious Windows + } else { + globResults, err = fs.Glob(fileSystem, fullPattern) + if err != nil { + if c := m.logger.Check(zapcore.ErrorLevel, "expanding glob"); c != nil { + c.Write(zap.Error(err)) + } + } + } + + // for each glob result, combine all the forms of the path + var candidates []matchCandidate + for _, result := range globResults { + candidates = append(candidates, matchCandidate{ + fullpath: result, + relative: strings.TrimPrefix(result, root), + splitRemainder: afterSplit, + }) + } + + return candidates + } + + // setPlaceholders creates the placeholders for the matched file + setPlaceholders := func(candidate matchCandidate, isDir bool) { + repl.Set("http.matchers.file.relative", filepath.ToSlash(candidate.relative)) + repl.Set("http.matchers.file.absolute", filepath.ToSlash(candidate.fullpath)) + repl.Set("http.matchers.file.remainder", filepath.ToSlash(candidate.splitRemainder)) + + fileType := "file" + if isDir { + fileType = "directory" + } + repl.Set("http.matchers.file.type", fileType) + } + + // match file according to the configured policy + switch m.TryPolicy { + case "", tryPolicyFirstExist, tryPolicyFirstExistFallback: + maxI := -1 + if m.TryPolicy == tryPolicyFirstExistFallback { + maxI = len(m.TryFiles) - 1 + } + + for i, pattern := range m.TryFiles { + // If the pattern is a status code, emit an error, + // which short-circuits the middleware pipeline and + // writes an HTTP error response. + if err := parseErrorCode(pattern); err != nil { + return false, err + } + + candidates := makeCandidates(pattern) + for _, c := range candidates { + // Skip the IO if using fallback policy and it's the latest item + if i == maxI { + setPlaceholders(c, false) + + return true, nil + } + + if info, exists := m.strictFileExists(fileSystem, c.fullpath); exists { + setPlaceholders(c, info.IsDir()) + return true, nil + } + } + } + + case tryPolicyLargestSize: + var largestSize int64 + var largest matchCandidate + var largestInfo os.FileInfo + for _, pattern := range m.TryFiles { + candidates := makeCandidates(pattern) + for _, c := range candidates { + info, err := fs.Stat(fileSystem, c.fullpath) + if err == nil && info.Size() > largestSize { + largestSize = info.Size() + largest = c + largestInfo = info + } + } + } + if largestInfo == nil { + return false, nil + } + setPlaceholders(largest, largestInfo.IsDir()) + return true, nil + + case tryPolicySmallestSize: + var smallestSize int64 + var smallest matchCandidate + var smallestInfo os.FileInfo + for _, pattern := range m.TryFiles { + candidates := makeCandidates(pattern) + for _, c := range candidates { + info, err := fs.Stat(fileSystem, c.fullpath) + if err == nil && (smallestSize == 0 || info.Size() < smallestSize) { + smallestSize = info.Size() + smallest = c + smallestInfo = info + } + } + } + if smallestInfo == nil { + return false, nil + } + setPlaceholders(smallest, smallestInfo.IsDir()) + return true, nil + + case tryPolicyMostRecentlyMod: + var recent matchCandidate + var recentInfo os.FileInfo + for _, pattern := range m.TryFiles { + candidates := makeCandidates(pattern) + for _, c := range candidates { + info, err := fs.Stat(fileSystem, c.fullpath) + if err == nil && + (recentInfo == nil || info.ModTime().After(recentInfo.ModTime())) { + recent = c + recentInfo = info + } + } + } + if recentInfo == nil { + return false, nil + } + setPlaceholders(recent, recentInfo.IsDir()) + return true, nil + } + + return false, nil +} + +// parseErrorCode checks if the input is a status +// code number, prefixed by "=", and returns an +// error if so. +func parseErrorCode(input string) error { + if len(input) > 1 && input[0] == '=' { + code, err := strconv.Atoi(input[1:]) + if err != nil || code < 100 || code > 999 { + return nil + } + return caddyhttp.Error(code, fmt.Errorf("%s", input[1:])) + } + return nil +} + +// strictFileExists returns true if file exists +// and matches the convention of the given file +// path. If the path ends in a forward slash, +// the file must also be a directory; if it does +// NOT end in a forward slash, the file must NOT +// be a directory. +func (m MatchFile) strictFileExists(fileSystem fs.FS, file string) (os.FileInfo, bool) { + info, err := fs.Stat(fileSystem, file) + if err != nil { + // in reality, this can be any error + // such as permission or even obscure + // ones like "is not a directory" (when + // trying to stat a file within a file); + // in those cases we can't be sure if + // the file exists, so we just treat any + // error as if it does not exist; see + // https://stackoverflow.com/a/12518877/1048862 + return nil, false + } + if strings.HasSuffix(file, separator) { + // by convention, file paths ending + // in a path separator must be a directory + return info, info.IsDir() + } + // by convention, file paths NOT ending + // in a path separator must NOT be a directory + return info, !info.IsDir() +} + +// firstSplit returns the first result where the path +// can be split in two by a value in m.SplitPath. The +// return values are the first piece of the path that +// ends with the split substring and the remainder. +// If the path cannot be split, the path is returned +// as-is (with no remainder). +func (m MatchFile) firstSplit(path string) (splitPart, remainder string) { + for _, split := range m.SplitPath { + if idx := indexFold(path, split); idx > -1 { + pos := idx + len(split) + // skip the split if it's not the final part of the filename + if pos != len(path) && !strings.HasPrefix(path[pos:], "/") { + continue + } + return path[:pos], path[pos:] + } + } + return path, "" +} + +// There is no strings.IndexFold() function like there is strings.EqualFold(), +// but we can use strings.EqualFold() to build our own case-insensitive +// substring search (as of Go 1.14). +func indexFold(haystack, needle string) int { + nlen := len(needle) + for i := 0; i+nlen < len(haystack); i++ { + if strings.EqualFold(haystack[i:i+nlen], needle) { + return i + } + } + return -1 +} + +// isCELTryFilesLiteral returns whether the expression resolves to a map literal containing +// only string keys with or a placeholder call. +func isCELTryFilesLiteral(e ast.Expr) bool { + switch e.Kind() { + case ast.MapKind: + mapExpr := e.AsMap() + for _, entry := range mapExpr.Entries() { + mapKey := entry.AsMapEntry().Key() + mapVal := entry.AsMapEntry().Value() + if !isCELStringLiteral(mapKey) { + return false + } + mapKeyStr := mapKey.AsLiteral().ConvertToType(types.StringType).Value() + if mapKeyStr == "try_files" || mapKeyStr == "split_path" { + if !isCELStringListLiteral(mapVal) { + return false + } + } else if mapKeyStr == "try_policy" || mapKeyStr == "root" { + if !(isCELStringExpr(mapVal)) { + return false + } + } else { + return false + } + } + return true + + case ast.UnspecifiedExprKind, ast.CallKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.SelectKind, ast.StructKind: + // appeasing the linter :) + } + return false +} + +// isCELStringExpr indicates whether the expression is a supported string expression +func isCELStringExpr(e ast.Expr) bool { + return isCELStringLiteral(e) || isCELCaddyPlaceholderCall(e) || isCELConcatCall(e) +} + +// isCELStringLiteral returns whether the expression is a CEL string literal. +func isCELStringLiteral(e ast.Expr) bool { + switch e.Kind() { + case ast.LiteralKind: + constant := e.AsLiteral() + switch constant.Type() { + case types.StringType: + return true + } + case ast.UnspecifiedExprKind, ast.CallKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.MapKind, ast.SelectKind, ast.StructKind: + // appeasing the linter :) + } + return false +} + +// isCELCaddyPlaceholderCall returns whether the expression is a caddy placeholder call. +func isCELCaddyPlaceholderCall(e ast.Expr) bool { + switch e.Kind() { + case ast.CallKind: + call := e.AsCall() + if call.FunctionName() == caddyhttp.CELPlaceholderFuncName { + return true + } + case ast.UnspecifiedExprKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.MapKind, ast.SelectKind, ast.StructKind: + // appeasing the linter :) + } + return false +} + +// isCELConcatCall tests whether the expression is a concat function (+) with string, placeholder, or +// other concat call arguments. +func isCELConcatCall(e ast.Expr) bool { + switch e.Kind() { + case ast.CallKind: + call := e.AsCall() + if call.Target().Kind() != ast.UnspecifiedExprKind { + return false + } + if call.FunctionName() != operators.Add { + return false + } + for _, arg := range call.Args() { + if !isCELStringExpr(arg) { + return false + } + } + return true + case ast.UnspecifiedExprKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.MapKind, ast.SelectKind, ast.StructKind: + // appeasing the linter :) + } + return false +} + +// isCELStringListLiteral returns whether the expression resolves to a list literal +// containing only string constants or a placeholder call. +func isCELStringListLiteral(e ast.Expr) bool { + switch e.Kind() { + case ast.ListKind: + list := e.AsList() + for _, elem := range list.Elements() { + if !isCELStringExpr(elem) { + return false + } + } + return true + case ast.UnspecifiedExprKind, ast.CallKind, ast.ComprehensionKind, ast.IdentKind, ast.LiteralKind, ast.MapKind, ast.SelectKind, ast.StructKind: + // appeasing the linter :) + } + return false +} + +// globSafeRepl replaces special glob characters with escaped +// equivalents. Note that the filepath godoc states that +// escaping is not done on Windows because of the separator. +var globSafeRepl = strings.NewReplacer( + "*", "\\*", + "[", "\\[", + "?", "\\?", +) + +const ( + tryPolicyFirstExist = "first_exist" + tryPolicyFirstExistFallback = "first_exist_fallback" + tryPolicyLargestSize = "largest_size" + tryPolicySmallestSize = "smallest_size" + tryPolicyMostRecentlyMod = "most_recently_modified" +) + +// Interface guards +var ( + _ caddy.Validator = (*MatchFile)(nil) + _ caddyhttp.RequestMatcherWithError = (*MatchFile)(nil) + _ caddyhttp.CELLibraryProducer = (*MatchFile)(nil) +) diff --git a/modules/caddyhttp/fileserver/matcher_test.go b/modules/caddyhttp/fileserver/matcher_test.go new file mode 100644 index 00000000000..b6697b9d880 --- /dev/null +++ b/modules/caddyhttp/fileserver/matcher_test.go @@ -0,0 +1,418 @@ +// 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 fileserver + +import ( + "context" + "net/http" + "net/http/httptest" + "net/url" + "os" + "runtime" + "testing" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/internal/filesystems" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func TestFileMatcher(t *testing.T) { + // Windows doesn't like colons in files names + isWindows := runtime.GOOS == "windows" + if !isWindows { + filename := "with:in-name.txt" + f, err := os.Create("./testdata/" + filename) + if err != nil { + t.Fail() + return + } + t.Cleanup(func() { + os.Remove("./testdata/" + filename) + }) + f.WriteString(filename) + f.Close() + } + + for i, tc := range []struct { + path string + expectedPath string + expectedType string + matched bool + }{ + { + path: "/foo.txt", + expectedPath: "/foo.txt", + expectedType: "file", + matched: true, + }, + { + path: "/foo.txt/", + expectedPath: "/foo.txt", + expectedType: "file", + matched: true, + }, + { + path: "/foo.txt?a=b", + expectedPath: "/foo.txt", + expectedType: "file", + matched: true, + }, + { + path: "/foodir", + expectedPath: "/foodir/", + expectedType: "directory", + matched: true, + }, + { + path: "/foodir/", + expectedPath: "/foodir/", + expectedType: "directory", + matched: true, + }, + { + path: "/foodir/foo.txt", + expectedPath: "/foodir/foo.txt", + expectedType: "file", + matched: true, + }, + { + path: "/missingfile.php", + matched: false, + }, + { + path: "ملف.txt", // the path file name is not escaped + expectedPath: "/ملف.txt", + expectedType: "file", + matched: true, + }, + { + path: url.PathEscape("ملف.txt"), // singly-escaped path + expectedPath: "/ملف.txt", + expectedType: "file", + matched: true, + }, + { + path: url.PathEscape(url.PathEscape("ملف.txt")), // doubly-escaped path + expectedPath: "/%D9%85%D9%84%D9%81.txt", + expectedType: "file", + matched: true, + }, + { + path: "./with:in-name.txt", // browsers send the request with the path as such + expectedPath: "/with:in-name.txt", + expectedType: "file", + matched: !isWindows, + }, + } { + m := &MatchFile{ + fsmap: &filesystems.FilesystemMap{}, + Root: "./testdata", + TryFiles: []string{"{http.request.uri.path}", "{http.request.uri.path}/"}, + } + + u, err := url.Parse(tc.path) + if err != nil { + t.Errorf("Test %d: parsing path: %v", i, err) + } + + req := &http.Request{URL: u} + repl := caddyhttp.NewTestReplacer(req) + + result, err := m.MatchWithError(req) + if err != nil { + t.Errorf("Test %d: unexpected error: %v", i, err) + } + if result != tc.matched { + t.Errorf("Test %d: expected match=%t, got %t", i, tc.matched, result) + } + + rel, ok := repl.Get("http.matchers.file.relative") + if !ok && result { + t.Errorf("Test %d: expected replacer value", i) + } + if !result { + continue + } + + if rel != tc.expectedPath { + t.Errorf("Test %d: actual path: %v, expected: %v", i, rel, tc.expectedPath) + } + + fileType, _ := repl.Get("http.matchers.file.type") + if fileType != tc.expectedType { + t.Errorf("Test %d: actual file type: %v, expected: %v", i, fileType, tc.expectedType) + } + } +} + +func TestPHPFileMatcher(t *testing.T) { + for i, tc := range []struct { + path string + expectedPath string + expectedType string + matched bool + }{ + { + path: "/index.php", + expectedPath: "/index.php", + expectedType: "file", + matched: true, + }, + { + path: "/index.php/somewhere", + expectedPath: "/index.php", + expectedType: "file", + matched: true, + }, + { + path: "/remote.php", + expectedPath: "/remote.php", + expectedType: "file", + matched: true, + }, + { + path: "/remote.php/somewhere", + expectedPath: "/remote.php", + expectedType: "file", + matched: true, + }, + { + path: "/missingfile.php", + matched: false, + }, + { + path: "/notphp.php.txt", + expectedPath: "/notphp.php.txt", + expectedType: "file", + matched: true, + }, + { + path: "/notphp.php.txt/", + expectedPath: "/notphp.php.txt", + expectedType: "file", + matched: true, + }, + { + path: "/notphp.php.txt.suffixed", + matched: false, + }, + { + path: "/foo.php.php/index.php", + expectedPath: "/foo.php.php/index.php", + expectedType: "file", + matched: true, + }, + { + // See https://github.com/caddyserver/caddy/issues/3623 + path: "/%E2%C3", + expectedPath: "/%E2%C3", + expectedType: "file", + matched: false, + }, + { + path: "/index.php?path={path}&{query}", + expectedPath: "/index.php", + expectedType: "file", + matched: true, + }, + } { + m := &MatchFile{ + fsmap: &filesystems.FilesystemMap{}, + Root: "./testdata", + TryFiles: []string{"{http.request.uri.path}", "{http.request.uri.path}/index.php"}, + SplitPath: []string{".php"}, + } + + u, err := url.Parse(tc.path) + if err != nil { + t.Errorf("Test %d: parsing path: %v", i, err) + } + + req := &http.Request{URL: u} + repl := caddyhttp.NewTestReplacer(req) + + result, err := m.MatchWithError(req) + if err != nil { + t.Errorf("Test %d: unexpected error: %v", i, err) + } + if result != tc.matched { + t.Errorf("Test %d: expected match=%t, got %t", i, tc.matched, result) + } + + rel, ok := repl.Get("http.matchers.file.relative") + if !ok && result { + t.Errorf("Test %d: expected replacer value", i) + } + if !result { + continue + } + + if rel != tc.expectedPath { + t.Errorf("Test %d: actual path: %v, expected: %v", i, rel, tc.expectedPath) + } + + fileType, _ := repl.Get("http.matchers.file.type") + if fileType != tc.expectedType { + t.Errorf("Test %d: actual file type: %v, expected: %v", i, fileType, tc.expectedType) + } + } +} + +func TestFirstSplit(t *testing.T) { + m := MatchFile{ + SplitPath: []string{".php"}, + fsmap: &filesystems.FilesystemMap{}, + } + actual, remainder := m.firstSplit("index.PHP/somewhere") + expected := "index.PHP" + expectedRemainder := "/somewhere" + if actual != expected { + t.Errorf("Expected split %s but got %s", expected, actual) + } + if remainder != expectedRemainder { + t.Errorf("Expected remainder %s but got %s", expectedRemainder, remainder) + } +} + +var expressionTests = []struct { + name string + expression *caddyhttp.MatchExpression + urlTarget string + httpMethod string + httpHeader *http.Header + wantErr bool + wantResult bool + clientCertificate []byte + expectedPath string +}{ + { + name: "file error no args (MatchFile)", + expression: &caddyhttp.MatchExpression{ + Expr: `file()`, + }, + urlTarget: "https://example.com/foo.txt", + wantResult: true, + }, + { + name: "file error bad try files (MatchFile)", + expression: &caddyhttp.MatchExpression{ + Expr: `file({"try_file": ["bad_arg"]})`, + }, + urlTarget: "https://example.com/foo", + wantErr: true, + }, + { + name: "file match short pattern index.php (MatchFile)", + expression: &caddyhttp.MatchExpression{ + Expr: `file("index.php")`, + }, + urlTarget: "https://example.com/foo", + wantResult: true, + }, + { + name: "file match short pattern foo.txt (MatchFile)", + expression: &caddyhttp.MatchExpression{ + Expr: `file({http.request.uri.path})`, + }, + urlTarget: "https://example.com/foo.txt", + wantResult: true, + }, + { + name: "file match index.php (MatchFile)", + expression: &caddyhttp.MatchExpression{ + Expr: `file({"root": "./testdata", "try_files": [{http.request.uri.path}, "/index.php"]})`, + }, + urlTarget: "https://example.com/foo", + wantResult: true, + }, + { + name: "file match long pattern foo.txt (MatchFile)", + expression: &caddyhttp.MatchExpression{ + Expr: `file({"root": "./testdata", "try_files": [{http.request.uri.path}]})`, + }, + urlTarget: "https://example.com/foo.txt", + wantResult: true, + }, + { + name: "file match long pattern foo.txt with concatenation (MatchFile)", + expression: &caddyhttp.MatchExpression{ + Expr: `file({"root": ".", "try_files": ["./testdata" + {http.request.uri.path}]})`, + }, + urlTarget: "https://example.com/foo.txt", + wantResult: true, + }, + { + name: "file not match long pattern (MatchFile)", + expression: &caddyhttp.MatchExpression{ + Expr: `file({"root": "./testdata", "try_files": [{http.request.uri.path}]})`, + }, + urlTarget: "https://example.com/nopenope.txt", + wantResult: false, + }, + { + name: "file match long pattern foo.txt with try_policy (MatchFile)", + expression: &caddyhttp.MatchExpression{ + Expr: `file({"root": "./testdata", "try_policy": "largest_size", "try_files": ["foo.txt", "large.txt"]})`, + }, + urlTarget: "https://example.com/", + wantResult: true, + expectedPath: "/large.txt", + }, +} + +func TestMatchExpressionMatch(t *testing.T) { + for _, tst := range expressionTests { + tc := tst + t.Run(tc.name, func(t *testing.T) { + caddyCtx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + err := tc.expression.Provision(caddyCtx) + if err != nil { + if !tc.wantErr { + t.Errorf("MatchExpression.Provision() error = %v, wantErr %v", err, tc.wantErr) + } + return + } + + req := httptest.NewRequest(tc.httpMethod, tc.urlTarget, nil) + if tc.httpHeader != nil { + req.Header = *tc.httpHeader + } + repl := caddyhttp.NewTestReplacer(req) + repl.Set("http.vars.root", "./testdata") + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + req = req.WithContext(ctx) + + matches, err := tc.expression.MatchWithError(req) + if err != nil { + t.Errorf("MatchExpression.Match() error = %v", err) + return + } + if matches != tc.wantResult { + t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tc.wantResult, tc.expression.Expr) + } + + if tc.expectedPath != "" { + path, ok := repl.Get("http.matchers.file.relative") + if !ok { + t.Errorf("MatchExpression.Match() expected to return path '%s', but got none", tc.expectedPath) + } + if path != tc.expectedPath { + t.Errorf("MatchExpression.Match() expected to return path '%s', but got '%s'", tc.expectedPath, path) + } + } + }) + } +} diff --git a/modules/caddyhttp/fileserver/staticfiles.go b/modules/caddyhttp/fileserver/staticfiles.go new file mode 100644 index 00000000000..2b0caecfc99 --- /dev/null +++ b/modules/caddyhttp/fileserver/staticfiles.go @@ -0,0 +1,802 @@ +// 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 fileserver + +import ( + "bytes" + "errors" + "fmt" + "io" + "io/fs" + weakrand "math/rand" + "mime" + "net/http" + "os" + "path" + "path/filepath" + "runtime" + "strconv" + "strings" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode" +) + +func init() { + caddy.RegisterModule(FileServer{}) +} + +// FileServer implements a handler that serves static files. +// +// The path of the file to serve is constructed by joining the site root +// and the sanitized request path. Any and all files within the root and +// links with targets outside the site root may therefore be accessed. +// For example, with a site root of `/www`, requests to `/foo/bar.txt` +// will serve the file at `/www/foo/bar.txt`. +// +// The request path is sanitized using the Go standard library's +// path.Clean() function (https://pkg.go.dev/path#Clean) before being +// joined to the root. Request paths must be valid and well-formed. +// +// For requests that access directories instead of regular files, +// Caddy will attempt to serve an index file if present. For example, +// a request to `/dir/` will attempt to serve `/dir/index.html` if +// it exists. The index file names to try are configurable. If a +// requested directory does not have an index file, Caddy writes a +// 404 response. Alternatively, file browsing can be enabled with +// the "browse" parameter which shows a list of files when directories +// are requested if no index file is present. If "browse" is enabled, +// Caddy may serve a JSON array of the directory listing when the `Accept` +// header mentions `application/json` with the following structure: +// +// [{ +// "name": "", +// "size": 0, +// "url": "", +// "mod_time": "", +// "mode": 0, +// "is_dir": false, +// "is_symlink": false +// }] +// +// with the `url` being relative to the request path and `mod_time` in the RFC 3339 format +// with sub-second precision. For any other value for the `Accept` header, the +// respective browse template is executed with `Content-Type: text/html`. +// +// By default, this handler will canonicalize URIs so that requests to +// directories end with a slash, but requests to regular files do not. +// This is enforced with HTTP redirects automatically and can be disabled. +// Canonicalization redirects are not issued, however, if a URI rewrite +// modified the last component of the path (the filename). +// +// This handler sets the Etag and Last-Modified headers for static files. +// It does not perform MIME sniffing to determine Content-Type based on +// contents, but does use the extension (if known); see the Go docs for +// details: https://pkg.go.dev/mime#TypeByExtension +// +// The file server properly handles requests with If-Match, +// If-Unmodified-Since, If-Modified-Since, If-None-Match, Range, and +// If-Range headers. It includes the file's modification time in the +// Last-Modified header of the response. +type FileServer struct { + // The file system implementation to use. By default, Caddy uses the local + // disk file system. + // + // if a non default filesystem is used, it must be first be registered in the globals section. + FileSystem string `json:"fs,omitempty"` + + // The path to the root of the site. Default is `{http.vars.root}` if set, + // or current working directory otherwise. This should be a trusted value. + // + // Note that a site root is not a sandbox. Although the file server does + // sanitize the request URI to prevent directory traversal, files (including + // links) within the site root may be directly accessed based on the request + // path. Files and folders within the root should be secure and trustworthy. + Root string `json:"root,omitempty"` + + // A list of files or folders to hide; the file server will pretend as if + // they don't exist. Accepts globular patterns like `*.ext` or `/foo/*/bar` + // as well as placeholders. Because site roots can be dynamic, this list + // uses file system paths, not request paths. To clarify, the base of + // relative paths is the current working directory, NOT the site root. + // + // Entries without a path separator (`/` or `\` depending on OS) will match + // any file or directory of that name regardless of its path. To hide only a + // specific file with a name that may not be unique, always use a path + // separator. For example, to hide all files or folder trees named "hidden", + // put "hidden" in the list. To hide only ./hidden, put "./hidden" in the list. + // + // When possible, all paths are resolved to their absolute form before + // comparisons are made. For maximum clarity and explictness, use complete, + // absolute paths; or, for greater portability, use relative paths instead. + Hide []string `json:"hide,omitempty"` + + // The names of files to try as index files if a folder is requested. + // Default: index.html, index.txt. + IndexNames []string `json:"index_names,omitempty"` + + // Enables file listings if a directory was requested and no index + // file is present. + Browse *Browse `json:"browse,omitempty"` + + // Use redirects to enforce trailing slashes for directories, or to + // remove trailing slash from URIs for files. Default is true. + // + // Canonicalization will not happen if the last element of the request's + // path (the filename) is changed in an internal rewrite, to avoid + // clobbering the explicit rewrite with implicit behavior. + CanonicalURIs *bool `json:"canonical_uris,omitempty"` + + // Override the status code written when successfully serving a file. + // Particularly useful when explicitly serving a file as display for + // an error, like a 404 page. A placeholder may be used. By default, + // the status code will typically be 200, or 206 for partial content. + StatusCode caddyhttp.WeakString `json:"status_code,omitempty"` + + // If pass-thru mode is enabled and a requested file is not found, + // it will invoke the next handler in the chain instead of returning + // a 404 error. By default, this is false (disabled). + PassThru bool `json:"pass_thru,omitempty"` + + // Selection of encoders to use to check for precompressed files. + PrecompressedRaw caddy.ModuleMap `json:"precompressed,omitempty" caddy:"namespace=http.precompressed"` + + // If the client has no strong preference (q-factor), choose these encodings in order. + // If no order specified here, the first encoding from the Accept-Encoding header + // that both client and server support is used + PrecompressedOrder []string `json:"precompressed_order,omitempty"` + precompressors map[string]encode.Precompressed + + // List of file extensions to try to read Etags from. + // If set, file Etags will be read from sidecar files + // with any of these suffixes, instead of generating + // our own Etag. + EtagFileExtensions []string `json:"etag_file_extensions,omitempty"` + + fsmap caddy.FileSystems + + logger *zap.Logger +} + +// CaddyModule returns the Caddy module information. +func (FileServer) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.file_server", + New: func() caddy.Module { return new(FileServer) }, + } +} + +// Provision sets up the static files responder. +func (fsrv *FileServer) Provision(ctx caddy.Context) error { + fsrv.logger = ctx.Logger() + + fsrv.fsmap = ctx.Filesystems() + + if fsrv.FileSystem == "" { + fsrv.FileSystem = "{http.vars.fs}" + } + + if fsrv.Root == "" { + fsrv.Root = "{http.vars.root}" + } + + if fsrv.IndexNames == nil { + fsrv.IndexNames = defaultIndexNames + } + + // for hide paths that are static (i.e. no placeholders), we can transform them into + // absolute paths before the server starts for very slight performance improvement + for i, h := range fsrv.Hide { + if !strings.Contains(h, "{") && strings.Contains(h, separator) { + if abs, err := caddy.FastAbs(h); err == nil { + fsrv.Hide[i] = abs + } + } + } + + // support precompressed sidecar files + mods, err := ctx.LoadModule(fsrv, "PrecompressedRaw") + if err != nil { + return fmt.Errorf("loading encoder modules: %v", err) + } + for modName, modIface := range mods.(map[string]any) { + p, ok := modIface.(encode.Precompressed) + if !ok { + return fmt.Errorf("module %s is not precompressor", modName) + } + ae := p.AcceptEncoding() + if ae == "" { + return fmt.Errorf("precompressor does not specify an Accept-Encoding value") + } + suffix := p.Suffix() + if suffix == "" { + return fmt.Errorf("precompressor does not specify a Suffix value") + } + if _, ok := fsrv.precompressors[ae]; ok { + return fmt.Errorf("precompressor already added: %s", ae) + } + if fsrv.precompressors == nil { + fsrv.precompressors = make(map[string]encode.Precompressed) + } + fsrv.precompressors[ae] = p + } + + if fsrv.Browse != nil { + // check sort options + for idx, sortOption := range fsrv.Browse.SortOptions { + switch idx { + case 0: + if sortOption != sortByName && sortOption != sortByNameDirFirst && sortOption != sortBySize && sortOption != sortByTime { + return fmt.Errorf("the first option must be one of the following: %s, %s, %s, %s, but got %s", sortByName, sortByNameDirFirst, sortBySize, sortByTime, sortOption) + } + case 1: + if sortOption != sortOrderAsc && sortOption != sortOrderDesc { + return fmt.Errorf("the second option must be one of the following: %s, %s, but got %s", sortOrderAsc, sortOrderDesc, sortOption) + } + default: + return fmt.Errorf("only max 2 sort options are allowed, but got %d", idx+1) + } + } + } + + return nil +} + +func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + + if runtime.GOOS == "windows" { + // reject paths with Alternate Data Streams (ADS) + if strings.Contains(r.URL.Path, ":") { + return caddyhttp.Error(http.StatusBadRequest, fmt.Errorf("illegal ADS path")) + } + // reject paths with "8.3" short names + trimmedPath := strings.TrimRight(r.URL.Path, ". ") // Windows ignores trailing dots and spaces, sigh + if len(path.Base(trimmedPath)) <= 12 && strings.Contains(trimmedPath, "~") { + return caddyhttp.Error(http.StatusBadRequest, fmt.Errorf("illegal short name")) + } + // both of those could bypass file hiding or possibly leak information even if the file is not hidden + } + + filesToHide := fsrv.transformHidePaths(repl) + + root := repl.ReplaceAll(fsrv.Root, ".") + fsName := repl.ReplaceAll(fsrv.FileSystem, "") + + fileSystem, ok := fsrv.fsmap.Get(fsName) + if !ok { + return caddyhttp.Error(http.StatusNotFound, fmt.Errorf("filesystem not found")) + } + + // remove any trailing `/` as it breaks fs.ValidPath() in the stdlib + filename := strings.TrimSuffix(caddyhttp.SanitizedPathJoin(root, r.URL.Path), "/") + + if c := fsrv.logger.Check(zapcore.DebugLevel, "sanitized path join"); c != nil { + c.Write( + zap.String("site_root", root), + zap.String("fs", fsName), + zap.String("request_path", r.URL.Path), + zap.String("result", filename), + ) + } + + // get information about the file + info, err := fs.Stat(fileSystem, filename) + if err != nil { + err = fsrv.mapDirOpenError(fileSystem, err, filename) + if errors.Is(err, fs.ErrNotExist) || errors.Is(err, fs.ErrInvalid) { + return fsrv.notFound(w, r, next) + } else if errors.Is(err, fs.ErrPermission) { + return caddyhttp.Error(http.StatusForbidden, err) + } + return caddyhttp.Error(http.StatusInternalServerError, err) + } + + // if the request mapped to a directory, see if + // there is an index file we can serve + var implicitIndexFile bool + if info.IsDir() && len(fsrv.IndexNames) > 0 { + for _, indexPage := range fsrv.IndexNames { + indexPage := repl.ReplaceAll(indexPage, "") + indexPath := caddyhttp.SanitizedPathJoin(filename, indexPage) + if fileHidden(indexPath, filesToHide) { + // pretend this file doesn't exist + if c := fsrv.logger.Check(zapcore.DebugLevel, "hiding index file"); c != nil { + c.Write( + zap.String("filename", indexPath), + zap.Strings("files_to_hide", filesToHide), + ) + } + continue + } + + indexInfo, err := fs.Stat(fileSystem, indexPath) + if err != nil { + continue + } + + // don't rewrite the request path to append + // the index file, because we might need to + // do a canonical-URL redirect below based + // on the URL as-is + + // we've chosen to use this index file, + // so replace the last file info and path + // with that of the index file + info = indexInfo + filename = indexPath + implicitIndexFile = true + if c := fsrv.logger.Check(zapcore.DebugLevel, "located index file"); c != nil { + c.Write(zap.String("filename", filename)) + } + break + } + } + + // if still referencing a directory, delegate + // to browse or return an error + if info.IsDir() { + if c := fsrv.logger.Check(zapcore.DebugLevel, "no index file in directory"); c != nil { + c.Write( + zap.String("path", filename), + zap.Strings("index_filenames", fsrv.IndexNames), + ) + } + if fsrv.Browse != nil && !fileHidden(filename, filesToHide) { + return fsrv.serveBrowse(fileSystem, root, filename, w, r, next) + } + return fsrv.notFound(w, r, next) + } + + // one last check to ensure the file isn't hidden (we might + // have changed the filename from when we last checked) + if fileHidden(filename, filesToHide) { + if c := fsrv.logger.Check(zapcore.DebugLevel, "hiding file"); c != nil { + c.Write( + zap.String("filename", filename), + zap.Strings("files_to_hide", filesToHide), + ) + } + return fsrv.notFound(w, r, next) + } + + // if URL canonicalization is enabled, we need to enforce trailing + // slash convention: if a directory, trailing slash; if a file, no + // trailing slash - not enforcing this can break relative hrefs + // in HTML (see https://github.com/caddyserver/caddy/issues/2741) + if fsrv.CanonicalURIs == nil || *fsrv.CanonicalURIs { + // Only redirect if the last element of the path (the filename) was not + // rewritten; if the admin wanted to rewrite to the canonical path, they + // would have, and we have to be very careful not to introduce unwanted + // redirects and especially redirect loops! + // See https://github.com/caddyserver/caddy/issues/4205. + origReq := r.Context().Value(caddyhttp.OriginalRequestCtxKey).(http.Request) + if path.Base(origReq.URL.Path) == path.Base(r.URL.Path) { + if implicitIndexFile && !strings.HasSuffix(origReq.URL.Path, "/") { + to := origReq.URL.Path + "/" + if c := fsrv.logger.Check(zapcore.DebugLevel, "redirecting to canonical URI (adding trailing slash for directory"); c != nil { + c.Write( + zap.String("from_path", origReq.URL.Path), + zap.String("to_path", to), + ) + } + return redirect(w, r, to) + } else if !implicitIndexFile && strings.HasSuffix(origReq.URL.Path, "/") { + to := origReq.URL.Path[:len(origReq.URL.Path)-1] + if c := fsrv.logger.Check(zapcore.DebugLevel, "redirecting to canonical URI (removing trailing slash for file"); c != nil { + c.Write( + zap.String("from_path", origReq.URL.Path), + zap.String("to_path", to), + ) + } + return redirect(w, r, to) + } + } + } + + var file fs.File + respHeader := w.Header() + + // etag is usually unset, but if the user knows what they're doing, let them override it + etag := respHeader.Get("Etag") + + // static file responses are often compressed, either on-the-fly + // or with precompressed sidecar files; in any case, the headers + // should contain "Vary: Accept-Encoding" even when not compressed + // so caches can craft a reliable key (according to REDbot results) + // see #5849 + respHeader.Add("Vary", "Accept-Encoding") + + // check for precompressed files + for _, ae := range encode.AcceptedEncodings(r, fsrv.PrecompressedOrder) { + precompress, ok := fsrv.precompressors[ae] + if !ok { + continue + } + compressedFilename := filename + precompress.Suffix() + compressedInfo, err := fs.Stat(fileSystem, compressedFilename) + if err != nil || compressedInfo.IsDir() { + if c := fsrv.logger.Check(zapcore.DebugLevel, "precompressed file not accessible"); c != nil { + c.Write(zap.String("filename", compressedFilename), zap.Error(err)) + } + continue + } + if c := fsrv.logger.Check(zapcore.DebugLevel, "opening compressed sidecar file"); c != nil { + c.Write(zap.String("filename", compressedFilename), zap.Error(err)) + } + file, err = fsrv.openFile(fileSystem, compressedFilename, w) + if err != nil { + if c := fsrv.logger.Check(zapcore.WarnLevel, "opening precompressed file failed"); c != nil { + c.Write(zap.String("filename", compressedFilename), zap.Error(err)) + } + if caddyErr, ok := err.(caddyhttp.HandlerError); ok && caddyErr.StatusCode == http.StatusServiceUnavailable { + return err + } + file = nil + continue + } + defer file.Close() + respHeader.Set("Content-Encoding", ae) + respHeader.Del("Accept-Ranges") + + // try to get the etag from pre computed files if an etag suffix list was provided + if etag == "" && fsrv.EtagFileExtensions != nil { + etag, err = fsrv.getEtagFromFile(fileSystem, compressedFilename) + if err != nil { + return err + } + } + + // don't assign info = compressedInfo because sidecars are kind + // of transparent; however we do need to set the Etag: + // https://caddy.community/t/gzipped-sidecar-file-wrong-same-etag/16793 + if etag == "" { + etag = calculateEtag(compressedInfo) + } + + break + } + + // no precompressed file found, use the actual file + if file == nil { + if c := fsrv.logger.Check(zapcore.DebugLevel, "opening file"); c != nil { + c.Write(zap.String("filename", filename)) + } + + // open the file + file, err = fsrv.openFile(fileSystem, filename, w) + if err != nil { + if herr, ok := err.(caddyhttp.HandlerError); ok && + herr.StatusCode == http.StatusNotFound { + return fsrv.notFound(w, r, next) + } + return err // error is already structured + } + defer file.Close() + // try to get the etag from pre computed files if an etag suffix list was provided + if etag == "" && fsrv.EtagFileExtensions != nil { + etag, err = fsrv.getEtagFromFile(fileSystem, filename) + if err != nil { + return err + } + } + if etag == "" { + etag = calculateEtag(info) + } + } + + // at this point, we're serving a file; Go std lib supports only + // GET and HEAD, which is sensible for a static file server - reject + // any other methods (see issue #5166) + if r.Method != http.MethodGet && r.Method != http.MethodHead { + // if we're in an error context, then it doesn't make sense + // to repeat the error; just continue because we're probably + // trying to write an error page response (see issue #5703) + if _, ok := r.Context().Value(caddyhttp.ErrorCtxKey).(error); !ok { + respHeader.Add("Allow", "GET, HEAD") + return caddyhttp.Error(http.StatusMethodNotAllowed, nil) + } + } + + // set the Etag - note that a conditional If-None-Match request is handled + // by http.ServeContent below, which checks against this Etag value + if etag != "" { + respHeader.Set("Etag", etag) + } + + if respHeader.Get("Content-Type") == "" { + mtyp := mime.TypeByExtension(filepath.Ext(filename)) + if mtyp == "" { + // do not allow Go to sniff the content-type; see https://www.youtube.com/watch?v=8t8JYpt0egE + respHeader["Content-Type"] = nil + } else { + respHeader.Set("Content-Type", mtyp) + } + } + + var statusCodeOverride int + + // if this handler exists in an error context (i.e. is part of a + // handler chain that is supposed to handle a previous error), + // we should set status code to the one from the error instead + // of letting http.ServeContent set the default (usually 200) + if reqErr, ok := r.Context().Value(caddyhttp.ErrorCtxKey).(error); ok { + statusCodeOverride = http.StatusInternalServerError + if handlerErr, ok := reqErr.(caddyhttp.HandlerError); ok { + if handlerErr.StatusCode > 0 { + statusCodeOverride = handlerErr.StatusCode + } + } + } + + // if a status code override is configured, run the replacer on it + if codeStr := fsrv.StatusCode.String(); codeStr != "" { + statusCodeOverride, err = strconv.Atoi(repl.ReplaceAll(codeStr, "")) + if err != nil { + return caddyhttp.Error(http.StatusInternalServerError, err) + } + } + + // if we do have an override from the previous two parts, then + // we wrap the response writer to intercept the WriteHeader call + if statusCodeOverride > 0 { + w = statusOverrideResponseWriter{ResponseWriter: w, code: statusCodeOverride} + } + + // let the standard library do what it does best; note, however, + // that errors generated by ServeContent are written immediately + // to the response, so we cannot handle them (but errors there + // are rare) + http.ServeContent(w, r, info.Name(), info.ModTime(), file.(io.ReadSeeker)) + + return nil +} + +// openFile opens the file at the given filename. If there was an error, +// the response is configured to inform the client how to best handle it +// and a well-described handler error is returned (do not wrap the +// returned error value). +func (fsrv *FileServer) openFile(fileSystem fs.FS, filename string, w http.ResponseWriter) (fs.File, error) { + file, err := fileSystem.Open(filename) + if err != nil { + err = fsrv.mapDirOpenError(fileSystem, err, filename) + if errors.Is(err, fs.ErrNotExist) { + if c := fsrv.logger.Check(zapcore.DebugLevel, "file not found"); c != nil { + c.Write(zap.String("filename", filename), zap.Error(err)) + } + return nil, caddyhttp.Error(http.StatusNotFound, err) + } else if errors.Is(err, fs.ErrPermission) { + if c := fsrv.logger.Check(zapcore.DebugLevel, "permission denied"); c != nil { + c.Write(zap.String("filename", filename), zap.Error(err)) + } + return nil, caddyhttp.Error(http.StatusForbidden, err) + } + // maybe the server is under load and ran out of file descriptors? + // have client wait arbitrary seconds to help prevent a stampede + //nolint:gosec + backoff := weakrand.Intn(maxBackoff-minBackoff) + minBackoff + w.Header().Set("Retry-After", strconv.Itoa(backoff)) + if c := fsrv.logger.Check(zapcore.DebugLevel, "retry after backoff"); c != nil { + c.Write(zap.String("filename", filename), zap.Int("backoff", backoff), zap.Error(err)) + } + return nil, caddyhttp.Error(http.StatusServiceUnavailable, err) + } + return file, nil +} + +// mapDirOpenError maps the provided non-nil error from opening name +// to a possibly better non-nil error. In particular, it turns OS-specific errors +// about opening files in non-directories into os.ErrNotExist. See golang/go#18984. +// Adapted from the Go standard library; originally written by Nathaniel Caza. +// https://go-review.googlesource.com/c/go/+/36635/ +// https://go-review.googlesource.com/c/go/+/36804/ +func (fsrv *FileServer) mapDirOpenError(fileSystem fs.FS, originalErr error, name string) error { + if errors.Is(originalErr, fs.ErrNotExist) || errors.Is(originalErr, fs.ErrPermission) { + return originalErr + } + + parts := strings.Split(name, separator) + for i := range parts { + if parts[i] == "" { + continue + } + fi, err := fs.Stat(fileSystem, strings.Join(parts[:i+1], separator)) + if err != nil { + return originalErr + } + if !fi.IsDir() { + return fs.ErrNotExist + } + } + + return originalErr +} + +// transformHidePaths performs replacements for all the elements of fsrv.Hide and +// makes them absolute paths (if they contain a path separator), then returns a +// new list of the transformed values. +func (fsrv *FileServer) transformHidePaths(repl *caddy.Replacer) []string { + hide := make([]string, len(fsrv.Hide)) + for i := range fsrv.Hide { + hide[i] = repl.ReplaceAll(fsrv.Hide[i], "") + if strings.Contains(hide[i], separator) { + abs, err := caddy.FastAbs(hide[i]) + if err == nil { + hide[i] = abs + } + } + } + return hide +} + +// fileHidden returns true if filename is hidden according to the hide list. +// filename must be a relative or absolute file system path, not a request +// URI path. It is expected that all the paths in the hide list are absolute +// paths or are singular filenames (without a path separator). +func fileHidden(filename string, hide []string) bool { + if len(hide) == 0 { + return false + } + + // all path comparisons use the complete absolute path if possible + filenameAbs, err := caddy.FastAbs(filename) + if err == nil { + filename = filenameAbs + } + + var components []string + + for _, h := range hide { + if !strings.Contains(h, separator) { + // if there is no separator in h, then we assume the user + // wants to hide any files or folders that match that + // name; thus we have to compare against each component + // of the filename, e.g. hiding "bar" would hide "/bar" + // as well as "/foo/bar/baz" but not "/barstool". + if len(components) == 0 { + components = strings.Split(filename, separator) + } + for _, c := range components { + if hidden, _ := filepath.Match(h, c); hidden { + return true + } + } + } else if strings.HasPrefix(filename, h) { + // if there is a separator in h, and filename is exactly + // prefixed with h, then we can do a prefix match so that + // "/foo" matches "/foo/bar" but not "/foobar". + withoutPrefix := strings.TrimPrefix(filename, h) + if strings.HasPrefix(withoutPrefix, separator) { + return true + } + } + + // in the general case, a glob match will suffice + if hidden, _ := filepath.Match(h, filename); hidden { + return true + } + } + + return false +} + +// notFound returns a 404 error or, if pass-thru is enabled, +// it calls the next handler in the chain. +func (fsrv *FileServer) notFound(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + if fsrv.PassThru { + return next.ServeHTTP(w, r) + } + return caddyhttp.Error(http.StatusNotFound, nil) +} + +// calculateEtag computes an entity tag using a strong validator +// without consuming the contents of the file. It requires the +// file info contain the correct size and modification time. +// It strives to implement the semantics regarding ETags as defined +// by RFC 9110 section 8.8.3 and 8.8.1. See +// https://www.rfc-editor.org/rfc/rfc9110.html#section-8.8.3. +// +// As our implementation uses file modification timestamp and size, +// note the following from RFC 9110 section 8.8.1: "A representation's +// modification time, if defined with only one-second resolution, +// might be a weak validator if it is possible for the representation to +// be modified twice during a single second and retrieved between those +// modifications." The ext4 file system, which underpins the vast majority +// of Caddy deployments, stores mod times with millisecond precision, +// which we consider precise enough to qualify as a strong validator. +func calculateEtag(d os.FileInfo) string { + mtime := d.ModTime() + if mtimeUnix := mtime.Unix(); mtimeUnix == 0 || mtimeUnix == 1 { + return "" // not useful anyway; see issue #5548 + } + var sb strings.Builder + sb.WriteRune('"') + sb.WriteString(strconv.FormatInt(mtime.UnixNano(), 36)) + sb.WriteString(strconv.FormatInt(d.Size(), 36)) + sb.WriteRune('"') + return sb.String() +} + +// Finds the first corresponding etag file for a given file in the file system and return its content +func (fsrv *FileServer) getEtagFromFile(fileSystem fs.FS, filename string) (string, error) { + for _, suffix := range fsrv.EtagFileExtensions { + etagFilename := filename + suffix + etag, err := fs.ReadFile(fileSystem, etagFilename) + if errors.Is(err, fs.ErrNotExist) { + continue + } + if err != nil { + return "", fmt.Errorf("cannot read etag from file %s: %v", etagFilename, err) + } + + // Etags should not contain newline characters + etag = bytes.ReplaceAll(etag, []byte("\n"), []byte{}) + + return string(etag), nil + } + return "", nil +} + +// redirect performs a redirect to a given path. The 'toPath' parameter +// MUST be solely a path, and MUST NOT include a query. +func redirect(w http.ResponseWriter, r *http.Request, toPath string) error { + for strings.HasPrefix(toPath, "//") { + // prevent path-based open redirects + toPath = strings.TrimPrefix(toPath, "/") + } + // preserve the query string if present + if r.URL.RawQuery != "" { + toPath += "?" + r.URL.RawQuery + } + http.Redirect(w, r, toPath, http.StatusPermanentRedirect) + return nil +} + +// statusOverrideResponseWriter intercepts WriteHeader calls +// to instead write the HTTP status code we want instead +// of the one http.ServeContent will use by default (usually 200) +type statusOverrideResponseWriter struct { + http.ResponseWriter + code int +} + +// WriteHeader intercepts calls by the stdlib to WriteHeader +// to instead write the HTTP status code we want. +func (wr statusOverrideResponseWriter) WriteHeader(int) { + wr.ResponseWriter.WriteHeader(wr.code) +} + +// Unwrap returns the underlying ResponseWriter, necessary for +// http.ResponseController to work correctly. +func (wr statusOverrideResponseWriter) Unwrap() http.ResponseWriter { + return wr.ResponseWriter +} + +var defaultIndexNames = []string{"index.html", "index.txt"} + +const ( + minBackoff, maxBackoff = 2, 5 + separator = string(filepath.Separator) +) + +// Interface guards +var ( + _ caddy.Provisioner = (*FileServer)(nil) + _ caddyhttp.MiddlewareHandler = (*FileServer)(nil) +) diff --git a/modules/caddyhttp/fileserver/staticfiles_test.go b/modules/caddyhttp/fileserver/staticfiles_test.go new file mode 100644 index 00000000000..5d6133c731d --- /dev/null +++ b/modules/caddyhttp/fileserver/staticfiles_test.go @@ -0,0 +1,130 @@ +// 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 fileserver + +import ( + "path/filepath" + "runtime" + "strings" + "testing" +) + +func TestFileHidden(t *testing.T) { + for i, tc := range []struct { + inputHide []string + inputPath string + expect bool + }{ + { + inputHide: nil, + inputPath: "", + expect: false, + }, + { + inputHide: []string{".gitignore"}, + inputPath: "/.gitignore", + expect: true, + }, + { + inputHide: []string{".git"}, + inputPath: "/.gitignore", + expect: false, + }, + { + inputHide: []string{"/.git"}, + inputPath: "/.gitignore", + expect: false, + }, + { + inputHide: []string{".git"}, + inputPath: "/.git", + expect: true, + }, + { + inputHide: []string{".git"}, + inputPath: "/.git/foo", + expect: true, + }, + { + inputHide: []string{".git"}, + inputPath: "/foo/.git/bar", + expect: true, + }, + { + inputHide: []string{"/prefix"}, + inputPath: "/prefix/foo", + expect: true, + }, + { + inputHide: []string{"/foo/*/bar"}, + inputPath: "/foo/asdf/bar", + expect: true, + }, + { + inputHide: []string{"*.txt"}, + inputPath: "/foo/bar.txt", + expect: true, + }, + { + inputHide: []string{"/foo/bar/*.txt"}, + inputPath: "/foo/bar/baz.txt", + expect: true, + }, + { + inputHide: []string{"/foo/bar/*.txt"}, + inputPath: "/foo/bar.txt", + expect: false, + }, + { + inputHide: []string{"/foo/bar/*.txt"}, + inputPath: "/foo/bar/index.html", + expect: false, + }, + { + inputHide: []string{"/foo"}, + inputPath: "/foo", + expect: true, + }, + { + inputHide: []string{"/foo"}, + inputPath: "/foobar", + expect: false, + }, + { + inputHide: []string{"first", "second"}, + inputPath: "/second", + expect: true, + }, + } { + if runtime.GOOS == "windows" { + if strings.HasPrefix(tc.inputPath, "/") { + tc.inputPath, _ = filepath.Abs(tc.inputPath) + } + tc.inputPath = filepath.FromSlash(tc.inputPath) + for i := range tc.inputHide { + if strings.HasPrefix(tc.inputHide[i], "/") { + tc.inputHide[i], _ = filepath.Abs(tc.inputHide[i]) + } + tc.inputHide[i] = filepath.FromSlash(tc.inputHide[i]) + } + } + + actual := fileHidden(tc.inputPath, tc.inputHide) + if actual != tc.expect { + t.Errorf("Test %d: Does %v hide %s? Got %t but expected %t", + i, tc.inputHide, tc.inputPath, actual, tc.expect) + } + } +} diff --git a/modules/caddyhttp/fileserver/testdata/%D9%85%D9%84%D9%81.txt b/modules/caddyhttp/fileserver/testdata/%D9%85%D9%84%D9%81.txt new file mode 100644 index 00000000000..0f4bf1a9903 --- /dev/null +++ b/modules/caddyhttp/fileserver/testdata/%D9%85%D9%84%D9%81.txt @@ -0,0 +1 @@ +%D9%85%D9%84%D9%81.txt \ No newline at end of file diff --git a/modules/caddyhttp/fileserver/testdata/foo.php.php/index.php b/modules/caddyhttp/fileserver/testdata/foo.php.php/index.php new file mode 100644 index 00000000000..4a2ac6b2f75 --- /dev/null +++ b/modules/caddyhttp/fileserver/testdata/foo.php.php/index.php @@ -0,0 +1 @@ +foo.php.php/index.php diff --git a/modules/caddyhttp/fileserver/testdata/foo.txt b/modules/caddyhttp/fileserver/testdata/foo.txt new file mode 100644 index 00000000000..996f1789ff6 --- /dev/null +++ b/modules/caddyhttp/fileserver/testdata/foo.txt @@ -0,0 +1 @@ +foo.txt \ No newline at end of file diff --git a/modules/caddyhttp/fileserver/testdata/foodir/bar.txt b/modules/caddyhttp/fileserver/testdata/foodir/bar.txt new file mode 100644 index 00000000000..df34bd20398 --- /dev/null +++ b/modules/caddyhttp/fileserver/testdata/foodir/bar.txt @@ -0,0 +1 @@ +foodir/bar.txt \ No newline at end of file diff --git a/modules/caddyhttp/fileserver/testdata/foodir/foo.txt b/modules/caddyhttp/fileserver/testdata/foodir/foo.txt new file mode 100644 index 00000000000..0e3335b42b6 --- /dev/null +++ b/modules/caddyhttp/fileserver/testdata/foodir/foo.txt @@ -0,0 +1 @@ +foodir/foo.txt \ No newline at end of file diff --git a/modules/caddyhttp/fileserver/testdata/index.php b/modules/caddyhttp/fileserver/testdata/index.php new file mode 100644 index 00000000000..0012f7d2344 --- /dev/null +++ b/modules/caddyhttp/fileserver/testdata/index.php @@ -0,0 +1 @@ +index.php \ No newline at end of file diff --git a/modules/caddyhttp/fileserver/testdata/large.txt b/modules/caddyhttp/fileserver/testdata/large.txt new file mode 100644 index 00000000000..c3662374432 --- /dev/null +++ b/modules/caddyhttp/fileserver/testdata/large.txt @@ -0,0 +1,3 @@ +This is a file with more content than the other files in this directory +such that tests using the largest_size policy pick this file, or the +smallest_size policy avoids this file. \ No newline at end of file diff --git a/modules/caddyhttp/fileserver/testdata/notphp.php.txt b/modules/caddyhttp/fileserver/testdata/notphp.php.txt new file mode 100644 index 00000000000..eba18761b0f --- /dev/null +++ b/modules/caddyhttp/fileserver/testdata/notphp.php.txt @@ -0,0 +1 @@ +notphp.php.txt diff --git a/modules/caddyhttp/fileserver/testdata/remote.php b/modules/caddyhttp/fileserver/testdata/remote.php new file mode 100644 index 00000000000..78f06a2ad55 --- /dev/null +++ b/modules/caddyhttp/fileserver/testdata/remote.php @@ -0,0 +1 @@ +remote.php \ No newline at end of file diff --git "a/modules/caddyhttp/fileserver/testdata/\331\205\331\204\331\201.txt" "b/modules/caddyhttp/fileserver/testdata/\331\205\331\204\331\201.txt" new file mode 100644 index 00000000000..9185828635d --- /dev/null +++ "b/modules/caddyhttp/fileserver/testdata/\331\205\331\204\331\201.txt" @@ -0,0 +1 @@ +ملف.txt \ No newline at end of file diff --git a/modules/caddyhttp/headers/caddyfile.go b/modules/caddyhttp/headers/caddyfile.go new file mode 100644 index 00000000000..f060471b100 --- /dev/null +++ b/modules/caddyhttp/headers/caddyfile.go @@ -0,0 +1,291 @@ +// 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 headers + +import ( + "fmt" + "net/http" + "reflect" + "strings" + + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func init() { + httpcaddyfile.RegisterDirective("header", parseCaddyfile) + httpcaddyfile.RegisterDirective("request_header", parseReqHdrCaddyfile) +} + +// parseCaddyfile sets up the handler for response headers from +// Caddyfile tokens. Syntax: +// +// header [] [[+|-|?|>] [] []] { +// [+] [ []] +// ? +// - +// > +// [defer] +// } +// +// Either a block can be opened or a single header field can be configured +// in the first line, but not both in the same directive. Header operations +// are deferred to write-time if any headers are being deleted or if the +// 'defer' subdirective is used. + appends a header value, - deletes a field, +// ? conditionally sets a value only if the header field is not already set, +// and > sets a field with defer enabled. +func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) { + h.Next() // consume directive name + matcherSet, err := h.ExtractMatcherSet() + if err != nil { + return nil, err + } + h.Next() // consume the directive name again (matcher parsing resets) + + makeHandler := func() Handler { + return Handler{ + Response: &RespHeaderOps{ + HeaderOps: &HeaderOps{}, + }, + } + } + handler, handlerWithRequire := makeHandler(), makeHandler() + + // first see if headers are in the initial line + var hasArgs bool + if h.NextArg() { + hasArgs = true + field := h.Val() + var value string + var replacement *string + if h.NextArg() { + value = h.Val() + } + if h.NextArg() { + arg := h.Val() + replacement = &arg + } + err := applyHeaderOp( + handler.Response.HeaderOps, + handler.Response, + field, + value, + replacement, + ) + if err != nil { + return nil, h.Err(err.Error()) + } + if len(handler.Response.HeaderOps.Delete) > 0 { + handler.Response.Deferred = true + } + } + + // if not, they should be in a block + for h.NextBlock(0) { + field := h.Val() + if field == "defer" { + handler.Response.Deferred = true + continue + } + if field == "match" { + responseMatchers := make(map[string]caddyhttp.ResponseMatcher) + err := caddyhttp.ParseNamedResponseMatcher(h.NewFromNextSegment(), responseMatchers) + if err != nil { + return nil, err + } + matcher := responseMatchers["match"] + handler.Response.Require = &matcher + continue + } + if hasArgs { + return nil, h.Err("cannot specify headers in both arguments and block") // because it would be weird + } + + // sometimes it is habitual for users to suffix a field name with a colon, + // as if they were writing a curl command or something; see + // https://caddy.community/t/v2-reverse-proxy-please-add-cors-example-to-the-docs/7349/19 + field = strings.TrimSuffix(field, ":") + + var value string + var replacement *string + if h.NextArg() { + value = h.Val() + } + if h.NextArg() { + arg := h.Val() + replacement = &arg + } + + handlerToUse := handler + if strings.HasPrefix(field, "?") { + handlerToUse = handlerWithRequire + } + + err := applyHeaderOp( + handlerToUse.Response.HeaderOps, + handlerToUse.Response, + field, + value, + replacement, + ) + if err != nil { + return nil, h.Err(err.Error()) + } + } + + var configValues []httpcaddyfile.ConfigValue + if !reflect.DeepEqual(handler, makeHandler()) { + configValues = append(configValues, h.NewRoute(matcherSet, handler)...) + } + if !reflect.DeepEqual(handlerWithRequire, makeHandler()) { + configValues = append(configValues, h.NewRoute(matcherSet, handlerWithRequire)...) + } + + return configValues, nil +} + +// parseReqHdrCaddyfile sets up the handler for request headers +// from Caddyfile tokens. Syntax: +// +// request_header [] [[+|-] [] []] +func parseReqHdrCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) { + h.Next() // consume directive name + matcherSet, err := h.ExtractMatcherSet() + if err != nil { + return nil, err + } + h.Next() // consume the directive name again (matcher parsing resets) + + configValues := []httpcaddyfile.ConfigValue{} + + if !h.NextArg() { + return nil, h.ArgErr() + } + field := h.Val() + + hdr := Handler{ + Request: &HeaderOps{}, + } + + // sometimes it is habitual for users to suffix a field name with a colon, + // as if they were writing a curl command or something; see + // https://caddy.community/t/v2-reverse-proxy-please-add-cors-example-to-the-docs/7349/19 + field = strings.TrimSuffix(field, ":") + + var value string + var replacement *string + if h.NextArg() { + value = h.Val() + } + if h.NextArg() { + arg := h.Val() + replacement = &arg + if h.NextArg() { + return nil, h.ArgErr() + } + } + + if hdr.Request == nil { + hdr.Request = new(HeaderOps) + } + if err := CaddyfileHeaderOp(hdr.Request, field, value, replacement); err != nil { + return nil, h.Err(err.Error()) + } + + configValues = append(configValues, h.NewRoute(matcherSet, hdr)...) + + if h.NextArg() { + return nil, h.ArgErr() + } + return configValues, nil +} + +// CaddyfileHeaderOp applies a new header operation according to +// field, value, and replacement. The field can be prefixed with +// "+" or "-" to specify adding or removing; otherwise, the value +// will be set (overriding any previous value). If replacement is +// non-nil, value will be treated as a regular expression which +// will be used to search and then replacement will be used to +// complete the substring replacement; in that case, any + or - +// prefix to field will be ignored. +func CaddyfileHeaderOp(ops *HeaderOps, field, value string, replacement *string) error { + return applyHeaderOp(ops, nil, field, value, replacement) +} + +func applyHeaderOp(ops *HeaderOps, respHeaderOps *RespHeaderOps, field, value string, replacement *string) error { + switch { + case strings.HasPrefix(field, "+"): // append + if ops.Add == nil { + ops.Add = make(http.Header) + } + ops.Add.Add(field[1:], value) + + case strings.HasPrefix(field, "-"): // delete + ops.Delete = append(ops.Delete, field[1:]) + if respHeaderOps != nil { + respHeaderOps.Deferred = true + } + + case strings.HasPrefix(field, "?"): // default (conditional on not existing) - response headers only + if respHeaderOps == nil { + return fmt.Errorf("%v: the default header modifier ('?') can only be used on response headers; for conditional manipulation of request headers, use matchers", field) + } + if respHeaderOps.Require == nil { + respHeaderOps.Require = &caddyhttp.ResponseMatcher{ + Headers: make(http.Header), + } + } + field = strings.TrimPrefix(field, "?") + respHeaderOps.Require.Headers[field] = nil + if respHeaderOps.Set == nil { + respHeaderOps.Set = make(http.Header) + } + respHeaderOps.Set.Set(field, value) + + case replacement != nil: // replace + // allow defer shortcut for replace syntax + if strings.HasPrefix(field, ">") && respHeaderOps != nil { + respHeaderOps.Deferred = true + } + if ops.Replace == nil { + ops.Replace = make(map[string][]Replacement) + } + field = strings.TrimLeft(field, "+-?>") + ops.Replace[field] = append( + ops.Replace[field], + Replacement{ + SearchRegexp: value, + Replace: *replacement, + }, + ) + + case strings.HasPrefix(field, ">"): // set (overwrite) with defer + if ops.Set == nil { + ops.Set = make(http.Header) + } + ops.Set.Set(field[1:], value) + if respHeaderOps != nil { + respHeaderOps.Deferred = true + } + + default: // set (overwrite) + if ops.Set == nil { + ops.Set = make(http.Header) + } + ops.Set.Set(field, value) + } + + return nil +} diff --git a/modules/caddyhttp/headers/headers.go b/modules/caddyhttp/headers/headers.go new file mode 100644 index 00000000000..c66bd414497 --- /dev/null +++ b/modules/caddyhttp/headers/headers.go @@ -0,0 +1,374 @@ +// 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 headers + +import ( + "fmt" + "net/http" + "regexp" + "strings" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func init() { + caddy.RegisterModule(Handler{}) +} + +// Handler is a middleware which modifies request and response headers. +// +// Changes to headers are applied immediately, except for the response +// headers when Deferred is true or when Required is set. In those cases, +// the changes are applied when the headers are written to the response. +// Note that deferred changes do not take effect if an error occurs later +// in the middleware chain. +// +// Properties in this module accept placeholders. +// +// Response header operations can be conditioned upon response status code +// and/or other header values. +type Handler struct { + Request *HeaderOps `json:"request,omitempty"` + Response *RespHeaderOps `json:"response,omitempty"` +} + +// CaddyModule returns the Caddy module information. +func (Handler) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.headers", + New: func() caddy.Module { return new(Handler) }, + } +} + +// Provision sets up h's configuration. +func (h *Handler) Provision(ctx caddy.Context) error { + if h.Request != nil { + err := h.Request.Provision(ctx) + if err != nil { + return err + } + } + if h.Response != nil { + err := h.Response.Provision(ctx) + if err != nil { + return err + } + } + return nil +} + +// Validate ensures h's configuration is valid. +func (h Handler) Validate() error { + if h.Request != nil { + err := h.Request.validate() + if err != nil { + return err + } + } + if h.Response != nil { + err := h.Response.validate() + if err != nil { + return err + } + } + return nil +} + +func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + + if h.Request != nil { + h.Request.ApplyToRequest(r) + } + + if h.Response != nil { + if h.Response.Deferred || h.Response.Require != nil { + w = &responseWriterWrapper{ + ResponseWriterWrapper: &caddyhttp.ResponseWriterWrapper{ResponseWriter: w}, + replacer: repl, + require: h.Response.Require, + headerOps: h.Response.HeaderOps, + } + } else { + h.Response.ApplyTo(w.Header(), repl) + } + } + + return next.ServeHTTP(w, r) +} + +// HeaderOps defines manipulations for HTTP headers. +type HeaderOps struct { + // Adds HTTP headers; does not replace any existing header fields. + Add http.Header `json:"add,omitempty"` + + // Sets HTTP headers; replaces existing header fields. + Set http.Header `json:"set,omitempty"` + + // Names of HTTP header fields to delete. Basic wildcards are supported: + // + // - Start with `*` for all field names with the given suffix; + // - End with `*` for all field names with the given prefix; + // - Start and end with `*` for all field names containing a substring. + Delete []string `json:"delete,omitempty"` + + // Performs in-situ substring replacements of HTTP headers. + // Keys are the field names on which to perform the associated replacements. + // If the field name is `*`, the replacements are performed on all header fields. + Replace map[string][]Replacement `json:"replace,omitempty"` +} + +// Provision sets up the header operations. +func (ops *HeaderOps) Provision(_ caddy.Context) error { + for fieldName, replacements := range ops.Replace { + for i, r := range replacements { + if r.SearchRegexp == "" { + continue + } + re, err := regexp.Compile(r.SearchRegexp) + if err != nil { + return fmt.Errorf("replacement %d for header field '%s': %v", i, fieldName, err) + } + replacements[i].re = re + } + } + return nil +} + +func (ops HeaderOps) validate() error { + for fieldName, replacements := range ops.Replace { + for _, r := range replacements { + if r.Search != "" && r.SearchRegexp != "" { + return fmt.Errorf("cannot specify both a substring search and a regular expression search for field '%s'", fieldName) + } + } + } + return nil +} + +// Replacement describes a string replacement, +// either a simple and fast substring search +// or a slower but more powerful regex search. +type Replacement struct { + // The substring to search for. + Search string `json:"search,omitempty"` + + // The regular expression to search with. + SearchRegexp string `json:"search_regexp,omitempty"` + + // The string with which to replace matches. + Replace string `json:"replace,omitempty"` + + re *regexp.Regexp +} + +// RespHeaderOps defines manipulations for response headers. +type RespHeaderOps struct { + *HeaderOps + + // If set, header operations will be deferred until + // they are written out and only performed if the + // response matches these criteria. + Require *caddyhttp.ResponseMatcher `json:"require,omitempty"` + + // If true, header operations will be deferred until + // they are written out. Superseded if Require is set. + // Usually you will need to set this to true if any + // fields are being deleted. + Deferred bool `json:"deferred,omitempty"` +} + +// ApplyTo applies ops to hdr using repl. +func (ops HeaderOps) ApplyTo(hdr http.Header, repl *caddy.Replacer) { + // before manipulating headers in other ways, check if there + // is configuration to delete all headers, and do that first + // because if a header is to be added, we don't want to delete + // it also + for _, fieldName := range ops.Delete { + fieldName = repl.ReplaceKnown(fieldName, "") + if fieldName == "*" { + clear(hdr) + } + } + + // add + for fieldName, vals := range ops.Add { + fieldName = repl.ReplaceKnown(fieldName, "") + for _, v := range vals { + hdr.Add(fieldName, repl.ReplaceKnown(v, "")) + } + } + + // set + for fieldName, vals := range ops.Set { + fieldName = repl.ReplaceKnown(fieldName, "") + var newVals []string + for i := range vals { + // append to new slice so we don't overwrite + // the original values in ops.Set + newVals = append(newVals, repl.ReplaceKnown(vals[i], "")) + } + hdr.Set(fieldName, strings.Join(newVals, ",")) + } + + // delete + for _, fieldName := range ops.Delete { + fieldName = strings.ToLower(repl.ReplaceKnown(fieldName, "")) + if fieldName == "*" { + continue // handled above + } + switch { + case strings.HasPrefix(fieldName, "*") && strings.HasSuffix(fieldName, "*"): + for existingField := range hdr { + if strings.Contains(strings.ToLower(existingField), fieldName[1:len(fieldName)-1]) { + delete(hdr, existingField) + } + } + case strings.HasPrefix(fieldName, "*"): + for existingField := range hdr { + if strings.HasSuffix(strings.ToLower(existingField), fieldName[1:]) { + delete(hdr, existingField) + } + } + case strings.HasSuffix(fieldName, "*"): + for existingField := range hdr { + if strings.HasPrefix(strings.ToLower(existingField), fieldName[:len(fieldName)-1]) { + delete(hdr, existingField) + } + } + default: + hdr.Del(fieldName) + } + } + + // replace + for fieldName, replacements := range ops.Replace { + fieldName = http.CanonicalHeaderKey(repl.ReplaceKnown(fieldName, "")) + + // all fields... + if fieldName == "*" { + for _, r := range replacements { + search := repl.ReplaceKnown(r.Search, "") + replace := repl.ReplaceKnown(r.Replace, "") + for fieldName, vals := range hdr { + for i := range vals { + if r.re != nil { + hdr[fieldName][i] = r.re.ReplaceAllString(hdr[fieldName][i], replace) + } else { + hdr[fieldName][i] = strings.ReplaceAll(hdr[fieldName][i], search, replace) + } + } + } + } + continue + } + + // ...or only with the named field + for _, r := range replacements { + search := repl.ReplaceKnown(r.Search, "") + replace := repl.ReplaceKnown(r.Replace, "") + for hdrFieldName, vals := range hdr { + // see issue #4330 for why we don't simply use hdr[fieldName] + if http.CanonicalHeaderKey(hdrFieldName) != fieldName { + continue + } + for i := range vals { + if r.re != nil { + hdr[hdrFieldName][i] = r.re.ReplaceAllString(hdr[hdrFieldName][i], replace) + } else { + hdr[hdrFieldName][i] = strings.ReplaceAll(hdr[hdrFieldName][i], search, replace) + } + } + } + } + } +} + +// ApplyToRequest applies ops to r, specially handling the Host +// header which the standard library does not include with the +// header map with all the others. This method mutates r.Host. +func (ops HeaderOps) ApplyToRequest(r *http.Request) { + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + + // capture the current Host header so we can + // reset to it when we're done + origHost, hadHost := r.Header["Host"] + + // append r.Host; this way, we know that our value + // was last in the list, and if an Add operation + // appended something else after it, that's probably + // fine because it's weird to have multiple Host + // headers anyway and presumably the one they added + // is the one they wanted + r.Header["Host"] = append(r.Header["Host"], r.Host) + + // apply header operations + ops.ApplyTo(r.Header, repl) + + // retrieve the last Host value (likely the one we appended) + if len(r.Header["Host"]) > 0 { + r.Host = r.Header["Host"][len(r.Header["Host"])-1] + } else { + r.Host = "" + } + + // reset the Host header slice + if hadHost { + r.Header["Host"] = origHost + } else { + delete(r.Header, "Host") + } +} + +// responseWriterWrapper defers response header +// operations until WriteHeader is called. +type responseWriterWrapper struct { + *caddyhttp.ResponseWriterWrapper + replacer *caddy.Replacer + require *caddyhttp.ResponseMatcher + headerOps *HeaderOps + wroteHeader bool +} + +func (rww *responseWriterWrapper) WriteHeader(status int) { + if rww.wroteHeader { + return + } + // 1xx responses aren't final; just informational + if status < 100 || status > 199 { + rww.wroteHeader = true + } + if rww.require == nil || rww.require.Match(status, rww.ResponseWriterWrapper.Header()) { + if rww.headerOps != nil { + rww.headerOps.ApplyTo(rww.ResponseWriterWrapper.Header(), rww.replacer) + } + } + rww.ResponseWriterWrapper.WriteHeader(status) +} + +func (rww *responseWriterWrapper) Write(d []byte) (int, error) { + if !rww.wroteHeader { + rww.WriteHeader(http.StatusOK) + } + return rww.ResponseWriterWrapper.Write(d) +} + +// Interface guards +var ( + _ caddy.Provisioner = (*Handler)(nil) + _ caddyhttp.MiddlewareHandler = (*Handler)(nil) + _ http.ResponseWriter = (*responseWriterWrapper)(nil) +) diff --git a/modules/caddyhttp/headers/headers_test.go b/modules/caddyhttp/headers/headers_test.go new file mode 100644 index 00000000000..9808c29c98d --- /dev/null +++ b/modules/caddyhttp/headers/headers_test.go @@ -0,0 +1,274 @@ +// 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 headers + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "reflect" + "testing" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func TestHandler(t *testing.T) { + for i, tc := range []struct { + handler Handler + reqHeader http.Header + respHeader http.Header + respStatusCode int + expectedReqHeader http.Header + expectedRespHeader http.Header + }{ + { + handler: Handler{ + Request: &HeaderOps{ + Add: http.Header{ + "Expose-Secrets": []string{"always"}, + }, + }, + }, + reqHeader: http.Header{ + "Expose-Secrets": []string{"i'm serious"}, + }, + expectedReqHeader: http.Header{ + "Expose-Secrets": []string{"i'm serious", "always"}, + }, + }, + { + handler: Handler{ + Request: &HeaderOps{ + Set: http.Header{ + "Who-Wins": []string{"batman"}, + }, + }, + }, + reqHeader: http.Header{ + "Who-Wins": []string{"joker"}, + }, + expectedReqHeader: http.Header{ + "Who-Wins": []string{"batman"}, + }, + }, + { + handler: Handler{ + Request: &HeaderOps{ + Delete: []string{"Kick-Me"}, + }, + }, + reqHeader: http.Header{ + "Kick-Me": []string{"if you can"}, + "Keep-Me": []string{"i swear i'm innocent"}, + }, + expectedReqHeader: http.Header{ + "Keep-Me": []string{"i swear i'm innocent"}, + }, + }, + { + handler: Handler{ + Request: &HeaderOps{ + Delete: []string{ + "*-suffix", + "prefix-*", + "*_*", + }, + }, + }, + reqHeader: http.Header{ + "Header-Suffix": []string{"lalala"}, + "Prefix-Test": []string{"asdf"}, + "Host_Header": []string{"silly django... sigh"}, // see issue #4830 + "Keep-Me": []string{"foofoofoo"}, + }, + expectedReqHeader: http.Header{ + "Keep-Me": []string{"foofoofoo"}, + }, + }, + { + handler: Handler{ + Request: &HeaderOps{ + Replace: map[string][]Replacement{ + "Best-Server": { + Replacement{ + Search: "NGINX", + Replace: "the Caddy web server", + }, + Replacement{ + SearchRegexp: `Apache(\d+)`, + Replace: "Caddy", + }, + }, + }, + }, + }, + reqHeader: http.Header{ + "Best-Server": []string{"it's NGINX, undoubtedly", "I love Apache2"}, + }, + expectedReqHeader: http.Header{ + "Best-Server": []string{"it's the Caddy web server, undoubtedly", "I love Caddy"}, + }, + }, + { + handler: Handler{ + Response: &RespHeaderOps{ + Require: &caddyhttp.ResponseMatcher{ + Headers: http.Header{ + "Cache-Control": nil, + }, + }, + HeaderOps: &HeaderOps{ + Add: http.Header{ + "Cache-Control": []string{"no-cache"}, + }, + }, + }, + }, + respHeader: http.Header{}, + expectedRespHeader: http.Header{ + "Cache-Control": []string{"no-cache"}, + }, + }, + { // same as above, but checks that response headers are left alone when "Require" conditions are unmet + handler: Handler{ + Response: &RespHeaderOps{ + Require: &caddyhttp.ResponseMatcher{ + Headers: http.Header{ + "Cache-Control": nil, + }, + }, + HeaderOps: &HeaderOps{ + Add: http.Header{ + "Cache-Control": []string{"no-cache"}, + }, + }, + }, + }, + respHeader: http.Header{ + "Cache-Control": []string{"something"}, + }, + expectedRespHeader: http.Header{ + "Cache-Control": []string{"something"}, + }, + }, + { + handler: Handler{ + Response: &RespHeaderOps{ + Require: &caddyhttp.ResponseMatcher{ + Headers: http.Header{ + "Cache-Control": []string{"no-cache"}, + }, + }, + HeaderOps: &HeaderOps{ + Delete: []string{"Cache-Control"}, + }, + }, + }, + respHeader: http.Header{ + "Cache-Control": []string{"no-cache"}, + }, + expectedRespHeader: http.Header{}, + }, + { + handler: Handler{ + Response: &RespHeaderOps{ + Require: &caddyhttp.ResponseMatcher{ + StatusCode: []int{5}, + }, + HeaderOps: &HeaderOps{ + Add: http.Header{ + "Fail-5xx": []string{"true"}, + }, + }, + }, + }, + respStatusCode: 503, + respHeader: http.Header{}, + expectedRespHeader: http.Header{ + "Fail-5xx": []string{"true"}, + }, + }, + { + handler: Handler{ + Request: &HeaderOps{ + Replace: map[string][]Replacement{ + "Case-Insensitive": { + Replacement{ + Search: "issue4330", + Replace: "issue #4330", + }, + }, + }, + }, + }, + reqHeader: http.Header{ + "case-insensitive": []string{"issue4330"}, + "Other-Header": []string{"issue4330"}, + }, + expectedReqHeader: http.Header{ + "case-insensitive": []string{"issue #4330"}, + "Other-Header": []string{"issue4330"}, + }, + }, + } { + rr := httptest.NewRecorder() + + req := &http.Request{Header: tc.reqHeader} + repl := caddy.NewReplacer() + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + req = req.WithContext(ctx) + + tc.handler.Provision(caddy.Context{}) + + next := nextHandler(func(w http.ResponseWriter, r *http.Request) error { + for k, hdrs := range tc.respHeader { + for _, v := range hdrs { + w.Header().Add(k, v) + } + } + + status := 200 + if tc.respStatusCode != 0 { + status = tc.respStatusCode + } + w.WriteHeader(status) + + if tc.expectedReqHeader != nil && !reflect.DeepEqual(r.Header, tc.expectedReqHeader) { + return fmt.Errorf("expected request header %v, got %v", tc.expectedReqHeader, r.Header) + } + + return nil + }) + + if err := tc.handler.ServeHTTP(rr, req, next); err != nil { + t.Errorf("Test %d: %v", i, err) + continue + } + + actual := rr.Header() + if tc.expectedRespHeader != nil && !reflect.DeepEqual(actual, tc.expectedRespHeader) { + t.Errorf("Test %d: expected response header %v, got %v", i, tc.expectedRespHeader, actual) + continue + } + } +} + +type nextHandler func(http.ResponseWriter, *http.Request) error + +func (f nextHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) error { + return f(w, r) +} diff --git a/modules/caddyhttp/http2listener.go b/modules/caddyhttp/http2listener.go new file mode 100644 index 00000000000..51b356a7779 --- /dev/null +++ b/modules/caddyhttp/http2listener.go @@ -0,0 +1,102 @@ +package caddyhttp + +import ( + "context" + "crypto/tls" + weakrand "math/rand" + "net" + "net/http" + "sync/atomic" + "time" + + "golang.org/x/net/http2" +) + +// http2Listener wraps the listener to solve the following problems: +// 1. server h2 natively without using h2c hack when listener handles tls connection but +// don't return *tls.Conn +// 2. graceful shutdown. the shutdown logic is copied from stdlib http.Server, it's an extra maintenance burden but +// whatever, the shutdown logic maybe extracted to be used with h2c graceful shutdown. http2.Server supports graceful shutdown +// sending GO_AWAY frame to connected clients, but doesn't track connection status. It requires explicit call of http2.ConfigureServer +type http2Listener struct { + cnt uint64 + net.Listener + server *http.Server + h2server *http2.Server +} + +type connectionStateConn interface { + net.Conn + ConnectionState() tls.ConnectionState +} + +func (h *http2Listener) Accept() (net.Conn, error) { + for { + conn, err := h.Listener.Accept() + if err != nil { + return nil, err + } + + if csc, ok := conn.(connectionStateConn); ok { + // *tls.Conn will return empty string because it's only populated after handshake is complete + if csc.ConnectionState().NegotiatedProtocol == http2.NextProtoTLS { + go h.serveHttp2(csc) + continue + } + } + + return conn, nil + } +} + +func (h *http2Listener) serveHttp2(csc connectionStateConn) { + atomic.AddUint64(&h.cnt, 1) + h.runHook(csc, http.StateNew) + defer func() { + csc.Close() + atomic.AddUint64(&h.cnt, ^uint64(0)) + h.runHook(csc, http.StateClosed) + }() + h.h2server.ServeConn(csc, &http2.ServeConnOpts{ + Context: h.server.ConnContext(context.Background(), csc), + BaseConfig: h.server, + Handler: h.server.Handler, + }) +} + +const shutdownPollIntervalMax = 500 * time.Millisecond + +func (h *http2Listener) Shutdown(ctx context.Context) error { + pollIntervalBase := time.Millisecond + nextPollInterval := func() time.Duration { + // Add 10% jitter. + //nolint:gosec + interval := pollIntervalBase + time.Duration(weakrand.Intn(int(pollIntervalBase/10))) + // Double and clamp for next time. + pollIntervalBase *= 2 + if pollIntervalBase > shutdownPollIntervalMax { + pollIntervalBase = shutdownPollIntervalMax + } + return interval + } + + timer := time.NewTimer(nextPollInterval()) + defer timer.Stop() + for { + if atomic.LoadUint64(&h.cnt) == 0 { + return nil + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-timer.C: + timer.Reset(nextPollInterval()) + } + } +} + +func (h *http2Listener) runHook(conn net.Conn, state http.ConnState) { + if h.server.ConnState != nil { + h.server.ConnState(conn, state) + } +} diff --git a/modules/caddyhttp/httpredirectlistener.go b/modules/caddyhttp/httpredirectlistener.go new file mode 100644 index 00000000000..ce9ac030875 --- /dev/null +++ b/modules/caddyhttp/httpredirectlistener.go @@ -0,0 +1,174 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "bufio" + "bytes" + "fmt" + "io" + "net" + "net/http" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func init() { + caddy.RegisterModule(HTTPRedirectListenerWrapper{}) +} + +// HTTPRedirectListenerWrapper provides HTTP->HTTPS redirects for +// connections that come on the TLS port as an HTTP request, +// by detecting using the first few bytes that it's not a TLS +// handshake, but instead an HTTP request. +// +// This is especially useful when using a non-standard HTTPS port. +// A user may simply type the address in their browser without the +// https:// scheme, which would cause the browser to attempt the +// connection over HTTP, but this would cause a "Client sent an +// HTTP request to an HTTPS server" error response. +// +// This listener wrapper must be placed BEFORE the "tls" listener +// wrapper, for it to work properly. +type HTTPRedirectListenerWrapper struct { + // MaxHeaderBytes is the maximum size to parse from a client's + // HTTP request headers. Default: 1 MB + MaxHeaderBytes int64 `json:"max_header_bytes,omitempty"` +} + +func (HTTPRedirectListenerWrapper) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.listeners.http_redirect", + New: func() caddy.Module { return new(HTTPRedirectListenerWrapper) }, + } +} + +func (h *HTTPRedirectListenerWrapper) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + return nil +} + +func (h *HTTPRedirectListenerWrapper) WrapListener(l net.Listener) net.Listener { + return &httpRedirectListener{l, h.MaxHeaderBytes} +} + +// httpRedirectListener is listener that checks the first few bytes +// of the request when the server is intended to accept HTTPS requests, +// to respond to an HTTP request with a redirect. +type httpRedirectListener struct { + net.Listener + maxHeaderBytes int64 +} + +// Accept waits for and returns the next connection to the listener, +// wrapping it with a httpRedirectConn. +func (l *httpRedirectListener) Accept() (net.Conn, error) { + c, err := l.Listener.Accept() + if err != nil { + return nil, err + } + + maxHeaderBytes := l.maxHeaderBytes + if maxHeaderBytes == 0 { + maxHeaderBytes = 1024 * 1024 + } + + return &httpRedirectConn{ + Conn: c, + limit: maxHeaderBytes, + r: bufio.NewReader(c), + }, nil +} + +type httpRedirectConn struct { + net.Conn + once bool + limit int64 + r *bufio.Reader +} + +// Read tries to peek at the first few bytes of the request, and if we get +// an error reading the headers, and that error was due to the bytes looking +// like an HTTP request, then we perform a HTTP->HTTPS redirect on the same +// port as the original connection. +func (c *httpRedirectConn) Read(p []byte) (int, error) { + if c.once { + return c.r.Read(p) + } + // no need to use sync.Once - net.Conn is not read from concurrently. + c.once = true + + firstBytes, err := c.r.Peek(5) + if err != nil { + return 0, err + } + + // If the request doesn't look like HTTP, then it's probably + // TLS bytes, and we don't need to do anything. + if !firstBytesLookLikeHTTP(firstBytes) { + return c.r.Read(p) + } + + // From now on, we can be almost certain the request is HTTP. + // The returned error will be non nil and caller are expected to + // close the connection. + + // Set the read limit, io.MultiReader is needed because + // when resetting, *bufio.Reader discards buffered data. + buffered, _ := c.r.Peek(c.r.Buffered()) + mr := io.MultiReader(bytes.NewReader(buffered), c.Conn) + c.r.Reset(io.LimitReader(mr, c.limit)) + + // Parse the HTTP request, so we can get the Host and URL to redirect to. + req, err := http.ReadRequest(c.r) + if err != nil { + return 0, fmt.Errorf("couldn't read HTTP request") + } + + // Build the redirect response, using the same Host and URL, + // but replacing the scheme with https. + headers := make(http.Header) + headers.Add("Location", "https://"+req.Host+req.URL.String()) + resp := &http.Response{ + Proto: "HTTP/1.0", + Status: "308 Permanent Redirect", + StatusCode: 308, + ProtoMajor: 1, + ProtoMinor: 0, + Header: headers, + } + + err = resp.Write(c.Conn) + if err != nil { + return 0, fmt.Errorf("couldn't write HTTP->HTTPS redirect") + } + + return 0, fmt.Errorf("redirected HTTP request on HTTPS port") +} + +// firstBytesLookLikeHTTP reports whether a TLS record header +// looks like it might've been a misdirected plaintext HTTP request. +func firstBytesLookLikeHTTP(hdr []byte) bool { + switch string(hdr[:5]) { + case "GET /", "HEAD ", "POST ", "PUT /", "OPTIO": + return true + } + return false +} + +var ( + _ caddy.ListenerWrapper = (*HTTPRedirectListenerWrapper)(nil) + _ caddyfile.Unmarshaler = (*HTTPRedirectListenerWrapper)(nil) +) diff --git a/modules/caddyhttp/intercept/intercept.go b/modules/caddyhttp/intercept/intercept.go new file mode 100644 index 00000000000..29889dcc0ed --- /dev/null +++ b/modules/caddyhttp/intercept/intercept.go @@ -0,0 +1,352 @@ +// 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 intercept + +import ( + "bytes" + "fmt" + "net/http" + "strconv" + "strings" + "sync" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func init() { + caddy.RegisterModule(Intercept{}) + httpcaddyfile.RegisterHandlerDirective("intercept", parseCaddyfile) +} + +// Intercept is a middleware that intercepts then replaces or modifies the original response. +// It can, for instance, be used to implement X-Sendfile/X-Accel-Redirect-like features +// when using modules like FrankenPHP or Caddy Snake. +// +// EXPERIMENTAL: Subject to change or removal. +type Intercept struct { + // List of handlers and their associated matchers to evaluate + // after successful response generation. + // The first handler that matches the original response will + // be invoked. The original response body will not be + // written to the client; + // it is up to the handler to finish handling the response. + // + // Three new placeholders are available in this handler chain: + // - `{http.intercept.status_code}` The status code from the response + // - `{http.intercept.header.*}` The headers from the response + HandleResponse []caddyhttp.ResponseHandler `json:"handle_response,omitempty"` + + // Holds the named response matchers from the Caddyfile while adapting + responseMatchers map[string]caddyhttp.ResponseMatcher + + // Holds the handle_response Caddyfile tokens while adapting + handleResponseSegments []*caddyfile.Dispenser + + logger *zap.Logger +} + +// CaddyModule returns the Caddy module information. +// +// EXPERIMENTAL: Subject to change or removal. +func (Intercept) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.intercept", + New: func() caddy.Module { return new(Intercept) }, + } +} + +// Provision ensures that i is set up properly before use. +// +// EXPERIMENTAL: Subject to change or removal. +func (irh *Intercept) Provision(ctx caddy.Context) error { + // set up any response routes + for i, rh := range irh.HandleResponse { + err := rh.Provision(ctx) + if err != nil { + return fmt.Errorf("provisioning response handler %d: %w", i, err) + } + } + + irh.logger = ctx.Logger() + + return nil +} + +var bufPool = sync.Pool{ + New: func() any { + return new(bytes.Buffer) + }, +} + +// TODO: handle status code replacement +// +// EXPERIMENTAL: Subject to change or removal. +type interceptedResponseHandler struct { + caddyhttp.ResponseRecorder + replacer *caddy.Replacer + handler caddyhttp.ResponseHandler + handlerIndex int + statusCode int +} + +// EXPERIMENTAL: Subject to change or removal. +func (irh interceptedResponseHandler) WriteHeader(statusCode int) { + if irh.statusCode != 0 && (statusCode < 100 || statusCode >= 200) { + irh.ResponseRecorder.WriteHeader(irh.statusCode) + + return + } + + irh.ResponseRecorder.WriteHeader(statusCode) +} + +// EXPERIMENTAL: Subject to change or removal. +func (ir Intercept) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + buf := bufPool.Get().(*bytes.Buffer) + buf.Reset() + defer bufPool.Put(buf) + + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + rec := interceptedResponseHandler{replacer: repl} + rec.ResponseRecorder = caddyhttp.NewResponseRecorder(w, buf, func(status int, header http.Header) bool { + // see if any response handler is configured for this original response + for i, rh := range ir.HandleResponse { + if rh.Match != nil && !rh.Match.Match(status, header) { + continue + } + rec.handler = rh + rec.handlerIndex = i + + // if configured to only change the status code, + // do that then stream + if statusCodeStr := rh.StatusCode.String(); statusCodeStr != "" { + sc, err := strconv.Atoi(repl.ReplaceAll(statusCodeStr, "")) + if err != nil { + rec.statusCode = http.StatusInternalServerError + } else { + rec.statusCode = sc + } + } + + return rec.statusCode == 0 + } + + return false + }) + + if err := next.ServeHTTP(rec, r); err != nil { + return err + } + if !rec.Buffered() { + return nil + } + + // set up the replacer so that parts of the original response can be + // used for routing decisions + for field, value := range rec.Header() { + repl.Set("http.intercept.header."+field, strings.Join(value, ",")) + } + repl.Set("http.intercept.status_code", rec.Status()) + + if c := ir.logger.Check(zapcore.DebugLevel, "handling response"); c != nil { + c.Write(zap.Int("handler", rec.handlerIndex)) + } + + // pass the request through the response handler routes + return rec.handler.Routes.Compile(next).ServeHTTP(w, r) +} + +// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax: +// +// intercept [] { +// # intercept original responses +// @name { +// status +// header [] +// } +// replace_status [] +// handle_response [] { +// +// } +// } +// +// The FinalizeUnmarshalCaddyfile method should be called after this +// to finalize parsing of "handle_response" blocks, if possible. +// +// EXPERIMENTAL: Subject to change or removal. +func (i *Intercept) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + // collect the response matchers defined as subdirectives + // prefixed with "@" for use with "handle_response" blocks + i.responseMatchers = make(map[string]caddyhttp.ResponseMatcher) + + d.Next() // consume the directive name + for d.NextBlock(0) { + // if the subdirective has an "@" prefix then we + // parse it as a response matcher for use with "handle_response" + if strings.HasPrefix(d.Val(), matcherPrefix) { + err := caddyhttp.ParseNamedResponseMatcher(d.NewFromNextSegment(), i.responseMatchers) + if err != nil { + return err + } + continue + } + + switch d.Val() { + case "handle_response": + // delegate the parsing of handle_response to the caller, + // since we need the httpcaddyfile.Helper to parse subroutes. + // See h.FinalizeUnmarshalCaddyfile + i.handleResponseSegments = append(i.handleResponseSegments, d.NewFromNextSegment()) + + case "replace_status": + args := d.RemainingArgs() + if len(args) != 1 && len(args) != 2 { + return d.Errf("must have one or two arguments: an optional response matcher, and a status code") + } + + responseHandler := caddyhttp.ResponseHandler{} + + if len(args) == 2 { + if !strings.HasPrefix(args[0], matcherPrefix) { + return d.Errf("must use a named response matcher, starting with '@'") + } + foundMatcher, ok := i.responseMatchers[args[0]] + if !ok { + return d.Errf("no named response matcher defined with name '%s'", args[0][1:]) + } + responseHandler.Match = &foundMatcher + responseHandler.StatusCode = caddyhttp.WeakString(args[1]) + } else if len(args) == 1 { + responseHandler.StatusCode = caddyhttp.WeakString(args[0]) + } + + // make sure there's no block, cause it doesn't make sense + if nesting := d.Nesting(); d.NextBlock(nesting) { + return d.Errf("cannot define routes for 'replace_status', use 'handle_response' instead.") + } + + i.HandleResponse = append( + i.HandleResponse, + responseHandler, + ) + + default: + return d.Errf("unrecognized subdirective %s", d.Val()) + } + } + + return nil +} + +// FinalizeUnmarshalCaddyfile finalizes the Caddyfile parsing which +// requires having an httpcaddyfile.Helper to function, to parse subroutes. +// +// EXPERIMENTAL: Subject to change or removal. +func (i *Intercept) FinalizeUnmarshalCaddyfile(helper httpcaddyfile.Helper) error { + for _, d := range i.handleResponseSegments { + // consume the "handle_response" token + d.Next() + args := d.RemainingArgs() + + // TODO: Remove this check at some point in the future + if len(args) == 2 { + return d.Errf("configuring 'handle_response' for status code replacement is no longer supported. Use 'replace_status' instead.") + } + + if len(args) > 1 { + return d.Errf("too many arguments for 'handle_response': %s", args) + } + + var matcher *caddyhttp.ResponseMatcher + if len(args) == 1 { + // the first arg should always be a matcher. + if !strings.HasPrefix(args[0], matcherPrefix) { + return d.Errf("must use a named response matcher, starting with '@'") + } + + foundMatcher, ok := i.responseMatchers[args[0]] + if !ok { + return d.Errf("no named response matcher defined with name '%s'", args[0][1:]) + } + matcher = &foundMatcher + } + + // parse the block as routes + handler, err := httpcaddyfile.ParseSegmentAsSubroute(helper.WithDispenser(d.NewFromNextSegment())) + if err != nil { + return err + } + subroute, ok := handler.(*caddyhttp.Subroute) + if !ok { + return helper.Errf("segment was not parsed as a subroute") + } + i.HandleResponse = append( + i.HandleResponse, + caddyhttp.ResponseHandler{ + Match: matcher, + Routes: subroute.Routes, + }, + ) + } + + // move the handle_response entries without a matcher to the end. + // we can't use sort.SliceStable because it will reorder the rest of the + // entries which may be undesirable because we don't have a good + // heuristic to use for sorting. + withoutMatchers := []caddyhttp.ResponseHandler{} + withMatchers := []caddyhttp.ResponseHandler{} + for _, hr := range i.HandleResponse { + if hr.Match == nil { + withoutMatchers = append(withoutMatchers, hr) + } else { + withMatchers = append(withMatchers, hr) + } + } + i.HandleResponse = append(withMatchers, withoutMatchers...) + + // clean up the bits we only needed for adapting + i.handleResponseSegments = nil + i.responseMatchers = nil + + return nil +} + +const matcherPrefix = "@" + +func parseCaddyfile(helper httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + var ir Intercept + if err := ir.UnmarshalCaddyfile(helper.Dispenser); err != nil { + return nil, err + } + + if err := ir.FinalizeUnmarshalCaddyfile(helper); err != nil { + return nil, err + } + + return ir, nil +} + +// Interface guards +var ( + _ caddy.Provisioner = (*Intercept)(nil) + _ caddyfile.Unmarshaler = (*Intercept)(nil) + _ caddyhttp.MiddlewareHandler = (*Intercept)(nil) +) diff --git a/modules/caddyhttp/invoke.go b/modules/caddyhttp/invoke.go new file mode 100644 index 00000000000..97fd1cc31e2 --- /dev/null +++ b/modules/caddyhttp/invoke.go @@ -0,0 +1,56 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "fmt" + "net/http" + + "github.com/caddyserver/caddy/v2" +) + +func init() { + caddy.RegisterModule(Invoke{}) +} + +// Invoke implements a handler that compiles and executes a +// named route that was defined on the server. +// +// EXPERIMENTAL: Subject to change or removal. +type Invoke struct { + // Name is the key of the named route to execute + Name string `json:"name,omitempty"` +} + +// CaddyModule returns the Caddy module information. +func (Invoke) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.invoke", + New: func() caddy.Module { return new(Invoke) }, + } +} + +func (invoke *Invoke) ServeHTTP(w http.ResponseWriter, r *http.Request, next Handler) error { + server := r.Context().Value(ServerCtxKey).(*Server) + if route, ok := server.NamedRoutes[invoke.Name]; ok { + return route.Compile(next).ServeHTTP(w, r) + } + return fmt.Errorf("invoke: route '%s' not found", invoke.Name) +} + +// Interface guards +var ( + _ MiddlewareHandler = (*Invoke)(nil) +) diff --git a/modules/caddyhttp/ip_matchers.go b/modules/caddyhttp/ip_matchers.go new file mode 100644 index 00000000000..5e0b356e7c8 --- /dev/null +++ b/modules/caddyhttp/ip_matchers.go @@ -0,0 +1,366 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "errors" + "fmt" + "net" + "net/http" + "net/netip" + "reflect" + "strings" + + "github.com/google/cel-go/cel" + "github.com/google/cel-go/common/types/ref" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/internal" +) + +// MatchRemoteIP matches requests by the remote IP address, +// i.e. the IP address of the direct connection to Caddy. +type MatchRemoteIP struct { + // The IPs or CIDR ranges to match. + Ranges []string `json:"ranges,omitempty"` + + // cidrs and zones vars should aligned always in the same + // length and indexes for matching later + cidrs []*netip.Prefix + zones []string + logger *zap.Logger +} + +// MatchClientIP matches requests by the client IP address, +// i.e. the resolved address, considering trusted proxies. +type MatchClientIP struct { + // The IPs or CIDR ranges to match. + Ranges []string `json:"ranges,omitempty"` + + // cidrs and zones vars should aligned always in the same + // length and indexes for matching later + cidrs []*netip.Prefix + zones []string + logger *zap.Logger +} + +func init() { + caddy.RegisterModule(MatchRemoteIP{}) + caddy.RegisterModule(MatchClientIP{}) +} + +// CaddyModule returns the Caddy module information. +func (MatchRemoteIP) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.matchers.remote_ip", + New: func() caddy.Module { return new(MatchRemoteIP) }, + } +} + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. +func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + // iterate to merge multiple matchers into one + for d.Next() { + for d.NextArg() { + if d.Val() == "forwarded" { + return d.Err("the 'forwarded' option is no longer supported; use the 'client_ip' matcher instead") + } + if d.Val() == "private_ranges" { + m.Ranges = append(m.Ranges, internal.PrivateRangesCIDR()...) + continue + } + m.Ranges = append(m.Ranges, d.Val()) + } + if d.NextBlock(0) { + return d.Err("malformed remote_ip matcher: blocks are not supported") + } + } + return nil +} + +// CELLibrary produces options that expose this matcher for use in CEL +// expression matchers. +// +// Example: +// +// expression remote_ip('192.168.0.0/16', '172.16.0.0/12', '10.0.0.0/8') +func (MatchRemoteIP) CELLibrary(ctx caddy.Context) (cel.Library, error) { + return CELMatcherImpl( + // name of the macro, this is the function name that users see when writing expressions. + "remote_ip", + // name of the function that the macro will be rewritten to call. + "remote_ip_match_request_list", + // internal data type of the MatchPath value. + []*cel.Type{cel.ListType(cel.StringType)}, + // function to convert a constant list of strings to a MatchPath instance. + func(data ref.Val) (RequestMatcherWithError, error) { + refStringList := reflect.TypeOf([]string{}) + strList, err := data.ConvertToNative(refStringList) + if err != nil { + return nil, err + } + + m := MatchRemoteIP{} + + for _, input := range strList.([]string) { + if input == "forwarded" { + return nil, errors.New("the 'forwarded' option is no longer supported; use the 'client_ip' matcher instead") + } + m.Ranges = append(m.Ranges, input) + } + + err = m.Provision(ctx) + return m, err + }, + ) +} + +// Provision parses m's IP ranges, either from IP or CIDR expressions. +func (m *MatchRemoteIP) Provision(ctx caddy.Context) error { + m.logger = ctx.Logger() + cidrs, zones, err := provisionCidrsZonesFromRanges(m.Ranges) + if err != nil { + return err + } + m.cidrs = cidrs + m.zones = zones + + return nil +} + +// Match returns true if r matches m. +func (m MatchRemoteIP) Match(r *http.Request) bool { + match, err := m.MatchWithError(r) + if err != nil { + SetVar(r.Context(), MatcherErrorVarKey, err) + } + return match +} + +// MatchWithError returns true if r matches m. +func (m MatchRemoteIP) MatchWithError(r *http.Request) (bool, error) { + // if handshake is not finished, we infer 0-RTT that has + // not verified remote IP; could be spoofed, so we throw + // HTTP 425 status to tell the client to try again after + // the handshake is complete + if r.TLS != nil && !r.TLS.HandshakeComplete { + return false, Error(http.StatusTooEarly, fmt.Errorf("TLS handshake not complete, remote IP cannot be verified")) + } + + address := r.RemoteAddr + clientIP, zoneID, err := parseIPZoneFromString(address) + if err != nil { + if c := m.logger.Check(zapcore.ErrorLevel, "getting remote "); c != nil { + c.Write(zap.Error(err)) + } + + return false, nil + } + matches, zoneFilter := matchIPByCidrZones(clientIP, zoneID, m.cidrs, m.zones) + if !matches && !zoneFilter { + if c := m.logger.Check(zapcore.DebugLevel, "zone ID from remote IP did not match"); c != nil { + c.Write(zap.String("zone", zoneID)) + } + } + return matches, nil +} + +// CaddyModule returns the Caddy module information. +func (MatchClientIP) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.matchers.client_ip", + New: func() caddy.Module { return new(MatchClientIP) }, + } +} + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. +func (m *MatchClientIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + // iterate to merge multiple matchers into one + for d.Next() { + for d.NextArg() { + if d.Val() == "private_ranges" { + m.Ranges = append(m.Ranges, internal.PrivateRangesCIDR()...) + continue + } + m.Ranges = append(m.Ranges, d.Val()) + } + if d.NextBlock(0) { + return d.Err("malformed client_ip matcher: blocks are not supported") + } + } + return nil +} + +// CELLibrary produces options that expose this matcher for use in CEL +// expression matchers. +// +// Example: +// +// expression client_ip('192.168.0.0/16', '172.16.0.0/12', '10.0.0.0/8') +func (MatchClientIP) CELLibrary(ctx caddy.Context) (cel.Library, error) { + return CELMatcherImpl( + // name of the macro, this is the function name that users see when writing expressions. + "client_ip", + // name of the function that the macro will be rewritten to call. + "client_ip_match_request_list", + // internal data type of the MatchPath value. + []*cel.Type{cel.ListType(cel.StringType)}, + // function to convert a constant list of strings to a MatchPath instance. + func(data ref.Val) (RequestMatcherWithError, error) { + refStringList := reflect.TypeOf([]string{}) + strList, err := data.ConvertToNative(refStringList) + if err != nil { + return nil, err + } + + m := MatchClientIP{ + Ranges: strList.([]string), + } + + err = m.Provision(ctx) + return m, err + }, + ) +} + +// Provision parses m's IP ranges, either from IP or CIDR expressions. +func (m *MatchClientIP) Provision(ctx caddy.Context) error { + m.logger = ctx.Logger() + cidrs, zones, err := provisionCidrsZonesFromRanges(m.Ranges) + if err != nil { + return err + } + m.cidrs = cidrs + m.zones = zones + return nil +} + +// Match returns true if r matches m. +func (m MatchClientIP) Match(r *http.Request) bool { + match, err := m.MatchWithError(r) + if err != nil { + SetVar(r.Context(), MatcherErrorVarKey, err) + } + return match +} + +// MatchWithError returns true if r matches m. +func (m MatchClientIP) MatchWithError(r *http.Request) (bool, error) { + // if handshake is not finished, we infer 0-RTT that has + // not verified remote IP; could be spoofed, so we throw + // HTTP 425 status to tell the client to try again after + // the handshake is complete + if r.TLS != nil && !r.TLS.HandshakeComplete { + return false, Error(http.StatusTooEarly, fmt.Errorf("TLS handshake not complete, remote IP cannot be verified")) + } + + address := GetVar(r.Context(), ClientIPVarKey).(string) + clientIP, zoneID, err := parseIPZoneFromString(address) + if err != nil { + m.logger.Error("getting client IP", zap.Error(err)) + return false, nil + } + matches, zoneFilter := matchIPByCidrZones(clientIP, zoneID, m.cidrs, m.zones) + if !matches && !zoneFilter { + m.logger.Debug("zone ID from client IP did not match", zap.String("zone", zoneID)) + } + return matches, nil +} + +func provisionCidrsZonesFromRanges(ranges []string) ([]*netip.Prefix, []string, error) { + cidrs := []*netip.Prefix{} + zones := []string{} + repl := caddy.NewReplacer() + for _, str := range ranges { + str = repl.ReplaceAll(str, "") + // Exclude the zone_id from the IP + if strings.Contains(str, "%") { + split := strings.Split(str, "%") + str = split[0] + // write zone identifiers in m.zones for matching later + zones = append(zones, split[1]) + } else { + zones = append(zones, "") + } + if strings.Contains(str, "/") { + ipNet, err := netip.ParsePrefix(str) + if err != nil { + return nil, nil, fmt.Errorf("parsing CIDR expression '%s': %v", str, err) + } + cidrs = append(cidrs, &ipNet) + } else { + ipAddr, err := netip.ParseAddr(str) + if err != nil { + return nil, nil, fmt.Errorf("invalid IP address: '%s': %v", str, err) + } + ipNew := netip.PrefixFrom(ipAddr, ipAddr.BitLen()) + cidrs = append(cidrs, &ipNew) + } + } + return cidrs, zones, nil +} + +func parseIPZoneFromString(address string) (netip.Addr, string, error) { + ipStr, _, err := net.SplitHostPort(address) + if err != nil { + ipStr = address // OK; probably didn't have a port + } + + // Some IPv6-Addresses can contain zone identifiers at the end, + // which are separated with "%" + zoneID := "" + if strings.Contains(ipStr, "%") { + split := strings.Split(ipStr, "%") + ipStr = split[0] + zoneID = split[1] + } + + ipAddr, err := netip.ParseAddr(ipStr) + if err != nil { + return netip.IPv4Unspecified(), "", err + } + + return ipAddr, zoneID, nil +} + +func matchIPByCidrZones(clientIP netip.Addr, zoneID string, cidrs []*netip.Prefix, zones []string) (bool, bool) { + zoneFilter := true + for i, ipRange := range cidrs { + if ipRange.Contains(clientIP) { + // Check if there are zone filters assigned and if they match. + if zones[i] == "" || zoneID == zones[i] { + return true, false + } + zoneFilter = false + } + } + return false, zoneFilter +} + +// Interface guards +var ( + _ RequestMatcherWithError = (*MatchRemoteIP)(nil) + _ caddy.Provisioner = (*MatchRemoteIP)(nil) + _ caddyfile.Unmarshaler = (*MatchRemoteIP)(nil) + _ CELLibraryProducer = (*MatchRemoteIP)(nil) + + _ RequestMatcherWithError = (*MatchClientIP)(nil) + _ caddy.Provisioner = (*MatchClientIP)(nil) + _ caddyfile.Unmarshaler = (*MatchClientIP)(nil) + _ CELLibraryProducer = (*MatchClientIP)(nil) +) diff --git a/modules/caddyhttp/ip_range.go b/modules/caddyhttp/ip_range.go new file mode 100644 index 00000000000..bfd76c14c3d --- /dev/null +++ b/modules/caddyhttp/ip_range.go @@ -0,0 +1,137 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "fmt" + "net/http" + "net/netip" + "strings" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/internal" +) + +func init() { + caddy.RegisterModule(StaticIPRange{}) +} + +// IPRangeSource gets a list of IP ranges. +// +// The request is passed as an argument to allow plugin implementations +// to have more flexibility. But, a plugin MUST NOT modify the request. +// The caller will have read the `r.RemoteAddr` before getting IP ranges. +// +// This should be a very fast function -- instant if possible. +// The list of IP ranges should be sourced as soon as possible if loaded +// from an external source (i.e. initially loaded during Provisioning), +// so that it's ready to be used when requests start getting handled. +// A read lock should probably be used to get the cached value if the +// ranges can change at runtime (e.g. periodically refreshed). +// Using a `caddy.UsagePool` may be a good idea to avoid having refetch +// the values when a config reload occurs, which would waste time. +// +// If the list of IP ranges cannot be sourced, then provisioning SHOULD +// fail. Getting the IP ranges at runtime MUST NOT fail, because it would +// cancel incoming requests. If refreshing the list fails, then the +// previous list of IP ranges should continue to be returned so that the +// server can continue to operate normally. +type IPRangeSource interface { + GetIPRanges(*http.Request) []netip.Prefix +} + +// StaticIPRange provides a static range of IP address prefixes (CIDRs). +type StaticIPRange struct { + // A static list of IP ranges (supports CIDR notation). + Ranges []string `json:"ranges,omitempty"` + + // Holds the parsed CIDR ranges from Ranges. + ranges []netip.Prefix +} + +// CaddyModule returns the Caddy module information. +func (StaticIPRange) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.ip_sources.static", + New: func() caddy.Module { return new(StaticIPRange) }, + } +} + +func (s *StaticIPRange) Provision(ctx caddy.Context) error { + for _, str := range s.Ranges { + prefix, err := CIDRExpressionToPrefix(str) + if err != nil { + return err + } + s.ranges = append(s.ranges, prefix) + } + + return nil +} + +func (s *StaticIPRange) GetIPRanges(_ *http.Request) []netip.Prefix { + return s.ranges +} + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. +func (m *StaticIPRange) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + if !d.Next() { + return nil + } + for d.NextArg() { + if d.Val() == "private_ranges" { + m.Ranges = append(m.Ranges, internal.PrivateRangesCIDR()...) + continue + } + m.Ranges = append(m.Ranges, d.Val()) + } + return nil +} + +// CIDRExpressionToPrefix takes a string which could be either a +// CIDR expression or a single IP address, and returns a netip.Prefix. +func CIDRExpressionToPrefix(expr string) (netip.Prefix, error) { + // Having a slash means it should be a CIDR expression + if strings.Contains(expr, "/") { + prefix, err := netip.ParsePrefix(expr) + if err != nil { + return netip.Prefix{}, fmt.Errorf("parsing CIDR expression: '%s': %v", expr, err) + } + return prefix, nil + } + + // Otherwise it's likely a single IP address + parsed, err := netip.ParseAddr(expr) + if err != nil { + return netip.Prefix{}, fmt.Errorf("invalid IP address: '%s': %v", expr, err) + } + prefix := netip.PrefixFrom(parsed, parsed.BitLen()) + return prefix, nil +} + +// Interface guards +var ( + _ caddy.Provisioner = (*StaticIPRange)(nil) + _ caddyfile.Unmarshaler = (*StaticIPRange)(nil) + _ IPRangeSource = (*StaticIPRange)(nil) +) + +// PrivateRangesCIDR returns a list of private CIDR range +// strings, which can be used as a configuration shortcut. +// Note: this function is used at least by mholt/caddy-l4. +func PrivateRangesCIDR() []string { + return internal.PrivateRangesCIDR() +} diff --git a/modules/caddyhttp/logging.go b/modules/caddyhttp/logging.go new file mode 100644 index 00000000000..87298ac3c6f --- /dev/null +++ b/modules/caddyhttp/logging.go @@ -0,0 +1,256 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "encoding/json" + "errors" + "net" + "net/http" + "strings" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" +) + +// ServerLogConfig describes a server's logging configuration. If +// enabled without customization, all requests to this server are +// logged to the default logger; logger destinations may be +// customized per-request-host. +type ServerLogConfig struct { + // The default logger name for all logs emitted by this server for + // hostnames that are not in the logger_names map. + DefaultLoggerName string `json:"default_logger_name,omitempty"` + + // LoggerNames maps request hostnames to one or more custom logger + // names. For example, a mapping of `"example.com": ["example"]` would + // cause access logs from requests with a Host of example.com to be + // emitted by a logger named "http.log.access.example". If there are + // multiple logger names, then the log will be emitted to all of them. + // If the logger name is an empty, the default logger is used, i.e. + // the logger "http.log.access". + // + // Keys must be hostnames (without ports), and may contain wildcards + // to match subdomains. The value is an array of logger names. + // + // For backwards compatibility, if the value is a string, it is treated + // as a single-element array. + LoggerNames map[string]StringArray `json:"logger_names,omitempty"` + + // By default, all requests to this server will be logged if + // access logging is enabled. This field lists the request + // hosts for which access logging should be disabled. + SkipHosts []string `json:"skip_hosts,omitempty"` + + // If true, requests to any host not appearing in the + // logger_names map will not be logged. + SkipUnmappedHosts bool `json:"skip_unmapped_hosts,omitempty"` + + // If true, credentials that are otherwise omitted, will be logged. + // The definition of credentials is defined by https://fetch.spec.whatwg.org/#credentials, + // and this includes some request and response headers, i.e `Cookie`, + // `Set-Cookie`, `Authorization`, and `Proxy-Authorization`. + ShouldLogCredentials bool `json:"should_log_credentials,omitempty"` + + // Log each individual handler that is invoked. + // Requires that the log emit at DEBUG level. + // + // NOTE: This may log the configuration of your + // HTTP handler modules; do not enable this in + // insecure contexts when there is sensitive + // data in the configuration. + // + // EXPERIMENTAL: Subject to change or removal. + Trace bool `json:"trace,omitempty"` +} + +// wrapLogger wraps logger in one or more logger named +// according to user preferences for the given host. +func (slc ServerLogConfig) wrapLogger(logger *zap.Logger, req *http.Request) []*zap.Logger { + // using the `log_name` directive or the `access_logger_names` variable, + // the logger names can be overridden for the current request + if names := GetVar(req.Context(), AccessLoggerNameVarKey); names != nil { + if namesSlice, ok := names.([]any); ok { + loggers := make([]*zap.Logger, 0, len(namesSlice)) + for _, loggerName := range namesSlice { + // no name, use the default logger + if loggerName == "" { + loggers = append(loggers, logger) + continue + } + // make a logger with the given name + loggers = append(loggers, logger.Named(loggerName.(string))) + } + return loggers + } + } + + // get the hostname from the request, with the port number stripped + host, _, err := net.SplitHostPort(req.Host) + if err != nil { + host = req.Host + } + + // get the logger names for this host from the config + hosts := slc.getLoggerHosts(host) + + // make a list of named loggers, or the default logger + loggers := make([]*zap.Logger, 0, len(hosts)) + for _, loggerName := range hosts { + // no name, use the default logger + if loggerName == "" { + loggers = append(loggers, logger) + continue + } + // make a logger with the given name + loggers = append(loggers, logger.Named(loggerName)) + } + return loggers +} + +func (slc ServerLogConfig) getLoggerHosts(host string) []string { + // try the exact hostname first + if hosts, ok := slc.LoggerNames[host]; ok { + return hosts + } + + // try matching wildcard domains if other non-specific loggers exist + labels := strings.Split(host, ".") + for i := range labels { + if labels[i] == "" { + continue + } + labels[i] = "*" + wildcardHost := strings.Join(labels, ".") + if hosts, ok := slc.LoggerNames[wildcardHost]; ok { + return hosts + } + } + + return []string{slc.DefaultLoggerName} +} + +func (slc *ServerLogConfig) clone() *ServerLogConfig { + clone := &ServerLogConfig{ + DefaultLoggerName: slc.DefaultLoggerName, + LoggerNames: make(map[string]StringArray), + SkipHosts: append([]string{}, slc.SkipHosts...), + SkipUnmappedHosts: slc.SkipUnmappedHosts, + ShouldLogCredentials: slc.ShouldLogCredentials, + } + for k, v := range slc.LoggerNames { + clone.LoggerNames[k] = append([]string{}, v...) + } + return clone +} + +// StringArray is a slices of strings, but also accepts +// a single string as a value when JSON unmarshaling, +// converting it to a slice of one string. +type StringArray []string + +// UnmarshalJSON satisfies json.Unmarshaler. +func (sa *StringArray) UnmarshalJSON(b []byte) error { + var jsonObj any + err := json.Unmarshal(b, &jsonObj) + if err != nil { + return err + } + switch obj := jsonObj.(type) { + case string: + *sa = StringArray([]string{obj}) + return nil + case []any: + s := make([]string, 0, len(obj)) + for _, v := range obj { + value, ok := v.(string) + if !ok { + return errors.New("unsupported type") + } + s = append(s, value) + } + *sa = StringArray(s) + return nil + } + return errors.New("unsupported type") +} + +// errLogValues inspects err and returns the status code +// to use, the error log message, and any extra fields. +// If err is a HandlerError, the returned values will +// have richer information. +func errLogValues(err error) (status int, msg string, fields func() []zapcore.Field) { + var handlerErr HandlerError + if errors.As(err, &handlerErr) { + status = handlerErr.StatusCode + if handlerErr.Err == nil { + msg = err.Error() + } else { + msg = handlerErr.Err.Error() + } + fields = func() []zapcore.Field { + return []zapcore.Field{ + zap.Int("status", handlerErr.StatusCode), + zap.String("err_id", handlerErr.ID), + zap.String("err_trace", handlerErr.Trace), + } + } + return + } + fields = func() []zapcore.Field { + return []zapcore.Field{ + zap.Error(err), + } + } + status = http.StatusInternalServerError + msg = err.Error() + return +} + +// ExtraLogFields is a list of extra fields to log with every request. +type ExtraLogFields struct { + fields []zapcore.Field +} + +// Add adds a field to the list of extra fields to log. +func (e *ExtraLogFields) Add(field zap.Field) { + e.fields = append(e.fields, field) +} + +// Set sets a field in the list of extra fields to log. +// If the field already exists, it is replaced. +func (e *ExtraLogFields) Set(field zap.Field) { + for i := range e.fields { + if e.fields[i].Key == field.Key { + e.fields[i] = field + return + } + } + e.fields = append(e.fields, field) +} + +const ( + // Variable name used to indicate that this request + // should be omitted from the access logs + LogSkipVar string = "log_skip" + + // For adding additional fields to the access logs + ExtraLogFieldsCtxKey caddy.CtxKey = "extra_log_fields" + + // Variable name used to indicate the logger to be used + AccessLoggerNameVarKey string = "access_logger_names" +) diff --git a/modules/caddyhttp/logging/caddyfile.go b/modules/caddyhttp/logging/caddyfile.go new file mode 100644 index 00000000000..010b48919a1 --- /dev/null +++ b/modules/caddyhttp/logging/caddyfile.go @@ -0,0 +1,53 @@ +// 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 logging + +import ( + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func init() { + httpcaddyfile.RegisterHandlerDirective("log_append", parseCaddyfile) +} + +// parseCaddyfile sets up the log_append handler from Caddyfile tokens. Syntax: +// +// log_append [] +func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + handler := new(LogAppend) + err := handler.UnmarshalCaddyfile(h.Dispenser) + return handler, err +} + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. +func (h *LogAppend) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume directive name + if !d.NextArg() { + return d.ArgErr() + } + h.Key = d.Val() + if !d.NextArg() { + return d.ArgErr() + } + h.Value = d.Val() + return nil +} + +// Interface guards +var ( + _ caddyfile.Unmarshaler = (*LogAppend)(nil) +) diff --git a/modules/caddyhttp/logging/logadd.go b/modules/caddyhttp/logging/logadd.go new file mode 100644 index 00000000000..3b554367f93 --- /dev/null +++ b/modules/caddyhttp/logging/logadd.go @@ -0,0 +1,94 @@ +// 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 logging + +import ( + "net/http" + "strings" + + "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func init() { + caddy.RegisterModule(LogAppend{}) +} + +// LogAppend implements a middleware that takes a key and value, where +// the key is the name of a log field and the value is a placeholder, +// or variable key, or constant value to use for that field. +type LogAppend struct { + // Key is the name of the log field. + Key string `json:"key,omitempty"` + + // Value is the value to use for the log field. + // If it is a placeholder (with surrounding `{}`), + // it will be evaluated when the log is written. + // If the value is a key that exists in the `vars` + // map, the value of that key will be used. Otherwise + // the value will be used as-is as a constant string. + Value string `json:"value,omitempty"` +} + +// CaddyModule returns the Caddy module information. +func (LogAppend) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.log_append", + New: func() caddy.Module { return new(LogAppend) }, + } +} + +func (h LogAppend) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + // Run the next handler in the chain first. + // If an error occurs, we still want to add + // any extra log fields that we can, so we + // hold onto the error and return it later. + handlerErr := next.ServeHTTP(w, r) + + // On the way back up the chain, add the extra log field + ctx := r.Context() + + vars := ctx.Value(caddyhttp.VarsCtxKey).(map[string]any) + repl := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + extra := ctx.Value(caddyhttp.ExtraLogFieldsCtxKey).(*caddyhttp.ExtraLogFields) + + var varValue any + if strings.HasPrefix(h.Value, "{") && + strings.HasSuffix(h.Value, "}") && + strings.Count(h.Value, "{") == 1 { + // the value looks like a placeholder, so get its value + varValue, _ = repl.Get(strings.Trim(h.Value, "{}")) + } else if val, ok := vars[h.Value]; ok { + // the value is a key in the vars map + varValue = val + } else { + // the value is a constant string + varValue = h.Value + } + + // Add the field to the extra log fields. + // We use zap.Any because it will reflect + // to the correct type for us. + extra.Add(zap.Any(h.Key, varValue)) + + return handlerErr +} + +// Interface guards +var ( + _ caddyhttp.MiddlewareHandler = (*LogAppend)(nil) +) diff --git a/modules/caddyhttp/map/caddyfile.go b/modules/caddyhttp/map/caddyfile.go new file mode 100644 index 00000000000..8f7b5d34e6b --- /dev/null +++ b/modules/caddyhttp/map/caddyfile.go @@ -0,0 +1,114 @@ +// 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 maphandler + +import ( + "strings" + + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func init() { + httpcaddyfile.RegisterHandlerDirective("map", parseCaddyfile) +} + +// parseCaddyfile sets up the map handler from Caddyfile tokens. Syntax: +// +// map [] { +// [~] +// default +// } +// +// If the input value is prefixed with a tilde (~), then the input will be parsed as a +// regular expression. +// +// The Caddyfile adapter treats outputs that are a literal hyphen (-) as a null/nil +// value. This is useful if you want to fall back to default for that particular output. +// +// The number of outputs for each mapping must not be more than the number of destinations. +// However, for convenience, there may be fewer outputs than destinations and any missing +// outputs will be filled in implicitly. +func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + h.Next() // consume directive name + + var handler Handler + + // source + if !h.NextArg() { + return nil, h.ArgErr() + } + handler.Source = h.Val() + + // destinations + handler.Destinations = h.RemainingArgs() + if len(handler.Destinations) == 0 { + return nil, h.Err("missing destination argument(s)") + } + for _, dest := range handler.Destinations { + if shorthand := httpcaddyfile.WasReplacedPlaceholderShorthand(dest); shorthand != "" { + return nil, h.Errf("destination %s conflicts with a Caddyfile placeholder shorthand", shorthand) + } + } + + // mappings + for h.NextBlock(0) { + // defaults are a special case + if h.Val() == "default" { + if len(handler.Defaults) > 0 { + return nil, h.Err("defaults already defined") + } + handler.Defaults = h.RemainingArgs() + for len(handler.Defaults) < len(handler.Destinations) { + handler.Defaults = append(handler.Defaults, "") + } + continue + } + + // every line maps an input value to one or more outputs + in := h.Val() + var outs []any + for h.NextArg() { + val := h.ScalarVal() + if val == "-" { + outs = append(outs, nil) + } else { + outs = append(outs, val) + } + } + + // cannot have more outputs than destinations + if len(outs) > len(handler.Destinations) { + return nil, h.Err("too many outputs") + } + + // for convenience, can have fewer outputs than destinations, but the + // underlying handler won't accept that, so we fill in nil values + for len(outs) < len(handler.Destinations) { + outs = append(outs, nil) + } + + // create the mapping + mapping := Mapping{Outputs: outs} + if strings.HasPrefix(in, "~") { + mapping.InputRegexp = in[1:] + } else { + mapping.Input = in + } + + handler.Mappings = append(handler.Mappings, mapping) + } + return handler, nil +} diff --git a/modules/caddyhttp/map/map.go b/modules/caddyhttp/map/map.go new file mode 100644 index 00000000000..d02085e7633 --- /dev/null +++ b/modules/caddyhttp/map/map.go @@ -0,0 +1,196 @@ +// 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 maphandler + +import ( + "fmt" + "net/http" + "regexp" + "slices" + "strings" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func init() { + caddy.RegisterModule(Handler{}) +} + +// Handler implements a middleware that maps inputs to outputs. Specifically, it +// compares a source value against the map inputs, and for one that matches, it +// applies the output values to each destination. Destinations become placeholder +// names. +// +// Mapped placeholders are not evaluated until they are used, so even for very +// large mappings, this handler is quite efficient. +type Handler struct { + // Source is the placeholder from which to get the input value. + Source string `json:"source,omitempty"` + + // Destinations are the names of placeholders in which to store the outputs. + // Destination values should be wrapped in braces, for example, {my_placeholder}. + Destinations []string `json:"destinations,omitempty"` + + // Mappings from source values (inputs) to destination values (outputs). + // The first matching, non-nil mapping will be applied. + Mappings []Mapping `json:"mappings,omitempty"` + + // If no mappings match or if the mapped output is null/nil, the associated + // default output will be applied (optional). + Defaults []string `json:"defaults,omitempty"` +} + +// CaddyModule returns the Caddy module information. +func (Handler) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.map", + New: func() caddy.Module { return new(Handler) }, + } +} + +// Provision sets up h. +func (h *Handler) Provision(_ caddy.Context) error { + for j, dest := range h.Destinations { + if strings.Count(dest, "{") != 1 || !strings.HasPrefix(dest, "{") { + return fmt.Errorf("destination must be a placeholder and only a placeholder") + } + h.Destinations[j] = strings.Trim(dest, "{}") + } + + for i, m := range h.Mappings { + if m.InputRegexp == "" { + continue + } + var err error + h.Mappings[i].re, err = regexp.Compile(m.InputRegexp) + if err != nil { + return fmt.Errorf("compiling regexp for mapping %d: %v", i, err) + } + } + + // TODO: improve efficiency even further by using an actual map type + // for the non-regexp mappings, OR sort them and do a binary search + + return nil +} + +// Validate ensures that h is configured properly. +func (h *Handler) Validate() error { + nDest, nDef := len(h.Destinations), len(h.Defaults) + if nDef > 0 && nDef != nDest { + return fmt.Errorf("%d destinations != %d defaults", nDest, nDef) + } + + seen := make(map[string]int) + for i, m := range h.Mappings { + // prevent confusing/ambiguous mappings + if m.Input != "" && m.InputRegexp != "" { + return fmt.Errorf("mapping %d has both input and input_regexp fields specified, which is confusing", i) + } + + // prevent duplicate mappings + input := m.Input + if m.InputRegexp != "" { + input = m.InputRegexp + } + if prev, ok := seen[input]; ok { + return fmt.Errorf("mapping %d has a duplicate input '%s' previously used with mapping %d", i, input, prev) + } + seen[input] = i + + // ensure mappings have 1:1 output-to-destination correspondence + nOut := len(m.Outputs) + if nOut != nDest { + return fmt.Errorf("mapping %d has %d outputs but there are %d destinations defined", i, nOut, nDest) + } + } + + return nil +} + +func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + + // defer work until a variable is actually evaluated by using replacer's Map callback + repl.Map(func(key string) (any, bool) { + // return early if the variable is not even a configured destination + destIdx := slices.Index(h.Destinations, key) + if destIdx < 0 { + return nil, false + } + + input := repl.ReplaceAll(h.Source, "") + + // find the first mapping matching the input and return + // the requested destination/output value + for _, m := range h.Mappings { + output := m.Outputs[destIdx] + if output == nil { + continue + } + outputStr := caddy.ToString(output) + + // evaluate regular expression if configured + if m.re != nil { + var result []byte + matches := m.re.FindStringSubmatchIndex(input) + if matches == nil { + continue + } + result = m.re.ExpandString(result, outputStr, input, matches) + return string(result), true + } + + // otherwise simple string comparison + if input == m.Input { + return repl.ReplaceAll(outputStr, ""), true + } + } + + // fall back to default if no match or if matched nil value + if len(h.Defaults) > destIdx { + return repl.ReplaceAll(h.Defaults[destIdx], ""), true + } + + return nil, true + }) + + return next.ServeHTTP(w, r) +} + +// Mapping describes a mapping from input to outputs. +type Mapping struct { + // The input value to match. Must be distinct from other mappings. + // Mutually exclusive to input_regexp. + Input string `json:"input,omitempty"` + + // The input regular expression to match. Mutually exclusive to input. + InputRegexp string `json:"input_regexp,omitempty"` + + // Upon a match with the input, each output is positionally correlated + // with each destination of the parent handler. An output that is null + // (nil) will be treated as if it was not mapped at all. + Outputs []any `json:"outputs,omitempty"` + + re *regexp.Regexp +} + +// Interface guards +var ( + _ caddy.Provisioner = (*Handler)(nil) + _ caddy.Validator = (*Handler)(nil) + _ caddyhttp.MiddlewareHandler = (*Handler)(nil) +) diff --git a/modules/caddyhttp/map/map_test.go b/modules/caddyhttp/map/map_test.go new file mode 100644 index 00000000000..3ff5e7115a7 --- /dev/null +++ b/modules/caddyhttp/map/map_test.go @@ -0,0 +1,152 @@ +package maphandler + +import ( + "context" + "net/http" + "net/http/httptest" + "reflect" + "testing" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func TestHandler(t *testing.T) { + for i, tc := range []struct { + handler Handler + reqURI string + expect map[string]any + }{ + { + reqURI: "/foo", + handler: Handler{ + Source: "{http.request.uri.path}", + Destinations: []string{"{output}"}, + Mappings: []Mapping{ + { + Input: "/foo", + Outputs: []any{"FOO"}, + }, + }, + }, + expect: map[string]any{ + "output": "FOO", + }, + }, + { + reqURI: "/abcdef", + handler: Handler{ + Source: "{http.request.uri.path}", + Destinations: []string{"{output}"}, + Mappings: []Mapping{ + { + InputRegexp: "(/abc)", + Outputs: []any{"ABC"}, + }, + }, + }, + expect: map[string]any{ + "output": "ABC", + }, + }, + { + reqURI: "/ABCxyzDEF", + handler: Handler{ + Source: "{http.request.uri.path}", + Destinations: []string{"{output}"}, + Mappings: []Mapping{ + { + InputRegexp: "(xyz)", + Outputs: []any{"...${1}..."}, + }, + }, + }, + expect: map[string]any{ + "output": "...xyz...", + }, + }, + { + // Test case from https://caddy.community/t/map-directive-and-regular-expressions/13866/14?u=matt + reqURI: "/?s=0%27+AND+%28SELECT+0+FROM+%28SELECT+count%28%2A%29%2C+CONCAT%28%28SELECT+%40%40version%29%2C+0x23%2C+FLOOR%28RAND%280%29%2A2%29%29+AS+x+FROM+information_schema.columns+GROUP+BY+x%29+y%29+-+-+%27", + handler: Handler{ + Source: "{http.request.uri}", + Destinations: []string{"{output}"}, + Mappings: []Mapping{ + { + InputRegexp: "(?i)(\\^|`|<|>|%|\\\\|\\{|\\}|\\|)", + Outputs: []any{"3"}, + }, + }, + }, + expect: map[string]any{ + "output": "3", + }, + }, + { + reqURI: "/foo", + handler: Handler{ + Source: "{http.request.uri.path}", + Destinations: []string{"{output}"}, + Mappings: []Mapping{ + { + Input: "/foo", + Outputs: []any{"{testvar}"}, + }, + }, + }, + expect: map[string]any{ + "output": "testing", + }, + }, + { + reqURI: "/foo", + handler: Handler{ + Source: "{http.request.uri.path}", + Destinations: []string{"{output}"}, + Defaults: []string{"default"}, + }, + expect: map[string]any{ + "output": "default", + }, + }, + { + reqURI: "/foo", + handler: Handler{ + Source: "{http.request.uri.path}", + Destinations: []string{"{output}"}, + Defaults: []string{"{testvar}"}, + }, + expect: map[string]any{ + "output": "testing", + }, + }, + } { + if err := tc.handler.Provision(caddy.Context{}); err != nil { + t.Fatalf("Test %d: Provisioning handler: %v", i, err) + } + + req, err := http.NewRequest(http.MethodGet, tc.reqURI, nil) + if err != nil { + t.Fatalf("Test %d: Creating request: %v", i, err) + } + repl := caddyhttp.NewTestReplacer(req) + repl.Set("testvar", "testing") + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + req = req.WithContext(ctx) + + rr := httptest.NewRecorder() + noop := caddyhttp.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) error { return nil }) + + if err := tc.handler.ServeHTTP(rr, req, noop); err != nil { + t.Errorf("Test %d: Handler returned error: %v", i, err) + continue + } + + for key, expected := range tc.expect { + actual, _ := repl.Get(key) + if !reflect.DeepEqual(actual, expected) { + t.Errorf("Test %d: Expected %#v but got %#v for {%s}", i, expected, actual, key) + } + } + } +} diff --git a/modules/caddyhttp/marshalers.go b/modules/caddyhttp/marshalers.go new file mode 100644 index 00000000000..9bce377f4b0 --- /dev/null +++ b/modules/caddyhttp/marshalers.go @@ -0,0 +1,126 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "crypto/tls" + "net" + "net/http" + "strings" + + "go.uber.org/zap/zapcore" +) + +// LoggableHTTPRequest makes an HTTP request loggable with zap.Object(). +type LoggableHTTPRequest struct { + *http.Request + + ShouldLogCredentials bool +} + +// MarshalLogObject satisfies the zapcore.ObjectMarshaler interface. +func (r LoggableHTTPRequest) MarshalLogObject(enc zapcore.ObjectEncoder) error { + ip, port, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + ip = r.RemoteAddr + port = "" + } + + enc.AddString("remote_ip", ip) + enc.AddString("remote_port", port) + if ip, ok := GetVar(r.Context(), ClientIPVarKey).(string); ok { + enc.AddString("client_ip", ip) + } + enc.AddString("proto", r.Proto) + enc.AddString("method", r.Method) + enc.AddString("host", r.Host) + enc.AddString("uri", r.RequestURI) + enc.AddObject("headers", LoggableHTTPHeader{ + Header: r.Header, + ShouldLogCredentials: r.ShouldLogCredentials, + }) + if r.TransferEncoding != nil { + enc.AddArray("transfer_encoding", LoggableStringArray(r.TransferEncoding)) + } + if r.TLS != nil { + enc.AddObject("tls", LoggableTLSConnState(*r.TLS)) + } + return nil +} + +// LoggableHTTPHeader makes an HTTP header loggable with zap.Object(). +// Headers with potentially sensitive information (Cookie, Set-Cookie, +// Authorization, and Proxy-Authorization) are logged with empty values. +type LoggableHTTPHeader struct { + http.Header + + ShouldLogCredentials bool +} + +// MarshalLogObject satisfies the zapcore.ObjectMarshaler interface. +func (h LoggableHTTPHeader) MarshalLogObject(enc zapcore.ObjectEncoder) error { + if h.Header == nil { + return nil + } + for key, val := range h.Header { + if !h.ShouldLogCredentials { + switch strings.ToLower(key) { + case "cookie", "set-cookie", "authorization", "proxy-authorization": + val = []string{"REDACTED"} // see #5669. I still think ▒▒▒▒ would be cool. + } + } + enc.AddArray(key, LoggableStringArray(val)) + } + return nil +} + +// LoggableStringArray makes a slice of strings marshalable for logging. +type LoggableStringArray []string + +// MarshalLogArray satisfies the zapcore.ArrayMarshaler interface. +func (sa LoggableStringArray) MarshalLogArray(enc zapcore.ArrayEncoder) error { + if sa == nil { + return nil + } + for _, s := range sa { + enc.AppendString(s) + } + return nil +} + +// LoggableTLSConnState makes a TLS connection state loggable with zap.Object(). +type LoggableTLSConnState tls.ConnectionState + +// MarshalLogObject satisfies the zapcore.ObjectMarshaler interface. +func (t LoggableTLSConnState) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddBool("resumed", t.DidResume) + enc.AddUint16("version", t.Version) + enc.AddUint16("cipher_suite", t.CipherSuite) + enc.AddString("proto", t.NegotiatedProtocol) + enc.AddString("server_name", t.ServerName) + if len(t.PeerCertificates) > 0 { + enc.AddString("client_common_name", t.PeerCertificates[0].Subject.CommonName) + enc.AddString("client_serial", t.PeerCertificates[0].SerialNumber.String()) + } + return nil +} + +// Interface guards +var ( + _ zapcore.ObjectMarshaler = (*LoggableHTTPRequest)(nil) + _ zapcore.ObjectMarshaler = (*LoggableHTTPHeader)(nil) + _ zapcore.ArrayMarshaler = (*LoggableStringArray)(nil) + _ zapcore.ObjectMarshaler = (*LoggableTLSConnState)(nil) +) diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go new file mode 100644 index 00000000000..e5ca28b95b6 --- /dev/null +++ b/modules/caddyhttp/matchers.go @@ -0,0 +1,1657 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "encoding/json" + "errors" + "fmt" + "net" + "net/http" + "net/textproto" + "net/url" + "path" + "reflect" + "regexp" + "runtime" + "slices" + "sort" + "strconv" + "strings" + + "github.com/google/cel-go/cel" + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" + "golang.org/x/net/idna" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +type ( + // MatchHost matches requests by the Host value (case-insensitive). + // + // When used in a top-level HTTP route, + // [qualifying domain names](/docs/automatic-https#hostname-requirements) + // may trigger [automatic HTTPS](/docs/automatic-https), which automatically + // provisions and renews certificates for you. Before doing this, you + // should ensure that DNS records for these domains are properly configured, + // especially A/AAAA pointed at your server. + // + // Automatic HTTPS can be + // [customized or disabled](/docs/modules/http#servers/automatic_https). + // + // Wildcards (`*`) may be used to represent exactly one label of the + // hostname, in accordance with RFC 1034 (because host matchers are also + // used for automatic HTTPS which influences TLS certificates). Thus, + // a host of `*` matches hosts like `localhost` or `internal` but not + // `example.com`. To catch all hosts, omit the host matcher entirely. + // + // The wildcard can be useful for matching all subdomains, for example: + // `*.example.com` matches `foo.example.com` but not `foo.bar.example.com`. + // + // Duplicate entries will return an error. + MatchHost []string + + // MatchPath case-insensitively matches requests by the URI's path. Path + // matching is exact, not prefix-based, giving you more control and clarity + // over matching. Wildcards (`*`) may be used: + // + // - At the end only, for a prefix match (`/prefix/*`) + // - At the beginning only, for a suffix match (`*.suffix`) + // - On both sides only, for a substring match (`*/contains/*`) + // - In the middle, for a globular match (`/accounts/*/info`) + // + // Slashes are significant; i.e. `/foo*` matches `/foo`, `/foo/`, `/foo/bar`, + // and `/foobar`; but `/foo/*` does not match `/foo` or `/foobar`. Valid + // paths start with a slash `/`. + // + // Because there are, in general, multiple possible escaped forms of any + // path, path matchers operate in unescaped space; that is, path matchers + // should be written in their unescaped form to prevent ambiguities and + // possible security issues, as all request paths will be normalized to + // their unescaped forms before matcher evaluation. + // + // However, escape sequences in a match pattern are supported; they are + // compared with the request's raw/escaped path for those bytes only. + // In other words, a matcher of `/foo%2Fbar` will match a request path + // of precisely `/foo%2Fbar`, but not `/foo/bar`. It follows that matching + // the literal percent sign (%) in normalized space can be done using the + // escaped form, `%25`. + // + // Even though wildcards (`*`) operate in the normalized space, the special + // escaped wildcard (`%*`), which is not a valid escape sequence, may be + // used in place of a span that should NOT be decoded; that is, `/bands/%*` + // will match `/bands/AC%2fDC` whereas `/bands/*` will not. + // + // Even though path matching is done in normalized space, the special + // wildcard `%*` may be used in place of a span that should NOT be decoded; + // that is, `/bands/%*/` will match `/bands/AC%2fDC/` whereas `/bands/*/` + // will not. + // + // This matcher is fast, so it does not support regular expressions or + // capture groups. For slower but more powerful matching, use the + // path_regexp matcher. (Note that due to the special treatment of + // escape sequences in matcher patterns, they may perform slightly slower + // in high-traffic environments.) + MatchPath []string + + // MatchPathRE matches requests by a regular expression on the URI's path. + // Path matching is performed in the unescaped (decoded) form of the path. + // + // Upon a match, it adds placeholders to the request: `{http.regexp.name.capture_group}` + // where `name` is the regular expression's name, and `capture_group` is either + // the named or positional capture group from the expression itself. If no name + // is given, then the placeholder omits the name: `{http.regexp.capture_group}` + // (potentially leading to collisions). + MatchPathRE struct{ MatchRegexp } + + // MatchMethod matches requests by the method. + MatchMethod []string + + // MatchQuery matches requests by the URI's query string. It takes a JSON object + // keyed by the query keys, with an array of string values to match for that key. + // Query key matches are exact, but wildcards may be used for value matches. Both + // keys and values may be placeholders. + // + // An example of the structure to match `?key=value&topic=api&query=something` is: + // + // ```json + // { + // "key": ["value"], + // "topic": ["api"], + // "query": ["*"] + // } + // ``` + // + // Invalid query strings, including those with bad escapings or illegal characters + // like semicolons, will fail to parse and thus fail to match. + // + // **NOTE:** Notice that query string values are arrays, not singular values. This is + // because repeated keys are valid in query strings, and each one may have a + // different value. This matcher will match for a key if any one of its configured + // values is assigned in the query string. Backend applications relying on query + // strings MUST take into consideration that query string values are arrays and can + // have multiple values. + MatchQuery url.Values + + // MatchHeader matches requests by header fields. The key is the field + // name and the array is the list of field values. It performs fast, + // exact string comparisons of the field values. Fast prefix, suffix, + // and substring matches can also be done by suffixing, prefixing, or + // surrounding the value with the wildcard `*` character, respectively. + // If a list is null, the header must not exist. If the list is empty, + // the field must simply exist, regardless of its value. + // + // **NOTE:** Notice that header values are arrays, not singular values. This is + // because repeated fields are valid in headers, and each one may have a + // different value. This matcher will match for a field if any one of its configured + // values matches in the header. Backend applications relying on headers MUST take + // into consideration that header field values are arrays and can have multiple + // values. + MatchHeader http.Header + + // MatchHeaderRE matches requests by a regular expression on header fields. + // + // Upon a match, it adds placeholders to the request: `{http.regexp.name.capture_group}` + // where `name` is the regular expression's name, and `capture_group` is either + // the named or positional capture group from the expression itself. If no name + // is given, then the placeholder omits the name: `{http.regexp.capture_group}` + // (potentially leading to collisions). + MatchHeaderRE map[string]*MatchRegexp + + // MatchProtocol matches requests by protocol. Recognized values are + // "http", "https", and "grpc" for broad protocol matches, or specific + // HTTP versions can be specified like so: "http/1", "http/1.1", + // "http/2", "http/3", or minimum versions: "http/2+", etc. + MatchProtocol string + + // MatchTLS matches HTTP requests based on the underlying + // TLS connection state. If this matcher is specified but + // the request did not come over TLS, it will never match. + // If this matcher is specified but is empty and the request + // did come in over TLS, it will always match. + MatchTLS struct { + // Matches if the TLS handshake has completed. QUIC 0-RTT early + // data may arrive before the handshake completes. Generally, it + // is unsafe to replay these requests if they are not idempotent; + // additionally, the remote IP of early data packets can more + // easily be spoofed. It is conventional to respond with HTTP 425 + // Too Early if the request cannot risk being processed in this + // state. + HandshakeComplete *bool `json:"handshake_complete,omitempty"` + } + + // MatchNot matches requests by negating the results of its matcher + // sets. A single "not" matcher takes one or more matcher sets. Each + // matcher set is OR'ed; in other words, if any matcher set returns + // true, the final result of the "not" matcher is false. Individual + // matchers within a set work the same (i.e. different matchers in + // the same set are AND'ed). + // + // NOTE: The generated docs which describe the structure of this + // module are wrong because of how this type unmarshals JSON in a + // custom way. The correct structure is: + // + // ```json + // [ + // {}, + // {} + // ] + // ``` + // + // where each of the array elements is a matcher set, i.e. an + // object keyed by matcher name. + MatchNot struct { + MatcherSetsRaw []caddy.ModuleMap `json:"-" caddy:"namespace=http.matchers"` + MatcherSets []MatcherSet `json:"-"` + } +) + +func init() { + caddy.RegisterModule(MatchHost{}) + caddy.RegisterModule(MatchPath{}) + caddy.RegisterModule(MatchPathRE{}) + caddy.RegisterModule(MatchMethod{}) + caddy.RegisterModule(MatchQuery{}) + caddy.RegisterModule(MatchHeader{}) + caddy.RegisterModule(MatchHeaderRE{}) + caddy.RegisterModule(new(MatchProtocol)) + caddy.RegisterModule(MatchTLS{}) + caddy.RegisterModule(MatchNot{}) +} + +// CaddyModule returns the Caddy module information. +func (MatchHost) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.matchers.host", + New: func() caddy.Module { return new(MatchHost) }, + } +} + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. +func (m *MatchHost) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + // iterate to merge multiple matchers into one + for d.Next() { + *m = append(*m, d.RemainingArgs()...) + if d.NextBlock(0) { + return d.Err("malformed host matcher: blocks are not supported") + } + } + return nil +} + +// Provision sets up and validates m, including making it more efficient for large lists. +func (m MatchHost) Provision(_ caddy.Context) error { + // check for duplicates; they are nonsensical and reduce efficiency + // (we could just remove them, but the user should know their config is erroneous) + seen := make(map[string]int, len(m)) + for i, host := range m { + asciiHost, err := idna.ToASCII(host) + if err != nil { + return fmt.Errorf("converting hostname '%s' to ASCII: %v", host, err) + } + if asciiHost != host { + m[i] = asciiHost + } + normalizedHost := strings.ToLower(asciiHost) + if firstI, ok := seen[normalizedHost]; ok { + return fmt.Errorf("host at index %d is repeated at index %d: %s", firstI, i, host) + } + seen[normalizedHost] = i + } + + if m.large() { + // sort the slice lexicographically, grouping "fuzzy" entries (wildcards and placeholders) + // at the front of the list; this allows us to use binary search for exact matches, which + // we have seen from experience is the most common kind of value in large lists; and any + // other kinds of values (wildcards and placeholders) are grouped in front so the linear + // search should find a match fairly quickly + sort.Slice(m, func(i, j int) bool { + iInexact, jInexact := m.fuzzy(m[i]), m.fuzzy(m[j]) + if iInexact && !jInexact { + return true + } + if !iInexact && jInexact { + return false + } + return m[i] < m[j] + }) + } + + return nil +} + +// Match returns true if r matches m. +func (m MatchHost) Match(r *http.Request) bool { + match, _ := m.MatchWithError(r) + return match +} + +// MatchWithError returns true if r matches m. +func (m MatchHost) MatchWithError(r *http.Request) (bool, error) { + reqHost, _, err := net.SplitHostPort(r.Host) + if err != nil { + // OK; probably didn't have a port + reqHost = r.Host + + // make sure we strip the brackets from IPv6 addresses + reqHost = strings.TrimPrefix(reqHost, "[") + reqHost = strings.TrimSuffix(reqHost, "]") + } + + if m.large() { + // fast path: locate exact match using binary search (about 100-1000x faster for large lists) + pos := sort.Search(len(m), func(i int) bool { + if m.fuzzy(m[i]) { + return false + } + return m[i] >= reqHost + }) + if pos < len(m) && m[pos] == reqHost { + return true, nil + } + } + + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + +outer: + for _, host := range m { + // fast path: if matcher is large, we already know we don't have an exact + // match, so we're only looking for fuzzy match now, which should be at the + // front of the list; if we have reached a value that is not fuzzy, there + // will be no match and we can short-circuit for efficiency + if m.large() && !m.fuzzy(host) { + break + } + + host = repl.ReplaceAll(host, "") + if strings.Contains(host, "*") { + patternParts := strings.Split(host, ".") + incomingParts := strings.Split(reqHost, ".") + if len(patternParts) != len(incomingParts) { + continue + } + for i := range patternParts { + if patternParts[i] == "*" { + continue + } + if !strings.EqualFold(patternParts[i], incomingParts[i]) { + continue outer + } + } + return true, nil + } else if strings.EqualFold(reqHost, host) { + return true, nil + } + } + + return false, nil +} + +// CELLibrary produces options that expose this matcher for use in CEL +// expression matchers. +// +// Example: +// +// expression host('localhost') +func (MatchHost) CELLibrary(ctx caddy.Context) (cel.Library, error) { + return CELMatcherImpl( + "host", + "host_match_request_list", + []*cel.Type{cel.ListType(cel.StringType)}, + func(data ref.Val) (RequestMatcherWithError, error) { + refStringList := reflect.TypeOf([]string{}) + strList, err := data.ConvertToNative(refStringList) + if err != nil { + return nil, err + } + matcher := MatchHost(strList.([]string)) + err = matcher.Provision(ctx) + return matcher, err + }, + ) +} + +// fuzzy returns true if the given hostname h is not a specific +// hostname, e.g. has placeholders or wildcards. +func (MatchHost) fuzzy(h string) bool { return strings.ContainsAny(h, "{*") } + +// large returns true if m is considered to be large. Optimizing +// the matcher for smaller lists has diminishing returns. +// See related benchmark function in test file to conduct experiments. +func (m MatchHost) large() bool { return len(m) > 100 } + +// CaddyModule returns the Caddy module information. +func (MatchPath) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.matchers.path", + New: func() caddy.Module { return new(MatchPath) }, + } +} + +// Provision lower-cases the paths in m to ensure case-insensitive matching. +func (m MatchPath) Provision(_ caddy.Context) error { + for i := range m { + if m[i] == "*" && i > 0 { + // will always match, so just put it first + m[0] = m[i] + break + } + m[i] = strings.ToLower(m[i]) + } + return nil +} + +// Match returns true if r matches m. +func (m MatchPath) Match(r *http.Request) bool { + match, _ := m.MatchWithError(r) + return match +} + +// MatchWithError returns true if r matches m. +func (m MatchPath) MatchWithError(r *http.Request) (bool, error) { + // Even though RFC 9110 says that path matching is case-sensitive + // (https://www.rfc-editor.org/rfc/rfc9110.html#section-4.2.3), + // we do case-insensitive matching to mitigate security issues + // related to differences between operating systems, applications, + // etc; if case-sensitive matching is needed, the regex matcher + // can be used instead. + reqPath := strings.ToLower(r.URL.Path) + + // See #2917; Windows ignores trailing dots and spaces + // when accessing files (sigh), potentially causing a + // security risk (cry) if PHP files end up being served + // as static files, exposing the source code, instead of + // being matched by *.php to be treated as PHP scripts. + if runtime.GOOS == "windows" { // issue #5613 + reqPath = strings.TrimRight(reqPath, ". ") + } + + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + + for _, matchPattern := range m { + matchPattern = repl.ReplaceAll(matchPattern, "") + + // special case: whole path is wildcard; this is unnecessary + // as it matches all requests, which is the same as no matcher + if matchPattern == "*" { + return true, nil + } + + // Clean the path, merge doubled slashes, etc. + // This ensures maliciously crafted requests can't bypass + // the path matcher. See #4407. Good security posture + // requires that we should do all we can to reduce any + // funny-looking paths into "normalized" forms such that + // weird variants can't sneak by. + // + // How we clean the path depends on the kind of pattern: + // we either merge slashes or we don't. If the pattern + // has double slashes, we preserve them in the path. + // + // TODO: Despite the fact that the *vast* majority of path + // matchers have only 1 pattern, a possible optimization is + // to remember the cleaned form of the path for future + // iterations; it's just that the way we clean depends on + // the kind of pattern. + + mergeSlashes := !strings.Contains(matchPattern, "//") + + // if '%' appears in the match pattern, we interpret that to mean + // the intent is to compare that part of the path in raw/escaped + // space; i.e. "%40"=="%40", not "@", and "%2F"=="%2F", not "/" + if strings.Contains(matchPattern, "%") { + reqPathForPattern := CleanPath(r.URL.EscapedPath(), mergeSlashes) + if m.matchPatternWithEscapeSequence(reqPathForPattern, matchPattern) { + return true, nil + } + + // doing prefix/suffix/substring matches doesn't make sense + continue + } + + reqPathForPattern := CleanPath(reqPath, mergeSlashes) + + // for substring, prefix, and suffix matching, only perform those + // special, fast matches if they are the only wildcards in the pattern; + // otherwise we assume a globular match if any * appears in the middle + + // special case: first and last characters are wildcard, + // treat it as a fast substring match + if strings.Count(matchPattern, "*") == 2 && + strings.HasPrefix(matchPattern, "*") && + strings.HasSuffix(matchPattern, "*") { + if strings.Contains(reqPathForPattern, matchPattern[1:len(matchPattern)-1]) { + return true, nil + } + continue + } + + // only perform prefix/suffix match if it is the only wildcard... + // I think that is more correct most of the time + if strings.Count(matchPattern, "*") == 1 { + // special case: first character is a wildcard, + // treat it as a fast suffix match + if strings.HasPrefix(matchPattern, "*") { + if strings.HasSuffix(reqPathForPattern, matchPattern[1:]) { + return true, nil + } + continue + } + + // special case: last character is a wildcard, + // treat it as a fast prefix match + if strings.HasSuffix(matchPattern, "*") { + if strings.HasPrefix(reqPathForPattern, matchPattern[:len(matchPattern)-1]) { + return true, nil + } + continue + } + } + + // at last, use globular matching, which also is exact matching + // if there are no glob/wildcard chars; we ignore the error here + // because we can't handle it anyway + matches, _ := path.Match(matchPattern, reqPathForPattern) + if matches { + return true, nil + } + } + return false, nil +} + +func (MatchPath) matchPatternWithEscapeSequence(escapedPath, matchPath string) bool { + // We would just compare the pattern against r.URL.Path, + // but the pattern contains %, indicating that we should + // compare at least some part of the path in raw/escaped + // space, not normalized space; so we build the string we + // will compare against by adding the normalized parts + // of the path, then switching to the escaped parts where + // the pattern hints to us wherever % is present. + var sb strings.Builder + + // iterate the pattern and escaped path in lock-step; + // increment iPattern every time we consume a char from the pattern, + // increment iPath every time we consume a char from the path; + // iPattern and iPath are our cursors/iterator positions for each string + var iPattern, iPath int + for { + if iPattern >= len(matchPath) || iPath >= len(escapedPath) { + break + } + + // get the next character from the request path + + pathCh := string(escapedPath[iPath]) + var escapedPathCh string + + // normalize (decode) escape sequences + if pathCh == "%" && len(escapedPath) >= iPath+3 { + // hold onto this in case we find out the intent is to match in escaped space here; + // we lowercase it even though technically the spec says: "For consistency, URI + // producers and normalizers should use uppercase hexadecimal digits for all percent- + // encodings" (RFC 3986 section 2.1) - we lowercased the matcher pattern earlier in + // provisioning so we do the same here to gain case-insensitivity in equivalence; + // besides, this string is never shown visibly + escapedPathCh = strings.ToLower(escapedPath[iPath : iPath+3]) + + var err error + pathCh, err = url.PathUnescape(escapedPathCh) + if err != nil { + // should be impossible unless EscapedPath() is giving us an invalid sequence! + return false + } + iPath += 2 // escape sequence is 2 bytes longer than normal char + } + + // now get the next character from the pattern + + normalize := true + switch matchPath[iPattern] { + case '%': + // escape sequence + + // if not a wildcard ("%*"), compare literally; consume next two bytes of pattern + if len(matchPath) >= iPattern+3 && matchPath[iPattern+1] != '*' { + sb.WriteString(escapedPathCh) + iPath++ + iPattern += 2 + break + } + + // escaped wildcard sequence; consume next byte only ('*') + iPattern++ + normalize = false + + fallthrough + case '*': + // wildcard, so consume until next matching character + remaining := escapedPath[iPath:] + until := len(escapedPath) - iPath // go until end of string... + if iPattern < len(matchPath)-1 { // ...unless the * is not at the end + nextCh := matchPath[iPattern+1] + until = strings.IndexByte(remaining, nextCh) + if until == -1 { + // terminating char of wildcard span not found, so definitely no match + return false + } + } + if until == 0 { + // empty span; nothing to add on this iteration + break + } + next := remaining[:until] + if normalize { + var err error + next, err = url.PathUnescape(next) + if err != nil { + return false // should be impossible anyway + } + } + sb.WriteString(next) + iPath += until + default: + sb.WriteString(pathCh) + iPath++ + } + + iPattern++ + } + + // we can now treat rawpath globs (%*) as regular globs (*) + matchPath = strings.ReplaceAll(matchPath, "%*", "*") + + // ignore error here because we can't handle it anyway= + matches, _ := path.Match(matchPath, sb.String()) + return matches +} + +// CELLibrary produces options that expose this matcher for use in CEL +// expression matchers. +// +// Example: +// +// expression path('*substring*', '*suffix') +func (MatchPath) CELLibrary(ctx caddy.Context) (cel.Library, error) { + return CELMatcherImpl( + // name of the macro, this is the function name that users see when writing expressions. + "path", + // name of the function that the macro will be rewritten to call. + "path_match_request_list", + // internal data type of the MatchPath value. + []*cel.Type{cel.ListType(cel.StringType)}, + // function to convert a constant list of strings to a MatchPath instance. + func(data ref.Val) (RequestMatcherWithError, error) { + refStringList := reflect.TypeOf([]string{}) + strList, err := data.ConvertToNative(refStringList) + if err != nil { + return nil, err + } + matcher := MatchPath(strList.([]string)) + err = matcher.Provision(ctx) + return matcher, err + }, + ) +} + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. +func (m *MatchPath) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + // iterate to merge multiple matchers into one + for d.Next() { + *m = append(*m, d.RemainingArgs()...) + if d.NextBlock(0) { + return d.Err("malformed path matcher: blocks are not supported") + } + } + return nil +} + +// CaddyModule returns the Caddy module information. +func (MatchPathRE) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.matchers.path_regexp", + New: func() caddy.Module { return new(MatchPathRE) }, + } +} + +// Match returns true if r matches m. +func (m MatchPathRE) Match(r *http.Request) bool { + match, _ := m.MatchWithError(r) + return match +} + +// MatchWithError returns true if r matches m. +func (m MatchPathRE) MatchWithError(r *http.Request) (bool, error) { + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + + // Clean the path, merges doubled slashes, etc. + // This ensures maliciously crafted requests can't bypass + // the path matcher. See #4407 + cleanedPath := cleanPath(r.URL.Path) + + return m.MatchRegexp.Match(cleanedPath, repl), nil +} + +// CELLibrary produces options that expose this matcher for use in CEL +// expression matchers. +// +// Example: +// +// expression path_regexp('^/bar') +func (MatchPathRE) CELLibrary(ctx caddy.Context) (cel.Library, error) { + unnamedPattern, err := CELMatcherImpl( + "path_regexp", + "path_regexp_request_string", + []*cel.Type{cel.StringType}, + func(data ref.Val) (RequestMatcherWithError, error) { + pattern := data.(types.String) + matcher := MatchPathRE{MatchRegexp{ + Name: ctx.Value(MatcherNameCtxKey).(string), + Pattern: string(pattern), + }} + err := matcher.Provision(ctx) + return matcher, err + }, + ) + if err != nil { + return nil, err + } + namedPattern, err := CELMatcherImpl( + "path_regexp", + "path_regexp_request_string_string", + []*cel.Type{cel.StringType, cel.StringType}, + func(data ref.Val) (RequestMatcherWithError, error) { + refStringList := reflect.TypeOf([]string{}) + params, err := data.ConvertToNative(refStringList) + if err != nil { + return nil, err + } + strParams := params.([]string) + name := strParams[0] + if name == "" { + name = ctx.Value(MatcherNameCtxKey).(string) + } + matcher := MatchPathRE{MatchRegexp{ + Name: name, + Pattern: strParams[1], + }} + err = matcher.Provision(ctx) + return matcher, err + }, + ) + if err != nil { + return nil, err + } + envOpts := append(unnamedPattern.CompileOptions(), namedPattern.CompileOptions()...) + prgOpts := append(unnamedPattern.ProgramOptions(), namedPattern.ProgramOptions()...) + return NewMatcherCELLibrary(envOpts, prgOpts), nil +} + +// CaddyModule returns the Caddy module information. +func (MatchMethod) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.matchers.method", + New: func() caddy.Module { return new(MatchMethod) }, + } +} + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. +func (m *MatchMethod) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + // iterate to merge multiple matchers into one + for d.Next() { + *m = append(*m, d.RemainingArgs()...) + if d.NextBlock(0) { + return d.Err("malformed method matcher: blocks are not supported") + } + } + return nil +} + +// Match returns true if r matches m. +func (m MatchMethod) Match(r *http.Request) bool { + match, _ := m.MatchWithError(r) + return match +} + +// MatchWithError returns true if r matches m. +func (m MatchMethod) MatchWithError(r *http.Request) (bool, error) { + return slices.Contains(m, r.Method), nil +} + +// CELLibrary produces options that expose this matcher for use in CEL +// expression matchers. +// +// Example: +// +// expression method('PUT', 'POST') +func (MatchMethod) CELLibrary(_ caddy.Context) (cel.Library, error) { + return CELMatcherImpl( + "method", + "method_request_list", + []*cel.Type{cel.ListType(cel.StringType)}, + func(data ref.Val) (RequestMatcherWithError, error) { + refStringList := reflect.TypeOf([]string{}) + strList, err := data.ConvertToNative(refStringList) + if err != nil { + return nil, err + } + return MatchMethod(strList.([]string)), nil + }, + ) +} + +// CaddyModule returns the Caddy module information. +func (MatchQuery) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.matchers.query", + New: func() caddy.Module { return new(MatchQuery) }, + } +} + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. +func (m *MatchQuery) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + if *m == nil { + *m = make(map[string][]string) + } + // iterate to merge multiple matchers into one + for d.Next() { + for _, query := range d.RemainingArgs() { + if query == "" { + continue + } + before, after, found := strings.Cut(query, "=") + if !found { + return d.Errf("malformed query matcher token: %s; must be in param=val format", d.Val()) + } + url.Values(*m).Add(before, after) + } + if d.NextBlock(0) { + return d.Err("malformed query matcher: blocks are not supported") + } + } + return nil +} + +// Match returns true if r matches m. An empty m matches an empty query string. +func (m MatchQuery) Match(r *http.Request) bool { + match, _ := m.MatchWithError(r) + return match +} + +// MatchWithError returns true if r matches m. +// An empty m matches an empty query string. +func (m MatchQuery) MatchWithError(r *http.Request) (bool, error) { + // If no query keys are configured, this only + // matches an empty query string. + if len(m) == 0 { + return len(r.URL.Query()) == 0, nil + } + + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + + // parse query string just once, for efficiency + parsed, err := url.ParseQuery(r.URL.RawQuery) + if err != nil { + // Illegal query string. Likely bad escape sequence or unescaped literals. + // Note that semicolons in query string have a controversial history. Summaries: + // - https://github.com/golang/go/issues/50034 + // - https://github.com/golang/go/issues/25192 + // Despite the URL WHATWG spec mandating the use of & separators for query strings, + // every URL parser implementation is different, and Filippo Valsorda rightly wrote: + // "Relying on parser alignment for security is doomed." Overall conclusion is that + // splitting on & and rejecting ; in key=value pairs is safer than accepting raw ;. + // We regard the Go team's decision as sound and thus reject malformed query strings. + return false, nil + } + + // Count the amount of matched keys, to ensure we AND + // between all configured query keys; all keys must + // match at least one value. + matchedKeys := 0 + for param, vals := range m { + param = repl.ReplaceAll(param, "") + paramVal, found := parsed[param] + if !found { + return false, nil + } + for _, v := range vals { + v = repl.ReplaceAll(v, "") + if slices.Contains(paramVal, v) || v == "*" { + matchedKeys++ + break + } + } + } + return matchedKeys == len(m), nil +} + +// CELLibrary produces options that expose this matcher for use in CEL +// expression matchers. +// +// Example: +// +// expression query({'sort': 'asc'}) || query({'foo': ['*bar*', 'baz']}) +func (MatchQuery) CELLibrary(_ caddy.Context) (cel.Library, error) { + return CELMatcherImpl( + "query", + "query_matcher_request_map", + []*cel.Type{CELTypeJSON}, + func(data ref.Val) (RequestMatcherWithError, error) { + mapStrListStr, err := CELValueToMapStrList(data) + if err != nil { + return nil, err + } + return MatchQuery(url.Values(mapStrListStr)), nil + }, + ) +} + +// CaddyModule returns the Caddy module information. +func (MatchHeader) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.matchers.header", + New: func() caddy.Module { return new(MatchHeader) }, + } +} + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. +func (m *MatchHeader) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + if *m == nil { + *m = make(map[string][]string) + } + // iterate to merge multiple matchers into one + for d.Next() { + var field, val string + if !d.Args(&field) { + return d.Errf("malformed header matcher: expected field") + } + + if strings.HasPrefix(field, "!") { + if len(field) == 1 { + return d.Errf("malformed header matcher: must have field name following ! character") + } + + field = field[1:] + headers := *m + headers[field] = nil + m = &headers + if d.NextArg() { + return d.Errf("malformed header matcher: null matching headers cannot have a field value") + } + } else { + if !d.NextArg() { + return d.Errf("malformed header matcher: expected both field and value") + } + + // If multiple header matchers with the same header field are defined, + // we want to add the existing to the list of headers (will be OR'ed) + val = d.Val() + http.Header(*m).Add(field, val) + } + + if d.NextBlock(0) { + return d.Err("malformed header matcher: blocks are not supported") + } + } + return nil +} + +// Match returns true if r matches m. +func (m MatchHeader) Match(r *http.Request) bool { + match, _ := m.MatchWithError(r) + return match +} + +// MatchWithError returns true if r matches m. +func (m MatchHeader) MatchWithError(r *http.Request) (bool, error) { + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + return matchHeaders(r.Header, http.Header(m), r.Host, r.TransferEncoding, repl), nil +} + +// CELLibrary produces options that expose this matcher for use in CEL +// expression matchers. +// +// Example: +// +// expression header({'content-type': 'image/png'}) +// expression header({'foo': ['bar', 'baz']}) // match bar or baz +func (MatchHeader) CELLibrary(_ caddy.Context) (cel.Library, error) { + return CELMatcherImpl( + "header", + "header_matcher_request_map", + []*cel.Type{CELTypeJSON}, + func(data ref.Val) (RequestMatcherWithError, error) { + mapStrListStr, err := CELValueToMapStrList(data) + if err != nil { + return nil, err + } + return MatchHeader(http.Header(mapStrListStr)), nil + }, + ) +} + +// getHeaderFieldVals returns the field values for the given fieldName from input. +// The host parameter should be obtained from the http.Request.Host field, and the +// transferEncoding from http.Request.TransferEncoding, since net/http removes them +// from the header map. +func getHeaderFieldVals(input http.Header, fieldName, host string, transferEncoding []string) []string { + fieldName = textproto.CanonicalMIMEHeaderKey(fieldName) + if fieldName == "Host" && host != "" { + return []string{host} + } + if fieldName == "Transfer-Encoding" && input[fieldName] == nil { + return transferEncoding + } + return input[fieldName] +} + +// matchHeaders returns true if input matches the criteria in against without regex. +// The host parameter should be obtained from the http.Request.Host field since +// net/http removes it from the header map. +func matchHeaders(input, against http.Header, host string, transferEncoding []string, repl *caddy.Replacer) bool { + for field, allowedFieldVals := range against { + actualFieldVals := getHeaderFieldVals(input, field, host, transferEncoding) + if allowedFieldVals != nil && len(allowedFieldVals) == 0 && actualFieldVals != nil { + // a non-nil but empty list of allowed values means + // match if the header field exists at all + continue + } + if allowedFieldVals == nil && actualFieldVals == nil { + // a nil list means match if the header does not exist at all + continue + } + var match bool + fieldVals: + for _, actualFieldVal := range actualFieldVals { + for _, allowedFieldVal := range allowedFieldVals { + if repl != nil { + allowedFieldVal = repl.ReplaceAll(allowedFieldVal, "") + } + switch { + case allowedFieldVal == "*": + match = true + case strings.HasPrefix(allowedFieldVal, "*") && strings.HasSuffix(allowedFieldVal, "*"): + match = strings.Contains(actualFieldVal, allowedFieldVal[1:len(allowedFieldVal)-1]) + case strings.HasPrefix(allowedFieldVal, "*"): + match = strings.HasSuffix(actualFieldVal, allowedFieldVal[1:]) + case strings.HasSuffix(allowedFieldVal, "*"): + match = strings.HasPrefix(actualFieldVal, allowedFieldVal[:len(allowedFieldVal)-1]) + default: + match = actualFieldVal == allowedFieldVal + } + if match { + break fieldVals + } + } + } + if !match { + return false + } + } + return true +} + +// CaddyModule returns the Caddy module information. +func (MatchHeaderRE) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.matchers.header_regexp", + New: func() caddy.Module { return new(MatchHeaderRE) }, + } +} + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. +func (m *MatchHeaderRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + if *m == nil { + *m = make(map[string]*MatchRegexp) + } + // iterate to merge multiple matchers into one + for d.Next() { + var first, second, third string + if !d.Args(&first, &second) { + return d.ArgErr() + } + + var name, field, val string + if d.Args(&third) { + name = first + field = second + val = third + } else { + field = first + val = second + } + + // Default to the named matcher's name, if no regexp name is provided + if name == "" { + name = d.GetContextString(caddyfile.MatcherNameCtxKey) + } + + // If there's already a pattern for this field + // then we would end up overwriting the old one + if (*m)[field] != nil { + return d.Errf("header_regexp matcher can only be used once per named matcher, per header field: %s", field) + } + + (*m)[field] = &MatchRegexp{Pattern: val, Name: name} + + if d.NextBlock(0) { + return d.Err("malformed header_regexp matcher: blocks are not supported") + } + } + return nil +} + +// Match returns true if r matches m. +func (m MatchHeaderRE) Match(r *http.Request) bool { + match, _ := m.MatchWithError(r) + return match +} + +// MatchWithError returns true if r matches m. +func (m MatchHeaderRE) MatchWithError(r *http.Request) (bool, error) { + for field, rm := range m { + actualFieldVals := getHeaderFieldVals(r.Header, field, r.Host, r.TransferEncoding) + match := false + fieldVal: + for _, actualFieldVal := range actualFieldVals { + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + if rm.Match(actualFieldVal, repl) { + match = true + break fieldVal + } + } + if !match { + return false, nil + } + } + return true, nil +} + +// Provision compiles m's regular expressions. +func (m MatchHeaderRE) Provision(ctx caddy.Context) error { + for _, rm := range m { + err := rm.Provision(ctx) + if err != nil { + return err + } + } + return nil +} + +// Validate validates m's regular expressions. +func (m MatchHeaderRE) Validate() error { + for _, rm := range m { + err := rm.Validate() + if err != nil { + return err + } + } + return nil +} + +// CELLibrary produces options that expose this matcher for use in CEL +// expression matchers. +// +// Example: +// +// expression header_regexp('foo', 'Field', 'fo+') +func (MatchHeaderRE) CELLibrary(ctx caddy.Context) (cel.Library, error) { + unnamedPattern, err := CELMatcherImpl( + "header_regexp", + "header_regexp_request_string_string", + []*cel.Type{cel.StringType, cel.StringType}, + func(data ref.Val) (RequestMatcherWithError, error) { + refStringList := reflect.TypeOf([]string{}) + params, err := data.ConvertToNative(refStringList) + if err != nil { + return nil, err + } + strParams := params.([]string) + matcher := MatchHeaderRE{} + matcher[strParams[0]] = &MatchRegexp{ + Pattern: strParams[1], + Name: ctx.Value(MatcherNameCtxKey).(string), + } + err = matcher.Provision(ctx) + return matcher, err + }, + ) + if err != nil { + return nil, err + } + namedPattern, err := CELMatcherImpl( + "header_regexp", + "header_regexp_request_string_string_string", + []*cel.Type{cel.StringType, cel.StringType, cel.StringType}, + func(data ref.Val) (RequestMatcherWithError, error) { + refStringList := reflect.TypeOf([]string{}) + params, err := data.ConvertToNative(refStringList) + if err != nil { + return nil, err + } + strParams := params.([]string) + name := strParams[0] + if name == "" { + name = ctx.Value(MatcherNameCtxKey).(string) + } + matcher := MatchHeaderRE{} + matcher[strParams[1]] = &MatchRegexp{ + Pattern: strParams[2], + Name: name, + } + err = matcher.Provision(ctx) + return matcher, err + }, + ) + if err != nil { + return nil, err + } + envOpts := append(unnamedPattern.CompileOptions(), namedPattern.CompileOptions()...) + prgOpts := append(unnamedPattern.ProgramOptions(), namedPattern.ProgramOptions()...) + return NewMatcherCELLibrary(envOpts, prgOpts), nil +} + +// CaddyModule returns the Caddy module information. +func (MatchProtocol) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.matchers.protocol", + New: func() caddy.Module { return new(MatchProtocol) }, + } +} + +// Match returns true if r matches m. +func (m MatchProtocol) Match(r *http.Request) bool { + match, _ := m.MatchWithError(r) + return match +} + +// MatchWithError returns true if r matches m. +func (m MatchProtocol) MatchWithError(r *http.Request) (bool, error) { + switch string(m) { + case "grpc": + return strings.HasPrefix(r.Header.Get("content-type"), "application/grpc"), nil + case "https": + return r.TLS != nil, nil + case "http": + return r.TLS == nil, nil + case "http/1.0": + return r.ProtoMajor == 1 && r.ProtoMinor == 0, nil + case "http/1.0+": + return r.ProtoAtLeast(1, 0), nil + case "http/1.1": + return r.ProtoMajor == 1 && r.ProtoMinor == 1, nil + case "http/1.1+": + return r.ProtoAtLeast(1, 1), nil + case "http/2": + return r.ProtoMajor == 2, nil + case "http/2+": + return r.ProtoAtLeast(2, 0), nil + case "http/3": + return r.ProtoMajor == 3, nil + case "http/3+": + return r.ProtoAtLeast(3, 0), nil + } + return false, nil +} + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. +func (m *MatchProtocol) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + // iterate to merge multiple matchers into one + for d.Next() { + var proto string + if !d.Args(&proto) { + return d.Err("expected exactly one protocol") + } + *m = MatchProtocol(proto) + } + return nil +} + +// CELLibrary produces options that expose this matcher for use in CEL +// expression matchers. +// +// Example: +// +// expression protocol('https') +func (MatchProtocol) CELLibrary(_ caddy.Context) (cel.Library, error) { + return CELMatcherImpl( + "protocol", + "protocol_request_string", + []*cel.Type{cel.StringType}, + func(data ref.Val) (RequestMatcherWithError, error) { + protocolStr, ok := data.(types.String) + if !ok { + return nil, errors.New("protocol argument was not a string") + } + return MatchProtocol(strings.ToLower(string(protocolStr))), nil + }, + ) +} + +// CaddyModule returns the Caddy module information. +func (MatchTLS) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.matchers.tls", + New: func() caddy.Module { return new(MatchTLS) }, + } +} + +// Match returns true if r matches m. +func (m MatchTLS) Match(r *http.Request) bool { + match, _ := m.MatchWithError(r) + return match +} + +// MatchWithError returns true if r matches m. +func (m MatchTLS) MatchWithError(r *http.Request) (bool, error) { + if r.TLS == nil { + return false, nil + } + if m.HandshakeComplete != nil { + if (!*m.HandshakeComplete && r.TLS.HandshakeComplete) || + (*m.HandshakeComplete && !r.TLS.HandshakeComplete) { + return false, nil + } + } + return true, nil +} + +// UnmarshalCaddyfile parses Caddyfile tokens for this matcher. Syntax: +// +// ... tls [early_data] +// +// EXPERIMENTAL SYNTAX: Subject to change. +func (m *MatchTLS) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + // iterate to merge multiple matchers into one + for d.Next() { + if d.NextArg() { + switch d.Val() { + case "early_data": + var false bool + m.HandshakeComplete = &false + } + } + if d.NextArg() { + return d.ArgErr() + } + if d.NextBlock(0) { + return d.Err("malformed tls matcher: blocks are not supported yet") + } + } + return nil +} + +// CaddyModule returns the Caddy module information. +func (MatchNot) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.matchers.not", + New: func() caddy.Module { return new(MatchNot) }, + } +} + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. +func (m *MatchNot) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + // iterate to merge multiple matchers into one + for d.Next() { + matcherSet, err := ParseCaddyfileNestedMatcherSet(d) + if err != nil { + return err + } + m.MatcherSetsRaw = append(m.MatcherSetsRaw, matcherSet) + } + return nil +} + +// UnmarshalJSON satisfies json.Unmarshaler. It puts the JSON +// bytes directly into m's MatcherSetsRaw field. +func (m *MatchNot) UnmarshalJSON(data []byte) error { + return json.Unmarshal(data, &m.MatcherSetsRaw) +} + +// MarshalJSON satisfies json.Marshaler by marshaling +// m's raw matcher sets. +func (m MatchNot) MarshalJSON() ([]byte, error) { + return json.Marshal(m.MatcherSetsRaw) +} + +// Provision loads the matcher modules to be negated. +func (m *MatchNot) Provision(ctx caddy.Context) error { + matcherSets, err := ctx.LoadModule(m, "MatcherSetsRaw") + if err != nil { + return fmt.Errorf("loading matcher sets: %v", err) + } + for _, modMap := range matcherSets.([]map[string]any) { + var ms MatcherSet + for _, modIface := range modMap { + if mod, ok := modIface.(RequestMatcherWithError); ok { + ms = append(ms, mod) + continue + } + if mod, ok := modIface.(RequestMatcher); ok { + ms = append(ms, mod) + continue + } + return fmt.Errorf("module is not a request matcher: %T", modIface) + } + m.MatcherSets = append(m.MatcherSets, ms) + } + return nil +} + +// Match returns true if r matches m. Since this matcher negates +// the embedded matchers, false is returned if any of its matcher +// sets return true. +func (m MatchNot) Match(r *http.Request) bool { + match, _ := m.MatchWithError(r) + return match +} + +// MatchWithError returns true if r matches m. Since this matcher +// negates the embedded matchers, false is returned if any of its +// matcher sets return true. +func (m MatchNot) MatchWithError(r *http.Request) (bool, error) { + for _, ms := range m.MatcherSets { + matches, err := ms.MatchWithError(r) + if err != nil { + return false, err + } + if matches { + return false, nil + } + } + return true, nil +} + +// MatchRegexp is an embedable type for matching +// using regular expressions. It adds placeholders +// to the request's replacer. +type MatchRegexp struct { + // A unique name for this regular expression. Optional, + // but useful to prevent overwriting captures from other + // regexp matchers. + Name string `json:"name,omitempty"` + + // The regular expression to evaluate, in RE2 syntax, + // which is the same general syntax used by Go, Perl, + // and Python. For details, see + // [Go's regexp package](https://golang.org/pkg/regexp/). + // Captures are accessible via placeholders. Unnamed + // capture groups are exposed as their numeric, 1-based + // index, while named capture groups are available by + // the capture group name. + Pattern string `json:"pattern"` + + compiled *regexp.Regexp +} + +// Provision compiles the regular expression. +func (mre *MatchRegexp) Provision(caddy.Context) error { + re, err := regexp.Compile(mre.Pattern) + if err != nil { + return fmt.Errorf("compiling matcher regexp %s: %v", mre.Pattern, err) + } + mre.compiled = re + return nil +} + +// Validate ensures mre is set up correctly. +func (mre *MatchRegexp) Validate() error { + if mre.Name != "" && !wordRE.MatchString(mre.Name) { + return fmt.Errorf("invalid regexp name (must contain only word characters): %s", mre.Name) + } + return nil +} + +// Match returns true if input matches the compiled regular +// expression in mre. It sets values on the replacer repl +// associated with capture groups, using the given scope +// (namespace). +func (mre *MatchRegexp) Match(input string, repl *caddy.Replacer) bool { + matches := mre.compiled.FindStringSubmatch(input) + if matches == nil { + return false + } + + // save all capture groups, first by index + for i, match := range matches { + keySuffix := "." + strconv.Itoa(i) + if mre.Name != "" { + repl.Set(regexpPlaceholderPrefix+"."+mre.Name+keySuffix, match) + } + repl.Set(regexpPlaceholderPrefix+keySuffix, match) + } + + // then by name + for i, name := range mre.compiled.SubexpNames() { + // skip the first element (the full match), and empty names + if i == 0 || name == "" { + continue + } + + keySuffix := "." + name + if mre.Name != "" { + repl.Set(regexpPlaceholderPrefix+"."+mre.Name+keySuffix, matches[i]) + } + repl.Set(regexpPlaceholderPrefix+keySuffix, matches[i]) + } + + return true +} + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. +func (mre *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + // iterate to merge multiple matchers into one + for d.Next() { + // If this is the second iteration of the loop + // then there's more than one path_regexp matcher + // and we would end up overwriting the old one + if mre.Pattern != "" { + return d.Err("regular expression can only be used once per named matcher") + } + + args := d.RemainingArgs() + switch len(args) { + case 1: + mre.Pattern = args[0] + case 2: + mre.Name = args[0] + mre.Pattern = args[1] + default: + return d.ArgErr() + } + + // Default to the named matcher's name, if no regexp name is provided + if mre.Name == "" { + mre.Name = d.GetContextString(caddyfile.MatcherNameCtxKey) + } + + if d.NextBlock(0) { + return d.Err("malformed path_regexp matcher: blocks are not supported") + } + } + return nil +} + +// ParseCaddyfileNestedMatcher parses the Caddyfile tokens for a nested +// matcher set, and returns its raw module map value. +func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, error) { + matcherMap := make(map[string]any) + + // in case there are multiple instances of the same matcher, concatenate + // their tokens (we expect that UnmarshalCaddyfile should be able to + // handle more than one segment); otherwise, we'd overwrite other + // instances of the matcher in this set + tokensByMatcherName := make(map[string][]caddyfile.Token) + for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); { + matcherName := d.Val() + tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...) + } + + for matcherName, tokens := range tokensByMatcherName { + mod, err := caddy.GetModule("http.matchers." + matcherName) + if err != nil { + return nil, d.Errf("getting matcher module '%s': %v", matcherName, err) + } + unm, ok := mod.New().(caddyfile.Unmarshaler) + if !ok { + return nil, d.Errf("matcher module '%s' is not a Caddyfile unmarshaler", matcherName) + } + err = unm.UnmarshalCaddyfile(caddyfile.NewDispenser(tokens)) + if err != nil { + return nil, err + } + if rm, ok := unm.(RequestMatcherWithError); ok { + matcherMap[matcherName] = rm + continue + } + if rm, ok := unm.(RequestMatcher); ok { + matcherMap[matcherName] = rm + continue + } + return nil, fmt.Errorf("matcher module '%s' is not a request matcher", matcherName) + } + + // we should now have a functional matcher, but we also + // need to be able to marshal as JSON, otherwise config + // adaptation will be missing the matchers! + matcherSet := make(caddy.ModuleMap) + for name, matcher := range matcherMap { + jsonBytes, err := json.Marshal(matcher) + if err != nil { + return nil, fmt.Errorf("marshaling %T matcher: %v", matcher, err) + } + matcherSet[name] = jsonBytes + } + + return matcherSet, nil +} + +var wordRE = regexp.MustCompile(`\w+`) + +const regexpPlaceholderPrefix = "http.regexp" + +// MatcherErrorVarKey is the key used for the variable that +// holds an optional error emitted from a request matcher, +// to short-circuit the handler chain, since matchers cannot +// return errors via the RequestMatcher interface. +// +// Deprecated: Matchers should implement RequestMatcherWithError +// which can return an error directly, instead of smuggling it +// through the vars map. +const MatcherErrorVarKey = "matchers.error" + +// Interface guards +var ( + _ RequestMatcherWithError = (*MatchHost)(nil) + _ caddy.Provisioner = (*MatchHost)(nil) + _ RequestMatcherWithError = (*MatchPath)(nil) + _ RequestMatcherWithError = (*MatchPathRE)(nil) + _ caddy.Provisioner = (*MatchPathRE)(nil) + _ RequestMatcherWithError = (*MatchMethod)(nil) + _ RequestMatcherWithError = (*MatchQuery)(nil) + _ RequestMatcherWithError = (*MatchHeader)(nil) + _ RequestMatcherWithError = (*MatchHeaderRE)(nil) + _ caddy.Provisioner = (*MatchHeaderRE)(nil) + _ RequestMatcherWithError = (*MatchProtocol)(nil) + _ RequestMatcherWithError = (*MatchNot)(nil) + _ caddy.Provisioner = (*MatchNot)(nil) + _ caddy.Provisioner = (*MatchRegexp)(nil) + + _ caddyfile.Unmarshaler = (*MatchHost)(nil) + _ caddyfile.Unmarshaler = (*MatchPath)(nil) + _ caddyfile.Unmarshaler = (*MatchPathRE)(nil) + _ caddyfile.Unmarshaler = (*MatchMethod)(nil) + _ caddyfile.Unmarshaler = (*MatchQuery)(nil) + _ caddyfile.Unmarshaler = (*MatchHeader)(nil) + _ caddyfile.Unmarshaler = (*MatchHeaderRE)(nil) + _ caddyfile.Unmarshaler = (*MatchProtocol)(nil) + _ caddyfile.Unmarshaler = (*VarsMatcher)(nil) + _ caddyfile.Unmarshaler = (*MatchVarsRE)(nil) + + _ CELLibraryProducer = (*MatchHost)(nil) + _ CELLibraryProducer = (*MatchPath)(nil) + _ CELLibraryProducer = (*MatchPathRE)(nil) + _ CELLibraryProducer = (*MatchMethod)(nil) + _ CELLibraryProducer = (*MatchQuery)(nil) + _ CELLibraryProducer = (*MatchHeader)(nil) + _ CELLibraryProducer = (*MatchHeaderRE)(nil) + _ CELLibraryProducer = (*MatchProtocol)(nil) + _ CELLibraryProducer = (*VarsMatcher)(nil) + _ CELLibraryProducer = (*MatchVarsRE)(nil) + + _ json.Marshaler = (*MatchNot)(nil) + _ json.Unmarshaler = (*MatchNot)(nil) +) diff --git a/modules/caddyhttp/matchers_test.go b/modules/caddyhttp/matchers_test.go new file mode 100644 index 00000000000..f7be6909efc --- /dev/null +++ b/modules/caddyhttp/matchers_test.go @@ -0,0 +1,1219 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "os" + "runtime" + "testing" + + "github.com/caddyserver/caddy/v2" +) + +func TestHostMatcher(t *testing.T) { + err := os.Setenv("GO_BENCHMARK_DOMAIN", "localhost") + if err != nil { + t.Errorf("error while setting up environment: %v", err) + } + + for i, tc := range []struct { + match MatchHost + input string + expect bool + }{ + { + match: MatchHost{}, + input: "example.com", + expect: false, + }, + { + match: MatchHost{"example.com"}, + input: "example.com", + expect: true, + }, + { + match: MatchHost{"EXAMPLE.COM"}, + input: "example.com", + expect: true, + }, + { + match: MatchHost{"example.com"}, + input: "EXAMPLE.COM", + expect: true, + }, + { + match: MatchHost{"example.com"}, + input: "foo.example.com", + expect: false, + }, + { + match: MatchHost{"example.com"}, + input: "EXAMPLE.COM", + expect: true, + }, + { + match: MatchHost{"foo.example.com"}, + input: "foo.example.com", + expect: true, + }, + { + match: MatchHost{"foo.example.com"}, + input: "bar.example.com", + expect: false, + }, + { + match: MatchHost{"éxàmplê.com"}, + input: "xn--xmpl-0na6cm.com", + expect: true, + }, + { + match: MatchHost{"*.example.com"}, + input: "example.com", + expect: false, + }, + { + match: MatchHost{"*.example.com"}, + input: "SUB.EXAMPLE.COM", + expect: true, + }, + { + match: MatchHost{"*.example.com"}, + input: "foo.example.com", + expect: true, + }, + { + match: MatchHost{"*.example.com"}, + input: "foo.bar.example.com", + expect: false, + }, + { + match: MatchHost{"*.example.com", "example.net"}, + input: "example.net", + expect: true, + }, + { + match: MatchHost{"example.net", "*.example.com"}, + input: "foo.example.com", + expect: true, + }, + { + match: MatchHost{"*.example.net", "*.*.example.com"}, + input: "foo.bar.example.com", + expect: true, + }, + { + match: MatchHost{"*.example.net", "sub.*.example.com"}, + input: "sub.foo.example.com", + expect: true, + }, + { + match: MatchHost{"*.example.net", "sub.*.example.com"}, + input: "sub.foo.example.net", + expect: false, + }, + { + match: MatchHost{"www.*.*"}, + input: "www.example.com", + expect: true, + }, + { + match: MatchHost{"example.com"}, + input: "example.com:5555", + expect: true, + }, + { + match: MatchHost{"{env.GO_BENCHMARK_DOMAIN}"}, + input: "localhost", + expect: true, + }, + { + match: MatchHost{"{env.GO_NONEXISTENT}"}, + input: "localhost", + expect: false, + }, + } { + req := &http.Request{Host: tc.input} + repl := caddy.NewReplacer() + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + req = req.WithContext(ctx) + + if err := tc.match.Provision(caddy.Context{}); err != nil { + t.Errorf("Test %d %v: provisioning failed: %v", i, tc.match, err) + } + + actual, err := tc.match.MatchWithError(req) + if err != nil { + t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err) + } + if actual != tc.expect { + t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input) + continue + } + } +} + +func TestPathMatcher(t *testing.T) { + for i, tc := range []struct { + match MatchPath // not URI-encoded because not parsing from a URI + input string // should be valid URI encoding (escaped) since it will become part of a request + expect bool + provisionErr bool + }{ + { + match: MatchPath{}, + input: "/", + expect: false, + }, + { + match: MatchPath{"/"}, + input: "/", + expect: true, + }, + { + match: MatchPath{"/foo/bar"}, + input: "/", + expect: false, + }, + { + match: MatchPath{"/foo/bar"}, + input: "/foo/bar", + expect: true, + }, + { + match: MatchPath{"/foo/bar/"}, + input: "/foo/bar", + expect: false, + }, + { + match: MatchPath{"/foo/bar/"}, + input: "/foo/bar/", + expect: true, + }, + { + match: MatchPath{"/foo/bar/", "/other"}, + input: "/other/", + expect: false, + }, + { + match: MatchPath{"/foo/bar/", "/other"}, + input: "/other", + expect: true, + }, + { + match: MatchPath{"*.ext"}, + input: "/foo/bar.ext", + expect: true, + }, + { + match: MatchPath{"*.php"}, + input: "/index.PHP", + expect: true, + }, + { + match: MatchPath{"*.ext"}, + input: "/foo/bar.ext", + expect: true, + }, + { + match: MatchPath{"/foo/*/baz"}, + input: "/foo/bar/baz", + expect: true, + }, + { + match: MatchPath{"/foo/*/baz/bam"}, + input: "/foo/bar/bam", + expect: false, + }, + { + match: MatchPath{"*substring*"}, + input: "/foo/substring/bar.txt", + expect: true, + }, + { + match: MatchPath{"/foo"}, + input: "/foo/bar", + expect: false, + }, + { + match: MatchPath{"/foo"}, + input: "/foo/bar", + expect: false, + }, + { + match: MatchPath{"/foo"}, + input: "/FOO", + expect: true, + }, + { + match: MatchPath{"/foo*"}, + input: "/FOOOO", + expect: true, + }, + { + match: MatchPath{"/foo/bar.txt"}, + input: "/foo/BAR.txt", + expect: true, + }, + { + match: MatchPath{"/foo*"}, + input: "//foo/bar", + expect: true, + }, + { + match: MatchPath{"/foo"}, + input: "//foo", + expect: true, + }, + { + match: MatchPath{"//foo"}, + input: "/foo", + expect: false, + }, + { + match: MatchPath{"//foo"}, + input: "//foo", + expect: true, + }, + { + match: MatchPath{"/foo//*"}, + input: "/foo//bar", + expect: true, + }, + { + match: MatchPath{"/foo//*"}, + input: "/foo/%2Fbar", + expect: true, + }, + { + match: MatchPath{"/foo/%2F*"}, + input: "/foo/%2Fbar", + expect: true, + }, + { + match: MatchPath{"/foo/%2F*"}, + input: "/foo//bar", + expect: false, + }, + { + match: MatchPath{"/foo//bar"}, + input: "/foo//bar", + expect: true, + }, + { + match: MatchPath{"/foo/*//bar"}, + input: "/foo///bar", + expect: true, + }, + { + match: MatchPath{"/foo/%*//bar"}, + input: "/foo///bar", + expect: true, + }, + { + match: MatchPath{"/foo/%*//bar"}, + input: "/foo//%2Fbar", + expect: true, + }, + { + match: MatchPath{"/foo*"}, + input: "/%2F/foo", + expect: true, + }, + { + match: MatchPath{"*"}, + input: "/", + expect: true, + }, + { + match: MatchPath{"*"}, + input: "/foo/bar", + expect: true, + }, + { + match: MatchPath{"**"}, + input: "/", + expect: true, + }, + { + match: MatchPath{"**"}, + input: "/foo/bar", + expect: true, + }, + // notice these next three test cases are the same normalized path but are written differently + { + match: MatchPath{"/%25@.txt"}, + input: "/%25@.txt", + expect: true, + }, + { + match: MatchPath{"/%25@.txt"}, + input: "/%25%40.txt", + expect: true, + }, + { + match: MatchPath{"/%25%40.txt"}, + input: "/%25%40.txt", + expect: true, + }, + { + match: MatchPath{"/bands/*/*"}, + input: "/bands/AC%2FDC/T.N.T", + expect: false, // because * operates in normalized space + }, + { + match: MatchPath{"/bands/%*/%*"}, + input: "/bands/AC%2FDC/T.N.T", + expect: true, + }, + { + match: MatchPath{"/bands/%*/%*"}, + input: "/bands/AC/DC/T.N.T", + expect: false, + }, + { + match: MatchPath{"/bands/%*"}, + input: "/bands/AC/DC", + expect: false, // not a suffix match + }, + { + match: MatchPath{"/bands/%*"}, + input: "/bands/AC%2FDC", + expect: true, + }, + { + match: MatchPath{"/foo%2fbar/baz"}, + input: "/foo%2Fbar/baz", + expect: true, + }, + { + match: MatchPath{"/foo%2fbar/baz"}, + input: "/foo/bar/baz", + expect: false, + }, + { + match: MatchPath{"/foo/bar/baz"}, + input: "/foo%2fbar/baz", + expect: true, + }, + } { + err := tc.match.Provision(caddy.Context{}) + if err == nil && tc.provisionErr { + t.Errorf("Test %d %v: Expected error provisioning, but there was no error", i, tc.match) + } + if err != nil && !tc.provisionErr { + t.Errorf("Test %d %v: Expected no error provisioning, but there was an error: %v", i, tc.match, err) + } + if tc.provisionErr { + continue // if it's not supposed to provision properly, pointless to test it + } + + u, err := url.ParseRequestURI(tc.input) + if err != nil { + t.Fatalf("Test %d (%v): Invalid request URI (should be rejected by Go's HTTP server): %v", i, tc.input, err) + } + req := &http.Request{URL: u} + repl := caddy.NewReplacer() + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + req = req.WithContext(ctx) + + actual, err := tc.match.MatchWithError(req) + if err != nil { + t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err) + } + if actual != tc.expect { + t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input) + continue + } + } +} + +func TestPathMatcherWindows(t *testing.T) { + // only Windows has this bug where it will ignore + // trailing dots and spaces in a filename + if runtime.GOOS != "windows" { + return + } + + req := &http.Request{URL: &url.URL{Path: "/index.php . . .."}} + repl := caddy.NewReplacer() + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + req = req.WithContext(ctx) + + match := MatchPath{"*.php"} + matched, err := match.MatchWithError(req) + if err != nil { + t.Errorf("Expected no error, but got: %v", err) + } + if !matched { + t.Errorf("Expected to match; should ignore trailing dots and spaces") + } +} + +func TestPathREMatcher(t *testing.T) { + for i, tc := range []struct { + match MatchPathRE + input string + expect bool + expectRepl map[string]string + }{ + { + match: MatchPathRE{}, + input: "/", + expect: true, + }, + { + match: MatchPathRE{MatchRegexp{Pattern: "/"}}, + input: "/", + expect: true, + }, + { + match: MatchPathRE{MatchRegexp{Pattern: "^/foo"}}, + input: "/foo", + expect: true, + }, + { + match: MatchPathRE{MatchRegexp{Pattern: "^/foo"}}, + input: "/foo/", + expect: true, + }, + { + match: MatchPathRE{MatchRegexp{Pattern: "^/foo"}}, + input: "//foo", + expect: true, + }, + { + match: MatchPathRE{MatchRegexp{Pattern: "^/foo"}}, + input: "//foo/", + expect: true, + }, + { + match: MatchPathRE{MatchRegexp{Pattern: "^/foo"}}, + input: "/%2F/foo/", + expect: true, + }, + { + match: MatchPathRE{MatchRegexp{Pattern: "/bar"}}, + input: "/foo/", + expect: false, + }, + { + match: MatchPathRE{MatchRegexp{Pattern: "^/bar"}}, + input: "/foo/bar", + expect: false, + }, + { + match: MatchPathRE{MatchRegexp{Pattern: "^/foo/(.*)/baz$", Name: "name"}}, + input: "/foo/bar/baz", + expect: true, + expectRepl: map[string]string{"name.1": "bar"}, + }, + { + match: MatchPathRE{MatchRegexp{Pattern: "^/foo/(?P.*)/baz$", Name: "name"}}, + input: "/foo/bar/baz", + expect: true, + expectRepl: map[string]string{"name.myparam": "bar"}, + }, + { + match: MatchPathRE{MatchRegexp{Pattern: "^/%@.txt"}}, + input: "/%25@.txt", + expect: true, + }, + { + match: MatchPathRE{MatchRegexp{Pattern: "^/%25@.txt"}}, + input: "/%25@.txt", + expect: false, + }, + } { + // compile the regexp and validate its name + err := tc.match.Provision(caddy.Context{}) + if err != nil { + t.Errorf("Test %d %v: Provisioning: %v", i, tc.match, err) + continue + } + err = tc.match.Validate() + if err != nil { + t.Errorf("Test %d %v: Validating: %v", i, tc.match, err) + continue + } + + // set up the fake request and its Replacer + u, err := url.ParseRequestURI(tc.input) + if err != nil { + t.Fatalf("Test %d: Bad input URI: %v", i, err) + } + req := &http.Request{URL: u} + repl := caddy.NewReplacer() + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + req = req.WithContext(ctx) + addHTTPVarsToReplacer(repl, req, httptest.NewRecorder()) + + actual, err := tc.match.MatchWithError(req) + if err != nil { + t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err) + } + if actual != tc.expect { + t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'", + i, tc.match.Pattern, tc.expect, actual, tc.input) + continue + } + + for key, expectVal := range tc.expectRepl { + placeholder := fmt.Sprintf("{http.regexp.%s}", key) + actualVal := repl.ReplaceAll(placeholder, "") + if actualVal != expectVal { + t.Errorf("Test %d [%v]: Expected placeholder {http.regexp.%s} to be '%s' but got '%s'", + i, tc.match.Pattern, key, expectVal, actualVal) + continue + } + } + } +} + +func TestHeaderMatcher(t *testing.T) { + repl := caddy.NewReplacer() + repl.Set("a", "foobar") + + for i, tc := range []struct { + match MatchHeader + input http.Header // make sure these are canonical cased (std lib will do that in a real request) + host string + expect bool + }{ + { + match: MatchHeader{"Field": []string{"foo"}}, + input: http.Header{"Field": []string{"foo"}}, + expect: true, + }, + { + match: MatchHeader{"Field": []string{"foo", "bar"}}, + input: http.Header{"Field": []string{"bar"}}, + expect: true, + }, + { + match: MatchHeader{"Field": []string{"foo", "bar"}}, + input: http.Header{"Alakazam": []string{"kapow"}}, + expect: false, + }, + { + match: MatchHeader{"Field": []string{"foo", "bar"}}, + input: http.Header{"Field": []string{"kapow"}}, + expect: false, + }, + { + match: MatchHeader{"Field": []string{"foo", "bar"}}, + input: http.Header{"Field": []string{"kapow", "foo"}}, + expect: true, + }, + { + match: MatchHeader{"Field1": []string{"foo"}, "Field2": []string{"bar"}}, + input: http.Header{"Field1": []string{"foo"}, "Field2": []string{"bar"}}, + expect: true, + }, + { + match: MatchHeader{"field1": []string{"foo"}, "field2": []string{"bar"}}, + input: http.Header{"Field1": []string{"foo"}, "Field2": []string{"bar"}}, + expect: true, + }, + { + match: MatchHeader{"field1": []string{"foo"}, "field2": []string{"bar"}}, + input: http.Header{"Field1": []string{"foo"}, "Field2": []string{"kapow"}}, + expect: false, + }, + { + match: MatchHeader{"field1": []string{"*"}}, + input: http.Header{"Field1": []string{"foo"}}, + expect: true, + }, + { + match: MatchHeader{"field1": []string{"*"}}, + input: http.Header{"Field2": []string{"foo"}}, + expect: false, + }, + { + match: MatchHeader{"Field1": []string{"foo*"}}, + input: http.Header{"Field1": []string{"foo"}}, + expect: true, + }, + { + match: MatchHeader{"Field1": []string{"foo*"}}, + input: http.Header{"Field1": []string{"asdf", "foobar"}}, + expect: true, + }, + { + match: MatchHeader{"Field1": []string{"*bar"}}, + input: http.Header{"Field1": []string{"asdf", "foobar"}}, + expect: true, + }, + { + match: MatchHeader{"host": []string{"localhost"}}, + input: http.Header{}, + host: "localhost", + expect: true, + }, + { + match: MatchHeader{"host": []string{"localhost"}}, + input: http.Header{}, + host: "caddyserver.com", + expect: false, + }, + { + match: MatchHeader{"Must-Not-Exist": nil}, + input: http.Header{}, + expect: true, + }, + { + match: MatchHeader{"Must-Not-Exist": nil}, + input: http.Header{"Must-Not-Exist": []string{"do not match"}}, + expect: false, + }, + { + match: MatchHeader{"Foo": []string{"{a}"}}, + input: http.Header{"Foo": []string{"foobar"}}, + expect: true, + }, + { + match: MatchHeader{"Foo": []string{"{a}"}}, + input: http.Header{"Foo": []string{"asdf"}}, + expect: false, + }, + { + match: MatchHeader{"Foo": []string{"{a}*"}}, + input: http.Header{"Foo": []string{"foobar-baz"}}, + expect: true, + }, + } { + req := &http.Request{Header: tc.input, Host: tc.host} + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + req = req.WithContext(ctx) + + actual, err := tc.match.MatchWithError(req) + if err != nil { + t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err) + } + if actual != tc.expect { + t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input) + continue + } + } +} + +func TestQueryMatcher(t *testing.T) { + for i, tc := range []struct { + scenario string + match MatchQuery + input string + expect bool + }{ + { + scenario: "non match against a specific value", + match: MatchQuery{"debug": []string{"1"}}, + input: "/", + expect: false, + }, + { + scenario: "match against a specific value", + match: MatchQuery{"debug": []string{"1"}}, + input: "/?debug=1", + expect: true, + }, + { + scenario: "match against a wildcard", + match: MatchQuery{"debug": []string{"*"}}, + input: "/?debug=something", + expect: true, + }, + { + scenario: "non match against a wildcarded", + match: MatchQuery{"debug": []string{"*"}}, + input: "/?other=something", + expect: false, + }, + { + scenario: "match against an empty value", + match: MatchQuery{"debug": []string{""}}, + input: "/?debug", + expect: true, + }, + { + scenario: "non match against an empty value", + match: MatchQuery{"debug": []string{""}}, + input: "/?someparam", + expect: false, + }, + { + scenario: "empty matcher value should match empty query", + match: MatchQuery{}, + input: "/?", + expect: true, + }, + { + scenario: "nil matcher value should NOT match a non-empty query", + match: MatchQuery{}, + input: "/?foo=bar", + expect: false, + }, + { + scenario: "non-nil matcher should NOT match an empty query", + match: MatchQuery{"": nil}, + input: "/?", + expect: false, + }, + { + scenario: "match against a placeholder value", + match: MatchQuery{"debug": []string{"{http.vars.debug}"}}, + input: "/?debug=1", + expect: true, + }, + { + scenario: "match against a placeholder key", + match: MatchQuery{"{http.vars.key}": []string{"1"}}, + input: "/?somekey=1", + expect: true, + }, + { + scenario: "do not match when not all query params are present", + match: MatchQuery{"debug": []string{"1"}, "foo": []string{"bar"}}, + input: "/?debug=1", + expect: false, + }, + { + scenario: "match when all query params are present", + match: MatchQuery{"debug": []string{"1"}, "foo": []string{"bar"}}, + input: "/?debug=1&foo=bar", + expect: true, + }, + { + scenario: "do not match when the value of a query param does not match", + match: MatchQuery{"debug": []string{"1"}, "foo": []string{"bar"}}, + input: "/?debug=2&foo=bar", + expect: false, + }, + { + scenario: "do not match when all the values the query params do not match", + match: MatchQuery{"debug": []string{"1"}, "foo": []string{"bar"}}, + input: "/?debug=2&foo=baz", + expect: false, + }, + { + scenario: "match against two values for the same key", + match: MatchQuery{"debug": []string{"1"}}, + input: "/?debug=1&debug=2", + expect: true, + }, + { + scenario: "match against two values for the same key", + match: MatchQuery{"debug": []string{"2", "1"}}, + input: "/?debug=2&debug=1", + expect: true, + }, + } { + + u, _ := url.Parse(tc.input) + + req := &http.Request{URL: u} + repl := caddy.NewReplacer() + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + repl.Set("http.vars.debug", "1") + repl.Set("http.vars.key", "somekey") + req = req.WithContext(ctx) + actual, err := tc.match.MatchWithError(req) + if err != nil { + t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err) + } + if actual != tc.expect { + t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input) + continue + } + } +} + +func TestHeaderREMatcher(t *testing.T) { + for i, tc := range []struct { + match MatchHeaderRE + input http.Header // make sure these are canonical cased (std lib will do that in a real request) + host string + expect bool + expectRepl map[string]string + }{ + { + match: MatchHeaderRE{"Field": &MatchRegexp{Pattern: "foo"}}, + input: http.Header{"Field": []string{"foo"}}, + expect: true, + }, + { + match: MatchHeaderRE{"Field": &MatchRegexp{Pattern: "$foo^"}}, + input: http.Header{"Field": []string{"foobar"}}, + expect: false, + }, + { + match: MatchHeaderRE{"Field": &MatchRegexp{Pattern: "^foo(.*)$", Name: "name"}}, + input: http.Header{"Field": []string{"foobar"}}, + expect: true, + expectRepl: map[string]string{"name.1": "bar"}, + }, + { + match: MatchHeaderRE{"Field": &MatchRegexp{Pattern: "^foo.*$", Name: "name"}}, + input: http.Header{"Field": []string{"barfoo", "foobar"}}, + expect: true, + }, + { + match: MatchHeaderRE{"host": &MatchRegexp{Pattern: "^localhost$", Name: "name"}}, + input: http.Header{}, + host: "localhost", + expect: true, + }, + { + match: MatchHeaderRE{"host": &MatchRegexp{Pattern: "^local$", Name: "name"}}, + input: http.Header{}, + host: "localhost", + expect: false, + }, + } { + // compile the regexp and validate its name + err := tc.match.Provision(caddy.Context{}) + if err != nil { + t.Errorf("Test %d %v: Provisioning: %v", i, tc.match, err) + continue + } + err = tc.match.Validate() + if err != nil { + t.Errorf("Test %d %v: Validating: %v", i, tc.match, err) + continue + } + + // set up the fake request and its Replacer + req := &http.Request{Header: tc.input, URL: new(url.URL), Host: tc.host} + repl := caddy.NewReplacer() + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + req = req.WithContext(ctx) + addHTTPVarsToReplacer(repl, req, httptest.NewRecorder()) + + actual, err := tc.match.MatchWithError(req) + if err != nil { + t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err) + } + if actual != tc.expect { + t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'", + i, tc.match, tc.expect, actual, tc.input) + continue + } + + for key, expectVal := range tc.expectRepl { + placeholder := fmt.Sprintf("{http.regexp.%s}", key) + actualVal := repl.ReplaceAll(placeholder, "") + if actualVal != expectVal { + t.Errorf("Test %d [%v]: Expected placeholder {http.regexp.%s} to be '%s' but got '%s'", + i, tc.match, key, expectVal, actualVal) + continue + } + } + } +} + +func BenchmarkHeaderREMatcher(b *testing.B) { + i := 0 + match := MatchHeaderRE{"Field": &MatchRegexp{Pattern: "^foo(.*)$", Name: "name"}} + input := http.Header{"Field": []string{"foobar"}} + var host string + err := match.Provision(caddy.Context{}) + if err != nil { + b.Errorf("Test %d %v: Provisioning: %v", i, match, err) + } + err = match.Validate() + if err != nil { + b.Errorf("Test %d %v: Validating: %v", i, match, err) + } + + // set up the fake request and its Replacer + req := &http.Request{Header: input, URL: new(url.URL), Host: host} + repl := caddy.NewReplacer() + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + req = req.WithContext(ctx) + addHTTPVarsToReplacer(repl, req, httptest.NewRecorder()) + for run := 0; run < b.N; run++ { + match.MatchWithError(req) + } +} + +func TestVarREMatcher(t *testing.T) { + for i, tc := range []struct { + desc string + match MatchVarsRE + input VarsMiddleware + expect bool + expectRepl map[string]string + }{ + { + desc: "match static value within var set by the VarsMiddleware succeeds", + match: MatchVarsRE{"Var1": &MatchRegexp{Pattern: "foo"}}, + input: VarsMiddleware{"Var1": "here is foo val"}, + expect: true, + }, + { + desc: "value set by VarsMiddleware not satisfying regexp matcher fails to match", + match: MatchVarsRE{"Var1": &MatchRegexp{Pattern: "$foo^"}}, + input: VarsMiddleware{"Var1": "foobar"}, + expect: false, + }, + { + desc: "successfully matched value is captured and its placeholder is added to replacer", + match: MatchVarsRE{"Var1": &MatchRegexp{Pattern: "^foo(.*)$", Name: "name"}}, + input: VarsMiddleware{"Var1": "foobar"}, + expect: true, + expectRepl: map[string]string{"name.1": "bar"}, + }, + { + desc: "matching against a value of standard variables succeeds", + match: MatchVarsRE{"{http.request.method}": &MatchRegexp{Pattern: "^G.[tT]$"}}, + input: VarsMiddleware{}, + expect: true, + }, + { + desc: "matching against value of var set by the VarsMiddleware and referenced by its placeholder succeeds", + match: MatchVarsRE{"{http.vars.Var1}": &MatchRegexp{Pattern: "[vV]ar[0-9]"}}, + input: VarsMiddleware{"Var1": "var1Value"}, + expect: true, + }, + } { + i := i // capture range value + tc := tc // capture range value + t.Run(tc.desc, func(t *testing.T) { + t.Parallel() + // compile the regexp and validate its name + err := tc.match.Provision(caddy.Context{}) + if err != nil { + t.Errorf("Test %d %v: Provisioning: %v", i, tc.match, err) + return + } + err = tc.match.Validate() + if err != nil { + t.Errorf("Test %d %v: Validating: %v", i, tc.match, err) + return + } + + // set up the fake request and its Replacer + req := &http.Request{URL: new(url.URL), Method: http.MethodGet} + repl := caddy.NewReplacer() + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + ctx = context.WithValue(ctx, VarsCtxKey, make(map[string]any)) + req = req.WithContext(ctx) + + addHTTPVarsToReplacer(repl, req, httptest.NewRecorder()) + + tc.input.ServeHTTP(httptest.NewRecorder(), req, emptyHandler) + + actual, err := tc.match.MatchWithError(req) + if err != nil { + t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err) + } + if actual != tc.expect { + t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'", + i, tc.match, tc.expect, actual, tc.input) + return + } + + for key, expectVal := range tc.expectRepl { + placeholder := fmt.Sprintf("{http.regexp.%s}", key) + actualVal := repl.ReplaceAll(placeholder, "") + if actualVal != expectVal { + t.Errorf("Test %d [%v]: Expected placeholder {http.regexp.%s} to be '%s' but got '%s'", + i, tc.match, key, expectVal, actualVal) + return + } + } + }) + } +} + +func TestNotMatcher(t *testing.T) { + for i, tc := range []struct { + host, path string + match MatchNot + expect bool + }{ + { + host: "example.com", path: "/", + match: MatchNot{}, + expect: true, + }, + { + host: "example.com", path: "/foo", + match: MatchNot{ + MatcherSets: []MatcherSet{ + { + MatchPath{"/foo"}, + }, + }, + }, + expect: false, + }, + { + host: "example.com", path: "/bar", + match: MatchNot{ + MatcherSets: []MatcherSet{ + { + MatchPath{"/foo"}, + }, + }, + }, + expect: true, + }, + { + host: "example.com", path: "/bar", + match: MatchNot{ + MatcherSets: []MatcherSet{ + { + MatchPath{"/foo"}, + }, + { + MatchHost{"example.com"}, + }, + }, + }, + expect: false, + }, + { + host: "example.com", path: "/bar", + match: MatchNot{ + MatcherSets: []MatcherSet{ + { + MatchPath{"/bar"}, + }, + { + MatchHost{"example.com"}, + }, + }, + }, + expect: false, + }, + { + host: "example.com", path: "/foo", + match: MatchNot{ + MatcherSets: []MatcherSet{ + { + MatchPath{"/bar"}, + }, + { + MatchHost{"sub.example.com"}, + }, + }, + }, + expect: true, + }, + { + host: "example.com", path: "/foo", + match: MatchNot{ + MatcherSets: []MatcherSet{ + { + MatchPath{"/foo"}, + MatchHost{"example.com"}, + }, + }, + }, + expect: false, + }, + { + host: "example.com", path: "/foo", + match: MatchNot{ + MatcherSets: []MatcherSet{ + { + MatchPath{"/bar"}, + MatchHost{"example.com"}, + }, + }, + }, + expect: true, + }, + } { + req := &http.Request{Host: tc.host, URL: &url.URL{Path: tc.path}} + repl := caddy.NewReplacer() + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + req = req.WithContext(ctx) + + actual, err := tc.match.MatchWithError(req) + if err != nil { + t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err) + } + if actual != tc.expect { + t.Errorf("Test %d %+v: Expected %t, got %t for: host=%s path=%s'", i, tc.match, tc.expect, actual, tc.host, tc.path) + continue + } + } +} + +func BenchmarkLargeHostMatcher(b *testing.B) { + // this benchmark simulates a large host matcher (thousands of entries) where each + // value is an exact hostname (not a placeholder or wildcard) - compare the results + // of this with and without the binary search (comment out the various fast path + // sections in Match) to conduct experiments + + const n = 10000 + lastHost := fmt.Sprintf("%d.example.com", n-1) + req := &http.Request{Host: lastHost} + repl := caddy.NewReplacer() + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + req = req.WithContext(ctx) + + matcher := make(MatchHost, n) + for i := 0; i < n; i++ { + matcher[i] = fmt.Sprintf("%d.example.com", i) + } + err := matcher.Provision(caddy.Context{}) + if err != nil { + b.Fatal(err) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + matcher.MatchWithError(req) + } +} + +func BenchmarkHostMatcherWithoutPlaceholder(b *testing.B) { + req := &http.Request{Host: "localhost"} + repl := caddy.NewReplacer() + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + req = req.WithContext(ctx) + + match := MatchHost{"localhost"} + + b.ResetTimer() + for i := 0; i < b.N; i++ { + match.MatchWithError(req) + } +} + +func BenchmarkHostMatcherWithPlaceholder(b *testing.B) { + err := os.Setenv("GO_BENCHMARK_DOMAIN", "localhost") + if err != nil { + b.Errorf("error while setting up environment: %v", err) + } + + req := &http.Request{Host: "localhost"} + repl := caddy.NewReplacer() + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + req = req.WithContext(ctx) + match := MatchHost{"{env.GO_BENCHMARK_DOMAIN}"} + + b.ResetTimer() + for i := 0; i < b.N; i++ { + match.MatchWithError(req) + } +} diff --git a/modules/caddyhttp/metrics.go b/modules/caddyhttp/metrics.go new file mode 100644 index 00000000000..9bb97e0b47b --- /dev/null +++ b/modules/caddyhttp/metrics.go @@ -0,0 +1,214 @@ +package caddyhttp + +import ( + "context" + "errors" + "net/http" + "strings" + "sync" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/internal/metrics" +) + +// Metrics configures metrics observations. +// EXPERIMENTAL and subject to change or removal. +type Metrics struct { + // Enable per-host metrics. Enabling this option may + // incur high-memory consumption, depending on the number of hosts + // managed by Caddy. + PerHost bool `json:"per_host,omitempty"` + + init sync.Once + httpMetrics *httpMetrics `json:"-"` +} + +type httpMetrics struct { + requestInFlight *prometheus.GaugeVec + requestCount *prometheus.CounterVec + requestErrors *prometheus.CounterVec + requestDuration *prometheus.HistogramVec + requestSize *prometheus.HistogramVec + responseSize *prometheus.HistogramVec + responseDuration *prometheus.HistogramVec +} + +func initHTTPMetrics(ctx caddy.Context, metrics *Metrics) { + const ns, sub = "caddy", "http" + registry := ctx.GetMetricsRegistry() + basicLabels := []string{"server", "handler"} + if metrics.PerHost { + basicLabels = append(basicLabels, "host") + } + metrics.httpMetrics.requestInFlight = promauto.With(registry).NewGaugeVec(prometheus.GaugeOpts{ + Namespace: ns, + Subsystem: sub, + Name: "requests_in_flight", + Help: "Number of requests currently handled by this server.", + }, basicLabels) + metrics.httpMetrics.requestErrors = promauto.With(registry).NewCounterVec(prometheus.CounterOpts{ + Namespace: ns, + Subsystem: sub, + Name: "request_errors_total", + Help: "Number of requests resulting in middleware errors.", + }, basicLabels) + metrics.httpMetrics.requestCount = promauto.With(registry).NewCounterVec(prometheus.CounterOpts{ + Namespace: ns, + Subsystem: sub, + Name: "requests_total", + Help: "Counter of HTTP(S) requests made.", + }, basicLabels) + + // TODO: allow these to be customized in the config + durationBuckets := prometheus.DefBuckets + sizeBuckets := prometheus.ExponentialBuckets(256, 4, 8) + + httpLabels := []string{"server", "handler", "code", "method"} + if metrics.PerHost { + httpLabels = append(httpLabels, "host") + } + metrics.httpMetrics.requestDuration = promauto.With(registry).NewHistogramVec(prometheus.HistogramOpts{ + Namespace: ns, + Subsystem: sub, + Name: "request_duration_seconds", + Help: "Histogram of round-trip request durations.", + Buckets: durationBuckets, + }, httpLabels) + metrics.httpMetrics.requestSize = promauto.With(registry).NewHistogramVec(prometheus.HistogramOpts{ + Namespace: ns, + Subsystem: sub, + Name: "request_size_bytes", + Help: "Total size of the request. Includes body", + Buckets: sizeBuckets, + }, httpLabels) + metrics.httpMetrics.responseSize = promauto.With(registry).NewHistogramVec(prometheus.HistogramOpts{ + Namespace: ns, + Subsystem: sub, + Name: "response_size_bytes", + Help: "Size of the returned response.", + Buckets: sizeBuckets, + }, httpLabels) + metrics.httpMetrics.responseDuration = promauto.With(registry).NewHistogramVec(prometheus.HistogramOpts{ + Namespace: ns, + Subsystem: sub, + Name: "response_duration_seconds", + Help: "Histogram of times to first byte in response bodies.", + Buckets: durationBuckets, + }, httpLabels) +} + +// serverNameFromContext extracts the current server name from the context. +// Returns "UNKNOWN" if none is available (should probably never happen). +func serverNameFromContext(ctx context.Context) string { + srv, ok := ctx.Value(ServerCtxKey).(*Server) + if !ok || srv == nil || srv.name == "" { + return "UNKNOWN" + } + return srv.name +} + +type metricsInstrumentedHandler struct { + handler string + mh MiddlewareHandler + metrics *Metrics +} + +func newMetricsInstrumentedHandler(ctx caddy.Context, handler string, mh MiddlewareHandler, metrics *Metrics) *metricsInstrumentedHandler { + metrics.init.Do(func() { + initHTTPMetrics(ctx, metrics) + }) + + return &metricsInstrumentedHandler{handler, mh, metrics} +} + +func (h *metricsInstrumentedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request, next Handler) error { + server := serverNameFromContext(r.Context()) + labels := prometheus.Labels{"server": server, "handler": h.handler} + method := metrics.SanitizeMethod(r.Method) + // the "code" value is set later, but initialized here to eliminate the possibility + // of a panic + statusLabels := prometheus.Labels{"server": server, "handler": h.handler, "method": method, "code": ""} + + if h.metrics.PerHost { + labels["host"] = strings.ToLower(r.Host) + statusLabels["host"] = strings.ToLower(r.Host) + } + + inFlight := h.metrics.httpMetrics.requestInFlight.With(labels) + inFlight.Inc() + defer inFlight.Dec() + + start := time.Now() + + // This is a _bit_ of a hack - it depends on the ShouldBufferFunc always + // being called when the headers are written. + // Effectively the same behaviour as promhttp.InstrumentHandlerTimeToWriteHeader. + writeHeaderRecorder := ShouldBufferFunc(func(status int, header http.Header) bool { + statusLabels["code"] = metrics.SanitizeCode(status) + ttfb := time.Since(start).Seconds() + h.metrics.httpMetrics.responseDuration.With(statusLabels).Observe(ttfb) + return false + }) + wrec := NewResponseRecorder(w, nil, writeHeaderRecorder) + err := h.mh.ServeHTTP(wrec, r, next) + dur := time.Since(start).Seconds() + h.metrics.httpMetrics.requestCount.With(labels).Inc() + + observeRequest := func(status int) { + // If the code hasn't been set yet, and we didn't encounter an error, we're + // probably falling through with an empty handler. + if statusLabels["code"] == "" { + // we still sanitize it, even though it's likely to be 0. A 200 is + // returned on fallthrough so we want to reflect that. + statusLabels["code"] = metrics.SanitizeCode(status) + } + + h.metrics.httpMetrics.requestDuration.With(statusLabels).Observe(dur) + h.metrics.httpMetrics.requestSize.With(statusLabels).Observe(float64(computeApproximateRequestSize(r))) + h.metrics.httpMetrics.responseSize.With(statusLabels).Observe(float64(wrec.Size())) + } + + if err != nil { + var handlerErr HandlerError + if errors.As(err, &handlerErr) { + observeRequest(handlerErr.StatusCode) + } + + h.metrics.httpMetrics.requestErrors.With(labels).Inc() + + return err + } + + observeRequest(wrec.Status()) + + return nil +} + +// taken from https://github.com/prometheus/client_golang/blob/6007b2b5cae01203111de55f753e76d8dac1f529/prometheus/promhttp/instrument_server.go#L298 +func computeApproximateRequestSize(r *http.Request) int { + s := 0 + if r.URL != nil { + s += len(r.URL.String()) + } + + s += len(r.Method) + s += len(r.Proto) + for name, values := range r.Header { + s += len(name) + for _, value := range values { + s += len(value) + } + } + s += len(r.Host) + + // N.B. r.Form and r.MultipartForm are assumed to be included in r.URL. + + if r.ContentLength != -1 { + s += int(r.ContentLength) + } + return s +} diff --git a/modules/caddyhttp/metrics_test.go b/modules/caddyhttp/metrics_test.go new file mode 100644 index 00000000000..4a0519b8769 --- /dev/null +++ b/modules/caddyhttp/metrics_test.go @@ -0,0 +1,385 @@ +package caddyhttp + +import ( + "context" + "errors" + "net/http" + "net/http/httptest" + "strings" + "sync" + "testing" + + "github.com/caddyserver/caddy/v2" + "github.com/prometheus/client_golang/prometheus/testutil" +) + +func TestServerNameFromContext(t *testing.T) { + ctx := context.Background() + expected := "UNKNOWN" + if actual := serverNameFromContext(ctx); actual != expected { + t.Errorf("Not equal: expected %q, but got %q", expected, actual) + } + + in := "foo" + ctx = context.WithValue(ctx, ServerCtxKey, &Server{name: in}) + if actual := serverNameFromContext(ctx); actual != in { + t.Errorf("Not equal: expected %q, but got %q", in, actual) + } +} + +func TestMetricsInstrumentedHandler(t *testing.T) { + ctx, _ := caddy.NewContext(caddy.Context{Context: context.Background()}) + metrics := &Metrics{ + init: sync.Once{}, + httpMetrics: &httpMetrics{}, + } + handlerErr := errors.New("oh noes") + response := []byte("hello world!") + h := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { + if actual := testutil.ToFloat64(metrics.httpMetrics.requestInFlight); actual != 1.0 { + t.Errorf("Not same: expected %#v, but got %#v", 1.0, actual) + } + if handlerErr == nil { + w.Write(response) + } + return handlerErr + }) + + mh := middlewareHandlerFunc(func(w http.ResponseWriter, r *http.Request, h Handler) error { + return h.ServeHTTP(w, r) + }) + + ih := newMetricsInstrumentedHandler(ctx, "bar", mh, metrics) + + r := httptest.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + + if actual := ih.ServeHTTP(w, r, h); actual != handlerErr { + t.Errorf("Not same: expected %#v, but got %#v", handlerErr, actual) + } + if actual := testutil.ToFloat64(metrics.httpMetrics.requestInFlight); actual != 0.0 { + t.Errorf("Not same: expected %#v, but got %#v", 0.0, actual) + } + + handlerErr = nil + if err := ih.ServeHTTP(w, r, h); err != nil { + t.Errorf("Received unexpected error: %v", err) + } + + // an empty handler - no errors, no header written + mh = middlewareHandlerFunc(func(w http.ResponseWriter, r *http.Request, h Handler) error { + return nil + }) + ih = newMetricsInstrumentedHandler(ctx, "empty", mh, metrics) + r = httptest.NewRequest("GET", "/", nil) + w = httptest.NewRecorder() + + if err := ih.ServeHTTP(w, r, h); err != nil { + t.Errorf("Received unexpected error: %v", err) + } + if actual := w.Result().StatusCode; actual != 200 { + t.Errorf("Not same: expected status code %#v, but got %#v", 200, actual) + } + if actual := w.Result().Header; len(actual) != 0 { + t.Errorf("Not empty: expected headers to be empty, but got %#v", actual) + } + + // handler returning an error with an HTTP status + mh = middlewareHandlerFunc(func(w http.ResponseWriter, r *http.Request, h Handler) error { + return Error(http.StatusTooManyRequests, nil) + }) + + ih = newMetricsInstrumentedHandler(ctx, "foo", mh, metrics) + + r = httptest.NewRequest("GET", "/", nil) + w = httptest.NewRecorder() + + if err := ih.ServeHTTP(w, r, nil); err == nil { + t.Errorf("expected error to be propagated") + } + + expected := ` + # HELP caddy_http_request_duration_seconds Histogram of round-trip request durations. + # TYPE caddy_http_request_duration_seconds histogram + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="0.005"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="0.01"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="0.025"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="0.05"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="0.1"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="0.25"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="0.5"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="1"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="2.5"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="5"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="10"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="+Inf"} 1 + caddy_http_request_duration_seconds_count{code="429",handler="foo",method="GET",server="UNKNOWN"} 1 + # HELP caddy_http_request_size_bytes Total size of the request. Includes body + # TYPE caddy_http_request_size_bytes histogram + caddy_http_request_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="256"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="1024"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="4096"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="16384"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="65536"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="262144"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="1.048576e+06"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="4.194304e+06"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="+Inf"} 1 + caddy_http_request_size_bytes_sum{code="200",handler="bar",method="GET",server="UNKNOWN"} 23 + caddy_http_request_size_bytes_count{code="200",handler="bar",method="GET",server="UNKNOWN"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="256"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="1024"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="4096"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="16384"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="65536"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="262144"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="1.048576e+06"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="4.194304e+06"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="+Inf"} 1 + caddy_http_request_size_bytes_sum{code="200",handler="empty",method="GET",server="UNKNOWN"} 23 + caddy_http_request_size_bytes_count{code="200",handler="empty",method="GET",server="UNKNOWN"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="256"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="1024"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="4096"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="16384"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="65536"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="262144"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="1.048576e+06"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="4.194304e+06"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="+Inf"} 1 + caddy_http_request_size_bytes_sum{code="429",handler="foo",method="GET",server="UNKNOWN"} 23 + caddy_http_request_size_bytes_count{code="429",handler="foo",method="GET",server="UNKNOWN"} 1 + # HELP caddy_http_response_size_bytes Size of the returned response. + # TYPE caddy_http_response_size_bytes histogram + caddy_http_response_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="256"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="1024"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="4096"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="16384"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="65536"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="262144"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="1.048576e+06"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="4.194304e+06"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="bar",method="GET",server="UNKNOWN",le="+Inf"} 1 + caddy_http_response_size_bytes_sum{code="200",handler="bar",method="GET",server="UNKNOWN"} 12 + caddy_http_response_size_bytes_count{code="200",handler="bar",method="GET",server="UNKNOWN"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="256"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="1024"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="4096"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="16384"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="65536"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="262144"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="1.048576e+06"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="4.194304e+06"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",method="GET",server="UNKNOWN",le="+Inf"} 1 + caddy_http_response_size_bytes_sum{code="200",handler="empty",method="GET",server="UNKNOWN"} 0 + caddy_http_response_size_bytes_count{code="200",handler="empty",method="GET",server="UNKNOWN"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="256"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="1024"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="4096"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="16384"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="65536"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="262144"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="1.048576e+06"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="4.194304e+06"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",method="GET",server="UNKNOWN",le="+Inf"} 1 + caddy_http_response_size_bytes_sum{code="429",handler="foo",method="GET",server="UNKNOWN"} 0 + caddy_http_response_size_bytes_count{code="429",handler="foo",method="GET",server="UNKNOWN"} 1 + # HELP caddy_http_request_errors_total Number of requests resulting in middleware errors. + # TYPE caddy_http_request_errors_total counter + caddy_http_request_errors_total{handler="bar",server="UNKNOWN"} 1 + caddy_http_request_errors_total{handler="foo",server="UNKNOWN"} 1 + ` + if err := testutil.GatherAndCompare(ctx.GetMetricsRegistry(), strings.NewReader(expected), + "caddy_http_request_size_bytes", + "caddy_http_response_size_bytes", + // caddy_http_request_duration_seconds_sum will vary based on how long the test took to run, + // so we check just the _bucket and _count metrics + "caddy_http_request_duration_seconds_bucket", + "caddy_http_request_duration_seconds_count", + "caddy_http_request_errors_total", + ); err != nil { + t.Errorf("received unexpected error: %s", err) + } +} + +func TestMetricsInstrumentedHandlerPerHost(t *testing.T) { + ctx, _ := caddy.NewContext(caddy.Context{Context: context.Background()}) + metrics := &Metrics{ + PerHost: true, + init: sync.Once{}, + httpMetrics: &httpMetrics{}, + } + handlerErr := errors.New("oh noes") + response := []byte("hello world!") + h := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { + if actual := testutil.ToFloat64(metrics.httpMetrics.requestInFlight); actual != 1.0 { + t.Errorf("Not same: expected %#v, but got %#v", 1.0, actual) + } + if handlerErr == nil { + w.Write(response) + } + return handlerErr + }) + + mh := middlewareHandlerFunc(func(w http.ResponseWriter, r *http.Request, h Handler) error { + return h.ServeHTTP(w, r) + }) + + ih := newMetricsInstrumentedHandler(ctx, "bar", mh, metrics) + + r := httptest.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + + if actual := ih.ServeHTTP(w, r, h); actual != handlerErr { + t.Errorf("Not same: expected %#v, but got %#v", handlerErr, actual) + } + if actual := testutil.ToFloat64(metrics.httpMetrics.requestInFlight); actual != 0.0 { + t.Errorf("Not same: expected %#v, but got %#v", 0.0, actual) + } + + handlerErr = nil + if err := ih.ServeHTTP(w, r, h); err != nil { + t.Errorf("Received unexpected error: %v", err) + } + + // an empty handler - no errors, no header written + mh = middlewareHandlerFunc(func(w http.ResponseWriter, r *http.Request, h Handler) error { + return nil + }) + ih = newMetricsInstrumentedHandler(ctx, "empty", mh, metrics) + r = httptest.NewRequest("GET", "/", nil) + w = httptest.NewRecorder() + + if err := ih.ServeHTTP(w, r, h); err != nil { + t.Errorf("Received unexpected error: %v", err) + } + if actual := w.Result().StatusCode; actual != 200 { + t.Errorf("Not same: expected status code %#v, but got %#v", 200, actual) + } + if actual := w.Result().Header; len(actual) != 0 { + t.Errorf("Not empty: expected headers to be empty, but got %#v", actual) + } + + // handler returning an error with an HTTP status + mh = middlewareHandlerFunc(func(w http.ResponseWriter, r *http.Request, h Handler) error { + return Error(http.StatusTooManyRequests, nil) + }) + + ih = newMetricsInstrumentedHandler(ctx, "foo", mh, metrics) + + r = httptest.NewRequest("GET", "/", nil) + w = httptest.NewRecorder() + + if err := ih.ServeHTTP(w, r, nil); err == nil { + t.Errorf("expected error to be propagated") + } + + expected := ` + # HELP caddy_http_request_duration_seconds Histogram of round-trip request durations. + # TYPE caddy_http_request_duration_seconds histogram + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="0.005"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="0.01"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="0.025"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="0.05"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="0.1"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="0.25"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="0.5"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="1"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="2.5"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="5"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="10"} 1 + caddy_http_request_duration_seconds_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="+Inf"} 1 + caddy_http_request_duration_seconds_count{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN"} 1 + # HELP caddy_http_request_size_bytes Total size of the request. Includes body + # TYPE caddy_http_request_size_bytes histogram + caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="256"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="1024"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="4096"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="16384"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="65536"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="262144"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="1.048576e+06"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="4.194304e+06"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="+Inf"} 1 + caddy_http_request_size_bytes_sum{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN"} 23 + caddy_http_request_size_bytes_count{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="256"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="1024"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="4096"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="16384"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="65536"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="262144"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="1.048576e+06"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="4.194304e+06"} 1 + caddy_http_request_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="+Inf"} 1 + caddy_http_request_size_bytes_sum{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN"} 23 + caddy_http_request_size_bytes_count{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="256"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="1024"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="4096"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="16384"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="65536"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="262144"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="1.048576e+06"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="4.194304e+06"} 1 + caddy_http_request_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="+Inf"} 1 + caddy_http_request_size_bytes_sum{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN"} 23 + caddy_http_request_size_bytes_count{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN"} 1 + # HELP caddy_http_response_size_bytes Size of the returned response. + # TYPE caddy_http_response_size_bytes histogram + caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="256"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="1024"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="4096"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="16384"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="65536"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="262144"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="1.048576e+06"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="4.194304e+06"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN",le="+Inf"} 1 + caddy_http_response_size_bytes_sum{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN"} 12 + caddy_http_response_size_bytes_count{code="200",handler="bar",host="example.com",method="GET",server="UNKNOWN"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="256"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="1024"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="4096"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="16384"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="65536"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="262144"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="1.048576e+06"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="4.194304e+06"} 1 + caddy_http_response_size_bytes_bucket{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN",le="+Inf"} 1 + caddy_http_response_size_bytes_sum{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN"} 0 + caddy_http_response_size_bytes_count{code="200",handler="empty",host="example.com",method="GET",server="UNKNOWN"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="256"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="1024"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="4096"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="16384"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="65536"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="262144"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="1.048576e+06"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="4.194304e+06"} 1 + caddy_http_response_size_bytes_bucket{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN",le="+Inf"} 1 + caddy_http_response_size_bytes_sum{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN"} 0 + caddy_http_response_size_bytes_count{code="429",handler="foo",host="example.com",method="GET",server="UNKNOWN"} 1 + # HELP caddy_http_request_errors_total Number of requests resulting in middleware errors. + # TYPE caddy_http_request_errors_total counter + caddy_http_request_errors_total{handler="bar",host="example.com",server="UNKNOWN"} 1 + caddy_http_request_errors_total{handler="foo",host="example.com",server="UNKNOWN"} 1 + ` + if err := testutil.GatherAndCompare(ctx.GetMetricsRegistry(), strings.NewReader(expected), + "caddy_http_request_size_bytes", + "caddy_http_response_size_bytes", + // caddy_http_request_duration_seconds_sum will vary based on how long the test took to run, + // so we check just the _bucket and _count metrics + "caddy_http_request_duration_seconds_bucket", + "caddy_http_request_duration_seconds_count", + "caddy_http_request_errors_total", + ); err != nil { + t.Errorf("received unexpected error: %s", err) + } +} + +type middlewareHandlerFunc func(http.ResponseWriter, *http.Request, Handler) error + +func (f middlewareHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request, h Handler) error { + return f(w, r, h) +} diff --git a/modules/caddyhttp/proxyprotocol/listenerwrapper.go b/modules/caddyhttp/proxyprotocol/listenerwrapper.go new file mode 100644 index 00000000000..f1d170c38ca --- /dev/null +++ b/modules/caddyhttp/proxyprotocol/listenerwrapper.go @@ -0,0 +1,144 @@ +// 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 proxyprotocol + +import ( + "net" + "net/netip" + "time" + + goproxy "github.com/pires/go-proxyproto" + + "github.com/caddyserver/caddy/v2" +) + +// ListenerWrapper provides PROXY protocol support to Caddy by implementing +// the caddy.ListenerWrapper interface. If a connection is received via Unix +// socket, it's trusted. Otherwise, it's checked against the Allow/Deny lists, +// then it's handled by the FallbackPolicy. +// +// It must be loaded before the `tls` listener because the PROXY protocol +// encapsulates the TLS data. +// +// Credit goes to https://github.com/mastercactapus/caddy2-proxyprotocol for having +// initially implemented this as a plugin. +type ListenerWrapper struct { + // Timeout specifies an optional maximum time for + // the PROXY header to be received. + // If zero, timeout is disabled. Default is 5s. + Timeout caddy.Duration `json:"timeout,omitempty"` + + // Allow is an optional list of CIDR ranges to + // allow/require PROXY headers from. + Allow []string `json:"allow,omitempty"` + allow []netip.Prefix + + // Deny is an optional list of CIDR ranges to + // deny PROXY headers from. + Deny []string `json:"deny,omitempty"` + deny []netip.Prefix + + // FallbackPolicy specifies the policy to use if the downstream + // IP address is not in the Allow list nor is in the Deny list. + // + // NOTE: The generated docs which describe the value of this + // field is wrong because of how this type unmarshals JSON in a + // custom way. The field expects a string, not a number. + // + // Accepted values are: IGNORE, USE, REJECT, REQUIRE, SKIP + // + // - IGNORE: address from PROXY header, but accept connection + // + // - USE: address from PROXY header + // + // - REJECT: connection when PROXY header is sent + // Note: even though the first read on the connection returns an error if + // a PROXY header is present, subsequent reads do not. It is the task of + // the code using the connection to handle that case properly. + // + // - REQUIRE: connection to send PROXY header, reject if not present + // Note: even though the first read on the connection returns an error if + // a PROXY header is not present, subsequent reads do not. It is the task + // of the code using the connection to handle that case properly. + // + // - SKIP: accepts a connection without requiring the PROXY header. + // Note: an example usage can be found in the SkipProxyHeaderForCIDR + // function. + // + // Default: IGNORE + // + // Policy definitions are here: https://pkg.go.dev/github.com/pires/go-proxyproto@v0.7.0#Policy + FallbackPolicy Policy `json:"fallback_policy,omitempty"` + + policy goproxy.ConnPolicyFunc +} + +// Provision sets up the listener wrapper. +func (pp *ListenerWrapper) Provision(ctx caddy.Context) error { + for _, cidr := range pp.Allow { + ipnet, err := netip.ParsePrefix(cidr) + if err != nil { + return err + } + pp.allow = append(pp.allow, ipnet) + } + for _, cidr := range pp.Deny { + ipnet, err := netip.ParsePrefix(cidr) + if err != nil { + return err + } + pp.deny = append(pp.deny, ipnet) + } + + pp.policy = func(options goproxy.ConnPolicyOptions) (goproxy.Policy, error) { + // trust unix sockets + if network := options.Upstream.Network(); caddy.IsUnixNetwork(network) || caddy.IsFdNetwork(network) { + return goproxy.USE, nil + } + ret := pp.FallbackPolicy + host, _, err := net.SplitHostPort(options.Upstream.String()) + if err != nil { + return goproxy.REJECT, err + } + + ip, err := netip.ParseAddr(host) + if err != nil { + return goproxy.REJECT, err + } + for _, ipnet := range pp.deny { + if ipnet.Contains(ip) { + return goproxy.REJECT, nil + } + } + for _, ipnet := range pp.allow { + if ipnet.Contains(ip) { + ret = PolicyUSE + break + } + } + return policyToGoProxyPolicy[ret], nil + } + return nil +} + +// WrapListener adds PROXY protocol support to the listener. +func (pp *ListenerWrapper) WrapListener(l net.Listener) net.Listener { + pl := &goproxy.Listener{ + Listener: l, + ReadHeaderTimeout: time.Duration(pp.Timeout), + } + pl.ConnPolicy = pp.policy + return pl +} diff --git a/modules/caddyhttp/proxyprotocol/module.go b/modules/caddyhttp/proxyprotocol/module.go new file mode 100644 index 00000000000..75a156a2071 --- /dev/null +++ b/modules/caddyhttp/proxyprotocol/module.go @@ -0,0 +1,87 @@ +// 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 proxyprotocol + +import ( + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func init() { + caddy.RegisterModule(ListenerWrapper{}) +} + +func (ListenerWrapper) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.listeners.proxy_protocol", + New: func() caddy.Module { return new(ListenerWrapper) }, + } +} + +// UnmarshalCaddyfile sets up the listener Listenerwrapper from Caddyfile tokens. Syntax: +// +// proxy_protocol { +// timeout +// allow +// deny +// fallback_policy +// } +func (w *ListenerWrapper) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume wrapper name + + // No same-line options are supported + if d.NextArg() { + return d.ArgErr() + } + + for d.NextBlock(0) { + switch d.Val() { + case "timeout": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("parsing proxy_protocol timeout duration: %v", err) + } + w.Timeout = caddy.Duration(dur) + + case "allow": + w.Allow = append(w.Allow, d.RemainingArgs()...) + case "deny": + w.Deny = append(w.Deny, d.RemainingArgs()...) + case "fallback_policy": + if !d.NextArg() { + return d.ArgErr() + } + p, err := parsePolicy(d.Val()) + if err != nil { + return d.WrapErr(err) + } + w.FallbackPolicy = p + default: + return d.ArgErr() + } + } + return nil +} + +// Interface guards +var ( + _ caddy.Provisioner = (*ListenerWrapper)(nil) + _ caddy.Module = (*ListenerWrapper)(nil) + _ caddy.ListenerWrapper = (*ListenerWrapper)(nil) + _ caddyfile.Unmarshaler = (*ListenerWrapper)(nil) +) diff --git a/modules/caddyhttp/proxyprotocol/policy.go b/modules/caddyhttp/proxyprotocol/policy.go new file mode 100644 index 00000000000..6dc8beb45dc --- /dev/null +++ b/modules/caddyhttp/proxyprotocol/policy.go @@ -0,0 +1,82 @@ +package proxyprotocol + +import ( + "errors" + "fmt" + "strings" + + goproxy "github.com/pires/go-proxyproto" +) + +type Policy int + +// as defined in: https://pkg.go.dev/github.com/pires/go-proxyproto@v0.7.0#Policy +const ( + // IGNORE address from PROXY header, but accept connection + PolicyIGNORE Policy = iota + // USE address from PROXY header + PolicyUSE + // REJECT connection when PROXY header is sent + // Note: even though the first read on the connection returns an error if + // a PROXY header is present, subsequent reads do not. It is the task of + // the code using the connection to handle that case properly. + PolicyREJECT + // REQUIRE connection to send PROXY header, reject if not present + // Note: even though the first read on the connection returns an error if + // a PROXY header is not present, subsequent reads do not. It is the task + // of the code using the connection to handle that case properly. + PolicyREQUIRE + // SKIP accepts a connection without requiring the PROXY header + // Note: an example usage can be found in the SkipProxyHeaderForCIDR + // function. + PolicySKIP +) + +var policyToGoProxyPolicy = map[Policy]goproxy.Policy{ + PolicyUSE: goproxy.USE, + PolicyIGNORE: goproxy.IGNORE, + PolicyREJECT: goproxy.REJECT, + PolicyREQUIRE: goproxy.REQUIRE, + PolicySKIP: goproxy.SKIP, +} + +var policyMap = map[Policy]string{ + PolicyUSE: "USE", + PolicyIGNORE: "IGNORE", + PolicyREJECT: "REJECT", + PolicyREQUIRE: "REQUIRE", + PolicySKIP: "SKIP", +} + +var policyMapRev = map[string]Policy{ + "USE": PolicyUSE, + "IGNORE": PolicyIGNORE, + "REJECT": PolicyREJECT, + "REQUIRE": PolicyREQUIRE, + "SKIP": PolicySKIP, +} + +// MarshalText implements the text marshaller method. +func (x Policy) MarshalText() ([]byte, error) { + return []byte(policyMap[x]), nil +} + +// UnmarshalText implements the text unmarshaller method. +func (x *Policy) UnmarshalText(text []byte) error { + name := string(text) + tmp, err := parsePolicy(name) + if err != nil { + return err + } + *x = tmp + return nil +} + +func parsePolicy(name string) (Policy, error) { + if x, ok := policyMapRev[strings.ToUpper(name)]; ok { + return x, nil + } + return Policy(0), fmt.Errorf("%s is %w", name, errInvalidPolicy) +} + +var errInvalidPolicy = errors.New("invalid policy") diff --git a/modules/caddyhttp/push/caddyfile.go b/modules/caddyhttp/push/caddyfile.go new file mode 100644 index 00000000000..f56db81f98f --- /dev/null +++ b/modules/caddyhttp/push/caddyfile.go @@ -0,0 +1,106 @@ +// 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 push + +import ( + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/headers" +) + +func init() { + httpcaddyfile.RegisterHandlerDirective("push", parseCaddyfile) +} + +// parseCaddyfile sets up the push handler. Syntax: +// +// push [] [] { +// [GET|HEAD] +// headers { +// [+] [ []] +// - +// } +// } +// +// A single resource can be specified inline without opening a +// block for the most common/simple case. Or, a block can be +// opened and multiple resources can be specified, one per +// line, optionally preceded by the method. The headers +// subdirective can be used to customize the headers that +// are set on each (synthetic) push request, using the same +// syntax as the 'header' directive for request headers. +// Placeholders are accepted in resource and header field +// name and value and replacement tokens. +func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + h.Next() // consume directive name + + handler := new(Handler) + + // inline resources + if h.NextArg() { + handler.Resources = append(handler.Resources, Resource{Target: h.Val()}) + } + + // optional block + for h.NextBlock(0) { + switch h.Val() { + case "headers": + if h.NextArg() { + return nil, h.ArgErr() + } + for nesting := h.Nesting(); h.NextBlock(nesting); { + var err error + + // include current token, which we treat as an argument here + args := []string{h.Val()} + args = append(args, h.RemainingArgs()...) + + if handler.Headers == nil { + handler.Headers = new(HeaderConfig) + } + + switch len(args) { + case 1: + err = headers.CaddyfileHeaderOp(&handler.Headers.HeaderOps, args[0], "", nil) + case 2: + err = headers.CaddyfileHeaderOp(&handler.Headers.HeaderOps, args[0], args[1], nil) + case 3: + err = headers.CaddyfileHeaderOp(&handler.Headers.HeaderOps, args[0], args[1], &args[2]) + default: + return nil, h.ArgErr() + } + + if err != nil { + return nil, h.Err(err.Error()) + } + } + + case "GET", "HEAD": + method := h.Val() + if !h.NextArg() { + return nil, h.ArgErr() + } + target := h.Val() + handler.Resources = append(handler.Resources, Resource{ + Method: method, + Target: target, + }) + + default: + handler.Resources = append(handler.Resources, Resource{Target: h.Val()}) + } + } + return handler, nil +} diff --git a/modules/caddyhttp/push/handler.go b/modules/caddyhttp/push/handler.go new file mode 100644 index 00000000000..1fbe53d8366 --- /dev/null +++ b/modules/caddyhttp/push/handler.go @@ -0,0 +1,263 @@ +// 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 push + +import ( + "fmt" + "net/http" + "strings" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/headers" +) + +func init() { + caddy.RegisterModule(Handler{}) +} + +// Handler is a middleware for HTTP/2 server push. Note that +// HTTP/2 server push has been deprecated by some clients and +// its use is discouraged unless you can accurately predict +// which resources actually need to be pushed to the client; +// it can be difficult to know what the client already has +// cached. Pushing unnecessary resources results in worse +// performance. Consider using HTTP 103 Early Hints instead. +// +// This handler supports pushing from Link headers; in other +// words, if the eventual response has Link headers, this +// handler will push the resources indicated by those headers, +// even without specifying any resources in its config. +type Handler struct { + // The resources to push. + Resources []Resource `json:"resources,omitempty"` + + // Headers to modify for the push requests. + Headers *HeaderConfig `json:"headers,omitempty"` + + logger *zap.Logger +} + +// CaddyModule returns the Caddy module information. +func (Handler) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.push", + New: func() caddy.Module { return new(Handler) }, + } +} + +// Provision sets up h. +func (h *Handler) Provision(ctx caddy.Context) error { + h.logger = ctx.Logger() + if h.Headers != nil { + err := h.Headers.Provision(ctx) + if err != nil { + return fmt.Errorf("provisioning header operations: %v", err) + } + } + return nil +} + +func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + pusher, ok := w.(http.Pusher) + if !ok { + return next.ServeHTTP(w, r) + } + + // short-circuit recursive pushes + if _, ok := r.Header[pushHeader]; ok { + return next.ServeHTTP(w, r) + } + + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + server := r.Context().Value(caddyhttp.ServerCtxKey).(*caddyhttp.Server) + shouldLogCredentials := server.Logs != nil && server.Logs.ShouldLogCredentials + + // create header for push requests + hdr := h.initializePushHeaders(r, repl) + + // push first! + for _, resource := range h.Resources { + if c := h.logger.Check(zapcore.DebugLevel, "pushing resource"); c != nil { + c.Write( + zap.String("uri", r.RequestURI), + zap.String("push_method", resource.Method), + zap.String("push_target", resource.Target), + zap.Object("push_headers", caddyhttp.LoggableHTTPHeader{ + Header: hdr, + ShouldLogCredentials: shouldLogCredentials, + }), + ) + } + err := pusher.Push(repl.ReplaceAll(resource.Target, "."), &http.PushOptions{ + Method: resource.Method, + Header: hdr, + }) + if err != nil { + // usually this means either that push is not + // supported or concurrent streams are full + break + } + } + + // wrap the response writer so that we can initiate push of any resources + // described in Link header fields before the response is written + lp := linkPusher{ + ResponseWriterWrapper: &caddyhttp.ResponseWriterWrapper{ResponseWriter: w}, + handler: h, + pusher: pusher, + header: hdr, + request: r, + } + + // serve only after pushing! + if err := next.ServeHTTP(lp, r); err != nil { + return err + } + + return nil +} + +func (h Handler) initializePushHeaders(r *http.Request, repl *caddy.Replacer) http.Header { + hdr := make(http.Header) + + // prevent recursive pushes + hdr.Set(pushHeader, "1") + + // set initial header fields; since exactly how headers should + // be implemented for server push is not well-understood, we + // are being conservative for now like httpd is: + // https://httpd.apache.org/docs/2.4/en/howto/http2.html#push + // we only copy some well-known, safe headers that are likely + // crucial when requesting certain kinds of content + for _, fieldName := range safeHeaders { + if vals, ok := r.Header[fieldName]; ok { + hdr[fieldName] = vals + } + } + + // user can customize the push request headers + if h.Headers != nil { + h.Headers.ApplyTo(hdr, repl) + } + + return hdr +} + +// servePreloadLinks parses Link headers from upstream and pushes +// resources described by them. If a resource has the "nopush" +// attribute or describes an external entity (meaning, the resource +// URI includes a scheme), it will not be pushed. +func (h Handler) servePreloadLinks(pusher http.Pusher, hdr http.Header, resources []string) { + for _, resource := range resources { + for _, resource := range parseLinkHeader(resource) { + if _, ok := resource.params["nopush"]; ok { + continue + } + if isRemoteResource(resource.uri) { + continue + } + err := pusher.Push(resource.uri, &http.PushOptions{ + Header: hdr, + }) + if err != nil { + return + } + } + } +} + +// Resource represents a request for a resource to push. +type Resource struct { + // Method is the request method, which must be GET or HEAD. + // Default is GET. + Method string `json:"method,omitempty"` + + // Target is the path to the resource being pushed. + Target string `json:"target,omitempty"` +} + +// HeaderConfig configures headers for synthetic push requests. +type HeaderConfig struct { + headers.HeaderOps +} + +// linkPusher is a http.ResponseWriter that intercepts +// the WriteHeader() call to ensure that any resources +// described by Link response headers get pushed before +// the response is allowed to be written. +type linkPusher struct { + *caddyhttp.ResponseWriterWrapper + handler Handler + pusher http.Pusher + header http.Header + request *http.Request +} + +func (lp linkPusher) WriteHeader(statusCode int) { + if links, ok := lp.ResponseWriter.Header()["Link"]; ok { + // only initiate these pushes if it hasn't been done yet + if val := caddyhttp.GetVar(lp.request.Context(), pushedLink); val == nil { + if c := lp.handler.logger.Check(zapcore.DebugLevel, "pushing Link resources"); c != nil { + c.Write(zap.Strings("linked", links)) + } + caddyhttp.SetVar(lp.request.Context(), pushedLink, true) + lp.handler.servePreloadLinks(lp.pusher, lp.header, links) + } + } + lp.ResponseWriter.WriteHeader(statusCode) +} + +// isRemoteResource returns true if resource starts with +// a scheme or is a protocol-relative URI. +func isRemoteResource(resource string) bool { + return strings.HasPrefix(resource, "//") || + strings.HasPrefix(resource, "http://") || + strings.HasPrefix(resource, "https://") +} + +// safeHeaders is a list of header fields that are +// safe to copy to push requests implicitly. It is +// assumed that requests for certain kinds of content +// would fail without these fields present. +var safeHeaders = []string{ + "Accept-Encoding", + "Accept-Language", + "Accept", + "Cache-Control", + "User-Agent", +} + +// pushHeader is a header field that gets added to push requests +// in order to avoid recursive/infinite pushes. +const pushHeader = "Caddy-Push" + +// pushedLink is the key for the variable on the request +// context that we use to remember whether we have already +// pushed resources from Link headers yet; otherwise, if +// multiple push handlers are invoked, it would repeat the +// pushing of Link headers. +const pushedLink = "http.handlers.push.pushed_link" + +// Interface guards +var ( + _ caddy.Provisioner = (*Handler)(nil) + _ caddyhttp.MiddlewareHandler = (*Handler)(nil) + _ http.ResponseWriter = (*linkPusher)(nil) + _ http.Pusher = (*linkPusher)(nil) +) diff --git a/modules/caddyhttp/push/link.go b/modules/caddyhttp/push/link.go new file mode 100644 index 00000000000..855dffd0509 --- /dev/null +++ b/modules/caddyhttp/push/link.go @@ -0,0 +1,77 @@ +// 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 push + +import ( + "strings" +) + +// linkResource contains the results of a parsed Link header. +type linkResource struct { + uri string + params map[string]string +} + +// parseLinkHeader is responsible for parsing Link header +// and returning list of found resources. +// +// Accepted formats are: +// +// Link: ; as=script +// Link: ; as=script,; as=style +// Link: ; +// +// where begins with a forward slash (/). +func parseLinkHeader(header string) []linkResource { + resources := []linkResource{} + + if header == "" { + return resources + } + + for _, link := range strings.Split(header, comma) { + l := linkResource{params: make(map[string]string)} + + li, ri := strings.Index(link, "<"), strings.Index(link, ">") + if li == -1 || ri == -1 { + continue + } + + l.uri = strings.TrimSpace(link[li+1 : ri]) + + for _, param := range strings.Split(strings.TrimSpace(link[ri+1:]), semicolon) { + before, after, isCut := strings.Cut(strings.TrimSpace(param), equal) + key := strings.TrimSpace(before) + if key == "" { + continue + } + if isCut { + l.params[key] = strings.TrimSpace(after) + } else { + l.params[key] = key + } + } + + resources = append(resources, l) + } + + return resources +} + +const ( + comma = "," + semicolon = ";" + equal = "=" +) diff --git a/modules/caddyhttp/push/link_test.go b/modules/caddyhttp/push/link_test.go new file mode 100644 index 00000000000..634bcb6dc3e --- /dev/null +++ b/modules/caddyhttp/push/link_test.go @@ -0,0 +1,85 @@ +// 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 push + +import ( + "reflect" + "testing" +) + +func TestParseLinkHeader(t *testing.T) { + testCases := []struct { + header string + expectedResources []linkResource + }{ + { + header: "; as=script", + expectedResources: []linkResource{{uri: "/resource", params: map[string]string{"as": "script"}}}, + }, + { + header: "", + expectedResources: []linkResource{{uri: "/resource", params: map[string]string{}}}, + }, + { + header: "; nopush", + expectedResources: []linkResource{{uri: "/resource", params: map[string]string{"nopush": "nopush"}}}, + }, + { + header: ";nopush;rel=next", + expectedResources: []linkResource{{uri: "/resource", params: map[string]string{"nopush": "nopush", "rel": "next"}}}, + }, + { + header: ";nopush;rel=next,;nopush", + expectedResources: []linkResource{ + {uri: "/resource", params: map[string]string{"nopush": "nopush", "rel": "next"}}, + {uri: "/resource2", params: map[string]string{"nopush": "nopush"}}, + }, + }, + { + header: ",", + expectedResources: []linkResource{ + {uri: "/resource", params: map[string]string{}}, + {uri: "/resource2", params: map[string]string{}}, + }, + }, + { + header: "malformed", + expectedResources: []linkResource{}, + }, + { + header: " ; ", + expectedResources: []linkResource{{uri: "/resource", params: map[string]string{}}}, + }, + } + + for i, test := range testCases { + actualResources := parseLinkHeader(test.header) + if !reflect.DeepEqual(actualResources, test.expectedResources) { + t.Errorf("Test %d (header: %s) - expected resources %v, got %v", + i, test.header, test.expectedResources, actualResources) + } + } +} diff --git a/modules/caddyhttp/replacer.go b/modules/caddyhttp/replacer.go new file mode 100644 index 00000000000..776aa6294b3 --- /dev/null +++ b/modules/caddyhttp/replacer.go @@ -0,0 +1,562 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "bytes" + "context" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "crypto/sha256" + "crypto/tls" + "crypto/x509" + "encoding/asn1" + "encoding/base64" + "encoding/pem" + "fmt" + "io" + "net" + "net/http" + "net/netip" + "net/textproto" + "net/url" + "path" + "strconv" + "strings" + "time" + + "github.com/google/uuid" + "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddytls" +) + +// NewTestReplacer creates a replacer for an http.Request +// for use in tests that are not in this package +func NewTestReplacer(req *http.Request) *caddy.Replacer { + repl := caddy.NewReplacer() + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + *req = *req.WithContext(ctx) + addHTTPVarsToReplacer(repl, req, nil) + return repl +} + +func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.ResponseWriter) { + SetVar(req.Context(), "start_time", time.Now()) + SetVar(req.Context(), "uuid", new(requestID)) + + httpVars := func(key string) (any, bool) { + if req != nil { + // query string parameters + if strings.HasPrefix(key, reqURIQueryReplPrefix) { + vals := req.URL.Query()[key[len(reqURIQueryReplPrefix):]] + // always return true, since the query param might + // be present only in some requests + return strings.Join(vals, ","), true + } + + // request header fields + if strings.HasPrefix(key, reqHeaderReplPrefix) { + field := key[len(reqHeaderReplPrefix):] + vals := req.Header[textproto.CanonicalMIMEHeaderKey(field)] + // always return true, since the header field might + // be present only in some requests + return strings.Join(vals, ","), true + } + + // cookies + if strings.HasPrefix(key, reqCookieReplPrefix) { + name := key[len(reqCookieReplPrefix):] + for _, cookie := range req.Cookies() { + if strings.EqualFold(name, cookie.Name) { + // always return true, since the cookie might + // be present only in some requests + return cookie.Value, true + } + } + } + + // http.request.tls.* + if strings.HasPrefix(key, reqTLSReplPrefix) { + return getReqTLSReplacement(req, key) + } + + switch key { + case "http.request.method": + return req.Method, true + case "http.request.scheme": + if req.TLS != nil { + return "https", true + } + return "http", true + case "http.request.proto": + return req.Proto, true + case "http.request.host": + host, _, err := net.SplitHostPort(req.Host) + if err != nil { + return req.Host, true // OK; there probably was no port + } + return host, true + case "http.request.port": + _, port, _ := net.SplitHostPort(req.Host) + if portNum, err := strconv.Atoi(port); err == nil { + return portNum, true + } + return port, true + case "http.request.hostport": + return req.Host, true + case "http.request.local": + localAddr, _ := req.Context().Value(http.LocalAddrContextKey).(net.Addr) + return localAddr.String(), true + case "http.request.local.host": + localAddr, _ := req.Context().Value(http.LocalAddrContextKey).(net.Addr) + host, _, err := net.SplitHostPort(localAddr.String()) + if err != nil { + // localAddr is host:port for tcp and udp sockets and /unix/socket.path + // for unix sockets. net.SplitHostPort only operates on tcp and udp sockets, + // not unix sockets and will fail with the latter. + // We assume when net.SplitHostPort fails, localAddr is a unix socket and thus + // already "split" and save to return. + return localAddr, true + } + return host, true + case "http.request.local.port": + localAddr, _ := req.Context().Value(http.LocalAddrContextKey).(net.Addr) + _, port, _ := net.SplitHostPort(localAddr.String()) + if portNum, err := strconv.Atoi(port); err == nil { + return portNum, true + } + return port, true + case "http.request.remote": + if req.TLS != nil && !req.TLS.HandshakeComplete { + // without a complete handshake (QUIC "early data") we can't trust the remote IP address to not be spoofed + return nil, true + } + return req.RemoteAddr, true + case "http.request.remote.host": + if req.TLS != nil && !req.TLS.HandshakeComplete { + // without a complete handshake (QUIC "early data") we can't trust the remote IP address to not be spoofed + return nil, true + } + host, _, err := net.SplitHostPort(req.RemoteAddr) + if err != nil { + // req.RemoteAddr is host:port for tcp and udp sockets and /unix/socket.path + // for unix sockets. net.SplitHostPort only operates on tcp and udp sockets, + // not unix sockets and will fail with the latter. + // We assume when net.SplitHostPort fails, req.RemoteAddr is a unix socket + // and thus already "split" and save to return. + return req.RemoteAddr, true + } + return host, true + case "http.request.remote.port": + _, port, _ := net.SplitHostPort(req.RemoteAddr) + if portNum, err := strconv.Atoi(port); err == nil { + return portNum, true + } + return port, true + + // current URI, including any internal rewrites + case "http.request.uri": + return req.URL.RequestURI(), true + case "http.request.uri.path": + return req.URL.Path, true + case "http.request.uri.path.file": + _, file := path.Split(req.URL.Path) + return file, true + case "http.request.uri.path.dir": + dir, _ := path.Split(req.URL.Path) + return dir, true + case "http.request.uri.path.file.base": + return strings.TrimSuffix(path.Base(req.URL.Path), path.Ext(req.URL.Path)), true + case "http.request.uri.path.file.ext": + return path.Ext(req.URL.Path), true + case "http.request.uri.query": + return req.URL.RawQuery, true + case "http.request.uri.prefixed_query": + if req.URL.RawQuery == "" { + return "", true + } + return "?" + req.URL.RawQuery, true + case "http.request.duration": + start := GetVar(req.Context(), "start_time").(time.Time) + return time.Since(start), true + case "http.request.duration_ms": + start := GetVar(req.Context(), "start_time").(time.Time) + return time.Since(start).Seconds() * 1e3, true // multiply seconds to preserve decimal (see #4666) + + case "http.request.uuid": + // fetch the UUID for this request + id := GetVar(req.Context(), "uuid").(*requestID) + + // set it to this request's access log + extra := req.Context().Value(ExtraLogFieldsCtxKey).(*ExtraLogFields) + extra.Set(zap.String("uuid", id.String())) + + return id.String(), true + + case "http.request.body": + if req.Body == nil { + return "", true + } + // normally net/http will close the body for us, but since we + // are replacing it with a fake one, we have to ensure we close + // the real body ourselves when we're done + defer req.Body.Close() + // read the request body into a buffer (can't pool because we + // don't know its lifetime and would have to make a copy anyway) + buf := new(bytes.Buffer) + _, _ = io.Copy(buf, req.Body) // can't handle error, so just ignore it + req.Body = io.NopCloser(buf) // replace real body with buffered data + return buf.String(), true + + // original request, before any internal changes + case "http.request.orig_method": + or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request) + return or.Method, true + case "http.request.orig_uri": + or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request) + return or.RequestURI, true + case "http.request.orig_uri.path": + or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request) + return or.URL.Path, true + case "http.request.orig_uri.path.file": + or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request) + _, file := path.Split(or.URL.Path) + return file, true + case "http.request.orig_uri.path.dir": + or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request) + dir, _ := path.Split(or.URL.Path) + return dir, true + case "http.request.orig_uri.query": + or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request) + return or.URL.RawQuery, true + case "http.request.orig_uri.prefixed_query": + or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request) + if or.URL.RawQuery == "" { + return "", true + } + return "?" + or.URL.RawQuery, true + } + + // remote IP range/prefix (e.g. keep top 24 bits of 1.2.3.4 => "1.2.3.0/24") + // syntax: "/V4,V6" where V4 = IPv4 bits, and V6 = IPv6 bits; if no comma, then same bit length used for both + // (EXPERIMENTAL) + if strings.HasPrefix(key, "http.request.remote.host/") { + host, _, err := net.SplitHostPort(req.RemoteAddr) + if err != nil { + host = req.RemoteAddr // assume no port, I guess? + } + addr, err := netip.ParseAddr(host) + if err != nil { + return host, true // not an IP address + } + // extract the bits from the end of the placeholder (start after "/") then split on "," + bitsBoth := key[strings.Index(key, "/")+1:] + ipv4BitsStr, ipv6BitsStr, cutOK := strings.Cut(bitsBoth, ",") + bitsStr := ipv4BitsStr + if addr.Is6() && cutOK { + bitsStr = ipv6BitsStr + } + // convert to integer then compute prefix + bits, err := strconv.Atoi(bitsStr) + if err != nil { + return "", true + } + prefix, err := addr.Prefix(bits) + if err != nil { + return "", true + } + return prefix.String(), true + } + + // hostname labels + if strings.HasPrefix(key, reqHostLabelsReplPrefix) { + idxStr := key[len(reqHostLabelsReplPrefix):] + idx, err := strconv.Atoi(idxStr) + if err != nil || idx < 0 { + return "", false + } + reqHost, _, err := net.SplitHostPort(req.Host) + if err != nil { + reqHost = req.Host // OK; assume there was no port + } + hostLabels := strings.Split(reqHost, ".") + if idx >= len(hostLabels) { + return "", true + } + return hostLabels[len(hostLabels)-idx-1], true + } + + // path parts + if strings.HasPrefix(key, reqURIPathReplPrefix) { + idxStr := key[len(reqURIPathReplPrefix):] + idx, err := strconv.Atoi(idxStr) + if err != nil { + return "", false + } + pathParts := strings.Split(req.URL.Path, "/") + if len(pathParts) > 0 && pathParts[0] == "" { + pathParts = pathParts[1:] + } + if idx < 0 { + return "", false + } + if idx >= len(pathParts) { + return "", true + } + return pathParts[idx], true + } + + // orig uri path parts + if strings.HasPrefix(key, reqOrigURIPathReplPrefix) { + idxStr := key[len(reqOrigURIPathReplPrefix):] + idx, err := strconv.Atoi(idxStr) + if err != nil { + return "", false + } + or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request) + pathParts := strings.Split(or.URL.Path, "/") + if len(pathParts) > 0 && pathParts[0] == "" { + pathParts = pathParts[1:] + } + if idx < 0 { + return "", false + } + if idx >= len(pathParts) { + return "", true + } + return pathParts[idx], true + } + + // middleware variables + if strings.HasPrefix(key, varsReplPrefix) { + varName := key[len(varsReplPrefix):] + raw := GetVar(req.Context(), varName) + // variables can be dynamic, so always return true + // even when it may not be set; treat as empty then + return raw, true + } + } + + if w != nil { + // response header fields + if strings.HasPrefix(key, respHeaderReplPrefix) { + field := key[len(respHeaderReplPrefix):] + vals := w.Header()[textproto.CanonicalMIMEHeaderKey(field)] + // always return true, since the header field might + // be present only in some responses + return strings.Join(vals, ","), true + } + } + + switch { + case key == "http.shutting_down": + server := req.Context().Value(ServerCtxKey).(*Server) + server.shutdownAtMu.RLock() + defer server.shutdownAtMu.RUnlock() + return !server.shutdownAt.IsZero(), true + case key == "http.time_until_shutdown": + server := req.Context().Value(ServerCtxKey).(*Server) + server.shutdownAtMu.RLock() + defer server.shutdownAtMu.RUnlock() + if server.shutdownAt.IsZero() { + return nil, true + } + return time.Until(server.shutdownAt), true + } + + return nil, false + } + + repl.Map(httpVars) +} + +func getReqTLSReplacement(req *http.Request, key string) (any, bool) { + if req == nil || req.TLS == nil { + return nil, false + } + + if len(key) < len(reqTLSReplPrefix) { + return nil, false + } + + field := strings.ToLower(key[len(reqTLSReplPrefix):]) + + if strings.HasPrefix(field, "client.") { + cert := getTLSPeerCert(req.TLS) + if cert == nil { + return nil, false + } + + // subject alternate names (SANs) + if strings.HasPrefix(field, "client.san.") { + field = field[len("client.san."):] + var fieldName string + var fieldValue any + switch { + case strings.HasPrefix(field, "dns_names"): + fieldName = "dns_names" + fieldValue = cert.DNSNames + case strings.HasPrefix(field, "emails"): + fieldName = "emails" + fieldValue = cert.EmailAddresses + case strings.HasPrefix(field, "ips"): + fieldName = "ips" + fieldValue = cert.IPAddresses + case strings.HasPrefix(field, "uris"): + fieldName = "uris" + fieldValue = cert.URIs + default: + return nil, false + } + field = field[len(fieldName):] + + // if no index was specified, return the whole list + if field == "" { + return fieldValue, true + } + if len(field) < 2 || field[0] != '.' { + return nil, false + } + field = field[1:] // trim '.' between field name and index + + // get the numeric index + idx, err := strconv.Atoi(field) + if err != nil || idx < 0 { + return nil, false + } + + // access the indexed element and return it + switch v := fieldValue.(type) { + case []string: + if idx >= len(v) { + return nil, true + } + return v[idx], true + case []net.IP: + if idx >= len(v) { + return nil, true + } + return v[idx], true + case []*url.URL: + if idx >= len(v) { + return nil, true + } + return v[idx], true + } + } + + switch field { + case "client.fingerprint": + return fmt.Sprintf("%x", sha256.Sum256(cert.Raw)), true + case "client.public_key", "client.public_key_sha256": + if cert.PublicKey == nil { + return nil, true + } + pubKeyBytes, err := marshalPublicKey(cert.PublicKey) + if err != nil { + return nil, true + } + if strings.HasSuffix(field, "_sha256") { + return fmt.Sprintf("%x", sha256.Sum256(pubKeyBytes)), true + } + return fmt.Sprintf("%x", pubKeyBytes), true + case "client.issuer": + return cert.Issuer, true + case "client.serial": + return cert.SerialNumber, true + case "client.subject": + return cert.Subject, true + case "client.certificate_pem": + block := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw} + return pem.EncodeToMemory(&block), true + case "client.certificate_der_base64": + return base64.StdEncoding.EncodeToString(cert.Raw), true + default: + return nil, false + } + } + + switch field { + case "version": + return caddytls.ProtocolName(req.TLS.Version), true + case "cipher_suite": + return tls.CipherSuiteName(req.TLS.CipherSuite), true + case "resumed": + return req.TLS.DidResume, true + case "proto": + return req.TLS.NegotiatedProtocol, true + case "proto_mutual": + // req.TLS.NegotiatedProtocolIsMutual is deprecated - it's always true. + return true, true + case "server_name": + return req.TLS.ServerName, true + } + return nil, false +} + +// marshalPublicKey returns the byte encoding of pubKey. +func marshalPublicKey(pubKey any) ([]byte, error) { + switch key := pubKey.(type) { + case *rsa.PublicKey: + return asn1.Marshal(key) + case *ecdsa.PublicKey: + e, err := key.ECDH() + if err != nil { + return nil, err + } + return e.Bytes(), nil + case ed25519.PublicKey: + return key, nil + } + return nil, fmt.Errorf("unrecognized public key type: %T", pubKey) +} + +// getTLSPeerCert retrieves the first peer certificate from a TLS session. +// Returns nil if no peer cert is in use. +func getTLSPeerCert(cs *tls.ConnectionState) *x509.Certificate { + if len(cs.PeerCertificates) == 0 { + return nil + } + return cs.PeerCertificates[0] +} + +type requestID struct { + value string +} + +// Lazy generates UUID string or return cached value if present +func (rid *requestID) String() string { + if rid.value == "" { + if id, err := uuid.NewRandom(); err == nil { + rid.value = id.String() + } + } + return rid.value +} + +const ( + reqCookieReplPrefix = "http.request.cookie." + reqHeaderReplPrefix = "http.request.header." + reqHostLabelsReplPrefix = "http.request.host.labels." + reqTLSReplPrefix = "http.request.tls." + reqURIPathReplPrefix = "http.request.uri.path." + reqURIQueryReplPrefix = "http.request.uri.query." + respHeaderReplPrefix = "http.response.header." + varsReplPrefix = "http.vars." + reqOrigURIPathReplPrefix = "http.request.orig_uri.path." +) diff --git a/modules/caddyhttp/replacer_test.go b/modules/caddyhttp/replacer_test.go new file mode 100644 index 00000000000..50a2e8c62cb --- /dev/null +++ b/modules/caddyhttp/replacer_test.go @@ -0,0 +1,232 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "context" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "net" + "net/http" + "net/http/httptest" + "testing" + + "github.com/caddyserver/caddy/v2" +) + +func TestHTTPVarReplacement(t *testing.T) { + req, _ := http.NewRequest(http.MethodGet, "/foo/bar.tar.gz", nil) + repl := caddy.NewReplacer() + localAddr, _ := net.ResolveTCPAddr("tcp", "192.168.159.1:80") + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + ctx = context.WithValue(ctx, http.LocalAddrContextKey, localAddr) + req = req.WithContext(ctx) + req.Host = "example.com:80" + req.RemoteAddr = "192.168.159.32:1234" + + clientCert := []byte(`-----BEGIN CERTIFICATE----- +MIIB9jCCAV+gAwIBAgIBAjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1DYWRk +eSBUZXN0IENBMB4XDTE4MDcyNDIxMzUwNVoXDTI4MDcyMTIxMzUwNVowHTEbMBkG +A1UEAwwSY2xpZW50LmxvY2FsZG9tYWluMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQDFDEpzF0ew68teT3xDzcUxVFaTII+jXH1ftHXxxP4BEYBU4q90qzeKFneF +z83I0nC0WAQ45ZwHfhLMYHFzHPdxr6+jkvKPASf0J2v2HDJuTM1bHBbik5Ls5eq+ +fVZDP8o/VHKSBKxNs8Goc2NTsr5b07QTIpkRStQK+RJALk4x9QIDAQABo0swSTAJ +BgNVHRMEAjAAMAsGA1UdDwQEAwIHgDAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8A +AAEwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADgYEANSjz2Sk+ +eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV +3Q9fgDkiUod+uIK0IynzIKvw+Cjg+3nx6NQ0IM0zo8c7v398RzB4apbXKZyeeqUH +9fNwfEi+OoXR6s+upSKobCmLGLGi9Na5s5g= +-----END CERTIFICATE-----`) + + block, _ := pem.Decode(clientCert) + if block == nil { + t.Fatalf("failed to decode PEM certificate") + } + + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + t.Fatalf("failed to decode PEM certificate: %v", err) + } + + req.TLS = &tls.ConnectionState{ + Version: tls.VersionTLS13, + HandshakeComplete: true, + ServerName: "example.com", + CipherSuite: tls.TLS_AES_256_GCM_SHA384, + PeerCertificates: []*x509.Certificate{cert}, + NegotiatedProtocol: "h2", + NegotiatedProtocolIsMutual: true, + } + + res := httptest.NewRecorder() + addHTTPVarsToReplacer(repl, req, res) + + for i, tc := range []struct { + get string + expect string + }{ + { + get: "http.request.scheme", + expect: "https", + }, + { + get: "http.request.method", + expect: http.MethodGet, + }, + { + get: "http.request.host", + expect: "example.com", + }, + { + get: "http.request.port", + expect: "80", + }, + { + get: "http.request.hostport", + expect: "example.com:80", + }, + { + get: "http.request.local.host", + expect: "192.168.159.1", + }, + { + get: "http.request.local.port", + expect: "80", + }, + { + get: "http.request.local", + expect: "192.168.159.1:80", + }, + { + get: "http.request.remote.host", + expect: "192.168.159.32", + }, + { + get: "http.request.remote.host/24", + expect: "192.168.159.0/24", + }, + { + get: "http.request.remote.host/24,32", + expect: "192.168.159.0/24", + }, + { + get: "http.request.remote.host/999", + expect: "", + }, + { + get: "http.request.remote.port", + expect: "1234", + }, + { + get: "http.request.host.labels.0", + expect: "com", + }, + { + get: "http.request.host.labels.1", + expect: "example", + }, + { + get: "http.request.host.labels.2", + expect: "", + }, + { + get: "http.request.uri.path.file", + expect: "bar.tar.gz", + }, + { + get: "http.request.uri.path.file.base", + expect: "bar.tar", + }, + { + // not ideal, but also most correct, given that files can have dots (example: index..html) TODO: maybe this isn't right.. + get: "http.request.uri.path.file.ext", + expect: ".gz", + }, + { + get: "http.request.tls.cipher_suite", + expect: "TLS_AES_256_GCM_SHA384", + }, + { + get: "http.request.tls.proto", + expect: "h2", + }, + { + get: "http.request.tls.proto_mutual", + expect: "true", + }, + { + get: "http.request.tls.resumed", + expect: "false", + }, + { + get: "http.request.tls.server_name", + expect: "example.com", + }, + { + get: "http.request.tls.version", + expect: "tls1.3", + }, + { + get: "http.request.tls.client.fingerprint", + expect: "9f57b7b497cceacc5459b76ac1c3afedbc12b300e728071f55f84168ff0f7702", + }, + { + get: "http.request.tls.client.issuer", + expect: "CN=Caddy Test CA", + }, + { + get: "http.request.tls.client.serial", + expect: "2", + }, + { + get: "http.request.tls.client.subject", + expect: "CN=client.localdomain", + }, + { + get: "http.request.tls.client.san.dns_names", + expect: "[localhost]", + }, + { + get: "http.request.tls.client.san.dns_names.0", + expect: "localhost", + }, + { + get: "http.request.tls.client.san.dns_names.1", + expect: "", + }, + { + get: "http.request.tls.client.san.ips", + expect: "[127.0.0.1]", + }, + { + get: "http.request.tls.client.san.ips.0", + expect: "127.0.0.1", + }, + { + get: "http.request.tls.client.certificate_pem", + expect: string(clientCert) + "\n", // returned value comes with a newline appended to it + }, + } { + actual, got := repl.GetString(tc.get) + if !got { + t.Errorf("Test %d: Expected to recognize the placeholder name, but didn't", i) + } + if actual != tc.expect { + t.Errorf("Test %d: Expected %s to be '%s' but got '%s'", + i, tc.get, tc.expect, actual) + } + } +} diff --git a/modules/caddyhttp/requestbody/caddyfile.go b/modules/caddyhttp/requestbody/caddyfile.go new file mode 100644 index 00000000000..8378ad7f471 --- /dev/null +++ b/modules/caddyhttp/requestbody/caddyfile.go @@ -0,0 +1,77 @@ +// 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 requestbody + +import ( + "time" + + "github.com/dustin/go-humanize" + + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func init() { + httpcaddyfile.RegisterHandlerDirective("request_body", parseCaddyfile) +} + +func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + h.Next() // consume directive name + + rb := new(RequestBody) + + // configuration should be in a block + for h.NextBlock(0) { + switch h.Val() { + case "max_size": + var sizeStr string + if !h.AllArgs(&sizeStr) { + return nil, h.ArgErr() + } + size, err := humanize.ParseBytes(sizeStr) + if err != nil { + return nil, h.Errf("parsing max_size: %v", err) + } + rb.MaxSize = int64(size) + + case "read_timeout": + var timeoutStr string + if !h.AllArgs(&timeoutStr) { + return nil, h.ArgErr() + } + timeout, err := time.ParseDuration(timeoutStr) + if err != nil { + return nil, h.Errf("parsing read_timeout: %v", err) + } + rb.ReadTimeout = timeout + + case "write_timeout": + var timeoutStr string + if !h.AllArgs(&timeoutStr) { + return nil, h.ArgErr() + } + timeout, err := time.ParseDuration(timeoutStr) + if err != nil { + return nil, h.Errf("parsing write_timeout: %v", err) + } + rb.WriteTimeout = timeout + + default: + return nil, h.Errf("unrecognized request_body subdirective '%s'", h.Val()) + } + } + + return rb, nil +} diff --git a/modules/caddyhttp/requestbody/requestbody.go b/modules/caddyhttp/requestbody/requestbody.go new file mode 100644 index 00000000000..830050416e9 --- /dev/null +++ b/modules/caddyhttp/requestbody/requestbody.go @@ -0,0 +1,106 @@ +// 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 requestbody + +import ( + "errors" + "io" + "net/http" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func init() { + caddy.RegisterModule(RequestBody{}) +} + +// RequestBody is a middleware for manipulating the request body. +type RequestBody struct { + // The maximum number of bytes to allow reading from the body by a later handler. + // If more bytes are read, an error with HTTP status 413 is returned. + MaxSize int64 `json:"max_size,omitempty"` + + // EXPERIMENTAL. Subject to change/removal. + ReadTimeout time.Duration `json:"read_timeout,omitempty"` + + // EXPERIMENTAL. Subject to change/removal. + WriteTimeout time.Duration `json:"write_timeout,omitempty"` + + logger *zap.Logger +} + +// CaddyModule returns the Caddy module information. +func (RequestBody) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.request_body", + New: func() caddy.Module { return new(RequestBody) }, + } +} + +func (rb *RequestBody) Provision(ctx caddy.Context) error { + rb.logger = ctx.Logger() + return nil +} + +func (rb RequestBody) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + if r.Body == nil { + return next.ServeHTTP(w, r) + } + if rb.MaxSize > 0 { + r.Body = errorWrapper{http.MaxBytesReader(w, r.Body, rb.MaxSize)} + } + if rb.ReadTimeout > 0 || rb.WriteTimeout > 0 { + //nolint:bodyclose + rc := http.NewResponseController(w) + if rb.ReadTimeout > 0 { + if err := rc.SetReadDeadline(time.Now().Add(rb.ReadTimeout)); err != nil { + if c := rb.logger.Check(zapcore.ErrorLevel, "could not set read deadline"); c != nil { + c.Write(zap.Error(err)) + } + } + } + if rb.WriteTimeout > 0 { + if err := rc.SetWriteDeadline(time.Now().Add(rb.WriteTimeout)); err != nil { + if c := rb.logger.Check(zapcore.ErrorLevel, "could not set write deadline"); c != nil { + c.Write(zap.Error(err)) + } + } + } + } + return next.ServeHTTP(w, r) +} + +// errorWrapper wraps errors that are returned from Read() +// so that they can be associated with a proper status code. +type errorWrapper struct { + io.ReadCloser +} + +func (ew errorWrapper) Read(p []byte) (n int, err error) { + n, err = ew.ReadCloser.Read(p) + var mbe *http.MaxBytesError + if errors.As(err, &mbe) { + err = caddyhttp.Error(http.StatusRequestEntityTooLarge, err) + } + return +} + +// Interface guard +var _ caddyhttp.MiddlewareHandler = (*RequestBody)(nil) diff --git a/modules/caddyhttp/responsematchers.go b/modules/caddyhttp/responsematchers.go new file mode 100644 index 00000000000..a6b34c76dbf --- /dev/null +++ b/modules/caddyhttp/responsematchers.go @@ -0,0 +1,119 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "net/http" + "strconv" + "strings" + + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +// ResponseMatcher is a type which can determine if an +// HTTP response matches some criteria. +type ResponseMatcher struct { + // If set, one of these status codes would be required. + // A one-digit status can be used to represent all codes + // in that class (e.g. 3 for all 3xx codes). + StatusCode []int `json:"status_code,omitempty"` + + // If set, each header specified must be one of the + // specified values, with the same logic used by the + // [request header matcher](/docs/json/apps/http/servers/routes/match/header/). + Headers http.Header `json:"headers,omitempty"` +} + +// Match returns true if the given statusCode and hdr match rm. +func (rm ResponseMatcher) Match(statusCode int, hdr http.Header) bool { + if !rm.matchStatusCode(statusCode) { + return false + } + return matchHeaders(hdr, rm.Headers, "", []string{}, nil) +} + +func (rm ResponseMatcher) matchStatusCode(statusCode int) bool { + if rm.StatusCode == nil { + return true + } + for _, code := range rm.StatusCode { + if StatusCodeMatches(statusCode, code) { + return true + } + } + return false +} + +// ParseNamedResponseMatcher parses the tokens of a named response matcher. +// +// @name { +// header [] +// status +// } +// +// Or, single line syntax: +// +// @name [header []] | [status ] +func ParseNamedResponseMatcher(d *caddyfile.Dispenser, matchers map[string]ResponseMatcher) error { + d.Next() // consume matcher name + definitionName := d.Val() + + if _, ok := matchers[definitionName]; ok { + return d.Errf("matcher is defined more than once: %s", definitionName) + } + + matcher := ResponseMatcher{} + for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); { + switch d.Val() { + case "header": + if matcher.Headers == nil { + matcher.Headers = http.Header{} + } + + // reuse the header request matcher's unmarshaler + headerMatcher := MatchHeader(matcher.Headers) + err := headerMatcher.UnmarshalCaddyfile(d.NewFromNextSegment()) + if err != nil { + return err + } + + matcher.Headers = http.Header(headerMatcher) + case "status": + if matcher.StatusCode == nil { + matcher.StatusCode = []int{} + } + + args := d.RemainingArgs() + if len(args) == 0 { + return d.ArgErr() + } + + for _, arg := range args { + if len(arg) == 3 && strings.HasSuffix(arg, "xx") { + arg = arg[:1] + } + statusNum, err := strconv.Atoi(arg) + if err != nil { + return d.Errf("bad status value '%s': %v", arg, err) + } + matcher.StatusCode = append(matcher.StatusCode, statusNum) + } + default: + return d.Errf("unrecognized response matcher %s", d.Val()) + } + } + matchers[definitionName] = matcher + return nil +} diff --git a/modules/caddyhttp/responsematchers_test.go b/modules/caddyhttp/responsematchers_test.go new file mode 100644 index 00000000000..f5bb6f18fbc --- /dev/null +++ b/modules/caddyhttp/responsematchers_test.go @@ -0,0 +1,169 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "net/http" + "testing" +) + +func TestResponseMatcher(t *testing.T) { + for i, tc := range []struct { + require ResponseMatcher + status int + hdr http.Header // make sure these are canonical cased (std lib will do that in a real request) + expect bool + }{ + { + require: ResponseMatcher{}, + status: 200, + expect: true, + }, + { + require: ResponseMatcher{ + StatusCode: []int{200}, + }, + status: 200, + expect: true, + }, + { + require: ResponseMatcher{ + StatusCode: []int{2}, + }, + status: 200, + expect: true, + }, + { + require: ResponseMatcher{ + StatusCode: []int{201}, + }, + status: 200, + expect: false, + }, + { + require: ResponseMatcher{ + StatusCode: []int{2}, + }, + status: 301, + expect: false, + }, + { + require: ResponseMatcher{ + StatusCode: []int{3}, + }, + status: 301, + expect: true, + }, + { + require: ResponseMatcher{ + StatusCode: []int{3}, + }, + status: 399, + expect: true, + }, + { + require: ResponseMatcher{ + StatusCode: []int{3}, + }, + status: 400, + expect: false, + }, + { + require: ResponseMatcher{ + StatusCode: []int{3, 4}, + }, + status: 400, + expect: true, + }, + { + require: ResponseMatcher{ + StatusCode: []int{3, 401}, + }, + status: 401, + expect: true, + }, + { + require: ResponseMatcher{ + Headers: http.Header{ + "Foo": []string{"bar"}, + }, + }, + hdr: http.Header{"Foo": []string{"bar"}}, + expect: true, + }, + { + require: ResponseMatcher{ + Headers: http.Header{ + "Foo2": []string{"bar"}, + }, + }, + hdr: http.Header{"Foo": []string{"bar"}}, + expect: false, + }, + { + require: ResponseMatcher{ + Headers: http.Header{ + "Foo": []string{"bar", "baz"}, + }, + }, + hdr: http.Header{"Foo": []string{"baz"}}, + expect: true, + }, + { + require: ResponseMatcher{ + Headers: http.Header{ + "Foo": []string{"bar"}, + "Foo2": []string{"baz"}, + }, + }, + hdr: http.Header{"Foo": []string{"baz"}}, + expect: false, + }, + { + require: ResponseMatcher{ + Headers: http.Header{ + "Foo": []string{"bar"}, + "Foo2": []string{"baz"}, + }, + }, + hdr: http.Header{"Foo": []string{"bar"}, "Foo2": []string{"baz"}}, + expect: true, + }, + { + require: ResponseMatcher{ + Headers: http.Header{ + "Foo": []string{"foo*"}, + }, + }, + hdr: http.Header{"Foo": []string{"foobar"}}, + expect: true, + }, + { + require: ResponseMatcher{ + Headers: http.Header{ + "Foo": []string{"foo*"}, + }, + }, + hdr: http.Header{"Foo": []string{"foobar"}}, + expect: true, + }, + } { + actual := tc.require.Match(tc.status, tc.hdr) + if actual != tc.expect { + t.Errorf("Test %d %v: Expected %t, got %t for HTTP %d %v", i, tc.require, tc.expect, actual, tc.status, tc.hdr) + continue + } + } +} diff --git a/modules/caddyhttp/responsewriter.go b/modules/caddyhttp/responsewriter.go new file mode 100644 index 00000000000..904c30c0352 --- /dev/null +++ b/modules/caddyhttp/responsewriter.go @@ -0,0 +1,344 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "bufio" + "bytes" + "fmt" + "io" + "net" + "net/http" +) + +// ResponseWriterWrapper wraps an underlying ResponseWriter and +// promotes its Pusher method as well. To use this type, embed +// a pointer to it within your own struct type that implements +// the http.ResponseWriter interface, then call methods on the +// embedded value. +type ResponseWriterWrapper struct { + http.ResponseWriter +} + +// Push implements http.Pusher. It simply calls the underlying +// ResponseWriter's Push method if there is one, or returns +// ErrNotImplemented otherwise. +func (rww *ResponseWriterWrapper) Push(target string, opts *http.PushOptions) error { + if pusher, ok := rww.ResponseWriter.(http.Pusher); ok { + return pusher.Push(target, opts) + } + return ErrNotImplemented +} + +// ReadFrom implements io.ReaderFrom. It retries to use io.ReaderFrom if available, +// then fallback to io.Copy. +// see: https://github.com/caddyserver/caddy/issues/6546 +func (rww *ResponseWriterWrapper) ReadFrom(r io.Reader) (n int64, err error) { + if rf, ok := rww.ResponseWriter.(io.ReaderFrom); ok { + return rf.ReadFrom(r) + } + return io.Copy(rww.ResponseWriter, r) +} + +// Unwrap returns the underlying ResponseWriter, necessary for +// http.ResponseController to work correctly. +func (rww *ResponseWriterWrapper) Unwrap() http.ResponseWriter { + return rww.ResponseWriter +} + +// ErrNotImplemented is returned when an underlying +// ResponseWriter does not implement the required method. +var ErrNotImplemented = fmt.Errorf("method not implemented") + +type responseRecorder struct { + *ResponseWriterWrapper + statusCode int + buf *bytes.Buffer + shouldBuffer ShouldBufferFunc + size int + wroteHeader bool + stream bool + + readSize *int +} + +// NewResponseRecorder returns a new ResponseRecorder that can be +// used instead of a standard http.ResponseWriter. The recorder is +// useful for middlewares which need to buffer a response and +// potentially process its entire body before actually writing the +// response to the underlying writer. Of course, buffering the entire +// body has a memory overhead, but sometimes there is no way to avoid +// buffering the whole response, hence the existence of this type. +// Still, if at all practical, handlers should strive to stream +// responses by wrapping Write and WriteHeader methods instead of +// buffering whole response bodies. +// +// Buffering is actually optional. The shouldBuffer function will +// be called just before the headers are written. If it returns +// true, the headers and body will be buffered by this recorder +// and not written to the underlying writer; if false, the headers +// will be written immediately and the body will be streamed out +// directly to the underlying writer. If shouldBuffer is nil, +// the response will never be buffered and will always be streamed +// directly to the writer. +// +// You can know if shouldBuffer returned true by calling Buffered(). +// +// The provided buffer buf should be obtained from a pool for best +// performance (see the sync.Pool type). +// +// Proper usage of a recorder looks like this: +// +// rec := caddyhttp.NewResponseRecorder(w, buf, shouldBuffer) +// err := next.ServeHTTP(rec, req) +// if err != nil { +// return err +// } +// if !rec.Buffered() { +// return nil +// } +// // process the buffered response here +// +// The header map is not buffered; i.e. the ResponseRecorder's Header() +// method returns the same header map of the underlying ResponseWriter. +// This is a crucial design decision to allow HTTP trailers to be +// flushed properly (https://github.com/caddyserver/caddy/issues/3236). +// +// Once you are ready to write the response, there are two ways you can +// do it. The easier way is to have the recorder do it: +// +// rec.WriteResponse() +// +// This writes the recorded response headers as well as the buffered body. +// Or, you may wish to do it yourself, especially if you manipulated the +// buffered body. First you will need to write the headers with the +// recorded status code, then write the body (this example writes the +// recorder's body buffer, but you might have your own body to write +// instead): +// +// w.WriteHeader(rec.Status()) +// io.Copy(w, rec.Buffer()) +// +// As a special case, 1xx responses are not buffered nor recorded +// because they are not the final response; they are passed through +// directly to the underlying ResponseWriter. +func NewResponseRecorder(w http.ResponseWriter, buf *bytes.Buffer, shouldBuffer ShouldBufferFunc) ResponseRecorder { + return &responseRecorder{ + ResponseWriterWrapper: &ResponseWriterWrapper{ResponseWriter: w}, + buf: buf, + shouldBuffer: shouldBuffer, + } +} + +// WriteHeader writes the headers with statusCode to the wrapped +// ResponseWriter unless the response is to be buffered instead. +// 1xx responses are never buffered. +func (rr *responseRecorder) WriteHeader(statusCode int) { + if rr.wroteHeader { + return + } + + // save statusCode always, in case HTTP middleware upgrades websocket + // connections by manually setting headers and writing status 101 + rr.statusCode = statusCode + + // decide whether we should buffer the response + if rr.shouldBuffer == nil { + rr.stream = true + } else { + rr.stream = !rr.shouldBuffer(rr.statusCode, rr.ResponseWriterWrapper.Header()) + } + + // 1xx responses aren't final; just informational + if statusCode < 100 || statusCode > 199 { + rr.wroteHeader = true + } + + // if informational or not buffered, immediately write header + if rr.stream || (100 <= statusCode && statusCode <= 199) { + rr.ResponseWriterWrapper.WriteHeader(statusCode) + } +} + +func (rr *responseRecorder) Write(data []byte) (int, error) { + rr.WriteHeader(http.StatusOK) + var n int + var err error + if rr.stream { + n, err = rr.ResponseWriterWrapper.Write(data) + } else { + n, err = rr.buf.Write(data) + } + + rr.size += n + return n, err +} + +func (rr *responseRecorder) ReadFrom(r io.Reader) (int64, error) { + rr.WriteHeader(http.StatusOK) + var n int64 + var err error + if rr.stream { + n, err = rr.ResponseWriterWrapper.ReadFrom(r) + } else { + n, err = rr.buf.ReadFrom(r) + } + + rr.size += int(n) + return n, err +} + +// Status returns the status code that was written, if any. +func (rr *responseRecorder) Status() int { + return rr.statusCode +} + +// Size returns the number of bytes written, +// not including the response headers. +func (rr *responseRecorder) Size() int { + return rr.size +} + +// Buffer returns the body buffer that rr was created with. +// You should still have your original pointer, though. +func (rr *responseRecorder) Buffer() *bytes.Buffer { + return rr.buf +} + +// Buffered returns whether rr has decided to buffer the response. +func (rr *responseRecorder) Buffered() bool { + return !rr.stream +} + +func (rr *responseRecorder) WriteResponse() error { + if rr.statusCode == 0 { + // could happen if no handlers actually wrote anything, + // and this prevents a panic; status must be > 0 + rr.WriteHeader(http.StatusOK) + } + if rr.stream { + return nil + } + rr.ResponseWriterWrapper.WriteHeader(rr.statusCode) + _, err := io.Copy(rr.ResponseWriterWrapper, rr.buf) + return err +} + +// FlushError will suppress actual flushing if the response is buffered. See: +// https://github.com/caddyserver/caddy/issues/6144 +func (rr *responseRecorder) FlushError() error { + if rr.stream { + //nolint:bodyclose + return http.NewResponseController(rr.ResponseWriterWrapper).Flush() + } + return nil +} + +// Private interface so it can only be used in this package +// #TODO: maybe export it later +func (rr *responseRecorder) setReadSize(size *int) { + rr.readSize = size +} + +func (rr *responseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) { + //nolint:bodyclose + conn, brw, err := http.NewResponseController(rr.ResponseWriterWrapper).Hijack() + if err != nil { + return nil, nil, err + } + // Per http documentation, returned bufio.Writer is empty, but bufio.Read maybe not + conn = &hijackedConn{conn, rr} + brw.Writer.Reset(conn) + + buffered := brw.Reader.Buffered() + if buffered != 0 { + conn.(*hijackedConn).updateReadSize(buffered) + data, _ := brw.Peek(buffered) + brw.Reader.Reset(io.MultiReader(bytes.NewReader(data), conn)) + // peek to make buffered data appear, as Reset will make it 0 + _, _ = brw.Peek(buffered) + } else { + brw.Reader.Reset(conn) + } + return conn, brw, nil +} + +// used to track the size of hijacked response writers +type hijackedConn struct { + net.Conn + rr *responseRecorder +} + +func (hc *hijackedConn) updateReadSize(n int) { + if hc.rr.readSize != nil { + *hc.rr.readSize += n + } +} + +func (hc *hijackedConn) Read(p []byte) (int, error) { + n, err := hc.Conn.Read(p) + hc.updateReadSize(n) + return n, err +} + +func (hc *hijackedConn) WriteTo(w io.Writer) (int64, error) { + n, err := io.Copy(w, hc.Conn) + hc.updateReadSize(int(n)) + return n, err +} + +func (hc *hijackedConn) Write(p []byte) (int, error) { + n, err := hc.Conn.Write(p) + hc.rr.size += n + return n, err +} + +func (hc *hijackedConn) ReadFrom(r io.Reader) (int64, error) { + n, err := io.Copy(hc.Conn, r) + hc.rr.size += int(n) + return n, err +} + +// ResponseRecorder is a http.ResponseWriter that records +// responses instead of writing them to the client. See +// docs for NewResponseRecorder for proper usage. +type ResponseRecorder interface { + http.ResponseWriter + Status() int + Buffer() *bytes.Buffer + Buffered() bool + Size() int + WriteResponse() error +} + +// ShouldBufferFunc is a function that returns true if the +// response should be buffered, given the pending HTTP status +// code and response headers. +type ShouldBufferFunc func(status int, header http.Header) bool + +// Interface guards +var ( + _ http.ResponseWriter = (*ResponseWriterWrapper)(nil) + _ ResponseRecorder = (*responseRecorder)(nil) + + // Implementing ReaderFrom can be such a significant + // optimization that it should probably be required! + // see PR #5022 (25%-50% speedup) + _ io.ReaderFrom = (*ResponseWriterWrapper)(nil) + _ io.ReaderFrom = (*responseRecorder)(nil) + _ io.ReaderFrom = (*hijackedConn)(nil) + + _ io.WriterTo = (*hijackedConn)(nil) +) diff --git a/modules/caddyhttp/responsewriter_test.go b/modules/caddyhttp/responsewriter_test.go new file mode 100644 index 00000000000..c08ad26a472 --- /dev/null +++ b/modules/caddyhttp/responsewriter_test.go @@ -0,0 +1,171 @@ +package caddyhttp + +import ( + "bytes" + "io" + "net/http" + "strings" + "testing" +) + +type responseWriterSpy interface { + http.ResponseWriter + Written() string + CalledReadFrom() bool +} + +var ( + _ responseWriterSpy = (*baseRespWriter)(nil) + _ responseWriterSpy = (*readFromRespWriter)(nil) +) + +// a barebones http.ResponseWriter mock +type baseRespWriter []byte + +func (brw *baseRespWriter) Write(d []byte) (int, error) { + *brw = append(*brw, d...) + return len(d), nil +} +func (brw *baseRespWriter) Header() http.Header { return nil } +func (brw *baseRespWriter) WriteHeader(statusCode int) {} +func (brw *baseRespWriter) Written() string { return string(*brw) } +func (brw *baseRespWriter) CalledReadFrom() bool { return false } + +// an http.ResponseWriter mock that supports ReadFrom +type readFromRespWriter struct { + baseRespWriter + called bool +} + +func (rf *readFromRespWriter) ReadFrom(r io.Reader) (int64, error) { + rf.called = true + return io.Copy(&rf.baseRespWriter, r) +} + +func (rf *readFromRespWriter) CalledReadFrom() bool { return rf.called } + +func TestResponseWriterWrapperReadFrom(t *testing.T) { + tests := map[string]struct { + responseWriter responseWriterSpy + wantReadFrom bool + }{ + "no ReadFrom": { + responseWriter: &baseRespWriter{}, + wantReadFrom: false, + }, + "has ReadFrom": { + responseWriter: &readFromRespWriter{}, + wantReadFrom: true, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + // what we expect middlewares to do: + type myWrapper struct { + *ResponseWriterWrapper + } + + wrapped := myWrapper{ + ResponseWriterWrapper: &ResponseWriterWrapper{ResponseWriter: tt.responseWriter}, + } + + const srcData = "boo!" + // hides everything but Read, since strings.Reader implements WriteTo it would + // take precedence over our ReadFrom. + src := struct{ io.Reader }{strings.NewReader(srcData)} + + if _, err := io.Copy(wrapped, src); err != nil { + t.Errorf("%s: Copy() err = %v", name, err) + } + + if got := tt.responseWriter.Written(); got != srcData { + t.Errorf("%s: data = %q, want %q", name, got, srcData) + } + + if tt.responseWriter.CalledReadFrom() != tt.wantReadFrom { + if tt.wantReadFrom { + t.Errorf("%s: ReadFrom() should have been called", name) + } else { + t.Errorf("%s: ReadFrom() should not have been called", name) + } + } + }) + } +} + +func TestResponseWriterWrapperUnwrap(t *testing.T) { + w := &ResponseWriterWrapper{&baseRespWriter{}} + + if _, ok := w.Unwrap().(*baseRespWriter); !ok { + t.Errorf("Unwrap() doesn't return the underlying ResponseWriter") + } +} + +func TestResponseRecorderReadFrom(t *testing.T) { + tests := map[string]struct { + responseWriter responseWriterSpy + shouldBuffer bool + wantReadFrom bool + }{ + "buffered plain": { + responseWriter: &baseRespWriter{}, + shouldBuffer: true, + wantReadFrom: false, + }, + "streamed plain": { + responseWriter: &baseRespWriter{}, + shouldBuffer: false, + wantReadFrom: false, + }, + "buffered ReadFrom": { + responseWriter: &readFromRespWriter{}, + shouldBuffer: true, + wantReadFrom: false, + }, + "streamed ReadFrom": { + responseWriter: &readFromRespWriter{}, + shouldBuffer: false, + wantReadFrom: true, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + var buf bytes.Buffer + + rr := NewResponseRecorder(tt.responseWriter, &buf, func(status int, header http.Header) bool { + return tt.shouldBuffer + }) + + const srcData = "boo!" + // hides everything but Read, since strings.Reader implements WriteTo it would + // take precedence over our ReadFrom. + src := struct{ io.Reader }{strings.NewReader(srcData)} + + if _, err := io.Copy(rr, src); err != nil { + t.Errorf("Copy() err = %v", err) + } + + wantStreamed := srcData + wantBuffered := "" + if tt.shouldBuffer { + wantStreamed = "" + wantBuffered = srcData + } + + if got := tt.responseWriter.Written(); got != wantStreamed { + t.Errorf("streamed data = %q, want %q", got, wantStreamed) + } + if got := buf.String(); got != wantBuffered { + t.Errorf("buffered data = %q, want %q", got, wantBuffered) + } + + if tt.responseWriter.CalledReadFrom() != tt.wantReadFrom { + if tt.wantReadFrom { + t.Errorf("ReadFrom() should have been called") + } else { + t.Errorf("ReadFrom() should not have been called") + } + } + }) + } +} diff --git a/modules/caddyhttp/reverseproxy/addresses.go b/modules/caddyhttp/reverseproxy/addresses.go new file mode 100644 index 00000000000..31f4aeb3502 --- /dev/null +++ b/modules/caddyhttp/reverseproxy/addresses.go @@ -0,0 +1,151 @@ +// 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 reverseproxy + +import ( + "fmt" + "net" + "net/url" + "strings" + + "github.com/caddyserver/caddy/v2" +) + +type parsedAddr struct { + network, scheme, host, port string + valid bool +} + +func (p parsedAddr) dialAddr() string { + if !p.valid { + return "" + } + // for simplest possible config, we only need to include + // the network portion if the user specified one + if p.network != "" { + return caddy.JoinNetworkAddress(p.network, p.host, p.port) + } + + // if the host is a placeholder, then we don't want to join with an empty port, + // because that would just append an extra ':' at the end of the address. + if p.port == "" && strings.Contains(p.host, "{") { + return p.host + } + return net.JoinHostPort(p.host, p.port) +} + +func (p parsedAddr) rangedPort() bool { + return strings.Contains(p.port, "-") +} + +func (p parsedAddr) replaceablePort() bool { + return strings.Contains(p.port, "{") && strings.Contains(p.port, "}") +} + +func (p parsedAddr) isUnix() bool { + return caddy.IsUnixNetwork(p.network) +} + +// parseUpstreamDialAddress parses configuration inputs for +// the dial address, including support for a scheme in front +// as a shortcut for the port number, and a network type, +// for example 'unix' to dial a unix socket. +func parseUpstreamDialAddress(upstreamAddr string) (parsedAddr, error) { + var network, scheme, host, port string + + if strings.Contains(upstreamAddr, "://") { + // we get a parsing error if a placeholder is specified + // so we return a more user-friendly error message instead + // to explain what to do instead + if strings.Contains(upstreamAddr, "{") { + return parsedAddr{}, fmt.Errorf("due to parsing difficulties, placeholders are not allowed when an upstream address contains a scheme") + } + + toURL, err := url.Parse(upstreamAddr) + if err != nil { + // if the error seems to be due to a port range, + // try to replace the port range with a dummy + // single port so that url.Parse() will succeed + if strings.Contains(err.Error(), "invalid port") && strings.Contains(err.Error(), "-") { + index := strings.LastIndex(upstreamAddr, ":") + if index == -1 { + return parsedAddr{}, fmt.Errorf("parsing upstream URL: %v", err) + } + portRange := upstreamAddr[index+1:] + if strings.Count(portRange, "-") != 1 { + return parsedAddr{}, fmt.Errorf("parsing upstream URL: parse \"%v\": port range invalid: %v", upstreamAddr, portRange) + } + toURL, err = url.Parse(strings.ReplaceAll(upstreamAddr, portRange, "0")) + if err != nil { + return parsedAddr{}, fmt.Errorf("parsing upstream URL: %v", err) + } + port = portRange + } else { + return parsedAddr{}, fmt.Errorf("parsing upstream URL: %v", err) + } + } + if port == "" { + port = toURL.Port() + } + + // there is currently no way to perform a URL rewrite between choosing + // a backend and proxying to it, so we cannot allow extra components + // in backend URLs + if toURL.Path != "" || toURL.RawQuery != "" || toURL.Fragment != "" { + return parsedAddr{}, fmt.Errorf("for now, URLs for proxy upstreams only support scheme, host, and port components") + } + + // ensure the port and scheme aren't in conflict + if toURL.Scheme == "http" && port == "443" { + return parsedAddr{}, fmt.Errorf("upstream address has conflicting scheme (http://) and port (:443, the HTTPS port)") + } + if toURL.Scheme == "https" && port == "80" { + return parsedAddr{}, fmt.Errorf("upstream address has conflicting scheme (https://) and port (:80, the HTTP port)") + } + if toURL.Scheme == "h2c" && port == "443" { + return parsedAddr{}, fmt.Errorf("upstream address has conflicting scheme (h2c://) and port (:443, the HTTPS port)") + } + + // if port is missing, attempt to infer from scheme + if port == "" { + switch toURL.Scheme { + case "", "http", "h2c": + port = "80" + case "https": + port = "443" + } + } + + scheme, host = toURL.Scheme, toURL.Hostname() + } else { + var err error + network, host, port, err = caddy.SplitNetworkAddress(upstreamAddr) + if err != nil { + host = upstreamAddr + } + // we can assume a port if only a hostname is specified, but use of a + // placeholder without a port likely means a port will be filled in + if port == "" && !strings.Contains(host, "{") && !caddy.IsUnixNetwork(network) && !caddy.IsFdNetwork(network) { + port = "80" + } + } + + // special case network to support both unix and h2c at the same time + if network == "unix+h2c" { + network = "unix" + scheme = "h2c" + } + return parsedAddr{network, scheme, host, port, true}, nil +} diff --git a/modules/caddyhttp/reverseproxy/addresses_test.go b/modules/caddyhttp/reverseproxy/addresses_test.go new file mode 100644 index 00000000000..0c514194290 --- /dev/null +++ b/modules/caddyhttp/reverseproxy/addresses_test.go @@ -0,0 +1,282 @@ +// 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 reverseproxy + +import "testing" + +func TestParseUpstreamDialAddress(t *testing.T) { + for i, tc := range []struct { + input string + expectHostPort string + expectScheme string + expectErr bool + }{ + { + input: "foo", + expectHostPort: "foo:80", + }, + { + input: "foo:1234", + expectHostPort: "foo:1234", + }, + { + input: "127.0.0.1", + expectHostPort: "127.0.0.1:80", + }, + { + input: "127.0.0.1:1234", + expectHostPort: "127.0.0.1:1234", + }, + { + input: "[::1]", + expectHostPort: "[::1]:80", + }, + { + input: "[::1]:1234", + expectHostPort: "[::1]:1234", + }, + { + input: "{foo}", + expectHostPort: "{foo}", + }, + { + input: "{foo}:80", + expectHostPort: "{foo}:80", + }, + { + input: "{foo}:{bar}", + expectHostPort: "{foo}:{bar}", + }, + { + input: "http://foo", + expectHostPort: "foo:80", + expectScheme: "http", + }, + { + input: "http://foo:1234", + expectHostPort: "foo:1234", + expectScheme: "http", + }, + { + input: "http://127.0.0.1", + expectHostPort: "127.0.0.1:80", + expectScheme: "http", + }, + { + input: "http://127.0.0.1:1234", + expectHostPort: "127.0.0.1:1234", + expectScheme: "http", + }, + { + input: "http://[::1]", + expectHostPort: "[::1]:80", + expectScheme: "http", + }, + { + input: "http://[::1]:80", + expectHostPort: "[::1]:80", + expectScheme: "http", + }, + { + input: "https://foo", + expectHostPort: "foo:443", + expectScheme: "https", + }, + { + input: "https://foo:1234", + expectHostPort: "foo:1234", + expectScheme: "https", + }, + { + input: "https://127.0.0.1", + expectHostPort: "127.0.0.1:443", + expectScheme: "https", + }, + { + input: "https://127.0.0.1:1234", + expectHostPort: "127.0.0.1:1234", + expectScheme: "https", + }, + { + input: "https://[::1]", + expectHostPort: "[::1]:443", + expectScheme: "https", + }, + { + input: "https://[::1]:1234", + expectHostPort: "[::1]:1234", + expectScheme: "https", + }, + { + input: "h2c://foo", + expectHostPort: "foo:80", + expectScheme: "h2c", + }, + { + input: "h2c://foo:1234", + expectHostPort: "foo:1234", + expectScheme: "h2c", + }, + { + input: "h2c://127.0.0.1", + expectHostPort: "127.0.0.1:80", + expectScheme: "h2c", + }, + { + input: "h2c://127.0.0.1:1234", + expectHostPort: "127.0.0.1:1234", + expectScheme: "h2c", + }, + { + input: "h2c://[::1]", + expectHostPort: "[::1]:80", + expectScheme: "h2c", + }, + { + input: "h2c://[::1]:1234", + expectHostPort: "[::1]:1234", + expectScheme: "h2c", + }, + { + input: "localhost:1001-1009", + expectHostPort: "localhost:1001-1009", + }, + { + input: "{host}:1001-1009", + expectHostPort: "{host}:1001-1009", + }, + { + input: "http://localhost:1001-1009", + expectHostPort: "localhost:1001-1009", + expectScheme: "http", + }, + { + input: "https://localhost:1001-1009", + expectHostPort: "localhost:1001-1009", + expectScheme: "https", + }, + { + input: "unix//var/php.sock", + expectHostPort: "unix//var/php.sock", + }, + { + input: "unix+h2c//var/grpc.sock", + expectHostPort: "unix//var/grpc.sock", + expectScheme: "h2c", + }, + { + input: "unix/{foo}", + expectHostPort: "unix/{foo}", + }, + { + input: "unix+h2c/{foo}", + expectHostPort: "unix/{foo}", + expectScheme: "h2c", + }, + { + input: "unix//foo/{foo}/bar", + expectHostPort: "unix//foo/{foo}/bar", + }, + { + input: "unix+h2c//foo/{foo}/bar", + expectHostPort: "unix//foo/{foo}/bar", + expectScheme: "h2c", + }, + { + input: "http://{foo}", + expectErr: true, + }, + { + input: "http:// :80", + expectErr: true, + }, + { + input: "http://localhost/path", + expectErr: true, + }, + { + input: "http://localhost?key=value", + expectErr: true, + }, + { + input: "http://localhost#fragment", + expectErr: true, + }, + { + input: "http://localhost:8001-8002-8003", + expectErr: true, + }, + { + input: "http://localhost:8001-8002/foo:bar", + expectErr: true, + }, + { + input: "http://localhost:8001-8002/foo:1", + expectErr: true, + }, + { + input: "http://localhost:8001-8002/foo:1-2", + expectErr: true, + }, + { + input: "http://localhost:8001-8002#foo:1", + expectErr: true, + }, + { + input: "http://foo:443", + expectErr: true, + }, + { + input: "https://foo:80", + expectErr: true, + }, + { + input: "h2c://foo:443", + expectErr: true, + }, + { + input: `unix/c:\absolute\path`, + expectHostPort: `unix/c:\absolute\path`, + }, + { + input: `unix+h2c/c:\absolute\path`, + expectHostPort: `unix/c:\absolute\path`, + expectScheme: "h2c", + }, + { + input: "unix/c:/absolute/path", + expectHostPort: "unix/c:/absolute/path", + }, + { + input: "unix+h2c/c:/absolute/path", + expectHostPort: "unix/c:/absolute/path", + expectScheme: "h2c", + }, + } { + actualAddr, err := parseUpstreamDialAddress(tc.input) + if tc.expectErr && err == nil { + t.Errorf("Test %d: Expected error but got %v", i, err) + } + if !tc.expectErr && err != nil { + t.Errorf("Test %d: Expected no error but got %v", i, err) + } + if actualAddr.dialAddr() != tc.expectHostPort { + t.Errorf("Test %d: input %s: Expected host and port '%s' but got '%s'", i, tc.input, tc.expectHostPort, actualAddr.dialAddr()) + } + if actualAddr.scheme != tc.expectScheme { + t.Errorf("Test %d: Expected scheme '%s' but got '%s'", i, tc.expectScheme, actualAddr.scheme) + } + } +} diff --git a/modules/caddyhttp/reverseproxy/admin.go b/modules/caddyhttp/reverseproxy/admin.go new file mode 100644 index 00000000000..7e72a4cdb51 --- /dev/null +++ b/modules/caddyhttp/reverseproxy/admin.go @@ -0,0 +1,120 @@ +// 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 reverseproxy + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/caddyserver/caddy/v2" +) + +func init() { + caddy.RegisterModule(adminUpstreams{}) +} + +// adminUpstreams is a module that provides the +// /reverse_proxy/upstreams endpoint for the Caddy admin +// API. This allows for checking the health of configured +// reverse proxy upstreams in the pool. +type adminUpstreams struct{} + +// upstreamStatus holds the status of a particular upstream +type upstreamStatus struct { + Address string `json:"address"` + NumRequests int `json:"num_requests"` + Fails int `json:"fails"` +} + +// CaddyModule returns the Caddy module information. +func (adminUpstreams) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "admin.api.reverse_proxy", + New: func() caddy.Module { return new(adminUpstreams) }, + } +} + +// Routes returns a route for the /reverse_proxy/upstreams endpoint. +func (al adminUpstreams) Routes() []caddy.AdminRoute { + return []caddy.AdminRoute{ + { + Pattern: "/reverse_proxy/upstreams", + Handler: caddy.AdminHandlerFunc(al.handleUpstreams), + }, + } +} + +// handleUpstreams reports the status of the reverse proxy +// upstream pool. +func (adminUpstreams) handleUpstreams(w http.ResponseWriter, r *http.Request) error { + if r.Method != http.MethodGet { + return caddy.APIError{ + HTTPStatus: http.StatusMethodNotAllowed, + Err: fmt.Errorf("method not allowed"), + } + } + + // Prep for a JSON response + w.Header().Set("Content-Type", "application/json") + enc := json.NewEncoder(w) + + // Collect the results to respond with + results := []upstreamStatus{} + + // Iterate over the upstream pool (needs to be fast) + var rangeErr error + hosts.Range(func(key, val any) bool { + address, ok := key.(string) + if !ok { + rangeErr = caddy.APIError{ + HTTPStatus: http.StatusInternalServerError, + Err: fmt.Errorf("could not type assert upstream address"), + } + return false + } + + upstream, ok := val.(*Host) + if !ok { + rangeErr = caddy.APIError{ + HTTPStatus: http.StatusInternalServerError, + Err: fmt.Errorf("could not type assert upstream struct"), + } + return false + } + + results = append(results, upstreamStatus{ + Address: address, + NumRequests: upstream.NumRequests(), + Fails: upstream.Fails(), + }) + return true + }) + + // If an error happened during the range, return it + if rangeErr != nil { + return rangeErr + } + + err := enc.Encode(results) + if err != nil { + return caddy.APIError{ + HTTPStatus: http.StatusInternalServerError, + Err: err, + } + } + + return nil +} diff --git a/modules/caddyhttp/reverseproxy/ascii.go b/modules/caddyhttp/reverseproxy/ascii.go new file mode 100644 index 00000000000..75b8220f353 --- /dev/null +++ b/modules/caddyhttp/reverseproxy/ascii.go @@ -0,0 +1,57 @@ +// 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. + +// Most of the code in this file was initially borrowed from the Go +// standard library and modified; It had this copyright notice: +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Original source, copied because the package was marked internal: +// https://github.com/golang/go/blob/5c489514bc5e61ad9b5b07bd7d8ec65d66a0512a/src/net/http/internal/ascii/print.go + +package reverseproxy + +// asciiEqualFold is strings.EqualFold, ASCII only. It reports whether s and t +// are equal, ASCII-case-insensitively. +func asciiEqualFold(s, t string) bool { + if len(s) != len(t) { + return false + } + for i := 0; i < len(s); i++ { + if asciiLower(s[i]) != asciiLower(t[i]) { + return false + } + } + return true +} + +// asciiLower returns the ASCII lowercase version of b. +func asciiLower(b byte) byte { + if 'A' <= b && b <= 'Z' { + return b + ('a' - 'A') + } + return b +} + +// asciiIsPrint returns whether s is ASCII and printable according to +// https://tools.ietf.org/html/rfc20#section-4.2. +func asciiIsPrint(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] < ' ' || s[i] > '~' { + return false + } + } + return true +} diff --git a/modules/caddyhttp/reverseproxy/ascii_test.go b/modules/caddyhttp/reverseproxy/ascii_test.go new file mode 100644 index 00000000000..de67963bd7c --- /dev/null +++ b/modules/caddyhttp/reverseproxy/ascii_test.go @@ -0,0 +1,114 @@ +// 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. + +// Most of the code in this file was initially borrowed from the Go +// standard library and modified; It had this copyright notice: +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Original source, copied because the package was marked internal: +// https://github.com/golang/go/blob/5c489514bc5e61ad9b5b07bd7d8ec65d66a0512a/src/net/http/internal/ascii/print_test.go + +package reverseproxy + +import "testing" + +func TestEqualFold(t *testing.T) { + tests := []struct { + name string + a, b string + want bool + }{ + { + name: "empty", + want: true, + }, + { + name: "simple match", + a: "CHUNKED", + b: "chunked", + want: true, + }, + { + name: "same string", + a: "chunked", + b: "chunked", + want: true, + }, + { + name: "Unicode Kelvin symbol", + a: "chunKed", // This "K" is 'KELVIN SIGN' (\u212A) + b: "chunked", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := asciiEqualFold(tt.a, tt.b); got != tt.want { + t.Errorf("AsciiEqualFold(%q,%q): got %v want %v", tt.a, tt.b, got, tt.want) + } + }) + } +} + +func TestIsPrint(t *testing.T) { + tests := []struct { + name string + in string + want bool + }{ + { + name: "empty", + want: true, + }, + { + name: "ASCII low", + in: "This is a space: ' '", + want: true, + }, + { + name: "ASCII high", + in: "This is a tilde: '~'", + want: true, + }, + { + name: "ASCII low non-print", + in: "This is a unit separator: \x1F", + want: false, + }, + { + name: "Ascii high non-print", + in: "This is a Delete: \x7F", + want: false, + }, + { + name: "Unicode letter", + in: "Today it's 280K outside: it's freezing!", // This "K" is 'KELVIN SIGN' (\u212A) + want: false, + }, + { + name: "Unicode emoji", + in: "Gophers like 🧀", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := asciiIsPrint(tt.in); got != tt.want { + t.Errorf("IsASCIIPrint(%q): got %v want %v", tt.in, got, tt.want) + } + }) + } +} diff --git a/modules/caddyhttp/reverseproxy/caddyfile.go b/modules/caddyhttp/reverseproxy/caddyfile.go new file mode 100644 index 00000000000..ab1dcdd029c --- /dev/null +++ b/modules/caddyhttp/reverseproxy/caddyfile.go @@ -0,0 +1,1673 @@ +// 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 reverseproxy + +import ( + "fmt" + "net" + "net/http" + "reflect" + "strconv" + "strings" + + "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/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/internal" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/headers" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite" + "github.com/caddyserver/caddy/v2/modules/caddytls" +) + +func init() { + httpcaddyfile.RegisterHandlerDirective("reverse_proxy", parseCaddyfile) + httpcaddyfile.RegisterHandlerDirective("copy_response", parseCopyResponseCaddyfile) + httpcaddyfile.RegisterHandlerDirective("copy_response_headers", parseCopyResponseHeadersCaddyfile) +} + +func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + rp := new(Handler) + err := rp.UnmarshalCaddyfile(h.Dispenser) + if err != nil { + return nil, err + } + err = rp.FinalizeUnmarshalCaddyfile(h) + if err != nil { + return nil, err + } + return rp, nil +} + +// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax: +// +// reverse_proxy [] [] { +// # backends +// to +// dynamic [...] +// +// # load balancing +// lb_policy [] +// lb_retries +// lb_try_duration +// lb_try_interval +// lb_retry_match +// +// # active health checking +// health_uri +// health_port +// health_interval +// health_passes +// health_fails +// health_timeout +// health_status +// health_body +// health_method +// health_request_body +// health_follow_redirects +// health_headers { +// [] +// } +// +// # passive health checking +// fail_duration +// max_fails +// unhealthy_status +// unhealthy_latency +// unhealthy_request_count +// +// # streaming +// flush_interval +// request_buffers +// response_buffers +// stream_timeout +// stream_close_delay +// verbose_logs +// +// # request manipulation +// trusted_proxies [private_ranges] +// header_up [+|-] [ []] +// header_down [+|-] [ []] +// method +// rewrite +// +// # round trip +// transport { +// ... +// } +// +// # optionally intercept responses from upstream +// @name { +// status +// header [] +// } +// replace_status [] +// handle_response [] { +// +// +// # special directives only available in handle_response +// copy_response [] [] { +// status +// } +// copy_response_headers [] { +// include +// exclude +// } +// } +// } +// +// Proxy upstream addresses should be network dial addresses such +// as `host:port`, or a URL such as `scheme://host:port`. Scheme +// and port may be inferred from other parts of the address/URL; if +// either are missing, defaults to HTTP. +// +// The FinalizeUnmarshalCaddyfile method should be called after this +// to finalize parsing of "handle_response" blocks, if possible. +func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + // currently, all backends must use the same scheme/protocol (the + // underlying JSON does not yet support per-backend transports) + var commonScheme string + + // we'll wait until the very end of parsing before + // validating and encoding the transport + var transport http.RoundTripper + var transportModuleName string + + // collect the response matchers defined as subdirectives + // prefixed with "@" for use with "handle_response" blocks + h.responseMatchers = make(map[string]caddyhttp.ResponseMatcher) + + // appendUpstream creates an upstream for address and adds + // it to the list. + appendUpstream := func(address string) error { + pa, err := parseUpstreamDialAddress(address) + if err != nil { + return d.WrapErr(err) + } + + // the underlying JSON does not yet support different + // transports (protocols or schemes) to each backend, + // so we remember the last one we see and compare them + + switch pa.scheme { + case "wss": + return d.Errf("the scheme wss:// is only supported in browsers; use https:// instead") + case "ws": + return d.Errf("the scheme ws:// is only supported in browsers; use http:// instead") + case "https", "http", "h2c", "": + // Do nothing or handle the valid schemes + default: + return d.Errf("unsupported URL scheme %s://", pa.scheme) + } + + if commonScheme != "" && pa.scheme != commonScheme { + return d.Errf("for now, all proxy upstreams must use the same scheme (transport protocol); expecting '%s://' but got '%s://'", + commonScheme, pa.scheme) + } + commonScheme = pa.scheme + + // if the port of upstream address contains a placeholder, only wrap it with the `Upstream` struct, + // delaying actual resolution of the address until request time. + if pa.replaceablePort() { + h.Upstreams = append(h.Upstreams, &Upstream{Dial: pa.dialAddr()}) + return nil + } + parsedAddr, err := caddy.ParseNetworkAddress(pa.dialAddr()) + if err != nil { + return d.WrapErr(err) + } + + if pa.isUnix() || !pa.rangedPort() { + // unix networks don't have ports + h.Upstreams = append(h.Upstreams, &Upstream{ + Dial: pa.dialAddr(), + }) + } else { + // expand a port range into multiple upstreams + for i := parsedAddr.StartPort; i <= parsedAddr.EndPort; i++ { + h.Upstreams = append(h.Upstreams, &Upstream{ + Dial: caddy.JoinNetworkAddress("", parsedAddr.Host, fmt.Sprint(i)), + }) + } + } + + return nil + } + + d.Next() // consume the directive name + for _, up := range d.RemainingArgs() { + err := appendUpstream(up) + if err != nil { + return fmt.Errorf("parsing upstream '%s': %w", up, err) + } + } + + for d.NextBlock(0) { + // if the subdirective has an "@" prefix then we + // parse it as a response matcher for use with "handle_response" + if strings.HasPrefix(d.Val(), matcherPrefix) { + err := caddyhttp.ParseNamedResponseMatcher(d.NewFromNextSegment(), h.responseMatchers) + if err != nil { + return err + } + continue + } + + switch d.Val() { + case "to": + args := d.RemainingArgs() + if len(args) == 0 { + return d.ArgErr() + } + for _, up := range args { + err := appendUpstream(up) + if err != nil { + return fmt.Errorf("parsing upstream '%s': %w", up, err) + } + } + + case "dynamic": + if !d.NextArg() { + return d.ArgErr() + } + if h.DynamicUpstreams != nil { + return d.Err("dynamic upstreams already specified") + } + dynModule := d.Val() + modID := "http.reverse_proxy.upstreams." + dynModule + unm, err := caddyfile.UnmarshalModule(d, modID) + if err != nil { + return err + } + source, ok := unm.(UpstreamSource) + if !ok { + return d.Errf("module %s (%T) is not an UpstreamSource", modID, unm) + } + h.DynamicUpstreamsRaw = caddyconfig.JSONModuleObject(source, "source", dynModule, nil) + + case "lb_policy": + if !d.NextArg() { + return d.ArgErr() + } + if h.LoadBalancing != nil && h.LoadBalancing.SelectionPolicyRaw != nil { + return d.Err("load balancing selection policy already specified") + } + name := d.Val() + modID := "http.reverse_proxy.selection_policies." + name + unm, err := caddyfile.UnmarshalModule(d, modID) + if err != nil { + return err + } + sel, ok := unm.(Selector) + if !ok { + return d.Errf("module %s (%T) is not a reverseproxy.Selector", modID, unm) + } + if h.LoadBalancing == nil { + h.LoadBalancing = new(LoadBalancing) + } + h.LoadBalancing.SelectionPolicyRaw = caddyconfig.JSONModuleObject(sel, "policy", name, nil) + + case "lb_retries": + if !d.NextArg() { + return d.ArgErr() + } + tries, err := strconv.Atoi(d.Val()) + if err != nil { + return d.Errf("bad lb_retries number '%s': %v", d.Val(), err) + } + if h.LoadBalancing == nil { + h.LoadBalancing = new(LoadBalancing) + } + h.LoadBalancing.Retries = tries + + case "lb_try_duration": + if !d.NextArg() { + return d.ArgErr() + } + if h.LoadBalancing == nil { + h.LoadBalancing = new(LoadBalancing) + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad duration value %s: %v", d.Val(), err) + } + h.LoadBalancing.TryDuration = caddy.Duration(dur) + + case "lb_try_interval": + if !d.NextArg() { + return d.ArgErr() + } + if h.LoadBalancing == nil { + h.LoadBalancing = new(LoadBalancing) + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad interval value '%s': %v", d.Val(), err) + } + h.LoadBalancing.TryInterval = caddy.Duration(dur) + + case "lb_retry_match": + matcherSet, err := caddyhttp.ParseCaddyfileNestedMatcherSet(d) + if err != nil { + return d.Errf("failed to parse lb_retry_match: %v", err) + } + if h.LoadBalancing == nil { + h.LoadBalancing = new(LoadBalancing) + } + h.LoadBalancing.RetryMatchRaw = append(h.LoadBalancing.RetryMatchRaw, matcherSet) + + case "health_uri": + if !d.NextArg() { + return d.ArgErr() + } + if h.HealthChecks == nil { + h.HealthChecks = new(HealthChecks) + } + if h.HealthChecks.Active == nil { + h.HealthChecks.Active = new(ActiveHealthChecks) + } + h.HealthChecks.Active.URI = d.Val() + + case "health_path": + if !d.NextArg() { + return d.ArgErr() + } + if h.HealthChecks == nil { + h.HealthChecks = new(HealthChecks) + } + if h.HealthChecks.Active == nil { + h.HealthChecks.Active = new(ActiveHealthChecks) + } + h.HealthChecks.Active.Path = d.Val() + caddy.Log().Named("config.adapter.caddyfile").Warn("the 'health_path' subdirective is deprecated, please use 'health_uri' instead!") + + case "health_upstream": + if !d.NextArg() { + return d.ArgErr() + } + if h.HealthChecks == nil { + h.HealthChecks = new(HealthChecks) + } + if h.HealthChecks.Active == nil { + h.HealthChecks.Active = new(ActiveHealthChecks) + } + _, port, err := net.SplitHostPort(d.Val()) + if err != nil { + return d.Errf("health_upstream is malformed '%s': %v", d.Val(), err) + } + _, err = strconv.Atoi(port) + if err != nil { + return d.Errf("bad port number '%s': %v", d.Val(), err) + } + h.HealthChecks.Active.Upstream = d.Val() + + case "health_port": + if !d.NextArg() { + return d.ArgErr() + } + if h.HealthChecks == nil { + h.HealthChecks = new(HealthChecks) + } + if h.HealthChecks.Active == nil { + h.HealthChecks.Active = new(ActiveHealthChecks) + } + if h.HealthChecks.Active.Upstream != "" { + return d.Errf("the 'health_port' subdirective is ignored if 'health_upstream' is used!") + } + portNum, err := strconv.Atoi(d.Val()) + if err != nil { + return d.Errf("bad port number '%s': %v", d.Val(), err) + } + h.HealthChecks.Active.Port = portNum + + case "health_headers": + healthHeaders := make(http.Header) + for nesting := d.Nesting(); d.NextBlock(nesting); { + key := d.Val() + values := d.RemainingArgs() + if len(values) == 0 { + values = append(values, "") + } + healthHeaders[key] = append(healthHeaders[key], values...) + } + if h.HealthChecks == nil { + h.HealthChecks = new(HealthChecks) + } + if h.HealthChecks.Active == nil { + h.HealthChecks.Active = new(ActiveHealthChecks) + } + h.HealthChecks.Active.Headers = healthHeaders + + case "health_method": + if !d.NextArg() { + return d.ArgErr() + } + if h.HealthChecks == nil { + h.HealthChecks = new(HealthChecks) + } + if h.HealthChecks.Active == nil { + h.HealthChecks.Active = new(ActiveHealthChecks) + } + h.HealthChecks.Active.Method = d.Val() + + case "health_request_body": + if !d.NextArg() { + return d.ArgErr() + } + if h.HealthChecks == nil { + h.HealthChecks = new(HealthChecks) + } + if h.HealthChecks.Active == nil { + h.HealthChecks.Active = new(ActiveHealthChecks) + } + h.HealthChecks.Active.Body = d.Val() + + case "health_interval": + if !d.NextArg() { + return d.ArgErr() + } + if h.HealthChecks == nil { + h.HealthChecks = new(HealthChecks) + } + if h.HealthChecks.Active == nil { + h.HealthChecks.Active = new(ActiveHealthChecks) + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad interval value %s: %v", d.Val(), err) + } + h.HealthChecks.Active.Interval = caddy.Duration(dur) + + case "health_timeout": + if !d.NextArg() { + return d.ArgErr() + } + if h.HealthChecks == nil { + h.HealthChecks = new(HealthChecks) + } + if h.HealthChecks.Active == nil { + h.HealthChecks.Active = new(ActiveHealthChecks) + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad timeout value %s: %v", d.Val(), err) + } + h.HealthChecks.Active.Timeout = caddy.Duration(dur) + + case "health_status": + if !d.NextArg() { + return d.ArgErr() + } + if h.HealthChecks == nil { + h.HealthChecks = new(HealthChecks) + } + if h.HealthChecks.Active == nil { + h.HealthChecks.Active = new(ActiveHealthChecks) + } + val := d.Val() + if len(val) == 3 && strings.HasSuffix(val, "xx") { + val = val[:1] + } + statusNum, err := strconv.Atoi(val) + if err != nil { + return d.Errf("bad status value '%s': %v", d.Val(), err) + } + h.HealthChecks.Active.ExpectStatus = statusNum + + case "health_body": + if !d.NextArg() { + return d.ArgErr() + } + if h.HealthChecks == nil { + h.HealthChecks = new(HealthChecks) + } + if h.HealthChecks.Active == nil { + h.HealthChecks.Active = new(ActiveHealthChecks) + } + h.HealthChecks.Active.ExpectBody = d.Val() + + case "health_follow_redirects": + if d.NextArg() { + return d.ArgErr() + } + if h.HealthChecks == nil { + h.HealthChecks = new(HealthChecks) + } + if h.HealthChecks.Active == nil { + h.HealthChecks.Active = new(ActiveHealthChecks) + } + h.HealthChecks.Active.FollowRedirects = true + + case "health_passes": + if !d.NextArg() { + return d.ArgErr() + } + if h.HealthChecks == nil { + h.HealthChecks = new(HealthChecks) + } + if h.HealthChecks.Active == nil { + h.HealthChecks.Active = new(ActiveHealthChecks) + } + passes, err := strconv.Atoi(d.Val()) + if err != nil { + return d.Errf("invalid passes count '%s': %v", d.Val(), err) + } + h.HealthChecks.Active.Passes = passes + + case "health_fails": + if !d.NextArg() { + return d.ArgErr() + } + if h.HealthChecks == nil { + h.HealthChecks = new(HealthChecks) + } + if h.HealthChecks.Active == nil { + h.HealthChecks.Active = new(ActiveHealthChecks) + } + fails, err := strconv.Atoi(d.Val()) + if err != nil { + return d.Errf("invalid fails count '%s': %v", d.Val(), err) + } + h.HealthChecks.Active.Fails = fails + + case "max_fails": + if !d.NextArg() { + return d.ArgErr() + } + if h.HealthChecks == nil { + h.HealthChecks = new(HealthChecks) + } + if h.HealthChecks.Passive == nil { + h.HealthChecks.Passive = new(PassiveHealthChecks) + } + maxFails, err := strconv.Atoi(d.Val()) + if err != nil { + return d.Errf("invalid maximum fail count '%s': %v", d.Val(), err) + } + h.HealthChecks.Passive.MaxFails = maxFails + + case "fail_duration": + if !d.NextArg() { + return d.ArgErr() + } + if h.HealthChecks == nil { + h.HealthChecks = new(HealthChecks) + } + if h.HealthChecks.Passive == nil { + h.HealthChecks.Passive = new(PassiveHealthChecks) + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad duration value '%s': %v", d.Val(), err) + } + h.HealthChecks.Passive.FailDuration = caddy.Duration(dur) + + case "unhealthy_request_count": + if !d.NextArg() { + return d.ArgErr() + } + if h.HealthChecks == nil { + h.HealthChecks = new(HealthChecks) + } + if h.HealthChecks.Passive == nil { + h.HealthChecks.Passive = new(PassiveHealthChecks) + } + maxConns, err := strconv.Atoi(d.Val()) + if err != nil { + return d.Errf("invalid maximum connection count '%s': %v", d.Val(), err) + } + h.HealthChecks.Passive.UnhealthyRequestCount = maxConns + + case "unhealthy_status": + args := d.RemainingArgs() + if len(args) == 0 { + return d.ArgErr() + } + if h.HealthChecks == nil { + h.HealthChecks = new(HealthChecks) + } + if h.HealthChecks.Passive == nil { + h.HealthChecks.Passive = new(PassiveHealthChecks) + } + for _, arg := range args { + if len(arg) == 3 && strings.HasSuffix(arg, "xx") { + arg = arg[:1] + } + statusNum, err := strconv.Atoi(arg) + if err != nil { + return d.Errf("bad status value '%s': %v", d.Val(), err) + } + h.HealthChecks.Passive.UnhealthyStatus = append(h.HealthChecks.Passive.UnhealthyStatus, statusNum) + } + + case "unhealthy_latency": + if !d.NextArg() { + return d.ArgErr() + } + if h.HealthChecks == nil { + h.HealthChecks = new(HealthChecks) + } + if h.HealthChecks.Passive == nil { + h.HealthChecks.Passive = new(PassiveHealthChecks) + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad duration value '%s': %v", d.Val(), err) + } + h.HealthChecks.Passive.UnhealthyLatency = caddy.Duration(dur) + + case "flush_interval": + if !d.NextArg() { + return d.ArgErr() + } + if fi, err := strconv.Atoi(d.Val()); err == nil { + h.FlushInterval = caddy.Duration(fi) + } else { + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad duration value '%s': %v", d.Val(), err) + } + h.FlushInterval = caddy.Duration(dur) + } + + case "request_buffers", "response_buffers": + subdir := d.Val() + if !d.NextArg() { + return d.ArgErr() + } + val := d.Val() + var size int64 + if val == "unlimited" { + size = -1 + } else { + usize, err := humanize.ParseBytes(val) + if err != nil { + return d.Errf("invalid byte size '%s': %v", val, err) + } + size = int64(usize) + } + if d.NextArg() { + return d.ArgErr() + } + if subdir == "request_buffers" { + h.RequestBuffers = size + } else if subdir == "response_buffers" { + h.ResponseBuffers = size + } + + case "stream_timeout": + if !d.NextArg() { + return d.ArgErr() + } + if fi, err := strconv.Atoi(d.Val()); err == nil { + h.StreamTimeout = caddy.Duration(fi) + } else { + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad duration value '%s': %v", d.Val(), err) + } + h.StreamTimeout = caddy.Duration(dur) + } + + case "stream_close_delay": + if !d.NextArg() { + return d.ArgErr() + } + if fi, err := strconv.Atoi(d.Val()); err == nil { + h.StreamCloseDelay = caddy.Duration(fi) + } else { + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad duration value '%s': %v", d.Val(), err) + } + h.StreamCloseDelay = caddy.Duration(dur) + } + + case "trusted_proxies": + for d.NextArg() { + if d.Val() == "private_ranges" { + h.TrustedProxies = append(h.TrustedProxies, internal.PrivateRangesCIDR()...) + continue + } + h.TrustedProxies = append(h.TrustedProxies, d.Val()) + } + + case "header_up": + var err error + + if h.Headers == nil { + h.Headers = new(headers.Handler) + } + if h.Headers.Request == nil { + h.Headers.Request = new(headers.HeaderOps) + } + args := d.RemainingArgs() + + switch len(args) { + case 1: + err = headers.CaddyfileHeaderOp(h.Headers.Request, args[0], "", nil) + case 2: + // some lint checks, I guess + if strings.EqualFold(args[0], "host") && (args[1] == "{hostport}" || args[1] == "{http.request.hostport}") { + caddy.Log().Named("caddyfile").Warn("Unnecessary header_up Host: the reverse proxy's default behavior is to pass headers to the upstream") + } + if strings.EqualFold(args[0], "x-forwarded-for") && (args[1] == "{remote}" || args[1] == "{http.request.remote}" || args[1] == "{remote_host}" || args[1] == "{http.request.remote.host}") { + caddy.Log().Named("caddyfile").Warn("Unnecessary header_up X-Forwarded-For: the reverse proxy's default behavior is to pass headers to the upstream") + } + if strings.EqualFold(args[0], "x-forwarded-proto") && (args[1] == "{scheme}" || args[1] == "{http.request.scheme}") { + caddy.Log().Named("caddyfile").Warn("Unnecessary header_up X-Forwarded-Proto: the reverse proxy's default behavior is to pass headers to the upstream") + } + if strings.EqualFold(args[0], "x-forwarded-host") && (args[1] == "{host}" || args[1] == "{http.request.host}" || args[1] == "{hostport}" || args[1] == "{http.request.hostport}") { + caddy.Log().Named("caddyfile").Warn("Unnecessary header_up X-Forwarded-Host: the reverse proxy's default behavior is to pass headers to the upstream") + } + err = headers.CaddyfileHeaderOp(h.Headers.Request, args[0], args[1], nil) + case 3: + err = headers.CaddyfileHeaderOp(h.Headers.Request, args[0], args[1], &args[2]) + default: + return d.ArgErr() + } + + if err != nil { + return d.Err(err.Error()) + } + + case "header_down": + var err error + + if h.Headers == nil { + h.Headers = new(headers.Handler) + } + if h.Headers.Response == nil { + h.Headers.Response = &headers.RespHeaderOps{ + HeaderOps: new(headers.HeaderOps), + } + } + args := d.RemainingArgs() + + switch len(args) { + case 1: + err = headers.CaddyfileHeaderOp(h.Headers.Response.HeaderOps, args[0], "", nil) + case 2: + err = headers.CaddyfileHeaderOp(h.Headers.Response.HeaderOps, args[0], args[1], nil) + case 3: + err = headers.CaddyfileHeaderOp(h.Headers.Response.HeaderOps, args[0], args[1], &args[2]) + default: + return d.ArgErr() + } + + if err != nil { + return d.Err(err.Error()) + } + + case "method": + if !d.NextArg() { + return d.ArgErr() + } + if h.Rewrite == nil { + h.Rewrite = &rewrite.Rewrite{} + } + h.Rewrite.Method = d.Val() + if d.NextArg() { + return d.ArgErr() + } + + case "rewrite": + if !d.NextArg() { + return d.ArgErr() + } + if h.Rewrite == nil { + h.Rewrite = &rewrite.Rewrite{} + } + h.Rewrite.URI = d.Val() + if d.NextArg() { + return d.ArgErr() + } + + case "transport": + if !d.NextArg() { + return d.ArgErr() + } + if h.TransportRaw != nil { + return d.Err("transport already specified") + } + transportModuleName = d.Val() + modID := "http.reverse_proxy.transport." + transportModuleName + unm, err := caddyfile.UnmarshalModule(d, modID) + if err != nil { + return err + } + rt, ok := unm.(http.RoundTripper) + if !ok { + return d.Errf("module %s (%T) is not a RoundTripper", modID, unm) + } + transport = rt + + case "handle_response": + // delegate the parsing of handle_response to the caller, + // since we need the httpcaddyfile.Helper to parse subroutes. + // See h.FinalizeUnmarshalCaddyfile + h.handleResponseSegments = append(h.handleResponseSegments, d.NewFromNextSegment()) + + case "replace_status": + args := d.RemainingArgs() + if len(args) != 1 && len(args) != 2 { + return d.Errf("must have one or two arguments: an optional response matcher, and a status code") + } + + responseHandler := caddyhttp.ResponseHandler{} + + if len(args) == 2 { + if !strings.HasPrefix(args[0], matcherPrefix) { + return d.Errf("must use a named response matcher, starting with '@'") + } + foundMatcher, ok := h.responseMatchers[args[0]] + if !ok { + return d.Errf("no named response matcher defined with name '%s'", args[0][1:]) + } + responseHandler.Match = &foundMatcher + responseHandler.StatusCode = caddyhttp.WeakString(args[1]) + } else if len(args) == 1 { + responseHandler.StatusCode = caddyhttp.WeakString(args[0]) + } + + // make sure there's no block, cause it doesn't make sense + if nesting := d.Nesting(); d.NextBlock(nesting) { + return d.Errf("cannot define routes for 'replace_status', use 'handle_response' instead.") + } + + h.HandleResponse = append( + h.HandleResponse, + responseHandler, + ) + + case "verbose_logs": + if h.VerboseLogs { + return d.Err("verbose_logs already specified") + } + h.VerboseLogs = true + + default: + return d.Errf("unrecognized subdirective %s", d.Val()) + } + } + + // if the scheme inferred from the backends' addresses is + // HTTPS, we will need a non-nil transport to enable TLS, + // or if H2C, to set the transport versions. + if (commonScheme == "https" || commonScheme == "h2c") && transport == nil { + transport = new(HTTPTransport) + transportModuleName = "http" + } + + // verify transport configuration, and finally encode it + if transport != nil { + if te, ok := transport.(TLSTransport); ok { + if commonScheme == "https" && !te.TLSEnabled() { + err := te.EnableTLS(new(TLSConfig)) + if err != nil { + return err + } + } + if commonScheme == "http" && te.TLSEnabled() { + return d.Errf("upstream address scheme is HTTP but transport is configured for HTTP+TLS (HTTPS)") + } + if te, ok := transport.(*HTTPTransport); ok && commonScheme == "h2c" { + te.Versions = []string{"h2c", "2"} + } + } else if commonScheme == "https" { + return d.Errf("upstreams are configured for HTTPS but transport module does not support TLS: %T", transport) + } + + // no need to encode empty default transport + if !reflect.DeepEqual(transport, new(HTTPTransport)) { + h.TransportRaw = caddyconfig.JSONModuleObject(transport, "protocol", transportModuleName, nil) + } + } + + return nil +} + +// FinalizeUnmarshalCaddyfile finalizes the Caddyfile parsing which +// requires having an httpcaddyfile.Helper to function, to parse subroutes. +func (h *Handler) FinalizeUnmarshalCaddyfile(helper httpcaddyfile.Helper) error { + for _, d := range h.handleResponseSegments { + // consume the "handle_response" token + d.Next() + args := d.RemainingArgs() + + // TODO: Remove this check at some point in the future + if len(args) == 2 { + return d.Errf("configuring 'handle_response' for status code replacement is no longer supported. Use 'replace_status' instead.") + } + + if len(args) > 1 { + return d.Errf("too many arguments for 'handle_response': %s", args) + } + + var matcher *caddyhttp.ResponseMatcher + if len(args) == 1 { + // the first arg should always be a matcher. + if !strings.HasPrefix(args[0], matcherPrefix) { + return d.Errf("must use a named response matcher, starting with '@'") + } + + foundMatcher, ok := h.responseMatchers[args[0]] + if !ok { + return d.Errf("no named response matcher defined with name '%s'", args[0][1:]) + } + matcher = &foundMatcher + } + + // parse the block as routes + handler, err := httpcaddyfile.ParseSegmentAsSubroute(helper.WithDispenser(d.NewFromNextSegment())) + if err != nil { + return err + } + subroute, ok := handler.(*caddyhttp.Subroute) + if !ok { + return helper.Errf("segment was not parsed as a subroute") + } + h.HandleResponse = append( + h.HandleResponse, + caddyhttp.ResponseHandler{ + Match: matcher, + Routes: subroute.Routes, + }, + ) + } + + // move the handle_response entries without a matcher to the end. + // we can't use sort.SliceStable because it will reorder the rest of the + // entries which may be undesirable because we don't have a good + // heuristic to use for sorting. + withoutMatchers := []caddyhttp.ResponseHandler{} + withMatchers := []caddyhttp.ResponseHandler{} + for _, hr := range h.HandleResponse { + if hr.Match == nil { + withoutMatchers = append(withoutMatchers, hr) + } else { + withMatchers = append(withMatchers, hr) + } + } + h.HandleResponse = append(withMatchers, withoutMatchers...) + + // clean up the bits we only needed for adapting + h.handleResponseSegments = nil + h.responseMatchers = nil + + return nil +} + +// UnmarshalCaddyfile deserializes Caddyfile tokens into h. +// +// transport http { +// read_buffer +// write_buffer +// max_response_header +// forward_proxy_url +// dial_timeout +// dial_fallback_delay +// response_header_timeout +// expect_continue_timeout +// resolvers +// tls +// tls_client_auth | +// tls_insecure_skip_verify +// tls_timeout +// tls_trusted_ca_certs +// tls_server_name +// tls_renegotiation +// tls_except_ports +// keepalive [off|] +// keepalive_interval +// keepalive_idle_conns +// keepalive_idle_conns_per_host +// versions +// compression off +// max_conns_per_host +// max_idle_conns_per_host +// } +func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume transport name + for d.NextBlock(0) { + switch d.Val() { + case "read_buffer": + if !d.NextArg() { + return d.ArgErr() + } + size, err := humanize.ParseBytes(d.Val()) + if err != nil { + return d.Errf("invalid read buffer size '%s': %v", d.Val(), err) + } + h.ReadBufferSize = int(size) + + case "write_buffer": + if !d.NextArg() { + return d.ArgErr() + } + size, err := humanize.ParseBytes(d.Val()) + if err != nil { + return d.Errf("invalid write buffer size '%s': %v", d.Val(), err) + } + h.WriteBufferSize = int(size) + + case "read_timeout": + if !d.NextArg() { + return d.ArgErr() + } + timeout, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("invalid read timeout duration '%s': %v", d.Val(), err) + } + h.ReadTimeout = caddy.Duration(timeout) + + case "write_timeout": + if !d.NextArg() { + return d.ArgErr() + } + timeout, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("invalid write timeout duration '%s': %v", d.Val(), err) + } + h.WriteTimeout = caddy.Duration(timeout) + + case "max_response_header": + if !d.NextArg() { + return d.ArgErr() + } + size, err := humanize.ParseBytes(d.Val()) + if err != nil { + return d.Errf("invalid max response header size '%s': %v", d.Val(), err) + } + h.MaxResponseHeaderSize = int64(size) + + case "proxy_protocol": + if !d.NextArg() { + return d.ArgErr() + } + switch proxyProtocol := d.Val(); proxyProtocol { + case "v1", "v2": + h.ProxyProtocol = proxyProtocol + default: + return d.Errf("invalid proxy protocol version '%s'", proxyProtocol) + } + + case "forward_proxy_url": + if !d.NextArg() { + return d.ArgErr() + } + h.ForwardProxyURL = d.Val() + + case "dial_timeout": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad timeout value '%s': %v", d.Val(), err) + } + h.DialTimeout = caddy.Duration(dur) + + case "dial_fallback_delay": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad fallback delay value '%s': %v", d.Val(), err) + } + h.FallbackDelay = caddy.Duration(dur) + + case "response_header_timeout": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad timeout value '%s': %v", d.Val(), err) + } + h.ResponseHeaderTimeout = caddy.Duration(dur) + + case "expect_continue_timeout": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad timeout value '%s': %v", d.Val(), err) + } + h.ExpectContinueTimeout = caddy.Duration(dur) + + case "resolvers": + if h.Resolver == nil { + h.Resolver = new(UpstreamResolver) + } + h.Resolver.Addresses = d.RemainingArgs() + if len(h.Resolver.Addresses) == 0 { + return d.Errf("must specify at least one resolver address") + } + + case "tls": + if h.TLS == nil { + h.TLS = new(TLSConfig) + } + + case "tls_client_auth": + if h.TLS == nil { + h.TLS = new(TLSConfig) + } + args := d.RemainingArgs() + switch len(args) { + case 1: + h.TLS.ClientCertificateAutomate = args[0] + case 2: + h.TLS.ClientCertificateFile = args[0] + h.TLS.ClientCertificateKeyFile = args[1] + default: + return d.ArgErr() + } + + case "tls_insecure_skip_verify": + if d.NextArg() { + return d.ArgErr() + } + if h.TLS == nil { + h.TLS = new(TLSConfig) + } + h.TLS.InsecureSkipVerify = true + + case "tls_curves": + args := d.RemainingArgs() + if len(args) == 0 { + return d.ArgErr() + } + if h.TLS == nil { + h.TLS = new(TLSConfig) + } + h.TLS.Curves = args + + case "tls_timeout": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad timeout value '%s': %v", d.Val(), err) + } + if h.TLS == nil { + h.TLS = new(TLSConfig) + } + h.TLS.HandshakeTimeout = caddy.Duration(dur) + + case "tls_trusted_ca_certs": + caddy.Log().Warn("The 'tls_trusted_ca_certs' field is deprecated. Use the 'tls_trust_pool' field instead.") + args := d.RemainingArgs() + if len(args) == 0 { + return d.ArgErr() + } + if h.TLS == nil { + h.TLS = new(TLSConfig) + } + if len(h.TLS.CARaw) != 0 { + return d.Err("cannot specify both 'tls_trust_pool' and 'tls_trusted_ca_certs") + } + h.TLS.RootCAPEMFiles = args + + case "tls_server_name": + if !d.NextArg() { + return d.ArgErr() + } + if h.TLS == nil { + h.TLS = new(TLSConfig) + } + h.TLS.ServerName = d.Val() + + case "tls_renegotiation": + if h.TLS == nil { + h.TLS = new(TLSConfig) + } + if !d.NextArg() { + return d.ArgErr() + } + switch renegotiation := d.Val(); renegotiation { + case "never", "once", "freely": + h.TLS.Renegotiation = renegotiation + default: + return d.ArgErr() + } + + case "tls_except_ports": + if h.TLS == nil { + h.TLS = new(TLSConfig) + } + h.TLS.ExceptPorts = d.RemainingArgs() + if len(h.TLS.ExceptPorts) == 0 { + return d.ArgErr() + } + + case "keepalive": + if !d.NextArg() { + return d.ArgErr() + } + if h.KeepAlive == nil { + h.KeepAlive = new(KeepAlive) + } + if d.Val() == "off" { + var disable bool + h.KeepAlive.Enabled = &disable + break + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad duration value '%s': %v", d.Val(), err) + } + h.KeepAlive.IdleConnTimeout = caddy.Duration(dur) + + case "keepalive_interval": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad interval value '%s': %v", d.Val(), err) + } + if h.KeepAlive == nil { + h.KeepAlive = new(KeepAlive) + } + h.KeepAlive.ProbeInterval = caddy.Duration(dur) + + case "keepalive_idle_conns": + if !d.NextArg() { + return d.ArgErr() + } + num, err := strconv.Atoi(d.Val()) + if err != nil { + return d.Errf("bad integer value '%s': %v", d.Val(), err) + } + if h.KeepAlive == nil { + h.KeepAlive = new(KeepAlive) + } + h.KeepAlive.MaxIdleConns = num + + case "keepalive_idle_conns_per_host": + if !d.NextArg() { + return d.ArgErr() + } + num, err := strconv.Atoi(d.Val()) + if err != nil { + return d.Errf("bad integer value '%s': %v", d.Val(), err) + } + if h.KeepAlive == nil { + h.KeepAlive = new(KeepAlive) + } + h.KeepAlive.MaxIdleConnsPerHost = num + + case "versions": + h.Versions = d.RemainingArgs() + if len(h.Versions) == 0 { + return d.ArgErr() + } + + case "compression": + if d.NextArg() { + if d.Val() == "off" { + var disable bool + h.Compression = &disable + } + } + + case "max_conns_per_host": + if !d.NextArg() { + return d.ArgErr() + } + num, err := strconv.Atoi(d.Val()) + if err != nil { + return d.Errf("bad integer value '%s': %v", d.Val(), err) + } + h.MaxConnsPerHost = num + + case "tls_trust_pool": + if !d.NextArg() { + return d.ArgErr() + } + modStem := d.Val() + modID := "tls.ca_pool.source." + modStem + unm, err := caddyfile.UnmarshalModule(d, modID) + if err != nil { + return err + } + ca, ok := unm.(caddytls.CA) + if !ok { + return d.Errf("module %s is not a caddytls.CA", modID) + } + if h.TLS == nil { + h.TLS = new(TLSConfig) + } + if len(h.TLS.RootCAPEMFiles) != 0 { + return d.Err("cannot specify both 'tls_trust_pool' and 'tls_trusted_ca_certs'") + } + if h.TLS.CARaw != nil { + return d.Err("cannot specify \"tls_trust_pool\" twice in caddyfile") + } + h.TLS.CARaw = caddyconfig.JSONModuleObject(ca, "provider", modStem, nil) + case "local_address": + if !d.NextArg() { + return d.ArgErr() + } + h.LocalAddress = d.Val() + default: + return d.Errf("unrecognized subdirective %s", d.Val()) + } + } + return nil +} + +func parseCopyResponseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + crh := new(CopyResponseHandler) + err := crh.UnmarshalCaddyfile(h.Dispenser) + if err != nil { + return nil, err + } + return crh, nil +} + +// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax: +// +// copy_response [] [] { +// status +// } +func (h *CopyResponseHandler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume directive name + + args := d.RemainingArgs() + if len(args) == 1 { + if num, err := strconv.Atoi(args[0]); err == nil && num > 0 { + h.StatusCode = caddyhttp.WeakString(args[0]) + return nil + } + } + + for d.NextBlock(0) { + switch d.Val() { + case "status": + if !d.NextArg() { + return d.ArgErr() + } + h.StatusCode = caddyhttp.WeakString(d.Val()) + default: + return d.Errf("unrecognized subdirective '%s'", d.Val()) + } + } + return nil +} + +func parseCopyResponseHeadersCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + crh := new(CopyResponseHeadersHandler) + err := crh.UnmarshalCaddyfile(h.Dispenser) + if err != nil { + return nil, err + } + return crh, nil +} + +// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax: +// +// copy_response_headers [] { +// include +// exclude +// } +func (h *CopyResponseHeadersHandler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume directive name + + args := d.RemainingArgs() + if len(args) > 0 { + return d.ArgErr() + } + + for d.NextBlock(0) { + switch d.Val() { + case "include": + h.Include = append(h.Include, d.RemainingArgs()...) + + case "exclude": + h.Exclude = append(h.Exclude, d.RemainingArgs()...) + + default: + return d.Errf("unrecognized subdirective '%s'", d.Val()) + } + } + return nil +} + +// UnmarshalCaddyfile deserializes Caddyfile tokens into h. +// +// dynamic srv [] { +// service +// proto +// name +// refresh +// resolvers +// dial_timeout +// dial_fallback_delay +// grace_period +// } +func (u *SRVUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume upstream source name + + args := d.RemainingArgs() + if len(args) > 1 { + return d.ArgErr() + } + if len(args) > 0 { + u.Name = args[0] + } + + for d.NextBlock(0) { + switch d.Val() { + case "service": + if !d.NextArg() { + return d.ArgErr() + } + if u.Service != "" { + return d.Errf("srv service has already been specified") + } + u.Service = d.Val() + + case "proto": + if !d.NextArg() { + return d.ArgErr() + } + if u.Proto != "" { + return d.Errf("srv proto has already been specified") + } + u.Proto = d.Val() + + case "name": + if !d.NextArg() { + return d.ArgErr() + } + if u.Name != "" { + return d.Errf("srv name has already been specified") + } + u.Name = d.Val() + + case "refresh": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("parsing refresh interval duration: %v", err) + } + u.Refresh = caddy.Duration(dur) + + case "resolvers": + if u.Resolver == nil { + u.Resolver = new(UpstreamResolver) + } + u.Resolver.Addresses = d.RemainingArgs() + if len(u.Resolver.Addresses) == 0 { + return d.Errf("must specify at least one resolver address") + } + + case "dial_timeout": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad timeout value '%s': %v", d.Val(), err) + } + u.DialTimeout = caddy.Duration(dur) + + case "dial_fallback_delay": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad delay value '%s': %v", d.Val(), err) + } + u.FallbackDelay = caddy.Duration(dur) + case "grace_period": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad grace period value '%s': %v", d.Val(), err) + } + u.GracePeriod = caddy.Duration(dur) + default: + return d.Errf("unrecognized srv option '%s'", d.Val()) + } + } + return nil +} + +// UnmarshalCaddyfile deserializes Caddyfile tokens into h. +// +// dynamic a [ +// port +// refresh +// resolvers +// dial_timeout +// dial_fallback_delay +// versions ipv4|ipv6 +// } +func (u *AUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume upstream source name + + args := d.RemainingArgs() + if len(args) > 2 { + return d.ArgErr() + } + if len(args) > 0 { + u.Name = args[0] + if len(args) == 2 { + u.Port = args[1] + } + } + + for d.NextBlock(0) { + switch d.Val() { + case "name": + if !d.NextArg() { + return d.ArgErr() + } + if u.Name != "" { + return d.Errf("a name has already been specified") + } + u.Name = d.Val() + + case "port": + if !d.NextArg() { + return d.ArgErr() + } + if u.Port != "" { + return d.Errf("a port has already been specified") + } + u.Port = d.Val() + + case "refresh": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("parsing refresh interval duration: %v", err) + } + u.Refresh = caddy.Duration(dur) + + case "resolvers": + if u.Resolver == nil { + u.Resolver = new(UpstreamResolver) + } + u.Resolver.Addresses = d.RemainingArgs() + if len(u.Resolver.Addresses) == 0 { + return d.Errf("must specify at least one resolver address") + } + + case "dial_timeout": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad timeout value '%s': %v", d.Val(), err) + } + u.DialTimeout = caddy.Duration(dur) + + case "dial_fallback_delay": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad delay value '%s': %v", d.Val(), err) + } + u.FallbackDelay = caddy.Duration(dur) + + case "versions": + args := d.RemainingArgs() + if len(args) == 0 { + return d.Errf("must specify at least one version") + } + + if u.Versions == nil { + u.Versions = &IPVersions{} + } + + trueBool := true + for _, arg := range args { + switch arg { + case "ipv4": + u.Versions.IPv4 = &trueBool + case "ipv6": + u.Versions.IPv6 = &trueBool + default: + return d.Errf("unsupported version: '%s'", arg) + } + } + + default: + return d.Errf("unrecognized a option '%s'", d.Val()) + } + } + return nil +} + +// UnmarshalCaddyfile deserializes Caddyfile tokens into h. +// +// dynamic multi { +// [...] +// } +func (u *MultiUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume upstream source name + + if d.NextArg() { + return d.ArgErr() + } + + for d.NextBlock(0) { + dynModule := d.Val() + modID := "http.reverse_proxy.upstreams." + dynModule + unm, err := caddyfile.UnmarshalModule(d, modID) + if err != nil { + return err + } + source, ok := unm.(UpstreamSource) + if !ok { + return d.Errf("module %s (%T) is not an UpstreamSource", modID, unm) + } + u.SourcesRaw = append(u.SourcesRaw, caddyconfig.JSONModuleObject(source, "source", dynModule, nil)) + } + return nil +} + +const matcherPrefix = "@" + +// Interface guards +var ( + _ caddyfile.Unmarshaler = (*Handler)(nil) + _ caddyfile.Unmarshaler = (*HTTPTransport)(nil) + _ caddyfile.Unmarshaler = (*SRVUpstreams)(nil) + _ caddyfile.Unmarshaler = (*AUpstreams)(nil) + _ caddyfile.Unmarshaler = (*MultiUpstreams)(nil) +) diff --git a/modules/caddyhttp/reverseproxy/command.go b/modules/caddyhttp/reverseproxy/command.go new file mode 100644 index 00000000000..f9304efa20b --- /dev/null +++ b/modules/caddyhttp/reverseproxy/command.go @@ -0,0 +1,319 @@ +// 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 reverseproxy + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + "strings" + + "github.com/spf13/cobra" + "go.uber.org/zap" + + caddycmd "github.com/caddyserver/caddy/v2/cmd" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/headers" + "github.com/caddyserver/caddy/v2/modules/caddytls" +) + +func init() { + caddycmd.RegisterCommand(caddycmd.Command{ + Name: "reverse-proxy", + Usage: `[--from ] [--to ] [--change-host-header] [--insecure] [--internal-certs] [--disable-redirects] [--header-up "Field: value"] [--header-down "Field: value"] [--access-log] [--debug]`, + Short: "A quick and production-ready reverse proxy", + Long: ` +A simple but production-ready reverse proxy. Useful for quick deployments, +demos, and development. + +Simply shuttles HTTP(S) traffic from the --from address to the --to address. +Multiple --to addresses may be specified by repeating the flag. + +Unless otherwise specified in the addresses, the --from address will be +assumed to be HTTPS if a hostname is given, and the --to address will be +assumed to be HTTP. + +If the --from address has a host or IP, Caddy will attempt to serve the +proxy over HTTPS with a certificate (unless overridden by the HTTP scheme +or port). + +If serving HTTPS: + --disable-redirects can be used to avoid binding to the HTTP port. + --internal-certs can be used to force issuance certs using the internal + CA instead of attempting to issue a public certificate. + +For proxying: + --header-up can be used to set a request header to send to the upstream. + --header-down can be used to set a response header to send back to the client. + --change-host-header sets the Host header on the request to the address + of the upstream, instead of defaulting to the incoming Host header. + This is a shortcut for --header-up "Host: {http.reverse_proxy.upstream.hostport}". + --insecure disables TLS verification with the upstream. WARNING: THIS + DISABLES SECURITY BY NOT VERIFYING THE UPSTREAM'S CERTIFICATE. +`, + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("from", "f", "localhost", "Address on which to receive traffic") + cmd.Flags().StringSliceP("to", "t", []string{}, "Upstream address(es) to which traffic should be sent") + cmd.Flags().BoolP("change-host-header", "c", false, "Set upstream Host header to address of upstream") + cmd.Flags().BoolP("insecure", "", false, "Disable TLS verification (WARNING: DISABLES SECURITY BY NOT VERIFYING TLS CERTIFICATES!)") + cmd.Flags().BoolP("disable-redirects", "r", false, "Disable HTTP->HTTPS redirects") + cmd.Flags().BoolP("internal-certs", "i", false, "Use internal CA for issuing certs") + cmd.Flags().StringSliceP("header-up", "H", []string{}, "Set a request header to send to the upstream (format: \"Field: value\")") + cmd.Flags().StringSliceP("header-down", "d", []string{}, "Set a response header to send back to the client (format: \"Field: value\")") + cmd.Flags().BoolP("access-log", "", false, "Enable the access log") + cmd.Flags().BoolP("debug", "v", false, "Enable verbose debug logs") + cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdReverseProxy) + }, + }) +} + +func cmdReverseProxy(fs caddycmd.Flags) (int, error) { + caddy.TrapSignals() + + from := fs.String("from") + changeHost := fs.Bool("change-host-header") + insecure := fs.Bool("insecure") + disableRedir := fs.Bool("disable-redirects") + internalCerts := fs.Bool("internal-certs") + accessLog := fs.Bool("access-log") + debug := fs.Bool("debug") + + httpPort := strconv.Itoa(caddyhttp.DefaultHTTPPort) + httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPSPort) + + to, err := fs.GetStringSlice("to") + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid to flag: %v", err) + } + if len(to) == 0 { + return caddy.ExitCodeFailedStartup, fmt.Errorf("--to is required") + } + + // set up the downstream address; assume missing information from given parts + fromAddr, err := httpcaddyfile.ParseAddress(from) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid downstream address %s: %v", from, err) + } + if fromAddr.Path != "" { + return caddy.ExitCodeFailedStartup, fmt.Errorf("paths are not allowed: %s", from) + } + if fromAddr.Scheme == "" { + if fromAddr.Port == httpPort || fromAddr.Host == "" { + fromAddr.Scheme = "http" + } else { + fromAddr.Scheme = "https" + } + } + if fromAddr.Port == "" { + if fromAddr.Scheme == "http" { + fromAddr.Port = httpPort + } else if fromAddr.Scheme == "https" { + fromAddr.Port = httpsPort + } + } + + // set up the upstream address; assume missing information from given parts + // mixing schemes isn't supported, so use first defined (if available) + toAddresses := make([]string, len(to)) + var toScheme string + for i, toLoc := range to { + addr, err := parseUpstreamDialAddress(toLoc) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid upstream address %s: %v", toLoc, err) + } + if addr.scheme != "" && toScheme == "" { + toScheme = addr.scheme + } + toAddresses[i] = addr.dialAddr() + } + + // proceed to build the handler and server + ht := HTTPTransport{} + if toScheme == "https" { + ht.TLS = new(TLSConfig) + if insecure { + ht.TLS.InsecureSkipVerify = true + } + } + + upstreamPool := UpstreamPool{} + for _, toAddr := range toAddresses { + parsedAddr, err := caddy.ParseNetworkAddress(toAddr) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid upstream address %s: %v", toAddr, err) + } + + if parsedAddr.StartPort == 0 && parsedAddr.EndPort == 0 { + // unix networks don't have ports + upstreamPool = append(upstreamPool, &Upstream{ + Dial: toAddr, + }) + } else { + // expand a port range into multiple upstreams + for i := parsedAddr.StartPort; i <= parsedAddr.EndPort; i++ { + upstreamPool = append(upstreamPool, &Upstream{ + Dial: caddy.JoinNetworkAddress("", parsedAddr.Host, fmt.Sprint(i)), + }) + } + } + } + + handler := Handler{ + TransportRaw: caddyconfig.JSONModuleObject(ht, "protocol", "http", nil), + Upstreams: upstreamPool, + } + + // set up header_up + headerUp, err := fs.GetStringSlice("header-up") + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid header flag: %v", err) + } + if len(headerUp) > 0 { + reqHdr := make(http.Header) + for i, h := range headerUp { + key, val, found := strings.Cut(h, ":") + key, val = strings.TrimSpace(key), strings.TrimSpace(val) + if !found || key == "" || val == "" { + return caddy.ExitCodeFailedStartup, fmt.Errorf("header-up %d: invalid format \"%s\" (expecting \"Field: value\")", i, h) + } + reqHdr.Set(key, val) + } + handler.Headers = &headers.Handler{ + Request: &headers.HeaderOps{ + Set: reqHdr, + }, + } + } + + // set up header_down + headerDown, err := fs.GetStringSlice("header-down") + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid header flag: %v", err) + } + if len(headerDown) > 0 { + respHdr := make(http.Header) + for i, h := range headerDown { + key, val, found := strings.Cut(h, ":") + key, val = strings.TrimSpace(key), strings.TrimSpace(val) + if !found || key == "" || val == "" { + return caddy.ExitCodeFailedStartup, fmt.Errorf("header-down %d: invalid format \"%s\" (expecting \"Field: value\")", i, h) + } + respHdr.Set(key, val) + } + if handler.Headers == nil { + handler.Headers = &headers.Handler{} + } + handler.Headers.Response = &headers.RespHeaderOps{ + HeaderOps: &headers.HeaderOps{ + Set: respHdr, + }, + } + } + + if changeHost { + if handler.Headers == nil { + handler.Headers = new(headers.Handler) + } + if handler.Headers.Request == nil { + handler.Headers.Request = new(headers.HeaderOps) + } + if handler.Headers.Request.Set == nil { + handler.Headers.Request.Set = http.Header{} + } + handler.Headers.Request.Set.Set("Host", "{http.reverse_proxy.upstream.hostport}") + } + + route := caddyhttp.Route{ + HandlersRaw: []json.RawMessage{ + caddyconfig.JSONModuleObject(handler, "handler", "reverse_proxy", nil), + }, + } + if fromAddr.Host != "" { + route.MatcherSetsRaw = []caddy.ModuleMap{ + { + "host": caddyconfig.JSON(caddyhttp.MatchHost{fromAddr.Host}, nil), + }, + } + } + + server := &caddyhttp.Server{ + Routes: caddyhttp.RouteList{route}, + Listen: []string{":" + fromAddr.Port}, + } + if accessLog { + server.Logs = &caddyhttp.ServerLogConfig{} + } + + if fromAddr.Scheme == "http" { + server.AutoHTTPS = &caddyhttp.AutoHTTPSConfig{Disabled: true} + } else if disableRedir { + server.AutoHTTPS = &caddyhttp.AutoHTTPSConfig{DisableRedir: true} + } + + httpApp := caddyhttp.App{ + Servers: map[string]*caddyhttp.Server{"proxy": server}, + } + + appsRaw := caddy.ModuleMap{ + "http": caddyconfig.JSON(httpApp, nil), + } + if internalCerts && fromAddr.Host != "" { + tlsApp := caddytls.TLS{ + Automation: &caddytls.AutomationConfig{ + Policies: []*caddytls.AutomationPolicy{{ + SubjectsRaw: []string{fromAddr.Host}, + IssuersRaw: []json.RawMessage{json.RawMessage(`{"module":"internal"}`)}, + }}, + }, + } + appsRaw["tls"] = caddyconfig.JSON(tlsApp, nil) + } + + var false bool + cfg := &caddy.Config{ + Admin: &caddy.AdminConfig{ + Disabled: true, + Config: &caddy.ConfigSettings{ + Persist: &false, + }, + }, + AppsRaw: appsRaw, + } + + if debug { + cfg.Logging = &caddy.Logging{ + Logs: map[string]*caddy.CustomLog{ + "default": {BaseLog: caddy.BaseLog{Level: zap.DebugLevel.CapitalString()}}, + }, + } + } + + err = caddy.Run(cfg) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + caddy.Log().Info("caddy proxying", zap.String("from", fromAddr.String()), zap.Strings("to", toAddresses)) + if len(toAddresses) > 1 { + caddy.Log().Info("using default load balancing policy", zap.String("policy", "random")) + } + + select {} +} diff --git a/modules/caddyhttp/reverseproxy/copyresponse.go b/modules/caddyhttp/reverseproxy/copyresponse.go new file mode 100644 index 00000000000..c1c9de92ba8 --- /dev/null +++ b/modules/caddyhttp/reverseproxy/copyresponse.go @@ -0,0 +1,190 @@ +// 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 reverseproxy + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func init() { + caddy.RegisterModule(CopyResponseHandler{}) + caddy.RegisterModule(CopyResponseHeadersHandler{}) +} + +// CopyResponseHandler is a special HTTP handler which may +// only be used within reverse_proxy's handle_response routes, +// to copy the proxy response. EXPERIMENTAL, subject to change. +type CopyResponseHandler struct { + // To write the upstream response's body but with a different + // status code, set this field to the desired status code. + StatusCode caddyhttp.WeakString `json:"status_code,omitempty"` + + ctx caddy.Context +} + +// CaddyModule returns the Caddy module information. +func (CopyResponseHandler) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.copy_response", + New: func() caddy.Module { return new(CopyResponseHandler) }, + } +} + +// Provision ensures that h is set up properly before use. +func (h *CopyResponseHandler) Provision(ctx caddy.Context) error { + h.ctx = ctx + return nil +} + +// ServeHTTP implements the Handler interface. +func (h CopyResponseHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request, _ caddyhttp.Handler) error { + repl := req.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + hrc, ok := req.Context().Value(proxyHandleResponseContextCtxKey).(*handleResponseContext) + + // don't allow this to be used outside of handle_response routes + if !ok { + return caddyhttp.Error(http.StatusInternalServerError, + fmt.Errorf("cannot use 'copy_response' outside of reverse_proxy's handle_response routes")) + } + + // allow a custom status code to be written; otherwise the + // status code from the upstream response is written + if codeStr := h.StatusCode.String(); codeStr != "" { + intVal, err := strconv.Atoi(repl.ReplaceAll(codeStr, "")) + if err != nil { + return caddyhttp.Error(http.StatusInternalServerError, err) + } + hrc.response.StatusCode = intVal + } + + // make sure the reverse_proxy handler doesn't try to call + // finalizeResponse again after we've already done it here. + hrc.isFinalized = true + + // write the response + return hrc.handler.finalizeResponse(rw, req, hrc.response, repl, hrc.start, hrc.logger) +} + +// CopyResponseHeadersHandler is a special HTTP handler which may +// only be used within reverse_proxy's handle_response routes, +// to copy headers from the proxy response. EXPERIMENTAL; +// subject to change. +type CopyResponseHeadersHandler struct { + // A list of header fields to copy from the response. + // Cannot be defined at the same time as Exclude. + Include []string `json:"include,omitempty"` + + // A list of header fields to skip copying from the response. + // Cannot be defined at the same time as Include. + Exclude []string `json:"exclude,omitempty"` + + includeMap map[string]struct{} + excludeMap map[string]struct{} + ctx caddy.Context +} + +// CaddyModule returns the Caddy module information. +func (CopyResponseHeadersHandler) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.copy_response_headers", + New: func() caddy.Module { return new(CopyResponseHeadersHandler) }, + } +} + +// Validate ensures the h's configuration is valid. +func (h *CopyResponseHeadersHandler) Validate() error { + if len(h.Exclude) > 0 && len(h.Include) > 0 { + return fmt.Errorf("cannot define both 'exclude' and 'include' lists at the same time") + } + + return nil +} + +// Provision ensures that h is set up properly before use. +func (h *CopyResponseHeadersHandler) Provision(ctx caddy.Context) error { + h.ctx = ctx + + // Optimize the include list by converting it to a map + if len(h.Include) > 0 { + h.includeMap = map[string]struct{}{} + } + for _, field := range h.Include { + h.includeMap[http.CanonicalHeaderKey(field)] = struct{}{} + } + + // Optimize the exclude list by converting it to a map + if len(h.Exclude) > 0 { + h.excludeMap = map[string]struct{}{} + } + for _, field := range h.Exclude { + h.excludeMap[http.CanonicalHeaderKey(field)] = struct{}{} + } + + return nil +} + +// ServeHTTP implements the Handler interface. +func (h CopyResponseHeadersHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next caddyhttp.Handler) error { + hrc, ok := req.Context().Value(proxyHandleResponseContextCtxKey).(*handleResponseContext) + + // don't allow this to be used outside of handle_response routes + if !ok { + return caddyhttp.Error(http.StatusInternalServerError, + fmt.Errorf("cannot use 'copy_response_headers' outside of reverse_proxy's handle_response routes")) + } + + for field, values := range hrc.response.Header { + // Check the include list first, skip + // the header if it's _not_ in this list. + if len(h.includeMap) > 0 { + if _, ok := h.includeMap[field]; !ok { + continue + } + } + + // Then, check the exclude list, skip + // the header if it _is_ in this list. + if len(h.excludeMap) > 0 { + if _, ok := h.excludeMap[field]; ok { + continue + } + } + + // Copy all the values for the header. + for _, value := range values { + rw.Header().Add(field, value) + } + } + + return next.ServeHTTP(rw, req) +} + +// Interface guards +var ( + _ caddyhttp.MiddlewareHandler = (*CopyResponseHandler)(nil) + _ caddyfile.Unmarshaler = (*CopyResponseHandler)(nil) + _ caddy.Provisioner = (*CopyResponseHandler)(nil) + + _ caddyhttp.MiddlewareHandler = (*CopyResponseHeadersHandler)(nil) + _ caddyfile.Unmarshaler = (*CopyResponseHeadersHandler)(nil) + _ caddy.Provisioner = (*CopyResponseHeadersHandler)(nil) + _ caddy.Validator = (*CopyResponseHeadersHandler)(nil) +) diff --git a/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go b/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go new file mode 100644 index 00000000000..5db73a4a23e --- /dev/null +++ b/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go @@ -0,0 +1,447 @@ +// 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 fastcgi + +import ( + "encoding/json" + "net/http" + "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/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/fileserver" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite" +) + +func init() { + httpcaddyfile.RegisterDirective("php_fastcgi", parsePHPFastCGI) +} + +// UnmarshalCaddyfile deserializes Caddyfile tokens into h. +// +// transport fastcgi { +// root +// split +// env +// resolve_root_symlink +// dial_timeout +// read_timeout +// write_timeout +// capture_stderr +// } +func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume transport name + for d.NextBlock(0) { + switch d.Val() { + case "root": + if !d.NextArg() { + return d.ArgErr() + } + t.Root = d.Val() + + case "split": + t.SplitPath = d.RemainingArgs() + if len(t.SplitPath) == 0 { + return d.ArgErr() + } + + case "env": + args := d.RemainingArgs() + if len(args) != 2 { + return d.ArgErr() + } + if t.EnvVars == nil { + t.EnvVars = make(map[string]string) + } + t.EnvVars[args[0]] = args[1] + + case "resolve_root_symlink": + if d.NextArg() { + return d.ArgErr() + } + t.ResolveRootSymlink = true + + case "dial_timeout": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad timeout value %s: %v", d.Val(), err) + } + t.DialTimeout = caddy.Duration(dur) + + case "read_timeout": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad timeout value %s: %v", d.Val(), err) + } + t.ReadTimeout = caddy.Duration(dur) + + case "write_timeout": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad timeout value %s: %v", d.Val(), err) + } + t.WriteTimeout = caddy.Duration(dur) + + case "capture_stderr": + if d.NextArg() { + return d.ArgErr() + } + t.CaptureStderr = true + + default: + return d.Errf("unrecognized subdirective %s", d.Val()) + } + } + return nil +} + +// parsePHPFastCGI parses the php_fastcgi directive, which has the same syntax +// as the reverse_proxy directive (in fact, the reverse_proxy's directive +// Unmarshaler is invoked by this function) but the resulting proxy is specially +// configured for most™️ PHP apps over FastCGI. A line such as this: +// +// php_fastcgi localhost:7777 +// +// is equivalent to a route consisting of: +// +// # Add trailing slash for directory requests +// # This redirection is automatically disabled if "{http.request.uri.path}/index.php" +// # doesn't appear in the try_files list +// @canonicalPath { +// file {path}/index.php +// not path */ +// } +// redir @canonicalPath {path}/ 308 +// +// # If the requested file does not exist, try index files and assume index.php always exists +// @indexFiles file { +// try_files {path} {path}/index.php index.php +// try_policy first_exist_fallback +// split_path .php +// } +// rewrite @indexFiles {http.matchers.file.relative} +// +// # Proxy PHP files to the FastCGI responder +// @phpFiles path *.php +// reverse_proxy @phpFiles localhost:7777 { +// transport fastcgi { +// split .php +// } +// } +// +// Thus, this directive produces multiple handlers, each with a different +// matcher because multiple consecutive handlers are necessary to support +// the common PHP use case. If this "common" config is not compatible +// with a user's PHP requirements, they can use a manual approach based +// on the example above to configure it precisely as they need. +// +// If a matcher is specified by the user, for example: +// +// php_fastcgi /subpath localhost:7777 +// +// then the resulting handlers are wrapped in a subroute that uses the +// user's matcher as a prerequisite to enter the subroute. In other +// words, the directive's matcher is necessary, but not sufficient. +func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) { + if !h.Next() { + return nil, h.ArgErr() + } + + // set up the transport for FastCGI, and specifically PHP + fcgiTransport := Transport{} + + // set up the set of file extensions allowed to execute PHP code + extensions := []string{".php"} + + // set the default index file for the try_files rewrites + indexFile := "index.php" + + // set up for explicitly overriding try_files + var tryFiles []string + + // if the user specified a matcher token, use that + // matcher in a route that wraps both of our routes; + // either way, strip the matcher token and pass + // the remaining tokens to the unmarshaler so that + // we can gain the rest of the reverse_proxy syntax + userMatcherSet, err := h.ExtractMatcherSet() + if err != nil { + return nil, err + } + + // make a new dispenser from the remaining tokens so that we + // can reset the dispenser back to this point for the + // reverse_proxy unmarshaler to read from it as well + dispenser := h.NewFromNextSegment() + + // read the subdirectives that we allow as overrides to + // the php_fastcgi shortcut + // NOTE: we delete the tokens as we go so that the reverse_proxy + // unmarshal doesn't see these subdirectives which it cannot handle + for dispenser.Next() { + for dispenser.NextBlock(0) { + // ignore any sub-subdirectives that might + // have the same name somewhere within + // the reverse_proxy passthrough tokens + if dispenser.Nesting() != 1 { + continue + } + + // parse the php_fastcgi subdirectives + switch dispenser.Val() { + case "root": + if !dispenser.NextArg() { + return nil, dispenser.ArgErr() + } + fcgiTransport.Root = dispenser.Val() + dispenser.DeleteN(2) + + case "split": + extensions = dispenser.RemainingArgs() + dispenser.DeleteN(len(extensions) + 1) + if len(extensions) == 0 { + return nil, dispenser.ArgErr() + } + + case "env": + args := dispenser.RemainingArgs() + dispenser.DeleteN(len(args) + 1) + if len(args) != 2 { + return nil, dispenser.ArgErr() + } + if fcgiTransport.EnvVars == nil { + fcgiTransport.EnvVars = make(map[string]string) + } + fcgiTransport.EnvVars[args[0]] = args[1] + + case "index": + args := dispenser.RemainingArgs() + dispenser.DeleteN(len(args) + 1) + if len(args) != 1 { + return nil, dispenser.ArgErr() + } + indexFile = args[0] + + case "try_files": + args := dispenser.RemainingArgs() + dispenser.DeleteN(len(args) + 1) + if len(args) < 1 { + return nil, dispenser.ArgErr() + } + tryFiles = args + + case "resolve_root_symlink": + args := dispenser.RemainingArgs() + dispenser.DeleteN(len(args) + 1) + fcgiTransport.ResolveRootSymlink = true + + case "dial_timeout": + if !dispenser.NextArg() { + return nil, dispenser.ArgErr() + } + dur, err := caddy.ParseDuration(dispenser.Val()) + if err != nil { + return nil, dispenser.Errf("bad timeout value %s: %v", dispenser.Val(), err) + } + fcgiTransport.DialTimeout = caddy.Duration(dur) + dispenser.DeleteN(2) + + case "read_timeout": + if !dispenser.NextArg() { + return nil, dispenser.ArgErr() + } + dur, err := caddy.ParseDuration(dispenser.Val()) + if err != nil { + return nil, dispenser.Errf("bad timeout value %s: %v", dispenser.Val(), err) + } + fcgiTransport.ReadTimeout = caddy.Duration(dur) + dispenser.DeleteN(2) + + case "write_timeout": + if !dispenser.NextArg() { + return nil, dispenser.ArgErr() + } + dur, err := caddy.ParseDuration(dispenser.Val()) + if err != nil { + return nil, dispenser.Errf("bad timeout value %s: %v", dispenser.Val(), err) + } + fcgiTransport.WriteTimeout = caddy.Duration(dur) + dispenser.DeleteN(2) + + case "capture_stderr": + args := dispenser.RemainingArgs() + dispenser.DeleteN(len(args) + 1) + fcgiTransport.CaptureStderr = true + } + } + } + + // reset the dispenser after we're done so that the reverse_proxy + // unmarshaler can read it from the start + dispenser.Reset() + + // set up a route list that we'll append to + routes := caddyhttp.RouteList{} + + // set the list of allowed path segments on which to split + fcgiTransport.SplitPath = extensions + + // if the index is turned off, we skip the redirect and try_files + if indexFile != "off" { + dirRedir := false + dirIndex := "{http.request.uri.path}/" + indexFile + tryPolicy := "first_exist_fallback" + + // if tryFiles wasn't overridden, use a reasonable default + if len(tryFiles) == 0 { + tryFiles = []string{"{http.request.uri.path}", dirIndex, indexFile} + dirRedir = true + } else { + if !strings.HasSuffix(tryFiles[len(tryFiles)-1], ".php") { + // use first_exist strategy if the last file is not a PHP file + tryPolicy = "" + } + + for _, tf := range tryFiles { + if tf == dirIndex { + dirRedir = true + + break + } + } + } + + if dirRedir { + // route to redirect to canonical path if index PHP file + redirMatcherSet := caddy.ModuleMap{ + "file": h.JSON(fileserver.MatchFile{ + TryFiles: []string{dirIndex}, + }), + "not": h.JSON(caddyhttp.MatchNot{ + MatcherSetsRaw: []caddy.ModuleMap{ + { + "path": h.JSON(caddyhttp.MatchPath{"*/"}), + }, + }, + }), + } + redirHandler := caddyhttp.StaticResponse{ + StatusCode: caddyhttp.WeakString(strconv.Itoa(http.StatusPermanentRedirect)), + Headers: http.Header{"Location": []string{"{http.request.orig_uri.path}/{http.request.orig_uri.prefixed_query}"}}, + } + redirRoute := caddyhttp.Route{ + MatcherSetsRaw: []caddy.ModuleMap{redirMatcherSet}, + HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(redirHandler, "handler", "static_response", nil)}, + } + + routes = append(routes, redirRoute) + } + + // route to rewrite to PHP index file + rewriteMatcherSet := caddy.ModuleMap{ + "file": h.JSON(fileserver.MatchFile{ + TryFiles: tryFiles, + TryPolicy: tryPolicy, + SplitPath: extensions, + }), + } + rewriteHandler := rewrite.Rewrite{ + URI: "{http.matchers.file.relative}", + } + rewriteRoute := caddyhttp.Route{ + MatcherSetsRaw: []caddy.ModuleMap{rewriteMatcherSet}, + HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(rewriteHandler, "handler", "rewrite", nil)}, + } + + routes = append(routes, rewriteRoute) + } + + // route to actually reverse proxy requests to PHP files; + // match only requests that are for PHP files + pathList := []string{} + for _, ext := range extensions { + pathList = append(pathList, "*"+ext) + } + rpMatcherSet := caddy.ModuleMap{ + "path": h.JSON(pathList), + } + + // create the reverse proxy handler which uses our FastCGI transport + rpHandler := &reverseproxy.Handler{ + TransportRaw: caddyconfig.JSONModuleObject(fcgiTransport, "protocol", "fastcgi", nil), + } + + // the rest of the config is specified by the user + // using the reverse_proxy directive syntax + dispenser.Next() // consume the directive name + err = rpHandler.UnmarshalCaddyfile(dispenser) + if err != nil { + return nil, err + } + err = rpHandler.FinalizeUnmarshalCaddyfile(h) + if err != nil { + return nil, err + } + + // create the final reverse proxy route which is + // conditional on matching PHP files + rpRoute := caddyhttp.Route{ + MatcherSetsRaw: []caddy.ModuleMap{rpMatcherSet}, + HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(rpHandler, "handler", "reverse_proxy", nil)}, + } + + subroute := caddyhttp.Subroute{ + Routes: append(routes, rpRoute), + } + + // the user's matcher is a prerequisite for ours, so + // wrap ours in a subroute and return that + if userMatcherSet != nil { + return []httpcaddyfile.ConfigValue{ + { + Class: "route", + Value: caddyhttp.Route{ + MatcherSetsRaw: []caddy.ModuleMap{userMatcherSet}, + HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(subroute, "handler", "subroute", nil)}, + }, + }, + }, nil + } + + // otherwise, return the literal subroute instead of + // individual routes, to ensure they stay together and + // are treated as a single unit, without necessarily + // creating an actual subroute in the output + return []httpcaddyfile.ConfigValue{ + { + Class: "route", + Value: subroute, + }, + }, nil +} diff --git a/modules/caddyhttp/reverseproxy/fastcgi/client.go b/modules/caddyhttp/reverseproxy/fastcgi/client.go new file mode 100644 index 00000000000..684394f53ca --- /dev/null +++ b/modules/caddyhttp/reverseproxy/fastcgi/client.go @@ -0,0 +1,381 @@ +// 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. + +// Forked Jan. 2015 from http://bitbucket.org/PinIdea/fcgi_client +// (which is forked from https://code.google.com/p/go-fastcgi-client/). +// This fork contains several fixes and improvements by Matt Holt and +// other contributors to the Caddy project. + +// Copyright 2012 Junqing Tan and The Go Authors +// Use of this source code is governed by a BSD-style +// Part of source code is from Go fcgi package + +package fastcgi + +import ( + "bufio" + "bytes" + "io" + "mime/multipart" + "net" + "net/http" + "net/http/httputil" + "net/textproto" + "net/url" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +// FCGIListenSockFileno describes listen socket file number. +const FCGIListenSockFileno uint8 = 0 + +// FCGIHeaderLen describes header length. +const FCGIHeaderLen uint8 = 8 + +// Version1 describes the version. +const Version1 uint8 = 1 + +// FCGINullRequestID describes the null request ID. +const FCGINullRequestID uint8 = 0 + +// FCGIKeepConn describes keep connection mode. +const FCGIKeepConn uint8 = 1 + +const ( + // BeginRequest is the begin request flag. + BeginRequest uint8 = iota + 1 + // AbortRequest is the abort request flag. + AbortRequest + // EndRequest is the end request flag. + EndRequest + // Params is the parameters flag. + Params + // Stdin is the standard input flag. + Stdin + // Stdout is the standard output flag. + Stdout + // Stderr is the standard error flag. + Stderr + // Data is the data flag. + Data + // GetValues is the get values flag. + GetValues + // GetValuesResult is the get values result flag. + GetValuesResult + // UnknownType is the unknown type flag. + UnknownType + // MaxType is the maximum type flag. + MaxType = UnknownType +) + +const ( + // Responder is the responder flag. + Responder uint8 = iota + 1 + // Authorizer is the authorizer flag. + Authorizer + // Filter is the filter flag. + Filter +) + +const ( + // RequestComplete is the completed request flag. + RequestComplete uint8 = iota + // CantMultiplexConns is the multiplexed connections flag. + CantMultiplexConns + // Overloaded is the overloaded flag. + Overloaded + // UnknownRole is the unknown role flag. + UnknownRole +) + +const ( + // MaxConns is the maximum connections flag. + MaxConns string = "MAX_CONNS" + // MaxRequests is the maximum requests flag. + MaxRequests string = "MAX_REQS" + // MultiplexConns is the multiplex connections flag. + MultiplexConns string = "MPXS_CONNS" +) + +const ( + maxWrite = 65500 // 65530 may work, but for compatibility + maxPad = 255 +) + +// for padding so we don't have to allocate all the time +// not synchronized because we don't care what the contents are +var pad [maxPad]byte + +// client implements a FastCGI client, which is a standard for +// interfacing external applications with Web servers. +type client struct { + rwc net.Conn + // keepAlive bool // TODO: implement + reqID uint16 + stderr bool + logger *zap.Logger +} + +// Do made the request and returns a io.Reader that translates the data read +// from fcgi responder out of fcgi packet before returning it. +func (c *client) Do(p map[string]string, req io.Reader) (r io.Reader, err error) { + // check for CONTENT_LENGTH, since the lack of it or wrong value will cause the backend to hang + if clStr, ok := p["CONTENT_LENGTH"]; !ok { + return nil, caddyhttp.Error(http.StatusLengthRequired, nil) + } else if _, err := strconv.ParseUint(clStr, 10, 64); err != nil { + // stdlib won't return a negative Content-Length, but we check just in case, + // the most likely cause is from a missing content length, which is -1 + return nil, caddyhttp.Error(http.StatusLengthRequired, err) + } + + writer := &streamWriter{c: c} + writer.buf = bufPool.Get().(*bytes.Buffer) + writer.buf.Reset() + defer bufPool.Put(writer.buf) + + err = writer.writeBeginRequest(uint16(Responder), 0) + if err != nil { + return + } + + writer.recType = Params + err = writer.writePairs(p) + if err != nil { + return + } + + writer.recType = Stdin + if req != nil { + _, err = io.Copy(writer, req) + if err != nil { + return nil, err + } + } + err = writer.FlushStream() + if err != nil { + return nil, err + } + + r = &streamReader{c: c} + return +} + +// clientCloser is a io.ReadCloser. It wraps a io.Reader with a Closer +// that closes the client connection. +type clientCloser struct { + rwc net.Conn + r *streamReader + io.Reader + + status int + logger *zap.Logger +} + +func (f clientCloser) Close() error { + stderr := f.r.stderr.Bytes() + if len(stderr) == 0 { + return f.rwc.Close() + } + + logLevel := zapcore.WarnLevel + if f.status >= 400 { + logLevel = zapcore.ErrorLevel + } + + if c := f.logger.Check(logLevel, "stderr"); c != nil { + c.Write(zap.ByteString("body", stderr)) + } + + return f.rwc.Close() +} + +// Request returns a HTTP Response with Header and Body +// from fcgi responder +func (c *client) Request(p map[string]string, req io.Reader) (resp *http.Response, err error) { + r, err := c.Do(p, req) + if err != nil { + return + } + + rb := bufio.NewReader(r) + tp := textproto.NewReader(rb) + resp = new(http.Response) + + // Parse the response headers. + mimeHeader, err := tp.ReadMIMEHeader() + if err != nil && err != io.EOF { + return + } + resp.Header = http.Header(mimeHeader) + + if resp.Header.Get("Status") != "" { + statusNumber, statusInfo, statusIsCut := strings.Cut(resp.Header.Get("Status"), " ") + resp.StatusCode, err = strconv.Atoi(statusNumber) + if err != nil { + return + } + if statusIsCut { + resp.Status = statusInfo + } + } else { + resp.StatusCode = http.StatusOK + } + + // TODO: fixTransferEncoding ? + resp.TransferEncoding = resp.Header["Transfer-Encoding"] + resp.ContentLength, _ = strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64) + + // wrap the response body in our closer + closer := clientCloser{ + rwc: c.rwc, + r: r.(*streamReader), + Reader: rb, + status: resp.StatusCode, + logger: noopLogger, + } + if chunked(resp.TransferEncoding) { + closer.Reader = httputil.NewChunkedReader(rb) + } + if c.stderr { + closer.logger = c.logger + } + resp.Body = closer + + return +} + +// Get issues a GET request to the fcgi responder. +func (c *client) Get(p map[string]string, body io.Reader, l int64) (resp *http.Response, err error) { + p["REQUEST_METHOD"] = "GET" + p["CONTENT_LENGTH"] = strconv.FormatInt(l, 10) + + return c.Request(p, body) +} + +// Head issues a HEAD request to the fcgi responder. +func (c *client) Head(p map[string]string) (resp *http.Response, err error) { + p["REQUEST_METHOD"] = "HEAD" + p["CONTENT_LENGTH"] = "0" + + return c.Request(p, nil) +} + +// Options issues an OPTIONS request to the fcgi responder. +func (c *client) Options(p map[string]string) (resp *http.Response, err error) { + p["REQUEST_METHOD"] = "OPTIONS" + p["CONTENT_LENGTH"] = "0" + + return c.Request(p, nil) +} + +// Post issues a POST request to the fcgi responder. with request body +// in the format that bodyType specified +func (c *client) Post(p map[string]string, method string, bodyType string, body io.Reader, l int64) (resp *http.Response, err error) { + if p == nil { + p = make(map[string]string) + } + + p["REQUEST_METHOD"] = strings.ToUpper(method) + + if len(p["REQUEST_METHOD"]) == 0 || p["REQUEST_METHOD"] == "GET" { + p["REQUEST_METHOD"] = "POST" + } + + p["CONTENT_LENGTH"] = strconv.FormatInt(l, 10) + if len(bodyType) > 0 { + p["CONTENT_TYPE"] = bodyType + } else { + p["CONTENT_TYPE"] = "application/x-www-form-urlencoded" + } + + return c.Request(p, body) +} + +// PostForm issues a POST to the fcgi responder, with form +// as a string key to a list values (url.Values) +func (c *client) PostForm(p map[string]string, data url.Values) (resp *http.Response, err error) { + body := bytes.NewReader([]byte(data.Encode())) + return c.Post(p, "POST", "application/x-www-form-urlencoded", body, int64(body.Len())) +} + +// PostFile issues a POST to the fcgi responder in multipart(RFC 2046) standard, +// with form as a string key to a list values (url.Values), +// and/or with file as a string key to a list file path. +func (c *client) PostFile(p map[string]string, data url.Values, file map[string]string) (resp *http.Response, err error) { + buf := &bytes.Buffer{} + writer := multipart.NewWriter(buf) + bodyType := writer.FormDataContentType() + + for key, val := range data { + for _, v0 := range val { + err = writer.WriteField(key, v0) + if err != nil { + return + } + } + } + + for key, val := range file { + fd, e := os.Open(val) + if e != nil { + return nil, e + } + defer fd.Close() + + part, e := writer.CreateFormFile(key, filepath.Base(val)) + if e != nil { + return nil, e + } + _, err = io.Copy(part, fd) + if err != nil { + return + } + } + + err = writer.Close() + if err != nil { + return + } + + return c.Post(p, "POST", bodyType, buf, int64(buf.Len())) +} + +// SetReadTimeout sets the read timeout for future calls that read from the +// fcgi responder. A zero value for t means no timeout will be set. +func (c *client) SetReadTimeout(t time.Duration) error { + if t != 0 { + return c.rwc.SetReadDeadline(time.Now().Add(t)) + } + return nil +} + +// SetWriteTimeout sets the write timeout for future calls that send data to +// the fcgi responder. A zero value for t means no timeout will be set. +func (c *client) SetWriteTimeout(t time.Duration) error { + if t != 0 { + return c.rwc.SetWriteDeadline(time.Now().Add(t)) + } + return nil +} + +// Checks whether chunked is part of the encodings stack +func chunked(te []string) bool { return len(te) > 0 && te[0] == "chunked" } diff --git a/modules/caddyhttp/reverseproxy/fastcgi/client_test.go b/modules/caddyhttp/reverseproxy/fastcgi/client_test.go new file mode 100644 index 00000000000..14a1cf684c7 --- /dev/null +++ b/modules/caddyhttp/reverseproxy/fastcgi/client_test.go @@ -0,0 +1,298 @@ +// 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. + +// NOTE: These tests were adapted from the original +// repository from which this package was forked. +// The tests are slow (~10s) and in dire need of rewriting. +// As such, the tests have been disabled to speed up +// automated builds until they can be properly written. + +package fastcgi + +import ( + "bytes" + "crypto/md5" + "encoding/binary" + "fmt" + "io" + "log" + "math/rand" + "net" + "net/http" + "net/http/fcgi" + "net/url" + "os" + "path/filepath" + "strconv" + "strings" + "testing" + "time" +) + +// test fcgi protocol includes: +// Get, Post, Post in multipart/form-data, and Post with files +// each key should be the md5 of the value or the file uploaded +// specify remote fcgi responder ip:port to test with php +// test failed if the remote fcgi(script) failed md5 verification +// and output "FAILED" in response +const ( + scriptFile = "/tank/www/fcgic_test.php" + // ipPort = "remote-php-serv:59000" + ipPort = "127.0.0.1:59000" +) + +var globalt *testing.T + +type FastCGIServer struct{} + +func (s FastCGIServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) { + if err := req.ParseMultipartForm(100000000); err != nil { + log.Printf("[ERROR] failed to parse: %v", err) + } + + stat := "PASSED" + fmt.Fprintln(resp, "-") + fileNum := 0 + { + length := 0 + for k0, v0 := range req.Form { + h := md5.New() + _, _ = io.WriteString(h, v0[0]) + _md5 := fmt.Sprintf("%x", h.Sum(nil)) + + length += len(k0) + length += len(v0[0]) + + // echo error when key != _md5(val) + if _md5 != k0 { + fmt.Fprintln(resp, "server:err ", _md5, k0) + stat = "FAILED" + } + } + if req.MultipartForm != nil { + fileNum = len(req.MultipartForm.File) + for kn, fns := range req.MultipartForm.File { + // fmt.Fprintln(resp, "server:filekey ", kn ) + length += len(kn) + for _, f := range fns { + fd, err := f.Open() + if err != nil { + log.Println("server:", err) + return + } + h := md5.New() + l0, err := io.Copy(h, fd) + if err != nil { + log.Println(err) + return + } + length += int(l0) + defer fd.Close() + md5 := fmt.Sprintf("%x", h.Sum(nil)) + // fmt.Fprintln(resp, "server:filemd5 ", md5 ) + + if kn != md5 { + fmt.Fprintln(resp, "server:err ", md5, kn) + stat = "FAILED" + } + // fmt.Fprintln(resp, "server:filename ", f.Filename ) + } + } + } + + fmt.Fprintln(resp, "server:got data length", length) + } + fmt.Fprintln(resp, "-"+stat+"-POST(", len(req.Form), ")-FILE(", fileNum, ")--") +} + +func sendFcgi(reqType int, fcgiParams map[string]string, data []byte, posts map[string]string, files map[string]string) (content []byte) { + conn, err := net.Dial("tcp", ipPort) + if err != nil { + log.Println("err:", err) + return + } + + fcgi := client{rwc: conn, reqID: 1} + + length := 0 + + var resp *http.Response + switch reqType { + case 0: + if len(data) > 0 { + length = len(data) + rd := bytes.NewReader(data) + resp, err = fcgi.Post(fcgiParams, "", "", rd, int64(rd.Len())) + } else if len(posts) > 0 { + values := url.Values{} + for k, v := range posts { + values.Set(k, v) + length += len(k) + 2 + len(v) + } + resp, err = fcgi.PostForm(fcgiParams, values) + } else { + rd := bytes.NewReader(data) + resp, err = fcgi.Get(fcgiParams, rd, int64(rd.Len())) + } + + default: + values := url.Values{} + for k, v := range posts { + values.Set(k, v) + length += len(k) + 2 + len(v) + } + + for k, v := range files { + fi, _ := os.Lstat(v) + length += len(k) + int(fi.Size()) + } + resp, err = fcgi.PostFile(fcgiParams, values, files) + } + + if err != nil { + log.Println("err:", err) + return + } + + defer resp.Body.Close() + content, _ = io.ReadAll(resp.Body) + + log.Println("c: send data length ≈", length, string(content)) + conn.Close() + time.Sleep(250 * time.Millisecond) + + if bytes.Contains(content, []byte("FAILED")) { + globalt.Error("Server return failed message") + } + + return +} + +func generateRandFile(size int) (p string, m string) { + p = filepath.Join(os.TempDir(), "fcgict"+strconv.Itoa(rand.Int())) + + // open output file + fo, err := os.Create(p) + if err != nil { + panic(err) + } + // close fo on exit and check for its returned error + defer func() { + if err := fo.Close(); err != nil { + panic(err) + } + }() + + h := md5.New() + for i := 0; i < size/16; i++ { + buf := make([]byte, 16) + binary.PutVarint(buf, rand.Int63()) + if _, err := fo.Write(buf); err != nil { + log.Printf("[ERROR] failed to write buffer: %v\n", err) + } + if _, err := h.Write(buf); err != nil { + log.Printf("[ERROR] failed to write buffer: %v\n", err) + } + } + m = fmt.Sprintf("%x", h.Sum(nil)) + return +} + +func DisabledTest(t *testing.T) { + // TODO: test chunked reader + globalt = t + + // server + go func() { + listener, err := net.Listen("tcp", ipPort) + if err != nil { + log.Println("listener creation failed: ", err) + } + + srv := new(FastCGIServer) + if err := fcgi.Serve(listener, srv); err != nil { + log.Print("[ERROR] failed to start server: ", err) + } + }() + + time.Sleep(250 * time.Millisecond) + + // init + fcgiParams := make(map[string]string) + fcgiParams["REQUEST_METHOD"] = "GET" + fcgiParams["SERVER_PROTOCOL"] = "HTTP/1.1" + // fcgi_params["GATEWAY_INTERFACE"] = "CGI/1.1" + fcgiParams["SCRIPT_FILENAME"] = scriptFile + + // simple GET + log.Println("test:", "get") + sendFcgi(0, fcgiParams, nil, nil, nil) + + // simple post data + log.Println("test:", "post") + sendFcgi(0, fcgiParams, []byte("c4ca4238a0b923820dcc509a6f75849b=1&7b8b965ad4bca0e41ab51de7b31363a1=n"), nil, nil) + + log.Println("test:", "post data (more than 60KB)") + data := "" + for i := 0x00; i < 0xff; i++ { + v0 := strings.Repeat(fmt.Sprint(i), 256) + h := md5.New() + _, _ = io.WriteString(h, v0) + k0 := fmt.Sprintf("%x", h.Sum(nil)) + data += k0 + "=" + url.QueryEscape(v0) + "&" + } + sendFcgi(0, fcgiParams, []byte(data), nil, nil) + + log.Println("test:", "post form (use url.Values)") + p0 := make(map[string]string, 1) + p0["c4ca4238a0b923820dcc509a6f75849b"] = "1" + p0["7b8b965ad4bca0e41ab51de7b31363a1"] = "n" + sendFcgi(1, fcgiParams, nil, p0, nil) + + log.Println("test:", "post forms (256 keys, more than 1MB)") + p1 := make(map[string]string, 1) + for i := 0x00; i < 0xff; i++ { + v0 := strings.Repeat(fmt.Sprint(i), 4096) + h := md5.New() + _, _ = io.WriteString(h, v0) + k0 := fmt.Sprintf("%x", h.Sum(nil)) + p1[k0] = v0 + } + sendFcgi(1, fcgiParams, nil, p1, nil) + + log.Println("test:", "post file (1 file, 500KB)) ") + f0 := make(map[string]string, 1) + path0, m0 := generateRandFile(500000) + f0[m0] = path0 + sendFcgi(1, fcgiParams, nil, p1, f0) + + log.Println("test:", "post multiple files (2 files, 5M each) and forms (256 keys, more than 1MB data") + path1, m1 := generateRandFile(5000000) + f0[m1] = path1 + sendFcgi(1, fcgiParams, nil, p1, f0) + + log.Println("test:", "post only files (2 files, 5M each)") + sendFcgi(1, fcgiParams, nil, nil, f0) + + log.Println("test:", "post only 1 file") + delete(f0, "m0") + sendFcgi(1, fcgiParams, nil, nil, f0) + + if err := os.Remove(path0); err != nil { + log.Println("[ERROR] failed to remove path: ", err) + } + if err := os.Remove(path1); err != nil { + log.Println("[ERROR] failed to remove path: ", err) + } +} diff --git a/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go b/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go new file mode 100644 index 00000000000..d451dd38019 --- /dev/null +++ b/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go @@ -0,0 +1,432 @@ +// 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 fastcgi + +import ( + "crypto/tls" + "fmt" + "net" + "net/http" + "path/filepath" + "strconv" + "strings" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy" + "github.com/caddyserver/caddy/v2/modules/caddytls" +) + +var noopLogger = zap.NewNop() + +func init() { + caddy.RegisterModule(Transport{}) +} + +// Transport facilitates FastCGI communication. +type Transport struct { + // Use this directory as the fastcgi root directory. Defaults to the root + // directory of the parent virtual host. + Root string `json:"root,omitempty"` + + // The path in the URL will be split into two, with the first piece ending + // with the value of SplitPath. The first piece will be assumed as the + // actual resource (CGI script) name, and the second piece will be set to + // PATH_INFO for the CGI script to use. + // + // Future enhancements should be careful to avoid CVE-2019-11043, + // which can be mitigated with use of a try_files-like behavior + // that 404s if the fastcgi path info is not found. + SplitPath []string `json:"split_path,omitempty"` + + // Path declared as root directory will be resolved to its absolute value + // after the evaluation of any symbolic links. + // Due to the nature of PHP opcache, root directory path is cached: when + // using a symlinked directory as root this could generate errors when + // symlink is changed without php-fpm being restarted; enabling this + // directive will set $_SERVER['DOCUMENT_ROOT'] to the real directory path. + ResolveRootSymlink bool `json:"resolve_root_symlink,omitempty"` + + // Extra environment variables. + EnvVars map[string]string `json:"env,omitempty"` + + // The duration used to set a deadline when connecting to an upstream. Default: `3s`. + DialTimeout caddy.Duration `json:"dial_timeout,omitempty"` + + // The duration used to set a deadline when reading from the FastCGI server. + ReadTimeout caddy.Duration `json:"read_timeout,omitempty"` + + // The duration used to set a deadline when sending to the FastCGI server. + WriteTimeout caddy.Duration `json:"write_timeout,omitempty"` + + // Capture and log any messages sent by the upstream on stderr. Logs at WARN + // level by default. If the response has a 4xx or 5xx status ERROR level will + // be used instead. + CaptureStderr bool `json:"capture_stderr,omitempty"` + + serverSoftware string + logger *zap.Logger +} + +// CaddyModule returns the Caddy module information. +func (Transport) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.reverse_proxy.transport.fastcgi", + New: func() caddy.Module { return new(Transport) }, + } +} + +// Provision sets up t. +func (t *Transport) Provision(ctx caddy.Context) error { + t.logger = ctx.Logger() + + if t.Root == "" { + t.Root = "{http.vars.root}" + } + + version, _ := caddy.Version() + t.serverSoftware = "Caddy/" + version + + // Set a relatively short default dial timeout. + // This is helpful to make load-balancer retries more speedy. + if t.DialTimeout == 0 { + t.DialTimeout = caddy.Duration(3 * time.Second) + } + + return nil +} + +// RoundTrip implements http.RoundTripper. +func (t Transport) RoundTrip(r *http.Request) (*http.Response, error) { + server := r.Context().Value(caddyhttp.ServerCtxKey).(*caddyhttp.Server) + + // Disallow null bytes in the request path, because + // PHP upstreams may do bad things, like execute a + // non-PHP file as PHP code. See #4574 + if strings.Contains(r.URL.Path, "\x00") { + return nil, caddyhttp.Error(http.StatusBadRequest, fmt.Errorf("invalid request path")) + } + + env, err := t.buildEnv(r) + if err != nil { + return nil, fmt.Errorf("building environment: %v", err) + } + + ctx := r.Context() + + // extract dial information from request (should have been embedded by the reverse proxy) + network, address := "tcp", r.URL.Host + if dialInfo, ok := reverseproxy.GetDialInfo(ctx); ok { + network = dialInfo.Network + address = dialInfo.Address + } + + logCreds := server.Logs != nil && server.Logs.ShouldLogCredentials + loggableReq := caddyhttp.LoggableHTTPRequest{ + Request: r, + ShouldLogCredentials: logCreds, + } + loggableEnv := loggableEnv{vars: env, logCredentials: logCreds} + + logger := t.logger.With( + zap.Object("request", loggableReq), + zap.Object("env", loggableEnv), + ) + if c := t.logger.Check(zapcore.DebugLevel, "roundtrip"); c != nil { + c.Write( + zap.String("dial", address), + zap.Object("env", loggableEnv), + zap.Object("request", loggableReq), + ) + } + + // connect to the backend + dialer := net.Dialer{Timeout: time.Duration(t.DialTimeout)} + conn, err := dialer.DialContext(ctx, network, address) + if err != nil { + return nil, fmt.Errorf("dialing backend: %v", err) + } + defer func() { + // conn will be closed with the response body unless there's an error + if err != nil { + conn.Close() + } + }() + + // create the client that will facilitate the protocol + client := client{ + rwc: conn, + reqID: 1, + logger: logger, + stderr: t.CaptureStderr, + } + + // read/write timeouts + if err = client.SetReadTimeout(time.Duration(t.ReadTimeout)); err != nil { + return nil, fmt.Errorf("setting read timeout: %v", err) + } + if err = client.SetWriteTimeout(time.Duration(t.WriteTimeout)); err != nil { + return nil, fmt.Errorf("setting write timeout: %v", err) + } + + contentLength := r.ContentLength + if contentLength == 0 { + contentLength, _ = strconv.ParseInt(r.Header.Get("Content-Length"), 10, 64) + } + + var resp *http.Response + switch r.Method { + case http.MethodHead: + resp, err = client.Head(env) + case http.MethodGet: + resp, err = client.Get(env, r.Body, contentLength) + case http.MethodOptions: + resp, err = client.Options(env) + default: + resp, err = client.Post(env, r.Method, r.Header.Get("Content-Type"), r.Body, contentLength) + } + if err != nil { + return nil, err + } + + return resp, nil +} + +// buildEnv returns a set of CGI environment variables for the request. +func (t Transport) buildEnv(r *http.Request) (envVars, error) { + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + + var env envVars + + // Separate remote IP and port; more lenient than net.SplitHostPort + var ip, port string + if idx := strings.LastIndex(r.RemoteAddr, ":"); idx > -1 { + ip = r.RemoteAddr[:idx] + port = r.RemoteAddr[idx+1:] + } else { + ip = r.RemoteAddr + } + + // Remove [] from IPv6 addresses + ip = strings.Replace(ip, "[", "", 1) + ip = strings.Replace(ip, "]", "", 1) + + // make sure file root is absolute + root, err := caddy.FastAbs(repl.ReplaceAll(t.Root, ".")) + if err != nil { + return nil, err + } + + if t.ResolveRootSymlink { + root, err = filepath.EvalSymlinks(root) + if err != nil { + return nil, err + } + } + + fpath := r.URL.Path + scriptName := fpath + + docURI := fpath + // split "actual path" from "path info" if configured + var pathInfo string + if splitPos := t.splitPos(fpath); splitPos > -1 { + docURI = fpath[:splitPos] + pathInfo = fpath[splitPos:] + + // Strip PATH_INFO from SCRIPT_NAME + scriptName = strings.TrimSuffix(scriptName, pathInfo) + } + + // Try to grab the path remainder from a file matcher + // if we didn't get a split result here. + // See https://github.com/caddyserver/caddy/issues/3718 + if pathInfo == "" { + pathInfo, _ = repl.GetString("http.matchers.file.remainder") + } + + // SCRIPT_FILENAME is the absolute path of SCRIPT_NAME + scriptFilename := caddyhttp.SanitizedPathJoin(root, scriptName) + + // Ensure the SCRIPT_NAME has a leading slash for compliance with RFC3875 + // Info: https://tools.ietf.org/html/rfc3875#section-4.1.13 + if scriptName != "" && !strings.HasPrefix(scriptName, "/") { + scriptName = "/" + scriptName + } + + // Get the request URL from context. The context stores the original URL in case + // it was changed by a middleware such as rewrite. By default, we pass the + // original URI in as the value of REQUEST_URI (the user can overwrite this + // if desired). Most PHP apps seem to want the original URI. Besides, this is + // how nginx defaults: http://stackoverflow.com/a/12485156/1048862 + origReq := r.Context().Value(caddyhttp.OriginalRequestCtxKey).(http.Request) + + requestScheme := "http" + if r.TLS != nil { + requestScheme = "https" + } + + reqHost, reqPort, err := net.SplitHostPort(r.Host) + if err != nil { + // whatever, just assume there was no port + reqHost = r.Host + } + + authUser, _ := repl.GetString("http.auth.user.id") + + // Some variables are unused but cleared explicitly to prevent + // the parent environment from interfering. + env = envVars{ + // Variables defined in CGI 1.1 spec + "AUTH_TYPE": "", // Not used + "CONTENT_LENGTH": r.Header.Get("Content-Length"), + "CONTENT_TYPE": r.Header.Get("Content-Type"), + "GATEWAY_INTERFACE": "CGI/1.1", + "PATH_INFO": pathInfo, + "QUERY_STRING": r.URL.RawQuery, + "REMOTE_ADDR": ip, + "REMOTE_HOST": ip, // For speed, remote host lookups disabled + "REMOTE_PORT": port, + "REMOTE_IDENT": "", // Not used + "REMOTE_USER": authUser, + "REQUEST_METHOD": r.Method, + "REQUEST_SCHEME": requestScheme, + "SERVER_NAME": reqHost, + "SERVER_PROTOCOL": r.Proto, + "SERVER_SOFTWARE": t.serverSoftware, + + // Other variables + "DOCUMENT_ROOT": root, + "DOCUMENT_URI": docURI, + "HTTP_HOST": r.Host, // added here, since not always part of headers + "REQUEST_URI": origReq.URL.RequestURI(), + "SCRIPT_FILENAME": scriptFilename, + "SCRIPT_NAME": scriptName, + } + + // compliance with the CGI specification requires that + // PATH_TRANSLATED should only exist if PATH_INFO is defined. + // Info: https://www.ietf.org/rfc/rfc3875 Page 14 + if env["PATH_INFO"] != "" { + env["PATH_TRANSLATED"] = caddyhttp.SanitizedPathJoin(root, pathInfo) // Info: http://www.oreilly.com/openbook/cgi/ch02_04.html + } + + // compliance with the CGI specification requires that + // the SERVER_PORT variable MUST be set to the TCP/IP port number on which this request is received from the client + // even if the port is the default port for the scheme and could otherwise be omitted from a URI. + // https://tools.ietf.org/html/rfc3875#section-4.1.15 + if reqPort != "" { + env["SERVER_PORT"] = reqPort + } else if requestScheme == "http" { + env["SERVER_PORT"] = "80" + } else if requestScheme == "https" { + env["SERVER_PORT"] = "443" + } + + // Some web apps rely on knowing HTTPS or not + if r.TLS != nil { + env["HTTPS"] = "on" + // and pass the protocol details in a manner compatible with apache's mod_ssl + // (which is why these have a SSL_ prefix and not TLS_). + v, ok := tlsProtocolStrings[r.TLS.Version] + if ok { + env["SSL_PROTOCOL"] = v + } + // and pass the cipher suite in a manner compatible with apache's mod_ssl + for _, cs := range caddytls.SupportedCipherSuites() { + if cs.ID == r.TLS.CipherSuite { + env["SSL_CIPHER"] = cs.Name + break + } + } + } + + // Add env variables from config (with support for placeholders in values) + for key, value := range t.EnvVars { + env[key] = repl.ReplaceAll(value, "") + } + + // Add all HTTP headers to env variables + for field, val := range r.Header { + header := strings.ToUpper(field) + header = headerNameReplacer.Replace(header) + env["HTTP_"+header] = strings.Join(val, ", ") + } + return env, nil +} + +// splitPos returns the index where path should +// be split based on t.SplitPath. +func (t Transport) splitPos(path string) int { + // TODO: from v1... + // if httpserver.CaseSensitivePath { + // return strings.Index(path, r.SplitPath) + // } + if len(t.SplitPath) == 0 { + return 0 + } + + lowerPath := strings.ToLower(path) + for _, split := range t.SplitPath { + if idx := strings.Index(lowerPath, strings.ToLower(split)); idx > -1 { + return idx + len(split) + } + } + return -1 +} + +type envVars map[string]string + +// loggableEnv is a simple type to allow for speeding up zap log encoding. +type loggableEnv struct { + vars envVars + logCredentials bool +} + +func (env loggableEnv) MarshalLogObject(enc zapcore.ObjectEncoder) error { + for k, v := range env.vars { + if !env.logCredentials { + switch strings.ToLower(k) { + case "http_cookie", "http_set_cookie", "http_authorization", "http_proxy_authorization": + v = "" + } + } + enc.AddString(k, v) + } + return nil +} + +// Map of supported protocols to Apache ssl_mod format +// Note that these are slightly different from SupportedProtocols in caddytls/config.go +var tlsProtocolStrings = map[uint16]string{ + tls.VersionTLS10: "TLSv1", + tls.VersionTLS11: "TLSv1.1", + tls.VersionTLS12: "TLSv1.2", + tls.VersionTLS13: "TLSv1.3", +} + +var headerNameReplacer = strings.NewReplacer(" ", "_", "-", "_") + +// Interface guards +var ( + _ zapcore.ObjectMarshaler = (*loggableEnv)(nil) + + _ caddy.Provisioner = (*Transport)(nil) + _ http.RoundTripper = (*Transport)(nil) +) diff --git a/modules/caddyhttp/reverseproxy/fastcgi/header.go b/modules/caddyhttp/reverseproxy/fastcgi/header.go new file mode 100644 index 00000000000..59dce715ee8 --- /dev/null +++ b/modules/caddyhttp/reverseproxy/fastcgi/header.go @@ -0,0 +1,32 @@ +// 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 fastcgi + +type header struct { + Version uint8 + Type uint8 + ID uint16 + ContentLength uint16 + PaddingLength uint8 + Reserved uint8 +} + +func (h *header) init(recType uint8, reqID uint16, contentLength int) { + h.Version = 1 + h.Type = recType + h.ID = reqID + h.ContentLength = uint16(contentLength) + h.PaddingLength = uint8(-contentLength & 7) +} diff --git a/modules/caddyhttp/reverseproxy/fastcgi/pool.go b/modules/caddyhttp/reverseproxy/fastcgi/pool.go new file mode 100644 index 00000000000..29017f11b1c --- /dev/null +++ b/modules/caddyhttp/reverseproxy/fastcgi/pool.go @@ -0,0 +1,26 @@ +// 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 fastcgi + +import ( + "bytes" + "sync" +) + +var bufPool = sync.Pool{ + New: func() any { + return new(bytes.Buffer) + }, +} diff --git a/modules/caddyhttp/reverseproxy/fastcgi/reader.go b/modules/caddyhttp/reverseproxy/fastcgi/reader.go new file mode 100644 index 00000000000..3a8e91deb09 --- /dev/null +++ b/modules/caddyhttp/reverseproxy/fastcgi/reader.go @@ -0,0 +1,44 @@ +// 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 fastcgi + +import ( + "bytes" + "io" +) + +type streamReader struct { + c *client + rec record + stderr bytes.Buffer +} + +func (w *streamReader) Read(p []byte) (n int, err error) { + for !w.rec.hasMore() { + err = w.rec.fill(w.c.rwc) + if err != nil { + return 0, err + } + + // standard error output + if w.rec.h.Type == Stderr { + if _, err = io.Copy(&w.stderr, &w.rec); err != nil { + return 0, err + } + } + } + + return w.rec.Read(p) +} diff --git a/modules/caddyhttp/reverseproxy/fastcgi/record.go b/modules/caddyhttp/reverseproxy/fastcgi/record.go new file mode 100644 index 00000000000..46c1f17bb4d --- /dev/null +++ b/modules/caddyhttp/reverseproxy/fastcgi/record.go @@ -0,0 +1,58 @@ +// 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 fastcgi + +import ( + "encoding/binary" + "errors" + "io" +) + +type record struct { + h header + lr io.LimitedReader + padding int64 +} + +func (rec *record) fill(r io.Reader) (err error) { + rec.lr.N = rec.padding + rec.lr.R = r + if _, err = io.Copy(io.Discard, rec); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &rec.h); err != nil { + return + } + if rec.h.Version != 1 { + err = errors.New("fcgi: invalid header version") + return + } + if rec.h.Type == EndRequest { + err = io.EOF + return + } + rec.lr.N = int64(rec.h.ContentLength) + rec.padding = int64(rec.h.PaddingLength) + return +} + +func (rec *record) Read(p []byte) (n int, err error) { + return rec.lr.Read(p) +} + +func (rec *record) hasMore() bool { + return rec.lr.N > 0 +} diff --git a/modules/caddyhttp/reverseproxy/fastcgi/writer.go b/modules/caddyhttp/reverseproxy/fastcgi/writer.go new file mode 100644 index 00000000000..3af00d9a16f --- /dev/null +++ b/modules/caddyhttp/reverseproxy/fastcgi/writer.go @@ -0,0 +1,145 @@ +// 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 fastcgi + +import ( + "bytes" + "encoding/binary" +) + +// streamWriter abstracts out the separation of a stream into discrete records. +// It only writes maxWrite bytes at a time. +type streamWriter struct { + c *client + h header + buf *bytes.Buffer + recType uint8 +} + +func (w *streamWriter) writeRecord(recType uint8, content []byte) (err error) { + w.h.init(recType, w.c.reqID, len(content)) + w.buf.Write(pad[:8]) + w.writeHeader() + w.buf.Write(content) + w.buf.Write(pad[:w.h.PaddingLength]) + _, err = w.buf.WriteTo(w.c.rwc) + return err +} + +func (w *streamWriter) writeBeginRequest(role uint16, flags uint8) error { + b := [8]byte{byte(role >> 8), byte(role), flags} + return w.writeRecord(BeginRequest, b[:]) +} + +func (w *streamWriter) Write(p []byte) (int, error) { + // init header + if w.buf.Len() < 8 { + w.buf.Write(pad[:8]) + } + + nn := 0 + for len(p) > 0 { + n := len(p) + nl := maxWrite + 8 - w.buf.Len() + if n > nl { + n = nl + w.buf.Write(p[:n]) + if err := w.Flush(); err != nil { + return nn, err + } + // reset headers + w.buf.Write(pad[:8]) + } else { + w.buf.Write(p[:n]) + } + nn += n + p = p[n:] + } + return nn, nil +} + +func (w *streamWriter) endStream() error { + // send empty record to close the stream + return w.writeRecord(w.recType, nil) +} + +func (w *streamWriter) writePairs(pairs map[string]string) error { + b := make([]byte, 8) + nn := 0 + // init headers + w.buf.Write(b) + for k, v := range pairs { + m := 8 + len(k) + len(v) + if m > maxWrite { + // param data size exceed 65535 bytes" + vl := maxWrite - 8 - len(k) + v = v[:vl] + } + n := encodeSize(b, uint32(len(k))) + n += encodeSize(b[n:], uint32(len(v))) + m = n + len(k) + len(v) + if (nn + m) > maxWrite { + if err := w.Flush(); err != nil { + return err + } + // reset headers + w.buf.Write(b) + nn = 0 + } + nn += m + w.buf.Write(b[:n]) + w.buf.WriteString(k) + w.buf.WriteString(v) + } + return w.FlushStream() +} + +func encodeSize(b []byte, size uint32) int { + if size > 127 { + size |= 1 << 31 + binary.BigEndian.PutUint32(b, size) + return 4 + } + b[0] = byte(size) + return 1 +} + +// writeHeader populate header wire data in buf, it abuses buffer.Bytes() modification +func (w *streamWriter) writeHeader() { + h := w.buf.Bytes()[:8] + h[0] = w.h.Version + h[1] = w.h.Type + binary.BigEndian.PutUint16(h[2:4], w.h.ID) + binary.BigEndian.PutUint16(h[4:6], w.h.ContentLength) + h[6] = w.h.PaddingLength + h[7] = w.h.Reserved +} + +// Flush write buffer data to the underlying connection, it assumes header data is the first 8 bytes of buf +func (w *streamWriter) Flush() error { + w.h.init(w.recType, w.c.reqID, w.buf.Len()-8) + w.writeHeader() + w.buf.Write(pad[:w.h.PaddingLength]) + _, err := w.buf.WriteTo(w.c.rwc) + return err +} + +// FlushStream flush data then end current stream +func (w *streamWriter) FlushStream() error { + if err := w.Flush(); err != nil { + return err + } + return w.endStream() +} diff --git a/modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go b/modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go new file mode 100644 index 00000000000..347f6dfbfe3 --- /dev/null +++ b/modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go @@ -0,0 +1,275 @@ +// 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 forwardauth + +import ( + "encoding/json" + "net/http" + "sort" + "strings" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/headers" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite" +) + +func init() { + httpcaddyfile.RegisterDirective("forward_auth", parseCaddyfile) +} + +// parseCaddyfile parses the forward_auth directive, which has the same syntax +// as the reverse_proxy directive (in fact, the reverse_proxy's directive +// Unmarshaler is invoked by this function) but the resulting proxy is specially +// configured for most™️ auth gateways that support forward auth. The typical +// config which looks something like this: +// +// forward_auth auth-gateway:9091 { +// uri /authenticate?redirect=https://auth.example.com +// copy_headers Remote-User Remote-Email +// } +// +// is equivalent to a reverse_proxy directive like this: +// +// reverse_proxy auth-gateway:9091 { +// method GET +// rewrite /authenticate?redirect=https://auth.example.com +// +// header_up X-Forwarded-Method {method} +// header_up X-Forwarded-Uri {uri} +// +// @good status 2xx +// handle_response @good { +// request_header { +// Remote-User {http.reverse_proxy.header.Remote-User} +// Remote-Email {http.reverse_proxy.header.Remote-Email} +// } +// } +// } +func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) { + if !h.Next() { + return nil, h.ArgErr() + } + + // if the user specified a matcher token, use that + // matcher in a route that wraps both of our routes; + // either way, strip the matcher token and pass + // the remaining tokens to the unmarshaler so that + // we can gain the rest of the reverse_proxy syntax + userMatcherSet, err := h.ExtractMatcherSet() + if err != nil { + return nil, err + } + + // make a new dispenser from the remaining tokens so that we + // can reset the dispenser back to this point for the + // reverse_proxy unmarshaler to read from it as well + dispenser := h.NewFromNextSegment() + + // create the reverse proxy handler + rpHandler := &reverseproxy.Handler{ + // set up defaults for header_up; reverse_proxy already deals with + // adding the other three X-Forwarded-* headers, but for this flow, + // we want to also send along the incoming method and URI since this + // request will have a rewritten URI and method. + Headers: &headers.Handler{ + Request: &headers.HeaderOps{ + Set: http.Header{ + "X-Forwarded-Method": []string{"{http.request.method}"}, + "X-Forwarded-Uri": []string{"{http.request.uri}"}, + }, + }, + }, + + // we always rewrite the method to GET, which implicitly + // turns off sending the incoming request's body, which + // allows later middleware handlers to consume it + Rewrite: &rewrite.Rewrite{ + Method: "GET", + }, + + HandleResponse: []caddyhttp.ResponseHandler{}, + } + + // collect the headers to copy from the auth response + // onto the original request, so they can get passed + // through to a backend app + headersToCopy := make(map[string]string) + + // read the subdirectives for configuring the forward_auth shortcut + // NOTE: we delete the tokens as we go so that the reverse_proxy + // unmarshal doesn't see these subdirectives which it cannot handle + for dispenser.Next() { + for dispenser.NextBlock(0) { + // ignore any sub-subdirectives that might + // have the same name somewhere within + // the reverse_proxy passthrough tokens + if dispenser.Nesting() != 1 { + continue + } + + // parse the forward_auth subdirectives + switch dispenser.Val() { + case "uri": + if !dispenser.NextArg() { + return nil, dispenser.ArgErr() + } + rpHandler.Rewrite.URI = dispenser.Val() + dispenser.DeleteN(2) + + case "copy_headers": + args := dispenser.RemainingArgs() + hadBlock := false + for nesting := dispenser.Nesting(); dispenser.NextBlock(nesting); { + hadBlock = true + args = append(args, dispenser.Val()) + } + + // directive name + args + dispenser.DeleteN(len(args) + 1) + if hadBlock { + // opening & closing brace + dispenser.DeleteN(2) + } + + for _, headerField := range args { + if strings.Contains(headerField, ">") { + parts := strings.Split(headerField, ">") + headersToCopy[parts[0]] = parts[1] + } else { + headersToCopy[headerField] = headerField + } + } + if len(headersToCopy) == 0 { + return nil, dispenser.ArgErr() + } + } + } + } + + // reset the dispenser after we're done so that the reverse_proxy + // unmarshaler can read it from the start + dispenser.Reset() + + // the auth target URI must not be empty + if rpHandler.Rewrite.URI == "" { + return nil, dispenser.Errf("the 'uri' subdirective is required") + } + + // Set up handler for good responses; when a response has 2xx status, + // then we will copy some headers from the response onto the original + // request, and allow handling to continue down the middleware chain, + // by _not_ executing a terminal handler. We must have at least one + // route in the response handler, even if it's no-op, so that the + // response handling logic in reverse_proxy doesn't skip this entry. + goodResponseHandler := caddyhttp.ResponseHandler{ + Match: &caddyhttp.ResponseMatcher{ + StatusCode: []int{2}, + }, + Routes: []caddyhttp.Route{ + { + HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject( + &caddyhttp.VarsMiddleware{}, + "handler", + "vars", + nil, + )}, + }, + }, + } + + // Sort the headers so that the order in the JSON output is deterministic. + sortedHeadersToCopy := make([]string, 0, len(headersToCopy)) + for k := range headersToCopy { + sortedHeadersToCopy = append(sortedHeadersToCopy, k) + } + sort.Strings(sortedHeadersToCopy) + + // Set up handlers to copy headers from the auth response onto the + // original request. We use vars matchers to test that the placeholder + // values aren't empty, because the header handler would not replace + // placeholders which have no value. + copyHeaderRoutes := []caddyhttp.Route{} + for _, from := range sortedHeadersToCopy { + to := http.CanonicalHeaderKey(headersToCopy[from]) + placeholderName := "http.reverse_proxy.header." + http.CanonicalHeaderKey(from) + handler := &headers.Handler{ + Request: &headers.HeaderOps{ + Set: http.Header{ + to: []string{"{" + placeholderName + "}"}, + }, + }, + } + copyHeaderRoutes = append(copyHeaderRoutes, caddyhttp.Route{ + MatcherSetsRaw: []caddy.ModuleMap{{ + "not": h.JSON(caddyhttp.MatchNot{MatcherSetsRaw: []caddy.ModuleMap{{ + "vars": h.JSON(caddyhttp.VarsMatcher{"{" + placeholderName + "}": []string{""}}), + }}}), + }}, + HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject( + handler, + "handler", + "headers", + nil, + )}, + }) + } + + goodResponseHandler.Routes = append(goodResponseHandler.Routes, copyHeaderRoutes...) + + // note that when a response has any other status than 2xx, then we + // use the reverse proxy's default behaviour of copying the response + // back to the client, so we don't need to explicitly add a response + // handler specifically for that behaviour; we do need the 2xx handler + // though, to make handling fall through to handlers deeper in the chain. + rpHandler.HandleResponse = append(rpHandler.HandleResponse, goodResponseHandler) + + // the rest of the config is specified by the user + // using the reverse_proxy directive syntax + dispenser.Next() // consume the directive name + err = rpHandler.UnmarshalCaddyfile(dispenser) + if err != nil { + return nil, err + } + err = rpHandler.FinalizeUnmarshalCaddyfile(h) + if err != nil { + return nil, err + } + + // create the final reverse proxy route + rpRoute := caddyhttp.Route{ + HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject( + rpHandler, + "handler", + "reverse_proxy", + nil, + )}, + } + + // apply the user's matcher if any + if userMatcherSet != nil { + rpRoute.MatcherSetsRaw = []caddy.ModuleMap{userMatcherSet} + } + + return []httpcaddyfile.ConfigValue{ + { + Class: "route", + Value: rpRoute, + }, + }, nil +} diff --git a/modules/caddyhttp/reverseproxy/healthchecks.go b/modules/caddyhttp/reverseproxy/healthchecks.go new file mode 100644 index 00000000000..f0ffee5b8f7 --- /dev/null +++ b/modules/caddyhttp/reverseproxy/healthchecks.go @@ -0,0 +1,628 @@ +// 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 reverseproxy + +import ( + "context" + "fmt" + "io" + "net" + "net/http" + "net/url" + "regexp" + "runtime/debug" + "slices" + "strconv" + "strings" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +// HealthChecks configures active and passive health checks. +type HealthChecks struct { + // Active health checks run in the background on a timer. To + // minimally enable active health checks, set either path or + // port (or both). Note that active health check status + // (healthy/unhealthy) is stored per-proxy-handler, not + // globally; this allows different handlers to use different + // criteria to decide what defines a healthy backend. + // + // Active health checks do not run for dynamic upstreams. + Active *ActiveHealthChecks `json:"active,omitempty"` + + // Passive health checks monitor proxied requests for errors or timeouts. + // To minimally enable passive health checks, specify at least an empty + // config object with fail_duration > 0. Passive health check state is + // shared (stored globally), so a failure from one handler will be counted + // by all handlers; but the tolerances or standards for what defines + // healthy/unhealthy backends is configured per-proxy-handler. + // + // Passive health checks technically do operate on dynamic upstreams, + // but are only effective for very busy proxies where the list of + // upstreams is mostly stable. This is because the shared/global + // state of upstreams is cleaned up when the upstreams are no longer + // used. Since dynamic upstreams are allocated dynamically at each + // request (specifically, each iteration of the proxy loop per request), + // they are also cleaned up after every request. Thus, if there is a + // moment when no requests are actively referring to a particular + // upstream host, the passive health check state will be reset because + // it will be garbage-collected. It is usually better for the dynamic + // upstream module to only return healthy, available backends instead. + Passive *PassiveHealthChecks `json:"passive,omitempty"` +} + +// ActiveHealthChecks holds configuration related to active +// health checks (that is, health checks which occur in a +// background goroutine independently). +type ActiveHealthChecks struct { + // Deprecated: Use 'uri' instead. This field will be removed. TODO: remove this field + Path string `json:"path,omitempty"` + + // The URI (path and query) to use for health checks + URI string `json:"uri,omitempty"` + + // The host:port to use (if different from the upstream's dial address) + // for health checks. This should be used in tandem with `health_header` and + // `{http.reverse_proxy.active.target_upstream}`. This can be helpful when + // creating an intermediate service to do a more thorough health check. + // If upstream is set, the active health check port is ignored. + Upstream string `json:"upstream,omitempty"` + + // The port to use (if different from the upstream's dial + // address) for health checks. If active upstream is set, + // this value is ignored. + Port int `json:"port,omitempty"` + + // HTTP headers to set on health check requests. + Headers http.Header `json:"headers,omitempty"` + + // The HTTP method to use for health checks (default "GET"). + Method string `json:"method,omitempty"` + + // The body to send with the health check request. + Body string `json:"body,omitempty"` + + // Whether to follow HTTP redirects in response to active health checks (default off). + FollowRedirects bool `json:"follow_redirects,omitempty"` + + // How frequently to perform active health checks (default 30s). + Interval caddy.Duration `json:"interval,omitempty"` + + // How long to wait for a response from a backend before + // considering it unhealthy (default 5s). + Timeout caddy.Duration `json:"timeout,omitempty"` + + // Number of consecutive health check passes before marking + // a previously unhealthy backend as healthy again (default 1). + Passes int `json:"passes,omitempty"` + + // Number of consecutive health check failures before marking + // a previously healthy backend as unhealthy (default 1). + Fails int `json:"fails,omitempty"` + + // The maximum response body to download from the backend + // during a health check. + MaxSize int64 `json:"max_size,omitempty"` + + // The HTTP status code to expect from a healthy backend. + ExpectStatus int `json:"expect_status,omitempty"` + + // A regular expression against which to match the response + // body of a healthy backend. + ExpectBody string `json:"expect_body,omitempty"` + + uri *url.URL + httpClient *http.Client + bodyRegexp *regexp.Regexp + logger *zap.Logger +} + +// Provision ensures that a is set up properly before use. +func (a *ActiveHealthChecks) Provision(ctx caddy.Context, h *Handler) error { + if !a.IsEnabled() { + return nil + } + + // Canonicalize the header keys ahead of time, since + // JSON unmarshaled headers may be incorrect + cleaned := http.Header{} + for key, hdrs := range a.Headers { + for _, val := range hdrs { + cleaned.Add(key, val) + } + } + a.Headers = cleaned + + // If Method is not set, default to GET + if a.Method == "" { + a.Method = http.MethodGet + } + + h.HealthChecks.Active.logger = h.logger.Named("health_checker.active") + + timeout := time.Duration(a.Timeout) + if timeout == 0 { + timeout = 5 * time.Second + } + + if a.Path != "" { + a.logger.Warn("the 'path' option is deprecated, please use 'uri' instead!") + } + + // parse the URI string (supports path and query) + if a.URI != "" { + parsedURI, err := url.Parse(a.URI) + if err != nil { + return err + } + a.uri = parsedURI + } + + a.httpClient = &http.Client{ + Timeout: timeout, + Transport: h.Transport, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + if !a.FollowRedirects { + return http.ErrUseLastResponse + } + return nil + }, + } + + for _, upstream := range h.Upstreams { + // if there's an alternative upstream for health-check provided in the config, + // then use it, otherwise use the upstream's dial address. if upstream is used, + // then the port is ignored. + if a.Upstream != "" { + upstream.activeHealthCheckUpstream = a.Upstream + } else if a.Port != 0 { + // if there's an alternative port for health-check provided in the config, + // then use it, otherwise use the port of upstream. + upstream.activeHealthCheckPort = a.Port + } + } + + if a.Interval == 0 { + a.Interval = caddy.Duration(30 * time.Second) + } + + if a.ExpectBody != "" { + var err error + a.bodyRegexp, err = regexp.Compile(a.ExpectBody) + if err != nil { + return fmt.Errorf("expect_body: compiling regular expression: %v", err) + } + } + + if a.Passes < 1 { + a.Passes = 1 + } + + if a.Fails < 1 { + a.Fails = 1 + } + + return nil +} + +// IsEnabled checks if the active health checks have +// the minimum config necessary to be enabled. +func (a *ActiveHealthChecks) IsEnabled() bool { + return a.Path != "" || a.URI != "" || a.Port != 0 +} + +// PassiveHealthChecks holds configuration related to passive +// health checks (that is, health checks which occur during +// the normal flow of request proxying). +type PassiveHealthChecks struct { + // How long to remember a failed request to a backend. A duration > 0 + // enables passive health checking. Default is 0. + FailDuration caddy.Duration `json:"fail_duration,omitempty"` + + // The number of failed requests within the FailDuration window to + // consider a backend as "down". Must be >= 1; default is 1. Requires + // that FailDuration be > 0. + MaxFails int `json:"max_fails,omitempty"` + + // Limits the number of simultaneous requests to a backend by + // marking the backend as "down" if it has this many concurrent + // requests or more. + UnhealthyRequestCount int `json:"unhealthy_request_count,omitempty"` + + // Count the request as failed if the response comes back with + // one of these status codes. + UnhealthyStatus []int `json:"unhealthy_status,omitempty"` + + // Count the request as failed if the response takes at least this + // long to receive. + UnhealthyLatency caddy.Duration `json:"unhealthy_latency,omitempty"` + + logger *zap.Logger +} + +// CircuitBreaker is a type that can act as an early-warning +// system for the health checker when backends are getting +// overloaded. This interface is still experimental and is +// subject to change. +type CircuitBreaker interface { + OK() bool + RecordMetric(statusCode int, latency time.Duration) +} + +// activeHealthChecker runs active health checks on a +// regular basis and blocks until +// h.HealthChecks.Active.stopChan is closed. +func (h *Handler) activeHealthChecker() { + defer func() { + if err := recover(); err != nil { + if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "active health checker panicked"); c != nil { + c.Write( + zap.Any("error", err), + zap.ByteString("stack", debug.Stack()), + ) + } + } + }() + ticker := time.NewTicker(time.Duration(h.HealthChecks.Active.Interval)) + h.doActiveHealthCheckForAllHosts() + for { + select { + case <-ticker.C: + h.doActiveHealthCheckForAllHosts() + case <-h.ctx.Done(): + ticker.Stop() + return + } + } +} + +// doActiveHealthCheckForAllHosts immediately performs a +// health checks for all upstream hosts configured by h. +func (h *Handler) doActiveHealthCheckForAllHosts() { + for _, upstream := range h.Upstreams { + go func(upstream *Upstream) { + defer func() { + if err := recover(); err != nil { + if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "active health checker panicked"); c != nil { + c.Write( + zap.Any("error", err), + zap.ByteString("stack", debug.Stack()), + ) + } + } + }() + + networkAddr, err := caddy.NewReplacer().ReplaceOrErr(upstream.Dial, true, true) + if err != nil { + if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "invalid use of placeholders in dial address for active health checks"); c != nil { + c.Write( + zap.String("address", networkAddr), + zap.Error(err), + ) + } + return + } + addr, err := caddy.ParseNetworkAddress(networkAddr) + if err != nil { + if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "bad network address"); c != nil { + c.Write( + zap.String("address", networkAddr), + zap.Error(err), + ) + } + return + } + if hcp := uint(upstream.activeHealthCheckPort); hcp != 0 { + if addr.IsUnixNetwork() || addr.IsFdNetwork() { + addr.Network = "tcp" // I guess we just assume TCP since we are using a port?? + } + addr.StartPort, addr.EndPort = hcp, hcp + } + if addr.PortRangeSize() != 1 { + if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "multiple addresses (upstream must map to only one address)"); c != nil { + c.Write( + zap.String("address", networkAddr), + ) + } + return + } + hostAddr := addr.JoinHostPort(0) + dialAddr := hostAddr + if addr.IsUnixNetwork() || addr.IsFdNetwork() { + // this will be used as the Host portion of a http.Request URL, and + // paths to socket files would produce an error when creating URL, + // so use a fake Host value instead; unix sockets are usually local + hostAddr = "localhost" + } + err = h.doActiveHealthCheck(DialInfo{Network: addr.Network, Address: dialAddr}, hostAddr, networkAddr, upstream) + if err != nil { + if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "active health check failed"); c != nil { + c.Write( + zap.String("address", hostAddr), + zap.Error(err), + ) + } + } + }(upstream) + } +} + +// doActiveHealthCheck performs a health check to upstream which +// can be reached at address hostAddr. The actual address for +// the request will be built according to active health checker +// config. The health status of the host will be updated +// according to whether it passes the health check. An error is +// returned only if the health check fails to occur or if marking +// the host's health status fails. +func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, networkAddr string, upstream *Upstream) error { + // create the URL for the request that acts as a health check + u := &url.URL{ + Scheme: "http", + Host: hostAddr, + } + + // split the host and port if possible, override the port if configured + host, port, err := net.SplitHostPort(hostAddr) + if err != nil { + host = hostAddr + } + + // ignore active health check port if active upstream is provided as the + // active upstream already contains the replacement port + if h.HealthChecks.Active.Upstream != "" { + u.Host = h.HealthChecks.Active.Upstream + } else if h.HealthChecks.Active.Port != 0 { + port := strconv.Itoa(h.HealthChecks.Active.Port) + u.Host = net.JoinHostPort(host, port) + } + + // this is kind of a hacky way to know if we should use HTTPS, but whatever + if tt, ok := h.Transport.(TLSTransport); ok && tt.TLSEnabled() { + u.Scheme = "https" + + // if the port is in the except list, flip back to HTTP + if ht, ok := h.Transport.(*HTTPTransport); ok && slices.Contains(ht.TLS.ExceptPorts, port) { + u.Scheme = "http" + } + } + + // if we have a provisioned uri, use that, otherwise use + // the deprecated Path option + if h.HealthChecks.Active.uri != nil { + u.Path = h.HealthChecks.Active.uri.Path + u.RawQuery = h.HealthChecks.Active.uri.RawQuery + } else { + u.Path = h.HealthChecks.Active.Path + } + + // replacer used for both body and headers. Only globals (env vars, system info, etc.) are available + repl := caddy.NewReplacer() + + // if body is provided, create a reader for it, otherwise nil + var requestBody io.Reader + if h.HealthChecks.Active.Body != "" { + // set body, using replacer + requestBody = strings.NewReader(repl.ReplaceAll(h.HealthChecks.Active.Body, "")) + } + + // attach dialing information to this request, as well as context values that + // may be expected by handlers of this request + ctx := h.ctx.Context + ctx = context.WithValue(ctx, caddy.ReplacerCtxKey, caddy.NewReplacer()) + ctx = context.WithValue(ctx, caddyhttp.VarsCtxKey, map[string]any{ + dialInfoVarKey: dialInfo, + }) + req, err := http.NewRequestWithContext(ctx, h.HealthChecks.Active.Method, u.String(), requestBody) + if err != nil { + return fmt.Errorf("making request: %v", err) + } + ctx = context.WithValue(ctx, caddyhttp.OriginalRequestCtxKey, *req) + req = req.WithContext(ctx) + + // set headers, using replacer + repl.Set("http.reverse_proxy.active.target_upstream", networkAddr) + for key, vals := range h.HealthChecks.Active.Headers { + key = repl.ReplaceAll(key, "") + if key == "Host" { + req.Host = repl.ReplaceAll(h.HealthChecks.Active.Headers.Get(key), "") + continue + } + for _, val := range vals { + req.Header.Add(key, repl.ReplaceKnown(val, "")) + } + } + + markUnhealthy := func() { + // increment failures and then check if it has reached the threshold to mark unhealthy + err := upstream.Host.countHealthFail(1) + if err != nil { + if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "could not count active health failure"); c != nil { + c.Write( + zap.String("host", upstream.Dial), + zap.Error(err), + ) + } + return + } + if upstream.Host.activeHealthFails() >= h.HealthChecks.Active.Fails { + // dispatch an event that the host newly became unhealthy + if upstream.setHealthy(false) { + h.events.Emit(h.ctx, "unhealthy", map[string]any{"host": hostAddr}) + upstream.Host.resetHealth() + } + } + } + + markHealthy := func() { + // increment passes and then check if it has reached the threshold to be healthy + err := upstream.Host.countHealthPass(1) + if err != nil { + if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "could not count active health pass"); c != nil { + c.Write( + zap.String("host", upstream.Dial), + zap.Error(err), + ) + } + return + } + if upstream.Host.activeHealthPasses() >= h.HealthChecks.Active.Passes { + if upstream.setHealthy(true) { + if c := h.HealthChecks.Active.logger.Check(zapcore.InfoLevel, "host is up"); c != nil { + c.Write(zap.String("host", hostAddr)) + } + h.events.Emit(h.ctx, "healthy", map[string]any{"host": hostAddr}) + upstream.Host.resetHealth() + } + } + } + + // do the request, being careful to tame the response body + resp, err := h.HealthChecks.Active.httpClient.Do(req) + if err != nil { + if c := h.HealthChecks.Active.logger.Check(zapcore.InfoLevel, "HTTP request failed"); c != nil { + c.Write( + zap.String("host", hostAddr), + zap.Error(err), + ) + } + markUnhealthy() + return nil + } + var body io.Reader = resp.Body + if h.HealthChecks.Active.MaxSize > 0 { + body = io.LimitReader(body, h.HealthChecks.Active.MaxSize) + } + defer func() { + // drain any remaining body so connection could be re-used + _, _ = io.Copy(io.Discard, body) + resp.Body.Close() + }() + + // if status code is outside criteria, mark down + if h.HealthChecks.Active.ExpectStatus > 0 { + if !caddyhttp.StatusCodeMatches(resp.StatusCode, h.HealthChecks.Active.ExpectStatus) { + if c := h.HealthChecks.Active.logger.Check(zapcore.InfoLevel, "unexpected status code"); c != nil { + c.Write( + zap.Int("status_code", resp.StatusCode), + zap.String("host", hostAddr), + ) + } + markUnhealthy() + return nil + } + } else if resp.StatusCode < 200 || resp.StatusCode >= 300 { + if c := h.HealthChecks.Active.logger.Check(zapcore.InfoLevel, "status code out of tolerances"); c != nil { + c.Write( + zap.Int("status_code", resp.StatusCode), + zap.String("host", hostAddr), + ) + } + markUnhealthy() + return nil + } + + // if body does not match regex, mark down + if h.HealthChecks.Active.bodyRegexp != nil { + bodyBytes, err := io.ReadAll(body) + if err != nil { + if c := h.HealthChecks.Active.logger.Check(zapcore.InfoLevel, "failed to read response body"); c != nil { + c.Write( + zap.String("host", hostAddr), + zap.Error(err), + ) + } + markUnhealthy() + return nil + } + if !h.HealthChecks.Active.bodyRegexp.Match(bodyBytes) { + if c := h.HealthChecks.Active.logger.Check(zapcore.InfoLevel, "response body failed expectations"); c != nil { + c.Write( + zap.String("host", hostAddr), + ) + } + markUnhealthy() + return nil + } + } + + // passed health check parameters, so mark as healthy + markHealthy() + + return nil +} + +// countFailure is used with passive health checks. It +// remembers 1 failure for upstream for the configured +// duration. If passive health checks are disabled or +// failure expiry is 0, this is a no-op. +func (h *Handler) countFailure(upstream *Upstream) { + // only count failures if passive health checking is enabled + // and if failures are configured have a non-zero expiry + if h.HealthChecks == nil || h.HealthChecks.Passive == nil { + return + } + failDuration := time.Duration(h.HealthChecks.Passive.FailDuration) + if failDuration == 0 { + return + } + + // count failure immediately + err := upstream.Host.countFail(1) + if err != nil { + if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "could not count failure"); c != nil { + c.Write( + zap.String("host", upstream.Dial), + zap.Error(err), + ) + } + return + } + + // forget it later + go func(host *Host, failDuration time.Duration) { + defer func() { + if err := recover(); err != nil { + if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "passive health check failure forgetter panicked"); c != nil { + c.Write( + zap.Any("error", err), + zap.ByteString("stack", debug.Stack()), + ) + } + } + }() + timer := time.NewTimer(failDuration) + select { + case <-h.ctx.Done(): + if !timer.Stop() { + <-timer.C + } + case <-timer.C: + } + err := host.countFail(-1) + if err != nil { + if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "could not forget failure"); c != nil { + c.Write( + zap.String("host", upstream.Dial), + zap.Error(err), + ) + } + } + }(upstream.Host, failDuration) +} diff --git a/modules/caddyhttp/reverseproxy/hosts.go b/modules/caddyhttp/reverseproxy/hosts.go new file mode 100644 index 00000000000..0a676e43147 --- /dev/null +++ b/modules/caddyhttp/reverseproxy/hosts.go @@ -0,0 +1,285 @@ +// 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 reverseproxy + +import ( + "context" + "fmt" + "net/http" + "net/netip" + "strconv" + "sync/atomic" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +// UpstreamPool is a collection of upstreams. +type UpstreamPool []*Upstream + +// Upstream bridges this proxy's configuration to the +// state of the backend host it is correlated with. +// Upstream values must not be copied. +type Upstream struct { + *Host `json:"-"` + + // The [network address](/docs/conventions#network-addresses) + // to dial to connect to the upstream. Must represent precisely + // one socket (i.e. no port ranges). A valid network address + // either has a host and port or is a unix socket address. + // + // Placeholders may be used to make the upstream dynamic, but be + // aware of the health check implications of this: a single + // upstream that represents numerous (perhaps arbitrary) backends + // can be considered down if one or enough of the arbitrary + // backends is down. Also be aware of open proxy vulnerabilities. + Dial string `json:"dial,omitempty"` + + // The maximum number of simultaneous requests to allow to + // this upstream. If set, overrides the global passive health + // check UnhealthyRequestCount value. + MaxRequests int `json:"max_requests,omitempty"` + + // TODO: This could be really useful, to bind requests + // with certain properties to specific backends + // HeaderAffinity string + // IPAffinity string + + activeHealthCheckPort int + activeHealthCheckUpstream string + healthCheckPolicy *PassiveHealthChecks + cb CircuitBreaker + unhealthy int32 // accessed atomically; status from active health checker +} + +// (pointer receiver necessary to avoid a race condition, since +// copying the Upstream reads the 'unhealthy' field which is +// accessed atomically) +func (u *Upstream) String() string { return u.Dial } + +// Available returns true if the remote host +// is available to receive requests. This is +// the method that should be used by selection +// policies, etc. to determine if a backend +// should be able to be sent a request. +func (u *Upstream) Available() bool { + return u.Healthy() && !u.Full() +} + +// Healthy returns true if the remote host +// is currently known to be healthy or "up". +// It consults the circuit breaker, if any. +func (u *Upstream) Healthy() bool { + healthy := u.healthy() + if healthy && u.healthCheckPolicy != nil { + healthy = u.Host.Fails() < u.healthCheckPolicy.MaxFails + } + if healthy && u.cb != nil { + healthy = u.cb.OK() + } + return healthy +} + +// Full returns true if the remote host +// cannot receive more requests at this time. +func (u *Upstream) Full() bool { + return u.MaxRequests > 0 && u.Host.NumRequests() >= u.MaxRequests +} + +// fillDialInfo returns a filled DialInfo for upstream u, using the request +// context. Note that the returned value is not a pointer. +func (u *Upstream) fillDialInfo(r *http.Request) (DialInfo, error) { + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + var addr caddy.NetworkAddress + + // use provided dial address + var err error + dial := repl.ReplaceAll(u.Dial, "") + addr, err = caddy.ParseNetworkAddress(dial) + if err != nil { + return DialInfo{}, fmt.Errorf("upstream %s: invalid dial address %s: %v", u.Dial, dial, err) + } + if numPorts := addr.PortRangeSize(); numPorts != 1 { + return DialInfo{}, fmt.Errorf("upstream %s: dial address must represent precisely one socket: %s represents %d", + u.Dial, dial, numPorts) + } + + return DialInfo{ + Upstream: u, + Network: addr.Network, + Address: addr.JoinHostPort(0), + Host: addr.Host, + Port: strconv.Itoa(int(addr.StartPort)), + }, nil +} + +func (u *Upstream) fillHost() { + host := new(Host) + existingHost, loaded := hosts.LoadOrStore(u.String(), host) + if loaded { + host = existingHost.(*Host) + } + u.Host = host +} + +// Host is the basic, in-memory representation of the state of a remote host. +// Its fields are accessed atomically and Host values must not be copied. +type Host struct { + numRequests int64 // must be 64-bit aligned on 32-bit systems (see https://golang.org/pkg/sync/atomic/#pkg-note-BUG) + fails int64 + activePasses int64 + activeFails int64 +} + +// NumRequests returns the number of active requests to the upstream. +func (h *Host) NumRequests() int { + return int(atomic.LoadInt64(&h.numRequests)) +} + +// Fails returns the number of recent failures with the upstream. +func (h *Host) Fails() int { + return int(atomic.LoadInt64(&h.fails)) +} + +// activeHealthPasses returns the number of consecutive active health check passes with the upstream. +func (h *Host) activeHealthPasses() int { + return int(atomic.LoadInt64(&h.activePasses)) +} + +// activeHealthFails returns the number of consecutive active health check failures with the upstream. +func (h *Host) activeHealthFails() int { + return int(atomic.LoadInt64(&h.activeFails)) +} + +// countRequest mutates the active request count by +// delta. It returns an error if the adjustment fails. +func (h *Host) countRequest(delta int) error { + result := atomic.AddInt64(&h.numRequests, int64(delta)) + if result < 0 { + return fmt.Errorf("count below 0: %d", result) + } + return nil +} + +// countFail mutates the recent failures count by +// delta. It returns an error if the adjustment fails. +func (h *Host) countFail(delta int) error { + result := atomic.AddInt64(&h.fails, int64(delta)) + if result < 0 { + return fmt.Errorf("count below 0: %d", result) + } + return nil +} + +// countHealthPass mutates the recent passes count by +// delta. It returns an error if the adjustment fails. +func (h *Host) countHealthPass(delta int) error { + result := atomic.AddInt64(&h.activePasses, int64(delta)) + if result < 0 { + return fmt.Errorf("count below 0: %d", result) + } + return nil +} + +// countHealthFail mutates the recent failures count by +// delta. It returns an error if the adjustment fails. +func (h *Host) countHealthFail(delta int) error { + result := atomic.AddInt64(&h.activeFails, int64(delta)) + if result < 0 { + return fmt.Errorf("count below 0: %d", result) + } + return nil +} + +// resetHealth resets the health check counters. +func (h *Host) resetHealth() { + atomic.StoreInt64(&h.activePasses, 0) + atomic.StoreInt64(&h.activeFails, 0) +} + +// healthy returns true if the upstream is not actively marked as unhealthy. +// (This returns the status only from the "active" health checks.) +func (u *Upstream) healthy() bool { + return atomic.LoadInt32(&u.unhealthy) == 0 +} + +// SetHealthy sets the upstream has healthy or unhealthy +// and returns true if the new value is different. This +// sets the status only for the "active" health checks. +func (u *Upstream) setHealthy(healthy bool) bool { + var unhealthy, compare int32 = 1, 0 + if healthy { + unhealthy, compare = 0, 1 + } + return atomic.CompareAndSwapInt32(&u.unhealthy, compare, unhealthy) +} + +// DialInfo contains information needed to dial a +// connection to an upstream host. This information +// may be different than that which is represented +// in a URL (for example, unix sockets don't have +// a host that can be represented in a URL, but +// they certainly have a network name and address). +type DialInfo struct { + // Upstream is the Upstream associated with + // this DialInfo. It may be nil. + Upstream *Upstream + + // The network to use. This should be one of + // the values that is accepted by net.Dial: + // https://golang.org/pkg/net/#Dial + Network string + + // The address to dial. Follows the same + // semantics and rules as net.Dial. + Address string + + // Host and Port are components of Address. + Host, Port string +} + +// String returns the Caddy network address form +// by joining the network and address with a +// forward slash. +func (di DialInfo) String() string { + return caddy.JoinNetworkAddress(di.Network, di.Host, di.Port) +} + +// GetDialInfo gets the upstream dialing info out of the context, +// and returns true if there was a valid value; false otherwise. +func GetDialInfo(ctx context.Context) (DialInfo, bool) { + dialInfo, ok := caddyhttp.GetVar(ctx, dialInfoVarKey).(DialInfo) + return dialInfo, ok +} + +// hosts is the global repository for hosts that are +// currently in use by active configuration(s). This +// allows the state of remote hosts to be preserved +// through config reloads. +var hosts = caddy.NewUsagePool() + +// dialInfoVarKey is the key used for the variable that holds +// the dial info for the upstream connection. +const dialInfoVarKey = "reverse_proxy.dial_info" + +// proxyProtocolInfoVarKey is the key used for the variable that holds +// the proxy protocol info for the upstream connection. +const proxyProtocolInfoVarKey = "reverse_proxy.proxy_protocol_info" + +// ProxyProtocolInfo contains information needed to write proxy protocol to a +// connection to an upstream host. +type ProxyProtocolInfo struct { + AddrPort netip.AddrPort +} diff --git a/modules/caddyhttp/reverseproxy/httptransport.go b/modules/caddyhttp/reverseproxy/httptransport.go new file mode 100644 index 00000000000..910033ca1a5 --- /dev/null +++ b/modules/caddyhttp/reverseproxy/httptransport.go @@ -0,0 +1,793 @@ +// 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 reverseproxy + +import ( + "context" + "crypto/tls" + "crypto/x509" + "encoding/base64" + "encoding/json" + "fmt" + weakrand "math/rand" + "net" + "net/http" + "net/url" + "os" + "reflect" + "slices" + "strings" + "time" + + "github.com/pires/go-proxyproto" + "github.com/quic-go/quic-go/http3" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "golang.org/x/net/http2" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddytls" +) + +func init() { + caddy.RegisterModule(HTTPTransport{}) +} + +// HTTPTransport is essentially a configuration wrapper for http.Transport. +// It defines a JSON structure useful when configuring the HTTP transport +// for Caddy's reverse proxy. It builds its http.Transport at Provision. +type HTTPTransport struct { + // TODO: It's possible that other transports (like fastcgi) might be + // able to borrow/use at least some of these config fields; if so, + // maybe move them into a type called CommonTransport and embed it? + + // Configures the DNS resolver used to resolve the IP address of upstream hostnames. + Resolver *UpstreamResolver `json:"resolver,omitempty"` + + // Configures TLS to the upstream. Setting this to an empty struct + // is sufficient to enable TLS with reasonable defaults. + TLS *TLSConfig `json:"tls,omitempty"` + + // Configures HTTP Keep-Alive (enabled by default). Should only be + // necessary if rigorous testing has shown that tuning this helps + // improve performance. + KeepAlive *KeepAlive `json:"keep_alive,omitempty"` + + // Whether to enable compression to upstream. Default: true + Compression *bool `json:"compression,omitempty"` + + // Maximum number of connections per host. Default: 0 (no limit) + MaxConnsPerHost int `json:"max_conns_per_host,omitempty"` + + // If non-empty, which PROXY protocol version to send when + // connecting to an upstream. Default: off. + ProxyProtocol string `json:"proxy_protocol,omitempty"` + + // URL to the server that the HTTP transport will use to proxy + // requests to the upstream. See http.Transport.Proxy for + // information regarding supported protocols. This value takes + // precedence over `HTTP_PROXY`, etc. + // + // Providing a value to this parameter results in + // requests flowing through the reverse_proxy in the following + // way: + // + // User Agent -> + // reverse_proxy -> + // forward_proxy_url -> upstream + // + // Default: http.ProxyFromEnvironment + ForwardProxyURL string `json:"forward_proxy_url,omitempty"` + + // How long to wait before timing out trying to connect to + // an upstream. Default: `3s`. + DialTimeout caddy.Duration `json:"dial_timeout,omitempty"` + + // How long to wait before spawning an RFC 6555 Fast Fallback + // connection. A negative value disables this. Default: `300ms`. + FallbackDelay caddy.Duration `json:"dial_fallback_delay,omitempty"` + + // How long to wait for reading response headers from server. Default: No timeout. + ResponseHeaderTimeout caddy.Duration `json:"response_header_timeout,omitempty"` + + // The length of time to wait for a server's first response + // headers after fully writing the request headers if the + // request has a header "Expect: 100-continue". Default: No timeout. + ExpectContinueTimeout caddy.Duration `json:"expect_continue_timeout,omitempty"` + + // The maximum bytes to read from response headers. Default: `10MiB`. + MaxResponseHeaderSize int64 `json:"max_response_header_size,omitempty"` + + // The size of the write buffer in bytes. Default: `4KiB`. + WriteBufferSize int `json:"write_buffer_size,omitempty"` + + // The size of the read buffer in bytes. Default: `4KiB`. + ReadBufferSize int `json:"read_buffer_size,omitempty"` + + // The maximum time to wait for next read from backend. Default: no timeout. + ReadTimeout caddy.Duration `json:"read_timeout,omitempty"` + + // The maximum time to wait for next write to backend. Default: no timeout. + WriteTimeout caddy.Duration `json:"write_timeout,omitempty"` + + // The versions of HTTP to support. As a special case, "h2c" + // can be specified to use H2C (HTTP/2 over Cleartext) to the + // upstream (this feature is experimental and subject to + // change or removal). Default: ["1.1", "2"] + // + // EXPERIMENTAL: "3" enables HTTP/3, but it must be the only + // version specified if enabled. Additionally, HTTPS must be + // enabled to the upstream as HTTP/3 requires TLS. Subject + // to change or removal while experimental. + Versions []string `json:"versions,omitempty"` + + // Specify the address to bind to when connecting to an upstream. In other words, + // it is the address the upstream sees as the remote address. + LocalAddress string `json:"local_address,omitempty"` + + // The pre-configured underlying HTTP transport. + Transport *http.Transport `json:"-"` + + h2cTransport *http2.Transport + h3Transport *http3.Transport // TODO: EXPERIMENTAL (May 2024) +} + +// CaddyModule returns the Caddy module information. +func (HTTPTransport) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.reverse_proxy.transport.http", + New: func() caddy.Module { return new(HTTPTransport) }, + } +} + +// Provision sets up h.Transport with a *http.Transport +// that is ready to use. +func (h *HTTPTransport) Provision(ctx caddy.Context) error { + if len(h.Versions) == 0 { + h.Versions = []string{"1.1", "2"} + } + + rt, err := h.NewTransport(ctx) + if err != nil { + return err + } + h.Transport = rt + + return nil +} + +// NewTransport builds a standard-lib-compatible http.Transport value from h. +func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, error) { + // Set keep-alive defaults if it wasn't otherwise configured + if h.KeepAlive == nil { + h.KeepAlive = &KeepAlive{ + ProbeInterval: caddy.Duration(30 * time.Second), + IdleConnTimeout: caddy.Duration(2 * time.Minute), + MaxIdleConnsPerHost: 32, // seems about optimal, see #2805 + } + } + + // Set a relatively short default dial timeout. + // This is helpful to make load-balancer retries more speedy. + if h.DialTimeout == 0 { + h.DialTimeout = caddy.Duration(3 * time.Second) + } + + dialer := &net.Dialer{ + Timeout: time.Duration(h.DialTimeout), + FallbackDelay: time.Duration(h.FallbackDelay), + } + + if h.LocalAddress != "" { + netaddr, err := caddy.ParseNetworkAddressWithDefaults(h.LocalAddress, "tcp", 0) + if err != nil { + return nil, err + } + if netaddr.PortRangeSize() > 1 { + return nil, fmt.Errorf("local_address must be a single address, not a port range") + } + switch netaddr.Network { + case "tcp", "tcp4", "tcp6": + dialer.LocalAddr, err = net.ResolveTCPAddr(netaddr.Network, netaddr.JoinHostPort(0)) + if err != nil { + return nil, err + } + case "unix", "unixgram", "unixpacket": + dialer.LocalAddr, err = net.ResolveUnixAddr(netaddr.Network, netaddr.JoinHostPort(0)) + if err != nil { + return nil, err + } + case "udp", "udp4", "udp6": + return nil, fmt.Errorf("local_address must be a TCP address, not a UDP address") + default: + return nil, fmt.Errorf("unsupported network") + } + } + if h.Resolver != nil { + err := h.Resolver.ParseAddresses() + if err != nil { + return nil, err + } + d := &net.Dialer{ + Timeout: time.Duration(h.DialTimeout), + FallbackDelay: time.Duration(h.FallbackDelay), + } + dialer.Resolver = &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, _, _ string) (net.Conn, error) { + //nolint:gosec + addr := h.Resolver.netAddrs[weakrand.Intn(len(h.Resolver.netAddrs))] + return d.DialContext(ctx, addr.Network, addr.JoinHostPort(0)) + }, + } + } + + dialContext := func(ctx context.Context, network, address string) (net.Conn, error) { + // For unix socket upstreams, we need to recover the dial info from + // the request's context, because the Host on the request's URL + // will have been modified by directing the request, overwriting + // the unix socket filename. + // Also, we need to avoid overwriting the address at this point + // when not necessary, because http.ProxyFromEnvironment may have + // modified the address according to the user's env proxy config. + if dialInfo, ok := GetDialInfo(ctx); ok { + if strings.HasPrefix(dialInfo.Network, "unix") { + network = dialInfo.Network + address = dialInfo.Address + } + } + + conn, err := dialer.DialContext(ctx, network, address) + if err != nil { + // identify this error as one that occurred during + // dialing, which can be important when trying to + // decide whether to retry a request + return nil, DialError{err} + } + + if h.ProxyProtocol != "" { + proxyProtocolInfo, ok := caddyhttp.GetVar(ctx, proxyProtocolInfoVarKey).(ProxyProtocolInfo) + if !ok { + return nil, fmt.Errorf("failed to get proxy protocol info from context") + } + var proxyv byte + switch h.ProxyProtocol { + case "v1": + proxyv = 1 + case "v2": + proxyv = 2 + default: + return nil, fmt.Errorf("unexpected proxy protocol version") + } + + // The src and dst have to be of the same address family. As we don't know the original + // dst address (it's kind of impossible to know) and this address is generally of very + // little interest, we just set it to all zeros. + var destAddr net.Addr + switch { + case proxyProtocolInfo.AddrPort.Addr().Is4(): + destAddr = &net.TCPAddr{ + IP: net.IPv4zero, + } + case proxyProtocolInfo.AddrPort.Addr().Is6(): + destAddr = &net.TCPAddr{ + IP: net.IPv6zero, + } + default: + return nil, fmt.Errorf("unexpected remote addr type in proxy protocol info") + } + sourceAddr := &net.TCPAddr{ + IP: proxyProtocolInfo.AddrPort.Addr().AsSlice(), + Port: int(proxyProtocolInfo.AddrPort.Port()), + Zone: proxyProtocolInfo.AddrPort.Addr().Zone(), + } + header := proxyproto.HeaderProxyFromAddrs(proxyv, sourceAddr, destAddr) + + // retain the log message structure + switch h.ProxyProtocol { + case "v1": + caddyCtx.Logger().Debug("sending proxy protocol header v1", zap.Any("header", header)) + case "v2": + caddyCtx.Logger().Debug("sending proxy protocol header v2", zap.Any("header", header)) + } + + _, err = header.WriteTo(conn) + if err != nil { + // identify this error as one that occurred during + // dialing, which can be important when trying to + // decide whether to retry a request + return nil, DialError{err} + } + } + + // if read/write timeouts are configured and this is a TCP connection, + // enforce the timeouts by wrapping the connection with our own type + if tcpConn, ok := conn.(*net.TCPConn); ok && (h.ReadTimeout > 0 || h.WriteTimeout > 0) { + conn = &tcpRWTimeoutConn{ + TCPConn: tcpConn, + readTimeout: time.Duration(h.ReadTimeout), + writeTimeout: time.Duration(h.WriteTimeout), + logger: caddyCtx.Logger(), + } + } + + return conn, nil + } + + // negotiate any HTTP/SOCKS proxy for the HTTP transport + var proxy func(*http.Request) (*url.URL, error) + if h.ForwardProxyURL != "" { + pUrl, err := url.Parse(h.ForwardProxyURL) + if err != nil { + return nil, fmt.Errorf("failed to parse transport proxy url: %v", err) + } + caddyCtx.Logger().Info("setting transport proxy url", zap.String("url", h.ForwardProxyURL)) + proxy = http.ProxyURL(pUrl) + } else { + proxy = http.ProxyFromEnvironment + } + + rt := &http.Transport{ + Proxy: proxy, + DialContext: dialContext, + MaxConnsPerHost: h.MaxConnsPerHost, + ResponseHeaderTimeout: time.Duration(h.ResponseHeaderTimeout), + ExpectContinueTimeout: time.Duration(h.ExpectContinueTimeout), + MaxResponseHeaderBytes: h.MaxResponseHeaderSize, + WriteBufferSize: h.WriteBufferSize, + ReadBufferSize: h.ReadBufferSize, + } + + if h.TLS != nil { + rt.TLSHandshakeTimeout = time.Duration(h.TLS.HandshakeTimeout) + var err error + rt.TLSClientConfig, err = h.TLS.MakeTLSClientConfig(caddyCtx) + if err != nil { + return nil, fmt.Errorf("making TLS client config: %v", err) + } + } + + if h.KeepAlive != nil { + dialer.KeepAlive = time.Duration(h.KeepAlive.ProbeInterval) + if h.KeepAlive.Enabled != nil { + rt.DisableKeepAlives = !*h.KeepAlive.Enabled + } + rt.MaxIdleConns = h.KeepAlive.MaxIdleConns + rt.MaxIdleConnsPerHost = h.KeepAlive.MaxIdleConnsPerHost + rt.IdleConnTimeout = time.Duration(h.KeepAlive.IdleConnTimeout) + } + + // The proxy protocol header can only be sent once right after opening the connection. + // So single connection must not be used for multiple requests, which can potentially + // come from different clients. + if !rt.DisableKeepAlives && h.ProxyProtocol != "" { + caddyCtx.Logger().Warn("disabling keepalives, they are incompatible with using PROXY protocol") + rt.DisableKeepAlives = true + } + + if h.Compression != nil { + rt.DisableCompression = !*h.Compression + } + + if slices.Contains(h.Versions, "2") { + if err := http2.ConfigureTransport(rt); err != nil { + return nil, err + } + } + + // configure HTTP/3 transport if enabled; however, this does not + // automatically fall back to lower versions like most web browsers + // do (that'd add latency and complexity, besides, we expect that + // site owners control the backends), so it must be exclusive + if len(h.Versions) == 1 && h.Versions[0] == "3" { + h.h3Transport = new(http3.Transport) + if h.TLS != nil { + var err error + h.h3Transport.TLSClientConfig, err = h.TLS.MakeTLSClientConfig(caddyCtx) + if err != nil { + return nil, fmt.Errorf("making TLS client config for HTTP/3 transport: %v", err) + } + } + } else if len(h.Versions) > 1 && slices.Contains(h.Versions, "3") { + return nil, fmt.Errorf("if HTTP/3 is enabled to the upstream, no other HTTP versions are supported") + } + + // if h2c is enabled, configure its transport (std lib http.Transport + // does not "HTTP/2 over cleartext TCP") + if slices.Contains(h.Versions, "h2c") { + // crafting our own http2.Transport doesn't allow us to utilize + // most of the customizations/preferences on the http.Transport, + // because, for some reason, only http2.ConfigureTransport() + // is allowed to set the unexported field that refers to a base + // http.Transport config; oh well + h2t := &http2.Transport{ + // kind of a hack, but for plaintext/H2C requests, pretend to dial TLS + DialTLSContext: func(ctx context.Context, network, address string, _ *tls.Config) (net.Conn, error) { + return dialContext(ctx, network, address) + }, + AllowHTTP: true, + } + if h.Compression != nil { + h2t.DisableCompression = !*h.Compression + } + h.h2cTransport = h2t + } + + return rt, nil +} + +// replaceTLSServername checks TLS servername to see if it needs replacing +// if it does need replacing, it creates a new cloned HTTPTransport object to avoid any races +// and does the replacing of the TLS servername on that and returns the new object +// if no replacement is necessary it returns the original +func (h *HTTPTransport) replaceTLSServername(repl *caddy.Replacer) *HTTPTransport { + // check whether we have TLS and need to replace the servername in the TLSClientConfig + if h.TLSEnabled() && strings.Contains(h.TLS.ServerName, "{") { + // make a new h, "copy" the parts we don't need to touch, add a new *tls.Config and replace servername + newtransport := &HTTPTransport{ + Resolver: h.Resolver, + TLS: h.TLS, + KeepAlive: h.KeepAlive, + Compression: h.Compression, + MaxConnsPerHost: h.MaxConnsPerHost, + DialTimeout: h.DialTimeout, + FallbackDelay: h.FallbackDelay, + ResponseHeaderTimeout: h.ResponseHeaderTimeout, + ExpectContinueTimeout: h.ExpectContinueTimeout, + MaxResponseHeaderSize: h.MaxResponseHeaderSize, + WriteBufferSize: h.WriteBufferSize, + ReadBufferSize: h.ReadBufferSize, + Versions: h.Versions, + Transport: h.Transport.Clone(), + h2cTransport: h.h2cTransport, + } + newtransport.Transport.TLSClientConfig.ServerName = repl.ReplaceAll(newtransport.Transport.TLSClientConfig.ServerName, "") + return newtransport + } + + return h +} + +// RoundTrip implements http.RoundTripper. +func (h *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) { + // Try to replace TLS servername if needed + repl := req.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + transport := h.replaceTLSServername(repl) + + transport.SetScheme(req) + + // use HTTP/3 if enabled (TODO: This is EXPERIMENTAL) + if h.h3Transport != nil { + return h.h3Transport.RoundTrip(req) + } + + // if H2C ("HTTP/2 over cleartext") is enabled and the upstream request is + // HTTP without TLS, use the alternate H2C-capable transport instead + if req.URL.Scheme == "http" && h.h2cTransport != nil { + // There is no dedicated DisableKeepAlives field in *http2.Transport. + // This is an alternative way to disable keep-alive. + req.Close = h.Transport.DisableKeepAlives + return h.h2cTransport.RoundTrip(req) + } + + return transport.Transport.RoundTrip(req) +} + +// SetScheme ensures that the outbound request req +// has the scheme set in its URL; the underlying +// http.Transport requires a scheme to be set. +// +// This method may be used by other transport modules +// that wrap/use this one. +func (h *HTTPTransport) SetScheme(req *http.Request) { + if req.URL.Scheme != "" { + return + } + if h.shouldUseTLS(req) { + req.URL.Scheme = "https" + } else { + req.URL.Scheme = "http" + } +} + +// shouldUseTLS returns true if TLS should be used for req. +func (h *HTTPTransport) shouldUseTLS(req *http.Request) bool { + if h.TLS == nil { + return false + } + + port := req.URL.Port() + for i := range h.TLS.ExceptPorts { + if h.TLS.ExceptPorts[i] == port { + return false + } + } + + return true +} + +// TLSEnabled returns true if TLS is enabled. +func (h HTTPTransport) TLSEnabled() bool { + return h.TLS != nil +} + +// EnableTLS enables TLS on the transport. +func (h *HTTPTransport) EnableTLS(base *TLSConfig) error { + h.TLS = base + return nil +} + +// Cleanup implements caddy.CleanerUpper and closes any idle connections. +func (h HTTPTransport) Cleanup() error { + if h.Transport == nil { + return nil + } + h.Transport.CloseIdleConnections() + return nil +} + +// TLSConfig holds configuration related to the TLS configuration for the +// transport/client. +type TLSConfig struct { + // Certificate authority module which provides the certificate pool of trusted certificates + CARaw json.RawMessage `json:"ca,omitempty" caddy:"namespace=tls.ca_pool.source inline_key=provider"` + + // Deprecated: Use the `ca` field with the `tls.ca_pool.source.inline` module instead. + // Optional list of base64-encoded DER-encoded CA certificates to trust. + RootCAPool []string `json:"root_ca_pool,omitempty"` + + // Deprecated: Use the `ca` field with the `tls.ca_pool.source.file` module instead. + // 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"` + + // PEM-encoded client certificate filename to present to servers. + ClientCertificateFile string `json:"client_certificate_file,omitempty"` + + // PEM-encoded key to use with the client certificate. + ClientCertificateKeyFile string `json:"client_certificate_key_file,omitempty"` + + // If specified, Caddy will use and automate a client certificate + // with this subject name. + ClientCertificateAutomate string `json:"client_certificate_automate,omitempty"` + + // If true, TLS verification of server certificates will be disabled. + // This is insecure and may be removed in the future. Do not use this + // option except in testing or local development environments. + InsecureSkipVerify bool `json:"insecure_skip_verify,omitempty"` + + // The duration to allow a TLS handshake to a server. Default: No timeout. + HandshakeTimeout caddy.Duration `json:"handshake_timeout,omitempty"` + + // The server name used when verifying the certificate received in the TLS + // handshake. By default, this will use the upstream address' host part. + // You only need to override this if your upstream address does not match the + // certificate the upstream is likely to use. For example if the upstream + // address is an IP address, then you would need to configure this to the + // hostname being served by the upstream server. Currently, this does not + // support placeholders because the TLS config is not provisioned on each + // connection, so a static value must be used. + ServerName string `json:"server_name,omitempty"` + + // TLS renegotiation level. TLS renegotiation is the act of performing + // subsequent handshakes on a connection after the first. + // The level can be: + // - "never": (the default) disables renegotiation. + // - "once": allows a remote server to request renegotiation once per connection. + // - "freely": allows a remote server to repeatedly request renegotiation. + Renegotiation string `json:"renegotiation,omitempty"` + + // Skip TLS ports specifies a list of upstream ports on which TLS should not be + // attempted even if it is configured. Handy when using dynamic upstreams that + // return HTTP and HTTPS endpoints too. + // When specified, TLS will automatically be configured on the transport. + // The value can be a list of any valid tcp port numbers, default empty. + ExceptPorts []string `json:"except_ports,omitempty"` + + // The list of elliptic curves to support. Caddy's + // defaults are modern and secure. + Curves []string `json:"curves,omitempty"` +} + +// MakeTLSClientConfig returns a tls.Config usable by a client to a backend. +// If there is no custom TLS configuration, a nil config may be returned. +func (t *TLSConfig) MakeTLSClientConfig(ctx caddy.Context) (*tls.Config, error) { + cfg := new(tls.Config) + + // client auth + if t.ClientCertificateFile != "" && t.ClientCertificateKeyFile == "" { + return nil, fmt.Errorf("client_certificate_file specified without client_certificate_key_file") + } + if t.ClientCertificateFile == "" && t.ClientCertificateKeyFile != "" { + return nil, fmt.Errorf("client_certificate_key_file specified without client_certificate_file") + } + if t.ClientCertificateFile != "" && t.ClientCertificateKeyFile != "" { + cert, err := tls.LoadX509KeyPair(t.ClientCertificateFile, t.ClientCertificateKeyFile) + if err != nil { + return nil, fmt.Errorf("loading client certificate key pair: %v", err) + } + cfg.Certificates = []tls.Certificate{cert} + } + if t.ClientCertificateAutomate != "" { + // TODO: use or enable ctx.IdentityCredentials() ... + tlsAppIface, err := ctx.App("tls") + if err != nil { + return nil, fmt.Errorf("getting tls app: %v", err) + } + tlsApp := tlsAppIface.(*caddytls.TLS) + err = tlsApp.Manage([]string{t.ClientCertificateAutomate}) + if err != nil { + return nil, fmt.Errorf("managing client certificate: %v", err) + } + cfg.GetClientCertificate = func(cri *tls.CertificateRequestInfo) (*tls.Certificate, error) { + certs := caddytls.AllMatchingCertificates(t.ClientCertificateAutomate) + var err error + for _, cert := range certs { + certCertificate := cert.Certificate // avoid taking address of iteration variable (gosec warning) + err = cri.SupportsCertificate(&certCertificate) + if err == nil { + return &cert.Certificate, nil + } + } + if err == nil { + err = fmt.Errorf("no client certificate found for automate name: %s", t.ClientCertificateAutomate) + } + return nil, err + } + } + + // trusted root CAs + if len(t.RootCAPool) > 0 || len(t.RootCAPEMFiles) > 0 { + ctx.Logger().Warn("root_ca_pool and root_ca_pem_files are deprecated. Use one of the tls.ca_pool.source modules instead") + rootPool := x509.NewCertPool() + for _, encodedCACert := range t.RootCAPool { + caCert, err := decodeBase64DERCert(encodedCACert) + if err != nil { + return nil, fmt.Errorf("parsing CA certificate: %v", err) + } + rootPool.AddCert(caCert) + } + for _, pemFile := range t.RootCAPEMFiles { + pemData, err := os.ReadFile(pemFile) + if err != nil { + return nil, fmt.Errorf("failed reading ca cert: %v", err) + } + rootPool.AppendCertsFromPEM(pemData) + } + cfg.RootCAs = rootPool + } + + if t.CARaw != nil { + if len(t.RootCAPool) > 0 || len(t.RootCAPEMFiles) > 0 { + return nil, fmt.Errorf("conflicting config for Root CA pool") + } + caRaw, err := ctx.LoadModule(t, "CARaw") + if err != nil { + return nil, fmt.Errorf("failed to load ca module: %v", err) + } + ca, ok := caRaw.(caddytls.CA) + if !ok { + return nil, fmt.Errorf("CA module '%s' is not a certificate pool provider", ca) + } + cfg.RootCAs = ca.CertPool() + } + + // Renegotiation + switch t.Renegotiation { + case "never", "": + cfg.Renegotiation = tls.RenegotiateNever + case "once": + cfg.Renegotiation = tls.RenegotiateOnceAsClient + case "freely": + cfg.Renegotiation = tls.RenegotiateFreelyAsClient + default: + return nil, fmt.Errorf("invalid TLS renegotiation level: %v", t.Renegotiation) + } + + // override for the server name used verify the TLS handshake + cfg.ServerName = t.ServerName + + // throw all security out the window + cfg.InsecureSkipVerify = t.InsecureSkipVerify + + curvesAdded := make(map[tls.CurveID]struct{}) + for _, curveName := range t.Curves { + curveID := caddytls.SupportedCurves[curveName] + if _, ok := curvesAdded[curveID]; !ok { + curvesAdded[curveID] = struct{}{} + cfg.CurvePreferences = append(cfg.CurvePreferences, curveID) + } + } + + // only return a config if it's not empty + if reflect.DeepEqual(cfg, new(tls.Config)) { + return nil, nil + } + + return cfg, nil +} + +// KeepAlive holds configuration pertaining to HTTP Keep-Alive. +type KeepAlive struct { + // Whether HTTP Keep-Alive is enabled. Default: `true` + Enabled *bool `json:"enabled,omitempty"` + + // How often to probe for liveness. Default: `30s`. + ProbeInterval caddy.Duration `json:"probe_interval,omitempty"` + + // Maximum number of idle connections. Default: `0`, which means no limit. + MaxIdleConns int `json:"max_idle_conns,omitempty"` + + // Maximum number of idle connections per host. Default: `32`. + MaxIdleConnsPerHost int `json:"max_idle_conns_per_host,omitempty"` + + // How long connections should be kept alive when idle. Default: `2m`. + IdleConnTimeout caddy.Duration `json:"idle_timeout,omitempty"` +} + +// tcpRWTimeoutConn enforces read/write timeouts for a TCP connection. +// If it fails to set deadlines, the error is logged but does not abort +// the read/write attempt (ignoring the error is consistent with what +// the standard library does: https://github.com/golang/go/blob/c5da4fb7ac5cb7434b41fc9a1df3bee66c7f1a4d/src/net/http/server.go#L981-L986) +type tcpRWTimeoutConn struct { + *net.TCPConn + readTimeout, writeTimeout time.Duration + logger *zap.Logger +} + +func (c *tcpRWTimeoutConn) Read(b []byte) (int, error) { + if c.readTimeout > 0 { + err := c.TCPConn.SetReadDeadline(time.Now().Add(c.readTimeout)) + if err != nil { + if ce := c.logger.Check(zapcore.ErrorLevel, "failed to set read deadline"); ce != nil { + ce.Write(zap.Error(err)) + } + } + } + return c.TCPConn.Read(b) +} + +func (c *tcpRWTimeoutConn) Write(b []byte) (int, error) { + if c.writeTimeout > 0 { + err := c.TCPConn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) + if err != nil { + if ce := c.logger.Check(zapcore.ErrorLevel, "failed to set write deadline"); ce != nil { + ce.Write(zap.Error(err)) + } + } + } + return c.TCPConn.Write(b) +} + +// decodeBase64DERCert base64-decodes, then DER-decodes, certStr. +func decodeBase64DERCert(certStr string) (*x509.Certificate, error) { + // decode base64 + derBytes, err := base64.StdEncoding.DecodeString(certStr) + if err != nil { + return nil, err + } + + // parse the DER-encoded certificate + return x509.ParseCertificate(derBytes) +} + +// Interface guards +var ( + _ caddy.Provisioner = (*HTTPTransport)(nil) + _ http.RoundTripper = (*HTTPTransport)(nil) + _ caddy.CleanerUpper = (*HTTPTransport)(nil) + _ TLSTransport = (*HTTPTransport)(nil) +) diff --git a/modules/caddyhttp/reverseproxy/httptransport_test.go b/modules/caddyhttp/reverseproxy/httptransport_test.go new file mode 100644 index 00000000000..46931c8b17c --- /dev/null +++ b/modules/caddyhttp/reverseproxy/httptransport_test.go @@ -0,0 +1,96 @@ +package reverseproxy + +import ( + "encoding/json" + "fmt" + "reflect" + "testing" + + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func TestHTTPTransportUnmarshalCaddyFileWithCaPools(t *testing.T) { + const test_der_1 = `MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==` + type args struct { + d *caddyfile.Dispenser + } + tests := []struct { + name string + args args + expectedTLSConfig TLSConfig + wantErr bool + }{ + { + name: "tls_trust_pool without a module argument returns an error", + args: args{ + d: caddyfile.NewTestDispenser( + `http { + tls_trust_pool + }`), + }, + wantErr: true, + }, + { + name: "providing both 'tls_trust_pool' and 'tls_trusted_ca_certs' returns an error", + args: args{ + d: caddyfile.NewTestDispenser(fmt.Sprintf( + `http { + tls_trust_pool inline %s + tls_trusted_ca_certs %s + }`, test_der_1, test_der_1)), + }, + wantErr: true, + }, + { + name: "setting 'tls_trust_pool' and 'tls_trusted_ca_certs' produces an error", + args: args{ + d: caddyfile.NewTestDispenser(fmt.Sprintf( + `http { + tls_trust_pool inline { + trust_der %s + } + tls_trusted_ca_certs %s + }`, test_der_1, test_der_1)), + }, + wantErr: true, + }, + { + name: "using 'inline' tls_trust_pool loads the module successfully", + args: args{ + d: caddyfile.NewTestDispenser(fmt.Sprintf( + `http { + tls_trust_pool inline { + trust_der %s + } + } + `, test_der_1)), + }, + expectedTLSConfig: TLSConfig{CARaw: json.RawMessage(fmt.Sprintf(`{"provider":"inline","trusted_ca_certs":["%s"]}`, test_der_1))}, + }, + { + name: "setting 'tls_trusted_ca_certs' and 'tls_trust_pool' produces an error", + args: args{ + d: caddyfile.NewTestDispenser(fmt.Sprintf( + `http { + tls_trusted_ca_certs %s + tls_trust_pool inline { + trust_der %s + } + }`, test_der_1, test_der_1)), + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ht := &HTTPTransport{} + if err := ht.UnmarshalCaddyfile(tt.args.d); (err != nil) != tt.wantErr { + t.Errorf("HTTPTransport.UnmarshalCaddyfile() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && !reflect.DeepEqual(&tt.expectedTLSConfig, ht.TLS) { + t.Errorf("HTTPTransport.UnmarshalCaddyfile() = %v, want %v", ht, tt.expectedTLSConfig) + } + }) + } +} diff --git a/modules/caddyhttp/reverseproxy/metrics.go b/modules/caddyhttp/reverseproxy/metrics.go new file mode 100644 index 00000000000..2488427304e --- /dev/null +++ b/modules/caddyhttp/reverseproxy/metrics.go @@ -0,0 +1,99 @@ +package reverseproxy + +import ( + "errors" + "runtime/debug" + "sync" + "time" + + "github.com/prometheus/client_golang/prometheus" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" +) + +var reverseProxyMetrics = struct { + once sync.Once + upstreamsHealthy *prometheus.GaugeVec + logger *zap.Logger +}{} + +func initReverseProxyMetrics(handler *Handler, registry *prometheus.Registry) { + const ns, sub = "caddy", "reverse_proxy" + + upstreamsLabels := []string{"upstream"} + reverseProxyMetrics.once.Do(func() { + reverseProxyMetrics.upstreamsHealthy = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: ns, + Subsystem: sub, + Name: "upstreams_healthy", + Help: "Health status of reverse proxy upstreams.", + }, upstreamsLabels) + }) + + // duplicate registration could happen if multiple sites with reverse proxy are configured; so ignore the error because + // there's no good way to capture having multiple sites with reverse proxy. If this happens, the metrics will be + // registered twice, but the second registration will be ignored. + if err := registry.Register(reverseProxyMetrics.upstreamsHealthy); err != nil && + !errors.Is(err, prometheus.AlreadyRegisteredError{ + ExistingCollector: reverseProxyMetrics.upstreamsHealthy, + NewCollector: reverseProxyMetrics.upstreamsHealthy, + }) { + panic(err) + } + + reverseProxyMetrics.logger = handler.logger.Named("reverse_proxy.metrics") +} + +type metricsUpstreamsHealthyUpdater struct { + handler *Handler +} + +func newMetricsUpstreamsHealthyUpdater(handler *Handler, ctx caddy.Context) *metricsUpstreamsHealthyUpdater { + initReverseProxyMetrics(handler, ctx.GetMetricsRegistry()) + reverseProxyMetrics.upstreamsHealthy.Reset() + + return &metricsUpstreamsHealthyUpdater{handler} +} + +func (m *metricsUpstreamsHealthyUpdater) init() { + go func() { + defer func() { + if err := recover(); err != nil { + if c := reverseProxyMetrics.logger.Check(zapcore.ErrorLevel, "upstreams healthy metrics updater panicked"); c != nil { + c.Write( + zap.Any("error", err), + zap.ByteString("stack", debug.Stack()), + ) + } + } + }() + + m.update() + + ticker := time.NewTicker(10 * time.Second) + for { + select { + case <-ticker.C: + m.update() + case <-m.handler.ctx.Done(): + ticker.Stop() + return + } + } + }() +} + +func (m *metricsUpstreamsHealthyUpdater) update() { + for _, upstream := range m.handler.Upstreams { + labels := prometheus.Labels{"upstream": upstream.Dial} + + gaugeValue := 0.0 + if upstream.Healthy() { + gaugeValue = 1.0 + } + + reverseProxyMetrics.upstreamsHealthy.With(labels).Set(gaugeValue) + } +} diff --git a/modules/caddyhttp/reverseproxy/reverseproxy.go b/modules/caddyhttp/reverseproxy/reverseproxy.go new file mode 100644 index 00000000000..d799c5a6e7e --- /dev/null +++ b/modules/caddyhttp/reverseproxy/reverseproxy.go @@ -0,0 +1,1550 @@ +// 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 reverseproxy + +import ( + "bytes" + "context" + "crypto/rand" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "net" + "net/http" + "net/http/httptrace" + "net/netip" + "net/textproto" + "net/url" + "strconv" + "strings" + "sync" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "golang.org/x/net/http/httpguts" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyevents" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/headers" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite" +) + +func init() { + caddy.RegisterModule(Handler{}) +} + +// Handler implements a highly configurable and production-ready reverse proxy. +// +// Upon proxying, this module sets the following placeholders (which can be used +// both within and after this handler; for example, in response headers): +// +// Placeholder | Description +// ------------|------------- +// `{http.reverse_proxy.upstream.address}` | The full address to the upstream as given in the config +// `{http.reverse_proxy.upstream.hostport}` | The host:port of the upstream +// `{http.reverse_proxy.upstream.host}` | The host of the upstream +// `{http.reverse_proxy.upstream.port}` | The port of the upstream +// `{http.reverse_proxy.upstream.requests}` | The approximate current number of requests to the upstream +// `{http.reverse_proxy.upstream.max_requests}` | The maximum approximate number of requests allowed to the upstream +// `{http.reverse_proxy.upstream.fails}` | The number of recent failed requests to the upstream +// `{http.reverse_proxy.upstream.latency}` | How long it took the proxy upstream to write the response header. +// `{http.reverse_proxy.upstream.latency_ms}` | Same as 'latency', but in milliseconds. +// `{http.reverse_proxy.upstream.duration}` | Time spent proxying to the upstream, including writing response body to client. +// `{http.reverse_proxy.upstream.duration_ms}` | Same as 'upstream.duration', but in milliseconds. +// `{http.reverse_proxy.duration}` | Total time spent proxying, including selecting an upstream, retries, and writing response. +// `{http.reverse_proxy.duration_ms}` | Same as 'duration', but in milliseconds. +// `{http.reverse_proxy.retries}` | The number of retries actually performed to communicate with an upstream. +type Handler struct { + // Configures the method of transport for the proxy. A transport + // is what performs the actual "round trip" to the backend. + // The default transport is plaintext HTTP. + TransportRaw json.RawMessage `json:"transport,omitempty" caddy:"namespace=http.reverse_proxy.transport inline_key=protocol"` + + // A circuit breaker may be used to relieve pressure on a backend + // that is beginning to exhibit symptoms of stress or latency. + // By default, there is no circuit breaker. + CBRaw json.RawMessage `json:"circuit_breaker,omitempty" caddy:"namespace=http.reverse_proxy.circuit_breakers inline_key=type"` + + // Load balancing distributes load/requests between backends. + LoadBalancing *LoadBalancing `json:"load_balancing,omitempty"` + + // Health checks update the status of backends, whether they are + // up or down. Down backends will not be proxied to. + HealthChecks *HealthChecks `json:"health_checks,omitempty"` + + // Upstreams is the static list of backends to proxy to. + Upstreams UpstreamPool `json:"upstreams,omitempty"` + + // A module for retrieving the list of upstreams dynamically. Dynamic + // upstreams are retrieved at every iteration of the proxy loop for + // each request (i.e. before every proxy attempt within every request). + // Active health checks do not work on dynamic upstreams, and passive + // health checks are only effective on dynamic upstreams if the proxy + // server is busy enough that concurrent requests to the same backends + // are continuous. Instead of health checks for dynamic upstreams, it + // is recommended that the dynamic upstream module only return available + // backends in the first place. + DynamicUpstreamsRaw json.RawMessage `json:"dynamic_upstreams,omitempty" caddy:"namespace=http.reverse_proxy.upstreams inline_key=source"` + + // Adjusts how often to flush the response buffer. By default, + // no periodic flushing is done. A negative value disables + // response buffering, and flushes immediately after each + // write to the client. This option is ignored when the upstream's + // response is recognized as a streaming response, or if its + // content length is -1; for such responses, writes are flushed + // to the client immediately. + FlushInterval caddy.Duration `json:"flush_interval,omitempty"` + + // A list of IP ranges (supports CIDR notation) from which + // X-Forwarded-* header values should be trusted. By default, + // no proxies are trusted, so existing values will be ignored + // when setting these headers. If the proxy is trusted, then + // existing values will be used when constructing the final + // header values. + TrustedProxies []string `json:"trusted_proxies,omitempty"` + + // Headers manipulates headers between Caddy and the backend. + // By default, all headers are passed-thru without changes, + // with the exceptions of special hop-by-hop headers. + // + // X-Forwarded-For, X-Forwarded-Proto and X-Forwarded-Host + // are also set implicitly. + Headers *headers.Handler `json:"headers,omitempty"` + + // If nonzero, the entire request body up to this size will be read + // and buffered in memory before being proxied to the backend. This + // should be avoided if at all possible for performance reasons, but + // could be useful if the backend is intolerant of read latency or + // chunked encodings. + RequestBuffers int64 `json:"request_buffers,omitempty"` + + // If nonzero, the entire response body up to this size will be read + // and buffered in memory before being proxied to the client. This + // should be avoided if at all possible for performance reasons, but + // could be useful if the backend has tighter memory constraints. + ResponseBuffers int64 `json:"response_buffers,omitempty"` + + // If nonzero, streaming requests such as WebSockets will be + // forcibly closed at the end of the timeout. Default: no timeout. + StreamTimeout caddy.Duration `json:"stream_timeout,omitempty"` + + // If nonzero, streaming requests such as WebSockets will not be + // closed when the proxy config is unloaded, and instead the stream + // will remain open until the delay is complete. In other words, + // enabling this prevents streams from closing when Caddy's config + // is reloaded. Enabling this may be a good idea to avoid a thundering + // herd of reconnecting clients which had their connections closed + // by the previous config closing. Default: no delay. + StreamCloseDelay caddy.Duration `json:"stream_close_delay,omitempty"` + + // If configured, rewrites the copy of the upstream request. + // Allows changing the request method and URI (path and query). + // Since the rewrite is applied to the copy, it does not persist + // past the reverse proxy handler. + // If the method is changed to `GET` or `HEAD`, the request body + // will not be copied to the backend. This allows a later request + // handler -- either in a `handle_response` route, or after -- to + // read the body. + // By default, no rewrite is performed, and the method and URI + // from the incoming request is used as-is for proxying. + Rewrite *rewrite.Rewrite `json:"rewrite,omitempty"` + + // List of handlers and their associated matchers to evaluate + // after successful roundtrips. The first handler that matches + // the response from a backend will be invoked. The response + // body from the backend will not be written to the client; + // it is up to the handler to finish handling the response. + // If passive health checks are enabled, any errors from the + // handler chain will not affect the health status of the + // backend. + // + // Three new placeholders are available in this handler chain: + // - `{http.reverse_proxy.status_code}` The status code from the response + // - `{http.reverse_proxy.status_text}` The status text from the response + // - `{http.reverse_proxy.header.*}` The headers from the response + HandleResponse []caddyhttp.ResponseHandler `json:"handle_response,omitempty"` + + // If set, the proxy will write very detailed logs about its + // inner workings. Enable this only when debugging, as it + // will produce a lot of output. + // + // EXPERIMENTAL: This feature is subject to change or removal. + VerboseLogs bool `json:"verbose_logs,omitempty"` + + Transport http.RoundTripper `json:"-"` + CB CircuitBreaker `json:"-"` + DynamicUpstreams UpstreamSource `json:"-"` + + // Holds the parsed CIDR ranges from TrustedProxies + trustedProxies []netip.Prefix + + // Holds the named response matchers from the Caddyfile while adapting + responseMatchers map[string]caddyhttp.ResponseMatcher + + // Holds the handle_response Caddyfile tokens while adapting + handleResponseSegments []*caddyfile.Dispenser + + // Stores upgraded requests (hijacked connections) for proper cleanup + connections map[io.ReadWriteCloser]openConnection + connectionsCloseTimer *time.Timer + connectionsMu *sync.Mutex + + ctx caddy.Context + logger *zap.Logger + events *caddyevents.App +} + +// CaddyModule returns the Caddy module information. +func (Handler) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.reverse_proxy", + New: func() caddy.Module { return new(Handler) }, + } +} + +// Provision ensures that h is set up properly before use. +func (h *Handler) Provision(ctx caddy.Context) error { + eventAppIface, err := ctx.App("events") + if err != nil { + return fmt.Errorf("getting events app: %v", err) + } + h.events = eventAppIface.(*caddyevents.App) + h.ctx = ctx + h.logger = ctx.Logger() + h.connections = make(map[io.ReadWriteCloser]openConnection) + h.connectionsMu = new(sync.Mutex) + + // warn about unsafe buffering config + if h.RequestBuffers == -1 || h.ResponseBuffers == -1 { + h.logger.Warn("UNLIMITED BUFFERING: buffering is enabled without any cap on buffer size, which can result in OOM crashes") + } + + // start by loading modules + if h.TransportRaw != nil { + mod, err := ctx.LoadModule(h, "TransportRaw") + if err != nil { + return fmt.Errorf("loading transport: %v", err) + } + h.Transport = mod.(http.RoundTripper) + // enable request buffering for fastcgi if not configured + // This is because most fastcgi servers are php-fpm that require the content length to be set to read the body, golang + // std has fastcgi implementation that doesn't need this value to process the body, but we can safely assume that's + // not used. + // http3 requests have a negative content length for GET and HEAD requests, if that header is not sent. + // see: https://github.com/caddyserver/caddy/issues/6678#issuecomment-2472224182 + // Though it appears even if CONTENT_LENGTH is invalid, php-fpm can handle just fine if the body is empty (no Stdin records sent). + // php-fpm will hang if there is any data in the body though, https://github.com/caddyserver/caddy/issues/5420#issuecomment-2415943516 + + // TODO: better default buffering for fastcgi requests without content length, in theory a value of 1 should be enough, make it bigger anyway + if module, ok := h.Transport.(caddy.Module); ok && module.CaddyModule().ID.Name() == "fastcgi" && h.RequestBuffers == 0 { + h.RequestBuffers = 4096 + } + } + if h.LoadBalancing != nil && h.LoadBalancing.SelectionPolicyRaw != nil { + mod, err := ctx.LoadModule(h.LoadBalancing, "SelectionPolicyRaw") + if err != nil { + return fmt.Errorf("loading load balancing selection policy: %s", err) + } + h.LoadBalancing.SelectionPolicy = mod.(Selector) + } + if h.CBRaw != nil { + mod, err := ctx.LoadModule(h, "CBRaw") + if err != nil { + return fmt.Errorf("loading circuit breaker: %s", err) + } + h.CB = mod.(CircuitBreaker) + } + if h.DynamicUpstreamsRaw != nil { + mod, err := ctx.LoadModule(h, "DynamicUpstreamsRaw") + if err != nil { + return fmt.Errorf("loading upstream source module: %v", err) + } + h.DynamicUpstreams = mod.(UpstreamSource) + } + + // parse trusted proxy CIDRs ahead of time + for _, str := range h.TrustedProxies { + if strings.Contains(str, "/") { + ipNet, err := netip.ParsePrefix(str) + if err != nil { + return fmt.Errorf("parsing CIDR expression: '%s': %v", str, err) + } + h.trustedProxies = append(h.trustedProxies, ipNet) + } else { + ipAddr, err := netip.ParseAddr(str) + if err != nil { + return fmt.Errorf("invalid IP address: '%s': %v", str, err) + } + ipNew := netip.PrefixFrom(ipAddr, ipAddr.BitLen()) + h.trustedProxies = append(h.trustedProxies, ipNew) + } + } + + // ensure any embedded headers handler module gets provisioned + // (see https://caddy.community/t/set-cookie-manipulation-in-reverse-proxy/7666?u=matt + // for what happens if we forget to provision it) + if h.Headers != nil { + err := h.Headers.Provision(ctx) + if err != nil { + return fmt.Errorf("provisioning embedded headers handler: %v", err) + } + } + + if h.Rewrite != nil { + err := h.Rewrite.Provision(ctx) + if err != nil { + return fmt.Errorf("provisioning rewrite: %v", err) + } + } + + // set up transport + if h.Transport == nil { + t := &HTTPTransport{} + err := t.Provision(ctx) + if err != nil { + return fmt.Errorf("provisioning default transport: %v", err) + } + h.Transport = t + } + + // set up load balancing + if h.LoadBalancing == nil { + h.LoadBalancing = new(LoadBalancing) + } + if h.LoadBalancing.SelectionPolicy == nil { + h.LoadBalancing.SelectionPolicy = RandomSelection{} + } + if h.LoadBalancing.TryDuration > 0 && h.LoadBalancing.TryInterval == 0 { + // a non-zero try_duration with a zero try_interval + // will always spin the CPU for try_duration if the + // upstream is local or low-latency; avoid that by + // defaulting to a sane wait period between attempts + h.LoadBalancing.TryInterval = caddy.Duration(250 * time.Millisecond) + } + lbMatcherSets, err := ctx.LoadModule(h.LoadBalancing, "RetryMatchRaw") + if err != nil { + return err + } + err = h.LoadBalancing.RetryMatch.FromInterface(lbMatcherSets) + if err != nil { + return err + } + + // set up upstreams + for _, u := range h.Upstreams { + h.provisionUpstream(u) + } + + if h.HealthChecks != nil { + // set defaults on passive health checks, if necessary + if h.HealthChecks.Passive != nil { + h.HealthChecks.Passive.logger = h.logger.Named("health_checker.passive") + if h.HealthChecks.Passive.MaxFails == 0 { + h.HealthChecks.Passive.MaxFails = 1 + } + } + + // if active health checks are enabled, configure them and start a worker + if h.HealthChecks.Active != nil { + err := h.HealthChecks.Active.Provision(ctx, h) + if err != nil { + return err + } + + if h.HealthChecks.Active.IsEnabled() { + go h.activeHealthChecker() + } + } + } + + // set up any response routes + for i, rh := range h.HandleResponse { + err := rh.Provision(ctx) + if err != nil { + return fmt.Errorf("provisioning response handler %d: %v", i, err) + } + } + + upstreamHealthyUpdater := newMetricsUpstreamsHealthyUpdater(h, ctx) + upstreamHealthyUpdater.init() + + return nil +} + +// Cleanup cleans up the resources made by h. +func (h *Handler) Cleanup() error { + err := h.cleanupConnections() + + // remove hosts from our config from the pool + for _, upstream := range h.Upstreams { + _, _ = hosts.Delete(upstream.String()) + } + + return err +} + +func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + + // prepare the request for proxying; this is needed only once + clonedReq, err := h.prepareRequest(r, repl) + if err != nil { + return caddyhttp.Error(http.StatusInternalServerError, + fmt.Errorf("preparing request for upstream round-trip: %v", err)) + } + // websocket over http2, assuming backend doesn't support this, the request will be modified to http1.1 upgrade + // TODO: once we can reliably detect backend support this, it can be removed for those backends + if r.ProtoMajor == 2 && r.Method == http.MethodConnect && r.Header.Get(":protocol") == "websocket" { + clonedReq.Header.Del(":protocol") + // keep the body for later use. http1.1 upgrade uses http.NoBody + caddyhttp.SetVar(clonedReq.Context(), "h2_websocket_body", clonedReq.Body) + clonedReq.Body = http.NoBody + clonedReq.Method = http.MethodGet + clonedReq.Header.Set("Upgrade", "websocket") + clonedReq.Header.Set("Connection", "Upgrade") + key := make([]byte, 16) + _, randErr := rand.Read(key) + if randErr != nil { + return randErr + } + clonedReq.Header["Sec-WebSocket-Key"] = []string{base64.StdEncoding.EncodeToString(key)} + } + + // we will need the original headers and Host value if + // header operations are configured; this is so that each + // retry can apply the modifications, because placeholders + // may be used which depend on the selected upstream for + // their values + reqHost := clonedReq.Host + reqHeader := clonedReq.Header + + start := time.Now() + defer func() { + // total proxying duration, including time spent on LB and retries + repl.Set("http.reverse_proxy.duration", time.Since(start)) + repl.Set("http.reverse_proxy.duration_ms", time.Since(start).Seconds()*1e3) // multiply seconds to preserve decimal (see #4666) + }() + + // in the proxy loop, each iteration is an attempt to proxy the request, + // and because we may retry some number of times, carry over the error + // from previous tries because of the nuances of load balancing & retries + var proxyErr error + var retries int + for { + // if the request body was buffered (and only the entire body, hence no body + // set to read from after the buffer), make reading from the body idempotent + // and reusable, so if a backend partially or fully reads the body but then + // produces an error, the request can be repeated to the next backend with + // the full body (retries should only happen for idempotent requests) (see #6259) + if reqBodyBuf, ok := r.Body.(bodyReadCloser); ok && reqBodyBuf.body == nil { + r.Body = io.NopCloser(bytes.NewReader(reqBodyBuf.buf.Bytes())) + } + + var done bool + done, proxyErr = h.proxyLoopIteration(clonedReq, r, w, proxyErr, start, retries, repl, reqHeader, reqHost, next) + if done { + break + } + if h.VerboseLogs { + var lbWait time.Duration + if h.LoadBalancing != nil { + lbWait = time.Duration(h.LoadBalancing.TryInterval) + } + if c := h.logger.Check(zapcore.DebugLevel, "retrying"); c != nil { + c.Write(zap.Error(proxyErr), zap.Duration("after", lbWait)) + } + } + retries++ + } + + // number of retries actually performed + repl.Set("http.reverse_proxy.retries", retries) + + if proxyErr != nil { + return statusError(proxyErr) + } + + return nil +} + +// proxyLoopIteration implements an iteration of the proxy loop. Despite the enormous amount of local state +// that has to be passed in, we brought this into its own method so that we could run defer more easily. +// It returns true when the loop is done and should break; false otherwise. The error value returned should +// be assigned to the proxyErr value for the next iteration of the loop (or the error handled after break). +func (h *Handler) proxyLoopIteration(r *http.Request, origReq *http.Request, w http.ResponseWriter, proxyErr error, start time.Time, retries int, + repl *caddy.Replacer, reqHeader http.Header, reqHost string, next caddyhttp.Handler, +) (bool, error) { + // get the updated list of upstreams + upstreams := h.Upstreams + if h.DynamicUpstreams != nil { + dUpstreams, err := h.DynamicUpstreams.GetUpstreams(r) + if err != nil { + if c := h.logger.Check(zapcore.ErrorLevel, "failed getting dynamic upstreams; falling back to static upstreams"); c != nil { + c.Write(zap.Error(err)) + } + } else { + upstreams = dUpstreams + for _, dUp := range dUpstreams { + h.provisionUpstream(dUp) + } + if c := h.logger.Check(zapcore.DebugLevel, "provisioned dynamic upstreams"); c != nil { + c.Write(zap.Int("count", len(dUpstreams))) + } + defer func() { + // these upstreams are dynamic, so they are only used for this iteration + // of the proxy loop; be sure to let them go away when we're done with them + for _, upstream := range dUpstreams { + _, _ = hosts.Delete(upstream.String()) + } + }() + } + } + + // choose an available upstream + upstream := h.LoadBalancing.SelectionPolicy.Select(upstreams, r, w) + if upstream == nil { + if proxyErr == nil { + proxyErr = caddyhttp.Error(http.StatusServiceUnavailable, errNoUpstream) + } + if !h.LoadBalancing.tryAgain(h.ctx, start, retries, proxyErr, r, h.logger) { + return true, proxyErr + } + return false, proxyErr + } + + // the dial address may vary per-request if placeholders are + // used, so perform those replacements here; the resulting + // DialInfo struct should have valid network address syntax + dialInfo, err := upstream.fillDialInfo(r) + if err != nil { + return true, fmt.Errorf("making dial info: %v", err) + } + + if c := h.logger.Check(zapcore.DebugLevel, "selected upstream"); c != nil { + c.Write( + zap.String("dial", dialInfo.Address), + zap.Int("total_upstreams", len(upstreams)), + ) + } + + // attach to the request information about how to dial the upstream; + // this is necessary because the information cannot be sufficiently + // or satisfactorily represented in a URL + caddyhttp.SetVar(r.Context(), dialInfoVarKey, dialInfo) + + // set placeholders with information about this upstream + repl.Set("http.reverse_proxy.upstream.address", dialInfo.String()) + repl.Set("http.reverse_proxy.upstream.hostport", dialInfo.Address) + repl.Set("http.reverse_proxy.upstream.host", dialInfo.Host) + repl.Set("http.reverse_proxy.upstream.port", dialInfo.Port) + repl.Set("http.reverse_proxy.upstream.requests", upstream.Host.NumRequests()) + repl.Set("http.reverse_proxy.upstream.max_requests", upstream.MaxRequests) + repl.Set("http.reverse_proxy.upstream.fails", upstream.Host.Fails()) + + // mutate request headers according to this upstream; + // because we're in a retry loop, we have to copy + // headers (and the r.Host value) from the original + // so that each retry is identical to the first + if h.Headers != nil && h.Headers.Request != nil { + r.Header = make(http.Header) + copyHeader(r.Header, reqHeader) + r.Host = reqHost + h.Headers.Request.ApplyToRequest(r) + } + + // proxy the request to that upstream + proxyErr = h.reverseProxy(w, r, origReq, repl, dialInfo, next) + if proxyErr == nil || errors.Is(proxyErr, context.Canceled) { + // context.Canceled happens when the downstream client + // cancels the request, which is not our failure + return true, nil + } + + // if the roundtrip was successful, don't retry the request or + // ding the health status of the upstream (an error can still + // occur after the roundtrip if, for example, a response handler + // after the roundtrip returns an error) + if succ, ok := proxyErr.(roundtripSucceededError); ok { + return true, succ.error + } + + // remember this failure (if enabled) + h.countFailure(upstream) + + // if we've tried long enough, break + if !h.LoadBalancing.tryAgain(h.ctx, start, retries, proxyErr, r, h.logger) { + return true, proxyErr + } + + return false, proxyErr +} + +// Mapping of the canonical form of the headers, to the RFC 6455 form, +// i.e. `WebSocket` with uppercase 'S'. +var websocketHeaderMapping = map[string]string{ + "Sec-Websocket-Accept": "Sec-WebSocket-Accept", + "Sec-Websocket-Extensions": "Sec-WebSocket-Extensions", + "Sec-Websocket-Key": "Sec-WebSocket-Key", + "Sec-Websocket-Protocol": "Sec-WebSocket-Protocol", + "Sec-Websocket-Version": "Sec-WebSocket-Version", +} + +// normalizeWebsocketHeaders ensures we use the standard casing as per +// RFC 6455, i.e. `WebSocket` with uppercase 'S'. Most servers don't +// care about this difference (read headers case insensitively), but +// some do, so this maximizes compatibility with upstreams. +// See https://github.com/caddyserver/caddy/pull/6621 +func normalizeWebsocketHeaders(header http.Header) { + for k, rk := range websocketHeaderMapping { + if v, ok := header[k]; ok { + delete(header, k) + header[rk] = v + } + } +} + +// prepareRequest clones req so that it can be safely modified without +// changing the original request or introducing data races. It then +// modifies it so that it is ready to be proxied, except for directing +// to a specific upstream. This method adjusts headers and other relevant +// properties of the cloned request and should be done just once (before +// proxying) regardless of proxy retries. This assumes that no mutations +// of the cloned request are performed by h during or after proxying. +func (h Handler) prepareRequest(req *http.Request, repl *caddy.Replacer) (*http.Request, error) { + req = cloneRequest(req) + + // if enabled, perform rewrites on the cloned request; if + // the method is GET or HEAD, prevent the request body + // from being copied to the upstream + if h.Rewrite != nil { + changed := h.Rewrite.Rewrite(req, repl) + if changed && (h.Rewrite.Method == "GET" || h.Rewrite.Method == "HEAD") { + req.ContentLength = 0 + req.Body = nil + } + } + + // if enabled, buffer client request; this should only be + // enabled if the upstream requires it and does not work + // with "slow clients" (gunicorn, etc.) - this obviously + // has a perf overhead and makes the proxy at risk of + // exhausting memory and more susceptible to slowloris + // attacks, so it is strongly recommended to only use this + // feature if absolutely required, if read timeouts are + // set, and if body size is limited + if h.RequestBuffers != 0 && req.Body != nil { + var readBytes int64 + req.Body, readBytes = h.bufferedBody(req.Body, h.RequestBuffers) + // set Content-Length when body is fully buffered + if b, ok := req.Body.(bodyReadCloser); ok && b.body == nil { + req.ContentLength = readBytes + req.Header.Set("Content-Length", strconv.FormatInt(req.ContentLength, 10)) + } + } + + if req.ContentLength == 0 { + req.Body = nil // Issue golang/go#16036: nil Body for http.Transport retries + } + + req.Close = false + + // if User-Agent is not set by client, then explicitly + // disable it so it's not set to default value by std lib + if _, ok := req.Header["User-Agent"]; !ok { + req.Header.Set("User-Agent", "") + } + + // Indicate if request has been conveyed in early data. + // RFC 8470: "An intermediary that forwards a request prior to the + // completion of the TLS handshake with its client MUST send it with + // the Early-Data header field set to “1” (i.e., it adds it if not + // present in the request). An intermediary MUST use the Early-Data + // header field if the request might have been subject to a replay and + // might already have been forwarded by it or another instance + // (see Section 6.2)." + if req.TLS != nil && !req.TLS.HandshakeComplete { + req.Header.Set("Early-Data", "1") + } + + reqUpgradeType := upgradeType(req.Header) + removeConnectionHeaders(req.Header) + + // Remove hop-by-hop headers to the backend. Especially + // important is "Connection" because we want a persistent + // connection, regardless of what the client sent to us. + // Issue golang/go#46313: don't skip if field is empty. + for _, h := range hopHeaders { + // Issue golang/go#21096: tell backend applications that care about trailer support + // that we support trailers. (We do, but we don't go out of our way to + // advertise that unless the incoming client request thought it was worth + // mentioning.) + if h == "Te" && httpguts.HeaderValuesContainsToken(req.Header["Te"], "trailers") { + req.Header.Set("Te", "trailers") + continue + } + req.Header.Del(h) + } + + // After stripping all the hop-by-hop connection headers above, add back any + // necessary for protocol upgrades, such as for websockets. + if reqUpgradeType != "" { + req.Header.Set("Connection", "Upgrade") + req.Header.Set("Upgrade", reqUpgradeType) + normalizeWebsocketHeaders(req.Header) + } + + // Set up the PROXY protocol info + address := caddyhttp.GetVar(req.Context(), caddyhttp.ClientIPVarKey).(string) + addrPort, err := netip.ParseAddrPort(address) + if err != nil { + // OK; probably didn't have a port + addr, err := netip.ParseAddr(address) + if err != nil { + // Doesn't seem like a valid ip address at all + } else { + // Ok, only the port was missing + addrPort = netip.AddrPortFrom(addr, 0) + } + } + proxyProtocolInfo := ProxyProtocolInfo{AddrPort: addrPort} + caddyhttp.SetVar(req.Context(), proxyProtocolInfoVarKey, proxyProtocolInfo) + + // Add the supported X-Forwarded-* headers + err = h.addForwardedHeaders(req) + if err != nil { + return nil, err + } + + // Via header(s) + req.Header.Add("Via", fmt.Sprintf("%d.%d Caddy", req.ProtoMajor, req.ProtoMinor)) + + return req, nil +} + +// addForwardedHeaders adds the de-facto standard X-Forwarded-* +// headers to the request before it is sent upstream. +// +// These headers are security sensitive, so care is taken to only +// use existing values for these headers from the incoming request +// if the client IP is trusted (i.e. coming from a trusted proxy +// sitting in front of this server). If the request didn't have +// the headers at all, then they will be added with the values +// that we can glean from the request. +func (h Handler) addForwardedHeaders(req *http.Request) error { + // Parse the remote IP, ignore the error as non-fatal, + // but the remote IP is required to continue, so we + // just return early. This should probably never happen + // though, unless some other module manipulated the request's + // remote address and used an invalid value. + clientIP, _, err := net.SplitHostPort(req.RemoteAddr) + if err != nil { + // Remove the `X-Forwarded-*` headers to avoid upstreams + // potentially trusting a header that came from the client + req.Header.Del("X-Forwarded-For") + req.Header.Del("X-Forwarded-Proto") + req.Header.Del("X-Forwarded-Host") + return nil + } + + // Client IP may contain a zone if IPv6, so we need + // to pull that out before parsing the IP + clientIP, _, _ = strings.Cut(clientIP, "%") + ipAddr, err := netip.ParseAddr(clientIP) + if err != nil { + return fmt.Errorf("invalid IP address: '%s': %v", clientIP, err) + } + + // Check if the client is a trusted proxy + trusted := caddyhttp.GetVar(req.Context(), caddyhttp.TrustedProxyVarKey).(bool) + for _, ipRange := range h.trustedProxies { + if ipRange.Contains(ipAddr) { + trusted = true + break + } + } + + // If we aren't the first proxy, and the proxy is trusted, + // retain prior X-Forwarded-For information as a comma+space + // separated list and fold multiple headers into one. + clientXFF := clientIP + prior, ok, omit := allHeaderValues(req.Header, "X-Forwarded-For") + if trusted && ok && prior != "" { + clientXFF = prior + ", " + clientXFF + } + if !omit { + req.Header.Set("X-Forwarded-For", clientXFF) + } + + // Set X-Forwarded-Proto; many backend apps expect this, + // so that they can properly craft URLs with the right + // scheme to match the original request + proto := "https" + if req.TLS == nil { + proto = "http" + } + prior, ok, omit = lastHeaderValue(req.Header, "X-Forwarded-Proto") + if trusted && ok && prior != "" { + proto = prior + } + if !omit { + req.Header.Set("X-Forwarded-Proto", proto) + } + + // Set X-Forwarded-Host; often this is redundant because + // we pass through the request Host as-is, but in situations + // where we proxy over HTTPS, the user may need to override + // Host themselves, so it's helpful to send the original too. + host := req.Host + prior, ok, omit = lastHeaderValue(req.Header, "X-Forwarded-Host") + if trusted && ok && prior != "" { + host = prior + } + if !omit { + req.Header.Set("X-Forwarded-Host", host) + } + + return nil +} + +// reverseProxy performs a round-trip to the given backend and processes the response with the client. +// (This method is mostly the beginning of what was borrowed from the net/http/httputil package in the +// Go standard library which was used as the foundation.) +func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origReq *http.Request, repl *caddy.Replacer, di DialInfo, next caddyhttp.Handler) error { + _ = di.Upstream.Host.countRequest(1) + //nolint:errcheck + defer di.Upstream.Host.countRequest(-1) + + // point the request to this upstream + h.directRequest(req, di) + + server := req.Context().Value(caddyhttp.ServerCtxKey).(*caddyhttp.Server) + shouldLogCredentials := server.Logs != nil && server.Logs.ShouldLogCredentials + + // Forward 1xx status codes, backported from https://github.com/golang/go/pull/53164 + var ( + roundTripMutex sync.Mutex + roundTripDone bool + ) + trace := &httptrace.ClientTrace{ + Got1xxResponse: func(code int, header textproto.MIMEHeader) error { + roundTripMutex.Lock() + defer roundTripMutex.Unlock() + if roundTripDone { + // If RoundTrip has returned, don't try to further modify + // the ResponseWriter's header map. + return nil + } + h := rw.Header() + copyHeader(h, http.Header(header)) + rw.WriteHeader(code) + + // Clear headers coming from the backend + // (it's not automatically done by ResponseWriter.WriteHeader() for 1xx responses) + clear(h) + + return nil + }, + } + req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace)) + + // do the round-trip + start := time.Now() + res, err := h.Transport.RoundTrip(req) + duration := time.Since(start) + + // record that the round trip is done for the 1xx response handler + roundTripMutex.Lock() + roundTripDone = true + roundTripMutex.Unlock() + + // emit debug log with values we know are safe, + // or if there is no error, emit fuller log entry + logger := h.logger.With( + zap.String("upstream", di.Upstream.String()), + zap.Duration("duration", duration), + zap.Object("request", caddyhttp.LoggableHTTPRequest{ + Request: req, + ShouldLogCredentials: shouldLogCredentials, + }), + ) + + const logMessage = "upstream roundtrip" + + if err != nil { + if c := logger.Check(zapcore.DebugLevel, logMessage); c != nil { + c.Write(zap.Error(err)) + } + return err + } + if c := logger.Check(zapcore.DebugLevel, logMessage); c != nil { + c.Write( + zap.Object("headers", caddyhttp.LoggableHTTPHeader{ + Header: res.Header, + ShouldLogCredentials: shouldLogCredentials, + }), + zap.Int("status", res.StatusCode), + ) + } + + // duration until upstream wrote response headers (roundtrip duration) + repl.Set("http.reverse_proxy.upstream.latency", duration) + repl.Set("http.reverse_proxy.upstream.latency_ms", duration.Seconds()*1e3) // multiply seconds to preserve decimal (see #4666) + + // update circuit breaker on current conditions + if di.Upstream.cb != nil { + di.Upstream.cb.RecordMetric(res.StatusCode, duration) + } + + // perform passive health checks (if enabled) + if h.HealthChecks != nil && h.HealthChecks.Passive != nil { + // strike if the status code matches one that is "bad" + for _, badStatus := range h.HealthChecks.Passive.UnhealthyStatus { + if caddyhttp.StatusCodeMatches(res.StatusCode, badStatus) { + h.countFailure(di.Upstream) + } + } + + // strike if the roundtrip took too long + if h.HealthChecks.Passive.UnhealthyLatency > 0 && + duration >= time.Duration(h.HealthChecks.Passive.UnhealthyLatency) { + h.countFailure(di.Upstream) + } + } + + // if enabled, buffer the response body + if h.ResponseBuffers != 0 { + res.Body, _ = h.bufferedBody(res.Body, h.ResponseBuffers) + } + + // see if any response handler is configured for this response from the backend + for i, rh := range h.HandleResponse { + if rh.Match != nil && !rh.Match.Match(res.StatusCode, res.Header) { + continue + } + + // if configured to only change the status code, + // do that then continue regular proxy response + if statusCodeStr := rh.StatusCode.String(); statusCodeStr != "" { + statusCode, err := strconv.Atoi(repl.ReplaceAll(statusCodeStr, "")) + if err != nil { + return caddyhttp.Error(http.StatusInternalServerError, err) + } + if statusCode != 0 { + res.StatusCode = statusCode + } + break + } + + // set up the replacer so that parts of the original response can be + // used for routing decisions + for field, value := range res.Header { + repl.Set("http.reverse_proxy.header."+field, strings.Join(value, ",")) + } + repl.Set("http.reverse_proxy.status_code", res.StatusCode) + repl.Set("http.reverse_proxy.status_text", res.Status) + + if c := logger.Check(zapcore.DebugLevel, "handling response"); c != nil { + c.Write(zap.Int("handler", i)) + } + + // we make some data available via request context to child routes + // so that they may inherit some options and functions from the + // handler, and be able to copy the response. + // we use the original request here, so that any routes from 'next' + // see the original request rather than the proxy cloned request. + hrc := &handleResponseContext{ + handler: h, + response: res, + start: start, + logger: logger, + } + ctx := origReq.Context() + ctx = context.WithValue(ctx, proxyHandleResponseContextCtxKey, hrc) + + // pass the request through the response handler routes + routeErr := rh.Routes.Compile(next).ServeHTTP(rw, origReq.WithContext(ctx)) + + // close the response body afterwards, since we don't need it anymore; + // either a route had 'copy_response' which already consumed the body, + // or some other terminal handler ran which doesn't need the response + // body after that point (e.g. 'file_server' for X-Accel-Redirect flow), + // or we fell through to subsequent handlers past this proxy + // (e.g. forward auth's 2xx response flow). + if !hrc.isFinalized { + res.Body.Close() + } + + // wrap any route error in roundtripSucceededError so caller knows that + // the roundtrip was successful and to not retry + if routeErr != nil { + return roundtripSucceededError{routeErr} + } + + // we're done handling the response, and we don't want to + // fall through to the default finalize/copy behaviour + return nil + } + + // copy the response body and headers back to the upstream client + return h.finalizeResponse(rw, req, res, repl, start, logger) +} + +// finalizeResponse prepares and copies the response. +func (h *Handler) finalizeResponse( + rw http.ResponseWriter, + req *http.Request, + res *http.Response, + repl *caddy.Replacer, + start time.Time, + logger *zap.Logger, +) error { + // deal with 101 Switching Protocols responses: (WebSocket, h2c, etc) + if res.StatusCode == http.StatusSwitchingProtocols { + var wg sync.WaitGroup + h.handleUpgradeResponse(logger, &wg, rw, req, res) + wg.Wait() + return nil + } + + removeConnectionHeaders(res.Header) + + for _, h := range hopHeaders { + res.Header.Del(h) + } + + // delete our Server header and use Via instead (see #6275) + rw.Header().Del("Server") + var protoPrefix string + if !strings.HasPrefix(strings.ToUpper(res.Proto), "HTTP/") { + protoPrefix = res.Proto[:strings.Index(res.Proto, "/")+1] + } + rw.Header().Add("Via", fmt.Sprintf("%s%d.%d Caddy", protoPrefix, res.ProtoMajor, res.ProtoMinor)) + + // apply any response header operations + if h.Headers != nil && h.Headers.Response != nil { + if h.Headers.Response.Require == nil || + h.Headers.Response.Require.Match(res.StatusCode, res.Header) { + h.Headers.Response.ApplyTo(res.Header, repl) + } + } + + copyHeader(rw.Header(), res.Header) + + // The "Trailer" header isn't included in the Transport's response, + // at least for *http.Transport. Build it up from Trailer. + announcedTrailers := len(res.Trailer) + if announcedTrailers > 0 { + trailerKeys := make([]string, 0, len(res.Trailer)) + for k := range res.Trailer { + trailerKeys = append(trailerKeys, k) + } + rw.Header().Add("Trailer", strings.Join(trailerKeys, ", ")) + } + + rw.WriteHeader(res.StatusCode) + if h.VerboseLogs { + logger.Debug("wrote header") + } + + err := h.copyResponse(rw, res.Body, h.flushInterval(req, res), logger) + errClose := res.Body.Close() // close now, instead of defer, to populate res.Trailer + if h.VerboseLogs || errClose != nil { + if c := logger.Check(zapcore.DebugLevel, "closed response body from upstream"); c != nil { + c.Write(zap.Error(errClose)) + } + } + if err != nil { + // we're streaming the response and we've already written headers, so + // there's nothing an error handler can do to recover at this point; + // we'll just log the error and abort the stream here and panic just as + // the standard lib's proxy to propagate the stream error. + // see issue https://github.com/caddyserver/caddy/issues/5951 + if c := logger.Check(zapcore.WarnLevel, "aborting with incomplete response"); c != nil { + c.Write(zap.Error(err)) + } + // no extra logging from stdlib + panic(http.ErrAbortHandler) + } + + if len(res.Trailer) > 0 { + // Force chunking if we saw a response trailer. + // This prevents net/http from calculating the length for short + // bodies and adding a Content-Length. + //nolint:bodyclose + http.NewResponseController(rw).Flush() + } + + // total duration spent proxying, including writing response body + repl.Set("http.reverse_proxy.upstream.duration", time.Since(start)) + repl.Set("http.reverse_proxy.upstream.duration_ms", time.Since(start).Seconds()*1e3) + + if len(res.Trailer) == announcedTrailers { + copyHeader(rw.Header(), res.Trailer) + return nil + } + + for k, vv := range res.Trailer { + k = http.TrailerPrefix + k + for _, v := range vv { + rw.Header().Add(k, v) + } + } + + if h.VerboseLogs { + logger.Debug("response finalized") + } + + return nil +} + +// tryAgain takes the time that the handler was initially invoked, +// the amount of retries already performed, as well as any error +// currently obtained, and the request being tried, and returns +// true if another attempt should be made at proxying the request. +// If true is returned, it has already blocked long enough before +// the next retry (i.e. no more sleeping is needed). If false is +// returned, the handler should stop trying to proxy the request. +func (lb LoadBalancing) tryAgain(ctx caddy.Context, start time.Time, retries int, proxyErr error, req *http.Request, logger *zap.Logger) bool { + // no retries are configured + if lb.TryDuration == 0 && lb.Retries == 0 { + return false + } + + // if we've tried long enough, break + if lb.TryDuration > 0 && time.Since(start) >= time.Duration(lb.TryDuration) { + return false + } + + // if we've reached the retry limit, break + if lb.Retries > 0 && retries >= lb.Retries { + return false + } + + // if the error occurred while dialing (i.e. a connection + // could not even be established to the upstream), then it + // should be safe to retry, since without a connection, no + // HTTP request can be transmitted; but if the error is not + // specifically a dialer error, we need to be careful + if proxyErr != nil { + _, isDialError := proxyErr.(DialError) + herr, isHandlerError := proxyErr.(caddyhttp.HandlerError) + + // if the error occurred after a connection was established, + // we have to assume the upstream received the request, and + // retries need to be carefully decided, because some requests + // are not idempotent + if !isDialError && !(isHandlerError && errors.Is(herr, errNoUpstream)) { + if lb.RetryMatch == nil && req.Method != "GET" { + // by default, don't retry requests if they aren't GET + return false + } + + match, err := lb.RetryMatch.AnyMatchWithError(req) + if err != nil { + logger.Error("error matching request for retry", zap.Error(err)) + return false + } + if !match { + return false + } + } + } + + // fast path; if the interval is zero, we don't need to wait + if lb.TryInterval == 0 { + return true + } + + // otherwise, wait and try the next available host + timer := time.NewTimer(time.Duration(lb.TryInterval)) + select { + case <-timer.C: + return true + case <-ctx.Done(): + if !timer.Stop() { + // if the timer has been stopped then read from the channel + <-timer.C + } + return false + } +} + +// directRequest modifies only req.URL so that it points to the upstream +// in the given DialInfo. It must modify ONLY the request URL. +func (Handler) directRequest(req *http.Request, di DialInfo) { + // we need a host, so set the upstream's host address + reqHost := di.Address + + // if the port equates to the scheme, strip the port because + // it's weird to make a request like http://example.com:80/. + if (req.URL.Scheme == "http" && di.Port == "80") || + (req.URL.Scheme == "https" && di.Port == "443") { + reqHost = di.Host + } + + req.URL.Host = reqHost +} + +func (h Handler) provisionUpstream(upstream *Upstream) { + // create or get the host representation for this upstream + upstream.fillHost() + + // give it the circuit breaker, if any + upstream.cb = h.CB + + // if the passive health checker has a non-zero UnhealthyRequestCount + // but the upstream has no MaxRequests set (they are the same thing, + // but the passive health checker is a default value for upstreams + // without MaxRequests), copy the value into this upstream, since the + // value in the upstream (MaxRequests) is what is used during + // availability checks + if h.HealthChecks != nil && + h.HealthChecks.Passive != nil && + h.HealthChecks.Passive.UnhealthyRequestCount > 0 && + upstream.MaxRequests == 0 { + upstream.MaxRequests = h.HealthChecks.Passive.UnhealthyRequestCount + } + + // upstreams need independent access to the passive + // health check policy because passive health checks + // run without access to h. + if h.HealthChecks != nil { + upstream.healthCheckPolicy = h.HealthChecks.Passive + } +} + +// bufferedBody reads originalBody into a buffer with maximum size of limit (-1 for unlimited), +// then returns a reader for the buffer along with how many bytes were buffered. Always close +// the return value when done with it, just like if it was the original body! If limit is 0 +// (which it shouldn't be), this function returns its input; i.e. is a no-op, for safety. +func (h Handler) bufferedBody(originalBody io.ReadCloser, limit int64) (io.ReadCloser, int64) { + if limit == 0 { + return originalBody, 0 + } + var written int64 + buf := bufPool.Get().(*bytes.Buffer) + buf.Reset() + if limit > 0 { + var err error + written, err = io.CopyN(buf, originalBody, limit) + if (err != nil && err != io.EOF) || written == limit { + return bodyReadCloser{ + Reader: io.MultiReader(buf, originalBody), + buf: buf, + body: originalBody, + }, written + } + } else { + written, _ = io.Copy(buf, originalBody) + } + originalBody.Close() // no point in keeping it open + return bodyReadCloser{ + Reader: buf, + buf: buf, + }, written +} + +// cloneRequest makes a semi-deep clone of origReq. +// +// Most of this code is borrowed from the Go stdlib reverse proxy, +// but we make a shallow-ish clone the request (deep clone only +// the headers and URL) so we can avoid manipulating the original +// request when using it to proxy upstream. This prevents request +// corruption and data races. +func cloneRequest(origReq *http.Request) *http.Request { + req := new(http.Request) + *req = *origReq + if origReq.URL != nil { + newURL := new(url.URL) + *newURL = *origReq.URL + if origReq.URL.User != nil { + newURL.User = new(url.Userinfo) + *newURL.User = *origReq.URL.User + } + // sanitize the request URL; we expect it to not contain the + // scheme and host since those should be determined by r.TLS + // and r.Host respectively, but some clients may include it + // in the request-line, which is technically valid in HTTP, + // but breaks reverseproxy behaviour, overriding how the + // dialer will behave. See #4237 for context. + newURL.Scheme = "" + newURL.Host = "" + req.URL = newURL + } + if origReq.Header != nil { + req.Header = origReq.Header.Clone() + } + if origReq.Trailer != nil { + req.Trailer = origReq.Trailer.Clone() + } + return req +} + +func copyHeader(dst, src http.Header) { + for k, vv := range src { + for _, v := range vv { + dst.Add(k, v) + } + } +} + +// allHeaderValues gets all values for a given header field, +// joined by a comma and space if more than one is set. If the +// header field is nil, then the omit is true, meaning some +// earlier logic in the server wanted to prevent this header from +// getting written at all. If the header is empty, then ok is +// false. Callers should still check that the value is not empty +// (the header field may be set but have an empty value). +func allHeaderValues(h http.Header, field string) (value string, ok bool, omit bool) { + values, ok := h[http.CanonicalHeaderKey(field)] + if ok && values == nil { + return "", true, true + } + if len(values) == 0 { + return "", false, false + } + return strings.Join(values, ", "), true, false +} + +// lastHeaderValue gets the last value for a given header field +// if more than one is set. If the header field is nil, then +// the omit is true, meaning some earlier logic in the server +// wanted to prevent this header from getting written at all. +// If the header is empty, then ok is false. Callers should +// still check that the value is not empty (the header field +// may be set but have an empty value). +func lastHeaderValue(h http.Header, field string) (value string, ok bool, omit bool) { + values, ok := h[http.CanonicalHeaderKey(field)] + if ok && values == nil { + return "", true, true + } + if len(values) == 0 { + return "", false, false + } + return values[len(values)-1], true, false +} + +func upgradeType(h http.Header) string { + if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") { + return "" + } + return strings.ToLower(h.Get("Upgrade")) +} + +// removeConnectionHeaders removes hop-by-hop headers listed in the "Connection" header of h. +// See RFC 7230, section 6.1 +func removeConnectionHeaders(h http.Header) { + for _, f := range h["Connection"] { + for _, sf := range strings.Split(f, ",") { + if sf = textproto.TrimString(sf); sf != "" { + h.Del(sf) + } + } + } +} + +// statusError returns an error value that has a status code. +func statusError(err error) error { + // errors proxying usually mean there is a problem with the upstream(s) + statusCode := http.StatusBadGateway + + // timeout errors have a standard status code (see issue #4823) + if err, ok := err.(net.Error); ok && err.Timeout() { + statusCode = http.StatusGatewayTimeout + } + + // if the client canceled the request (usually this means they closed + // the connection, so they won't see any response), we can report it + // as a client error (4xx) and not a server error (5xx); unfortunately + // the Go standard library, at least at time of writing in late 2020, + // obnoxiously wraps the exported, standard context.Canceled error with + // an unexported garbage value that we have to do a substring check for: + // https://github.com/golang/go/blob/6965b01ea248cabb70c3749fd218b36089a21efb/src/net/net.go#L416-L430 + if errors.Is(err, context.Canceled) || strings.Contains(err.Error(), "operation was canceled") { + // regrettably, there is no standard error code for "client closed connection", but + // for historical reasons we can use a code that a lot of people are already using; + // using 5xx is problematic for users; see #3748 + statusCode = 499 + } + return caddyhttp.Error(statusCode, err) +} + +// LoadBalancing has parameters related to load balancing. +type LoadBalancing struct { + // A selection policy is how to choose an available backend. + // The default policy is random selection. + SelectionPolicyRaw json.RawMessage `json:"selection_policy,omitempty" caddy:"namespace=http.reverse_proxy.selection_policies inline_key=policy"` + + // How many times to retry selecting available backends for each + // request if the next available host is down. If try_duration is + // also configured, then retries may stop early if the duration + // is reached. By default, retries are disabled (zero). + Retries int `json:"retries,omitempty"` + + // How long to try selecting available backends for each request + // if the next available host is down. Clients will wait for up + // to this long while the load balancer tries to find an available + // upstream host. If retries is also configured, tries may stop + // early if the maximum retries is reached. By default, retries + // are disabled (zero duration). + TryDuration caddy.Duration `json:"try_duration,omitempty"` + + // How long to wait between selecting the next host from the pool. + // Default is 250ms if try_duration is enabled, otherwise zero. Only + // relevant when a request to an upstream host fails. Be aware that + // setting this to 0 with a non-zero try_duration can cause the CPU + // to spin if all backends are down and latency is very low. + TryInterval caddy.Duration `json:"try_interval,omitempty"` + + // A list of matcher sets that restricts with which requests retries are + // allowed. A request must match any of the given matcher sets in order + // to be retried if the connection to the upstream succeeded but the + // subsequent round-trip failed. If the connection to the upstream failed, + // a retry is always allowed. If unspecified, only GET requests will be + // allowed to be retried. Note that a retry is done with the next available + // host according to the load balancing policy. + RetryMatchRaw caddyhttp.RawMatcherSets `json:"retry_match,omitempty" caddy:"namespace=http.matchers"` + + SelectionPolicy Selector `json:"-"` + RetryMatch caddyhttp.MatcherSets `json:"-"` +} + +// Selector selects an available upstream from the pool. +type Selector interface { + Select(UpstreamPool, *http.Request, http.ResponseWriter) *Upstream +} + +// UpstreamSource gets the list of upstreams that can be used when +// proxying a request. Returned upstreams will be load balanced and +// health-checked. This should be a very fast function -- instant +// if possible -- and the return value must be as stable as possible. +// In other words, the list of upstreams should ideally not change much +// across successive calls. If the list of upstreams changes or the +// ordering is not stable, load balancing will suffer. This function +// may be called during each retry, multiple times per request, and as +// such, needs to be instantaneous. The returned slice will not be +// modified. +type UpstreamSource interface { + GetUpstreams(*http.Request) ([]*Upstream, error) +} + +// Hop-by-hop headers. These are removed when sent to the backend. +// As of RFC 7230, hop-by-hop headers are required to appear in the +// Connection header field. These are the headers defined by the +// obsoleted RFC 2616 (section 13.5.1) and are used for backward +// compatibility. +var hopHeaders = []string{ + "Alt-Svc", + "Connection", + "Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google + "Keep-Alive", + "Proxy-Authenticate", + "Proxy-Authorization", + "Te", // canonicalized version of "TE" + "Trailer", // not Trailers per URL above; https://www.rfc-editor.org/errata_search.php?eid=4522 + "Transfer-Encoding", + "Upgrade", +} + +// DialError is an error that specifically occurs +// in a call to Dial or DialContext. +type DialError struct{ error } + +// TLSTransport is implemented by transports +// that are capable of using TLS. +type TLSTransport interface { + // TLSEnabled returns true if the transport + // has TLS enabled, false otherwise. + TLSEnabled() bool + + // EnableTLS enables TLS within the transport + // if it is not already, using the provided + // value as a basis for the TLS config. + EnableTLS(base *TLSConfig) error +} + +// roundtripSucceededError is an error type that is returned if the +// roundtrip succeeded, but an error occurred after-the-fact. +type roundtripSucceededError struct{ error } + +// bodyReadCloser is a reader that, upon closing, will return +// its buffer to the pool and close the underlying body reader. +type bodyReadCloser struct { + io.Reader + buf *bytes.Buffer + body io.ReadCloser +} + +func (brc bodyReadCloser) Close() error { + bufPool.Put(brc.buf) + if brc.body != nil { + return brc.body.Close() + } + return nil +} + +// bufPool is used for buffering requests and responses. +var bufPool = sync.Pool{ + New: func() any { + return new(bytes.Buffer) + }, +} + +// handleResponseContext carries some contextual information about the +// current proxy handling. +type handleResponseContext struct { + // handler is the active proxy handler instance, so that + // routes like copy_response may inherit some config + // options and have access to handler methods. + handler *Handler + + // response is the actual response received from the proxy + // roundtrip, to potentially be copied if a copy_response + // handler is in the handle_response routes. + response *http.Response + + // start is the time just before the proxy roundtrip was + // performed, used for logging. + start time.Time + + // logger is the prepared logger which is used to write logs + // with the request, duration, and selected upstream attached. + logger *zap.Logger + + // isFinalized is whether the response has been finalized, + // i.e. copied and closed, to make sure that it doesn't + // happen twice. + isFinalized bool +} + +// proxyHandleResponseContextCtxKey is the context key for the active proxy handler +// so that handle_response routes can inherit some config options +// from the proxy handler. +const proxyHandleResponseContextCtxKey caddy.CtxKey = "reverse_proxy_handle_response_context" + +// errNoUpstream occurs when there are no upstream available. +var errNoUpstream = fmt.Errorf("no upstreams available") + +// Interface guards +var ( + _ caddy.Provisioner = (*Handler)(nil) + _ caddy.CleanerUpper = (*Handler)(nil) + _ caddyhttp.MiddlewareHandler = (*Handler)(nil) +) diff --git a/modules/caddyhttp/reverseproxy/selectionpolicies.go b/modules/caddyhttp/reverseproxy/selectionpolicies.go new file mode 100644 index 00000000000..fcf7f90f647 --- /dev/null +++ b/modules/caddyhttp/reverseproxy/selectionpolicies.go @@ -0,0 +1,901 @@ +// 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 reverseproxy + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + weakrand "math/rand" + "net" + "net/http" + "strconv" + "strings" + "sync/atomic" + "time" + + "github.com/cespare/xxhash/v2" + + "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" +) + +func init() { + caddy.RegisterModule(RandomSelection{}) + caddy.RegisterModule(RandomChoiceSelection{}) + caddy.RegisterModule(LeastConnSelection{}) + caddy.RegisterModule(RoundRobinSelection{}) + caddy.RegisterModule(WeightedRoundRobinSelection{}) + caddy.RegisterModule(FirstSelection{}) + caddy.RegisterModule(IPHashSelection{}) + caddy.RegisterModule(ClientIPHashSelection{}) + caddy.RegisterModule(URIHashSelection{}) + caddy.RegisterModule(QueryHashSelection{}) + caddy.RegisterModule(HeaderHashSelection{}) + caddy.RegisterModule(CookieHashSelection{}) +} + +// RandomSelection is a policy that selects +// an available host at random. +type RandomSelection struct{} + +// CaddyModule returns the Caddy module information. +func (RandomSelection) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.reverse_proxy.selection_policies.random", + New: func() caddy.Module { return new(RandomSelection) }, + } +} + +// Select returns an available host, if any. +func (r RandomSelection) Select(pool UpstreamPool, request *http.Request, _ http.ResponseWriter) *Upstream { + return selectRandomHost(pool) +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. +func (r *RandomSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume policy name + if d.NextArg() { + return d.ArgErr() + } + return nil +} + +// WeightedRoundRobinSelection is a policy that selects +// a host based on weighted round-robin ordering. +type WeightedRoundRobinSelection struct { + // The weight of each upstream in order, + // corresponding with the list of upstreams configured. + Weights []int `json:"weights,omitempty"` + index uint32 + totalWeight int +} + +// CaddyModule returns the Caddy module information. +func (WeightedRoundRobinSelection) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.reverse_proxy.selection_policies.weighted_round_robin", + New: func() caddy.Module { + return new(WeightedRoundRobinSelection) + }, + } +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. +func (r *WeightedRoundRobinSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume policy name + + args := d.RemainingArgs() + if len(args) == 0 { + return d.ArgErr() + } + + for _, weight := range args { + weightInt, err := strconv.Atoi(weight) + if err != nil { + return d.Errf("invalid weight value '%s': %v", weight, err) + } + if weightInt < 0 { + return d.Errf("invalid weight value '%s': weight should be non-negative", weight) + } + r.Weights = append(r.Weights, weightInt) + } + return nil +} + +// Provision sets up r. +func (r *WeightedRoundRobinSelection) Provision(ctx caddy.Context) error { + for _, weight := range r.Weights { + r.totalWeight += weight + } + return nil +} + +// Select returns an available host, if any. +func (r *WeightedRoundRobinSelection) Select(pool UpstreamPool, _ *http.Request, _ http.ResponseWriter) *Upstream { + if len(pool) == 0 { + return nil + } + if len(r.Weights) < 2 { + return pool[0] + } + var index, totalWeight int + var weights []int + + for _, w := range r.Weights { + if w > 0 { + weights = append(weights, w) + } + } + currentWeight := int(atomic.AddUint32(&r.index, 1)) % r.totalWeight + for i, weight := range weights { + totalWeight += weight + if currentWeight < totalWeight { + index = i + break + } + } + + upstreams := make([]*Upstream, 0, len(weights)) + for i, upstream := range pool { + if !upstream.Available() || r.Weights[i] == 0 { + continue + } + upstreams = append(upstreams, upstream) + if len(upstreams) == cap(upstreams) { + break + } + } + if len(upstreams) == 0 { + return nil + } + return upstreams[index%len(upstreams)] +} + +// RandomChoiceSelection is a policy that selects +// two or more available hosts at random, then +// chooses the one with the least load. +type RandomChoiceSelection struct { + // The size of the sub-pool created from the larger upstream pool. The default value + // is 2 and the maximum at selection time is the size of the upstream pool. + Choose int `json:"choose,omitempty"` +} + +// CaddyModule returns the Caddy module information. +func (RandomChoiceSelection) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.reverse_proxy.selection_policies.random_choose", + New: func() caddy.Module { return new(RandomChoiceSelection) }, + } +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. +func (r *RandomChoiceSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume policy name + + if !d.NextArg() { + return d.ArgErr() + } + chooseStr := d.Val() + choose, err := strconv.Atoi(chooseStr) + if err != nil { + return d.Errf("invalid choice value '%s': %v", chooseStr, err) + } + r.Choose = choose + return nil +} + +// Provision sets up r. +func (r *RandomChoiceSelection) Provision(ctx caddy.Context) error { + if r.Choose == 0 { + r.Choose = 2 + } + return nil +} + +// Validate ensures that r's configuration is valid. +func (r RandomChoiceSelection) Validate() error { + if r.Choose < 2 { + return fmt.Errorf("choose must be at least 2") + } + return nil +} + +// Select returns an available host, if any. +func (r RandomChoiceSelection) Select(pool UpstreamPool, _ *http.Request, _ http.ResponseWriter) *Upstream { + k := r.Choose + if k > len(pool) { + k = len(pool) + } + choices := make([]*Upstream, k) + for i, upstream := range pool { + if !upstream.Available() { + continue + } + j := weakrand.Intn(i + 1) //nolint:gosec + if j < k { + choices[j] = upstream + } + } + return leastRequests(choices) +} + +// LeastConnSelection is a policy that selects the +// host with the least active requests. If multiple +// hosts have the same fewest number, one is chosen +// randomly. The term "conn" or "connection" is used +// in this policy name due to its similar meaning in +// other software, but our load balancer actually +// counts active requests rather than connections, +// since these days requests are multiplexed onto +// shared connections. +type LeastConnSelection struct{} + +// CaddyModule returns the Caddy module information. +func (LeastConnSelection) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.reverse_proxy.selection_policies.least_conn", + New: func() caddy.Module { return new(LeastConnSelection) }, + } +} + +// Select selects the up host with the least number of connections in the +// pool. If more than one host has the same least number of connections, +// one of the hosts is chosen at random. +func (LeastConnSelection) Select(pool UpstreamPool, _ *http.Request, _ http.ResponseWriter) *Upstream { + var bestHost *Upstream + var count int + leastReqs := -1 + + for _, host := range pool { + if !host.Available() { + continue + } + numReqs := host.NumRequests() + if leastReqs == -1 || numReqs < leastReqs { + leastReqs = numReqs + count = 0 + } + + // among hosts with same least connections, perform a reservoir + // sample: https://en.wikipedia.org/wiki/Reservoir_sampling + if numReqs == leastReqs { + count++ + if count == 1 || (weakrand.Int()%count) == 0 { //nolint:gosec + bestHost = host + } + } + } + + return bestHost +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. +func (r *LeastConnSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume policy name + if d.NextArg() { + return d.ArgErr() + } + return nil +} + +// RoundRobinSelection is a policy that selects +// a host based on round-robin ordering. +type RoundRobinSelection struct { + robin uint32 +} + +// CaddyModule returns the Caddy module information. +func (RoundRobinSelection) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.reverse_proxy.selection_policies.round_robin", + New: func() caddy.Module { return new(RoundRobinSelection) }, + } +} + +// Select returns an available host, if any. +func (r *RoundRobinSelection) Select(pool UpstreamPool, _ *http.Request, _ http.ResponseWriter) *Upstream { + n := uint32(len(pool)) + if n == 0 { + return nil + } + for i := uint32(0); i < n; i++ { + robin := atomic.AddUint32(&r.robin, 1) + host := pool[robin%n] + if host.Available() { + return host + } + } + return nil +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. +func (r *RoundRobinSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume policy name + if d.NextArg() { + return d.ArgErr() + } + return nil +} + +// FirstSelection is a policy that selects +// the first available host. +type FirstSelection struct{} + +// CaddyModule returns the Caddy module information. +func (FirstSelection) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.reverse_proxy.selection_policies.first", + New: func() caddy.Module { return new(FirstSelection) }, + } +} + +// Select returns an available host, if any. +func (FirstSelection) Select(pool UpstreamPool, _ *http.Request, _ http.ResponseWriter) *Upstream { + for _, host := range pool { + if host.Available() { + return host + } + } + return nil +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. +func (r *FirstSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume policy name + if d.NextArg() { + return d.ArgErr() + } + return nil +} + +// IPHashSelection is a policy that selects a host +// based on hashing the remote IP of the request. +type IPHashSelection struct{} + +// CaddyModule returns the Caddy module information. +func (IPHashSelection) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.reverse_proxy.selection_policies.ip_hash", + New: func() caddy.Module { return new(IPHashSelection) }, + } +} + +// Select returns an available host, if any. +func (IPHashSelection) Select(pool UpstreamPool, req *http.Request, _ http.ResponseWriter) *Upstream { + clientIP, _, err := net.SplitHostPort(req.RemoteAddr) + if err != nil { + clientIP = req.RemoteAddr + } + return hostByHashing(pool, clientIP) +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. +func (r *IPHashSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume policy name + if d.NextArg() { + return d.ArgErr() + } + return nil +} + +// ClientIPHashSelection is a policy that selects a host +// based on hashing the client IP of the request, as determined +// by the HTTP app's trusted proxies settings. +type ClientIPHashSelection struct{} + +// CaddyModule returns the Caddy module information. +func (ClientIPHashSelection) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.reverse_proxy.selection_policies.client_ip_hash", + New: func() caddy.Module { return new(ClientIPHashSelection) }, + } +} + +// Select returns an available host, if any. +func (ClientIPHashSelection) Select(pool UpstreamPool, req *http.Request, _ http.ResponseWriter) *Upstream { + address := caddyhttp.GetVar(req.Context(), caddyhttp.ClientIPVarKey).(string) + clientIP, _, err := net.SplitHostPort(address) + if err != nil { + clientIP = address // no port + } + return hostByHashing(pool, clientIP) +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. +func (r *ClientIPHashSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume policy name + if d.NextArg() { + return d.ArgErr() + } + return nil +} + +// URIHashSelection is a policy that selects a +// host by hashing the request URI. +type URIHashSelection struct{} + +// CaddyModule returns the Caddy module information. +func (URIHashSelection) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.reverse_proxy.selection_policies.uri_hash", + New: func() caddy.Module { return new(URIHashSelection) }, + } +} + +// Select returns an available host, if any. +func (URIHashSelection) Select(pool UpstreamPool, req *http.Request, _ http.ResponseWriter) *Upstream { + return hostByHashing(pool, req.RequestURI) +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. +func (r *URIHashSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume policy name + if d.NextArg() { + return d.ArgErr() + } + return nil +} + +// QueryHashSelection is a policy that selects +// a host based on a given request query parameter. +type QueryHashSelection struct { + // The query key whose value is to be hashed and used for upstream selection. + Key string `json:"key,omitempty"` + + // The fallback policy to use if the query key is not present. Defaults to `random`. + FallbackRaw json.RawMessage `json:"fallback,omitempty" caddy:"namespace=http.reverse_proxy.selection_policies inline_key=policy"` + fallback Selector +} + +// CaddyModule returns the Caddy module information. +func (QueryHashSelection) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.reverse_proxy.selection_policies.query", + New: func() caddy.Module { return new(QueryHashSelection) }, + } +} + +// Provision sets up the module. +func (s *QueryHashSelection) Provision(ctx caddy.Context) error { + if s.Key == "" { + return fmt.Errorf("query key is required") + } + if s.FallbackRaw == nil { + s.FallbackRaw = caddyconfig.JSONModuleObject(RandomSelection{}, "policy", "random", nil) + } + mod, err := ctx.LoadModule(s, "FallbackRaw") + if err != nil { + return fmt.Errorf("loading fallback selection policy: %s", err) + } + s.fallback = mod.(Selector) + return nil +} + +// Select returns an available host, if any. +func (s QueryHashSelection) Select(pool UpstreamPool, req *http.Request, _ http.ResponseWriter) *Upstream { + // Since the query may have multiple values for the same key, + // we'll join them to avoid a problem where the user can control + // the upstream that the request goes to by sending multiple values + // for the same key, when the upstream only considers the first value. + // Keep in mind that a client changing the order of the values may + // affect which upstream is selected, but this is a semantically + // different request, because the order of the values is significant. + vals := strings.Join(req.URL.Query()[s.Key], ",") + if vals == "" { + return s.fallback.Select(pool, req, nil) + } + return hostByHashing(pool, vals) +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. +func (s *QueryHashSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume policy name + + if !d.NextArg() { + return d.ArgErr() + } + s.Key = d.Val() + + for d.NextBlock(0) { + switch d.Val() { + case "fallback": + if !d.NextArg() { + return d.ArgErr() + } + if s.FallbackRaw != nil { + return d.Err("fallback selection policy already specified") + } + mod, err := loadFallbackPolicy(d) + if err != nil { + return err + } + s.FallbackRaw = mod + default: + return d.Errf("unrecognized option '%s'", d.Val()) + } + } + return nil +} + +// HeaderHashSelection is a policy that selects +// a host based on a given request header. +type HeaderHashSelection struct { + // The HTTP header field whose value is to be hashed and used for upstream selection. + Field string `json:"field,omitempty"` + + // The fallback policy to use if the header is not present. Defaults to `random`. + FallbackRaw json.RawMessage `json:"fallback,omitempty" caddy:"namespace=http.reverse_proxy.selection_policies inline_key=policy"` + fallback Selector +} + +// CaddyModule returns the Caddy module information. +func (HeaderHashSelection) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.reverse_proxy.selection_policies.header", + New: func() caddy.Module { return new(HeaderHashSelection) }, + } +} + +// Provision sets up the module. +func (s *HeaderHashSelection) Provision(ctx caddy.Context) error { + if s.Field == "" { + return fmt.Errorf("header field is required") + } + if s.FallbackRaw == nil { + s.FallbackRaw = caddyconfig.JSONModuleObject(RandomSelection{}, "policy", "random", nil) + } + mod, err := ctx.LoadModule(s, "FallbackRaw") + if err != nil { + return fmt.Errorf("loading fallback selection policy: %s", err) + } + s.fallback = mod.(Selector) + return nil +} + +// Select returns an available host, if any. +func (s HeaderHashSelection) Select(pool UpstreamPool, req *http.Request, _ http.ResponseWriter) *Upstream { + // The Host header should be obtained from the req.Host field + // since net/http removes it from the header map. + if s.Field == "Host" && req.Host != "" { + return hostByHashing(pool, req.Host) + } + + val := req.Header.Get(s.Field) + if val == "" { + return s.fallback.Select(pool, req, nil) + } + return hostByHashing(pool, val) +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. +func (s *HeaderHashSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume policy name + + if !d.NextArg() { + return d.ArgErr() + } + s.Field = d.Val() + + for d.NextBlock(0) { + switch d.Val() { + case "fallback": + if !d.NextArg() { + return d.ArgErr() + } + if s.FallbackRaw != nil { + return d.Err("fallback selection policy already specified") + } + mod, err := loadFallbackPolicy(d) + if err != nil { + return err + } + s.FallbackRaw = mod + default: + return d.Errf("unrecognized option '%s'", d.Val()) + } + } + return nil +} + +// CookieHashSelection is a policy that selects +// a host based on a given cookie name. +type CookieHashSelection struct { + // The HTTP cookie name whose value is to be hashed and used for upstream selection. + Name string `json:"name,omitempty"` + // Secret to hash (Hmac256) chosen upstream in cookie + Secret string `json:"secret,omitempty"` + // The cookie's Max-Age before it expires. Default is no expiry. + MaxAge caddy.Duration `json:"max_age,omitempty"` + + // The fallback policy to use if the cookie is not present. Defaults to `random`. + FallbackRaw json.RawMessage `json:"fallback,omitempty" caddy:"namespace=http.reverse_proxy.selection_policies inline_key=policy"` + fallback Selector +} + +// CaddyModule returns the Caddy module information. +func (CookieHashSelection) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.reverse_proxy.selection_policies.cookie", + New: func() caddy.Module { return new(CookieHashSelection) }, + } +} + +// Provision sets up the module. +func (s *CookieHashSelection) Provision(ctx caddy.Context) error { + if s.Name == "" { + s.Name = "lb" + } + if s.FallbackRaw == nil { + s.FallbackRaw = caddyconfig.JSONModuleObject(RandomSelection{}, "policy", "random", nil) + } + mod, err := ctx.LoadModule(s, "FallbackRaw") + if err != nil { + return fmt.Errorf("loading fallback selection policy: %s", err) + } + s.fallback = mod.(Selector) + return nil +} + +// Select returns an available host, if any. +func (s CookieHashSelection) Select(pool UpstreamPool, req *http.Request, w http.ResponseWriter) *Upstream { + // selects a new Host using the fallback policy (typically random) + // and write a sticky session cookie to the response. + selectNewHost := func() *Upstream { + upstream := s.fallback.Select(pool, req, w) + if upstream == nil { + return nil + } + sha, err := hashCookie(s.Secret, upstream.Dial) + if err != nil { + return upstream + } + cookie := &http.Cookie{ + Name: s.Name, + Value: sha, + Path: "/", + Secure: false, + } + isProxyHttps := false + if trusted, ok := caddyhttp.GetVar(req.Context(), caddyhttp.TrustedProxyVarKey).(bool); ok && trusted { + xfp, xfpOk, _ := lastHeaderValue(req.Header, "X-Forwarded-Proto") + isProxyHttps = xfpOk && xfp == "https" + } + if req.TLS != nil || isProxyHttps { + cookie.Secure = true + cookie.SameSite = http.SameSiteNoneMode + } + if s.MaxAge > 0 { + cookie.MaxAge = int(time.Duration(s.MaxAge).Seconds()) + } + http.SetCookie(w, cookie) + return upstream + } + + cookie, err := req.Cookie(s.Name) + // If there's no cookie, select a host using the fallback policy + if err != nil || cookie == nil { + return selectNewHost() + } + // If the cookie is present, loop over the available upstreams until we find a match + cookieValue := cookie.Value + for _, upstream := range pool { + if !upstream.Available() { + continue + } + sha, err := hashCookie(s.Secret, upstream.Dial) + if err == nil && sha == cookieValue { + return upstream + } + } + // If there is no matching host, select a host using the fallback policy + return selectNewHost() +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax: +// +// lb_policy cookie [ []] { +// fallback +// max_age +// } +// +// By default name is `lb` +func (s *CookieHashSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + args := d.RemainingArgs() + switch len(args) { + case 1: + case 2: + s.Name = args[1] + case 3: + s.Name = args[1] + s.Secret = args[2] + default: + return d.ArgErr() + } + for d.NextBlock(0) { + switch d.Val() { + case "fallback": + if !d.NextArg() { + return d.ArgErr() + } + if s.FallbackRaw != nil { + return d.Err("fallback selection policy already specified") + } + mod, err := loadFallbackPolicy(d) + if err != nil { + return err + } + s.FallbackRaw = mod + case "max_age": + if !d.NextArg() { + return d.ArgErr() + } + if s.MaxAge != 0 { + return d.Err("cookie max_age already specified") + } + maxAge, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("invalid duration: %s", d.Val()) + } + if maxAge <= 0 { + return d.Errf("invalid duration: %s, max_age should be non-zero and positive", d.Val()) + } + if d.NextArg() { + return d.ArgErr() + } + s.MaxAge = caddy.Duration(maxAge) + default: + return d.Errf("unrecognized option '%s'", d.Val()) + } + } + return nil +} + +// hashCookie hashes (HMAC 256) some data with the secret +func hashCookie(secret string, data string) (string, error) { + h := hmac.New(sha256.New, []byte(secret)) + _, err := h.Write([]byte(data)) + if err != nil { + return "", err + } + return hex.EncodeToString(h.Sum(nil)), nil +} + +// selectRandomHost returns a random available host +func selectRandomHost(pool []*Upstream) *Upstream { + // use reservoir sampling because the number of available + // hosts isn't known: https://en.wikipedia.org/wiki/Reservoir_sampling + var randomHost *Upstream + var count int + for _, upstream := range pool { + if !upstream.Available() { + continue + } + // (n % 1 == 0) holds for all n, therefore a + // upstream will always be chosen if there is at + // least one available + count++ + if (weakrand.Int() % count) == 0 { //nolint:gosec + randomHost = upstream + } + } + return randomHost +} + +// leastRequests returns the host with the +// least number of active requests to it. +// If more than one host has the same +// least number of active requests, then +// one of those is chosen at random. +func leastRequests(upstreams []*Upstream) *Upstream { + if len(upstreams) == 0 { + return nil + } + var best []*Upstream + var bestReqs int = -1 + for _, upstream := range upstreams { + if upstream == nil { + continue + } + reqs := upstream.NumRequests() + if reqs == 0 { + return upstream + } + // If bestReqs was just initialized to -1 + // we need to append upstream also + if reqs <= bestReqs || bestReqs == -1 { + bestReqs = reqs + best = append(best, upstream) + } + } + if len(best) == 0 { + return nil + } + if len(best) == 1 { + return best[0] + } + return best[weakrand.Intn(len(best))] //nolint:gosec +} + +// hostByHashing returns an available host from pool based on a hashable string s. +func hostByHashing(pool []*Upstream, s string) *Upstream { + // Highest Random Weight (HRW, or "Rendezvous") hashing, + // guarantees stability when the list of upstreams changes; + // see https://medium.com/i0exception/rendezvous-hashing-8c00e2fb58b0, + // https://randorithms.com/2020/12/26/rendezvous-hashing.html, + // and https://en.wikipedia.org/wiki/Rendezvous_hashing. + var highestHash uint64 + var upstream *Upstream + for _, up := range pool { + if !up.Available() { + continue + } + h := hash(up.String() + s) // important to hash key and server together + if h > highestHash { + highestHash = h + upstream = up + } + } + return upstream +} + +// hash calculates a fast hash based on s. +func hash(s string) uint64 { + h := xxhash.New() + _, _ = h.Write([]byte(s)) + return h.Sum64() +} + +func loadFallbackPolicy(d *caddyfile.Dispenser) (json.RawMessage, error) { + name := d.Val() + modID := "http.reverse_proxy.selection_policies." + name + unm, err := caddyfile.UnmarshalModule(d, modID) + if err != nil { + return nil, err + } + sel, ok := unm.(Selector) + if !ok { + return nil, d.Errf("module %s (%T) is not a reverseproxy.Selector", modID, unm) + } + return caddyconfig.JSONModuleObject(sel, "policy", name, nil), nil +} + +// Interface guards +var ( + _ Selector = (*RandomSelection)(nil) + _ Selector = (*RandomChoiceSelection)(nil) + _ Selector = (*LeastConnSelection)(nil) + _ Selector = (*RoundRobinSelection)(nil) + _ Selector = (*WeightedRoundRobinSelection)(nil) + _ Selector = (*FirstSelection)(nil) + _ Selector = (*IPHashSelection)(nil) + _ Selector = (*ClientIPHashSelection)(nil) + _ Selector = (*URIHashSelection)(nil) + _ Selector = (*QueryHashSelection)(nil) + _ Selector = (*HeaderHashSelection)(nil) + _ Selector = (*CookieHashSelection)(nil) + + _ caddy.Validator = (*RandomChoiceSelection)(nil) + + _ caddy.Provisioner = (*RandomChoiceSelection)(nil) + _ caddy.Provisioner = (*WeightedRoundRobinSelection)(nil) + + _ caddyfile.Unmarshaler = (*RandomChoiceSelection)(nil) + _ caddyfile.Unmarshaler = (*WeightedRoundRobinSelection)(nil) +) diff --git a/modules/caddyhttp/reverseproxy/selectionpolicies_test.go b/modules/caddyhttp/reverseproxy/selectionpolicies_test.go new file mode 100644 index 00000000000..580abbdde32 --- /dev/null +++ b/modules/caddyhttp/reverseproxy/selectionpolicies_test.go @@ -0,0 +1,850 @@ +// 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 reverseproxy + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func testPool() UpstreamPool { + return UpstreamPool{ + {Host: new(Host), Dial: "0.0.0.1"}, + {Host: new(Host), Dial: "0.0.0.2"}, + {Host: new(Host), Dial: "0.0.0.3"}, + } +} + +func TestRoundRobinPolicy(t *testing.T) { + pool := testPool() + rrPolicy := RoundRobinSelection{} + req, _ := http.NewRequest("GET", "/", nil) + + h := rrPolicy.Select(pool, req, nil) + // First selected host is 1, because counter starts at 0 + // and increments before host is selected + if h != pool[1] { + t.Error("Expected first round robin host to be second host in the pool.") + } + h = rrPolicy.Select(pool, req, nil) + if h != pool[2] { + t.Error("Expected second round robin host to be third host in the pool.") + } + h = rrPolicy.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected third round robin host to be first host in the pool.") + } + // mark host as down + pool[1].setHealthy(false) + h = rrPolicy.Select(pool, req, nil) + if h != pool[2] { + t.Error("Expected to skip down host.") + } + // mark host as up + pool[1].setHealthy(true) + + h = rrPolicy.Select(pool, req, nil) + if h == pool[2] { + t.Error("Expected to balance evenly among healthy hosts") + } + // mark host as full + pool[1].countRequest(1) + pool[1].MaxRequests = 1 + h = rrPolicy.Select(pool, req, nil) + if h != pool[2] { + t.Error("Expected to skip full host.") + } +} + +func TestWeightedRoundRobinPolicy(t *testing.T) { + pool := testPool() + wrrPolicy := WeightedRoundRobinSelection{ + Weights: []int{3, 2, 1}, + totalWeight: 6, + } + req, _ := http.NewRequest("GET", "/", nil) + + h := wrrPolicy.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected first weighted round robin host to be first host in the pool.") + } + h = wrrPolicy.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected second weighted round robin host to be first host in the pool.") + } + // Third selected host is 1, because counter starts at 0 + // and increments before host is selected + h = wrrPolicy.Select(pool, req, nil) + if h != pool[1] { + t.Error("Expected third weighted round robin host to be second host in the pool.") + } + h = wrrPolicy.Select(pool, req, nil) + if h != pool[1] { + t.Error("Expected fourth weighted round robin host to be second host in the pool.") + } + h = wrrPolicy.Select(pool, req, nil) + if h != pool[2] { + t.Error("Expected fifth weighted round robin host to be third host in the pool.") + } + h = wrrPolicy.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected sixth weighted round robin host to be first host in the pool.") + } + + // mark host as down + pool[0].setHealthy(false) + h = wrrPolicy.Select(pool, req, nil) + if h != pool[1] { + t.Error("Expected to skip down host.") + } + // mark host as up + pool[0].setHealthy(true) + + h = wrrPolicy.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected to select first host on availability.") + } + // mark host as full + pool[1].countRequest(1) + pool[1].MaxRequests = 1 + h = wrrPolicy.Select(pool, req, nil) + if h != pool[2] { + t.Error("Expected to skip full host.") + } +} + +func TestWeightedRoundRobinPolicyWithZeroWeight(t *testing.T) { + pool := testPool() + wrrPolicy := WeightedRoundRobinSelection{ + Weights: []int{0, 2, 1}, + totalWeight: 3, + } + req, _ := http.NewRequest("GET", "/", nil) + + h := wrrPolicy.Select(pool, req, nil) + if h != pool[1] { + t.Error("Expected first weighted round robin host to be second host in the pool.") + } + + h = wrrPolicy.Select(pool, req, nil) + if h != pool[2] { + t.Error("Expected second weighted round robin host to be third host in the pool.") + } + + h = wrrPolicy.Select(pool, req, nil) + if h != pool[1] { + t.Error("Expected third weighted round robin host to be second host in the pool.") + } + + // mark second host as down + pool[1].setHealthy(false) + h = wrrPolicy.Select(pool, req, nil) + if h != pool[2] { + t.Error("Expect select next available host.") + } + + h = wrrPolicy.Select(pool, req, nil) + if h != pool[2] { + t.Error("Expect select only available host.") + } + // mark second host as up + pool[1].setHealthy(true) + + h = wrrPolicy.Select(pool, req, nil) + if h != pool[1] { + t.Error("Expect select first host on availability.") + } + + // test next select in full cycle + expected := []*Upstream{pool[1], pool[2], pool[1], pool[1], pool[2], pool[1]} + for i, want := range expected { + got := wrrPolicy.Select(pool, req, nil) + if want != got { + t.Errorf("Selection %d: got host[%s], want host[%s]", i+1, got, want) + } + } +} + +func TestLeastConnPolicy(t *testing.T) { + pool := testPool() + lcPolicy := LeastConnSelection{} + req, _ := http.NewRequest("GET", "/", nil) + + pool[0].countRequest(10) + pool[1].countRequest(10) + h := lcPolicy.Select(pool, req, nil) + if h != pool[2] { + t.Error("Expected least connection host to be third host.") + } + pool[2].countRequest(100) + h = lcPolicy.Select(pool, req, nil) + if h != pool[0] && h != pool[1] { + t.Error("Expected least connection host to be first or second host.") + } +} + +func TestIPHashPolicy(t *testing.T) { + pool := testPool() + ipHash := IPHashSelection{} + req, _ := http.NewRequest("GET", "/", nil) + + // We should be able to predict where every request is routed. + req.RemoteAddr = "172.0.0.1:80" + h := ipHash.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected ip hash policy host to be the first host.") + } + req.RemoteAddr = "172.0.0.2:80" + h = ipHash.Select(pool, req, nil) + if h != pool[1] { + t.Error("Expected ip hash policy host to be the second host.") + } + req.RemoteAddr = "172.0.0.3:80" + h = ipHash.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected ip hash policy host to be the first host.") + } + req.RemoteAddr = "172.0.0.4:80" + h = ipHash.Select(pool, req, nil) + if h != pool[1] { + t.Error("Expected ip hash policy host to be the second host.") + } + + // we should get the same results without a port + req.RemoteAddr = "172.0.0.1" + h = ipHash.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected ip hash policy host to be the first host.") + } + req.RemoteAddr = "172.0.0.2" + h = ipHash.Select(pool, req, nil) + if h != pool[1] { + t.Error("Expected ip hash policy host to be the second host.") + } + req.RemoteAddr = "172.0.0.3" + h = ipHash.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected ip hash policy host to be the first host.") + } + req.RemoteAddr = "172.0.0.4" + h = ipHash.Select(pool, req, nil) + if h != pool[1] { + t.Error("Expected ip hash policy host to be the second host.") + } + + // we should get a healthy host if the original host is unhealthy and a + // healthy host is available + req.RemoteAddr = "172.0.0.4" + pool[1].setHealthy(false) + h = ipHash.Select(pool, req, nil) + if h != pool[2] { + t.Error("Expected ip hash policy host to be the third host.") + } + + req.RemoteAddr = "172.0.0.2" + h = ipHash.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected ip hash policy host to be the first host.") + } + pool[1].setHealthy(true) + + req.RemoteAddr = "172.0.0.3" + pool[2].setHealthy(false) + h = ipHash.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected ip hash policy host to be the first host.") + } + req.RemoteAddr = "172.0.0.4" + h = ipHash.Select(pool, req, nil) + if h != pool[1] { + t.Error("Expected ip hash policy host to be the second host.") + } + + // We should be able to resize the host pool and still be able to predict + // where a req will be routed with the same IP's used above + pool = UpstreamPool{ + {Host: new(Host), Dial: "0.0.0.2"}, + {Host: new(Host), Dial: "0.0.0.3"}, + } + req.RemoteAddr = "172.0.0.1:80" + h = ipHash.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected ip hash policy host to be the first host.") + } + req.RemoteAddr = "172.0.0.2:80" + h = ipHash.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected ip hash policy host to be the first host.") + } + req.RemoteAddr = "172.0.0.3:80" + h = ipHash.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected ip hash policy host to be the first host.") + } + req.RemoteAddr = "172.0.0.4:80" + h = ipHash.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected ip hash policy host to be the first host.") + } + + // We should get nil when there are no healthy hosts + pool[0].setHealthy(false) + pool[1].setHealthy(false) + h = ipHash.Select(pool, req, nil) + if h != nil { + t.Error("Expected ip hash policy host to be nil.") + } + + // Reproduce #4135 + pool = UpstreamPool{ + {Host: new(Host)}, + {Host: new(Host)}, + {Host: new(Host)}, + {Host: new(Host)}, + {Host: new(Host)}, + {Host: new(Host)}, + {Host: new(Host)}, + {Host: new(Host)}, + {Host: new(Host)}, + } + pool[0].setHealthy(false) + pool[1].setHealthy(false) + pool[2].setHealthy(false) + pool[3].setHealthy(false) + pool[4].setHealthy(false) + pool[5].setHealthy(false) + pool[6].setHealthy(false) + pool[7].setHealthy(false) + pool[8].setHealthy(true) + + // We should get a result back when there is one healthy host left. + h = ipHash.Select(pool, req, nil) + if h == nil { + // If it is nil, it means we missed a host even though one is available + t.Error("Expected ip hash policy host to not be nil, but it is nil.") + } +} + +func TestClientIPHashPolicy(t *testing.T) { + pool := testPool() + ipHash := ClientIPHashSelection{} + req, _ := http.NewRequest("GET", "/", nil) + req = req.WithContext(context.WithValue(req.Context(), caddyhttp.VarsCtxKey, make(map[string]any))) + + // We should be able to predict where every request is routed. + caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.1:80") + h := ipHash.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected ip hash policy host to be the first host.") + } + caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.2:80") + h = ipHash.Select(pool, req, nil) + if h != pool[1] { + t.Error("Expected ip hash policy host to be the second host.") + } + caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.3:80") + h = ipHash.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected ip hash policy host to be the first host.") + } + caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4:80") + h = ipHash.Select(pool, req, nil) + if h != pool[1] { + t.Error("Expected ip hash policy host to be the second host.") + } + + // we should get the same results without a port + caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.1") + h = ipHash.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected ip hash policy host to be the first host.") + } + caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.2") + h = ipHash.Select(pool, req, nil) + if h != pool[1] { + t.Error("Expected ip hash policy host to be the second host.") + } + caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.3") + h = ipHash.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected ip hash policy host to be the first host.") + } + caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4") + h = ipHash.Select(pool, req, nil) + if h != pool[1] { + t.Error("Expected ip hash policy host to be the second host.") + } + + // we should get a healthy host if the original host is unhealthy and a + // healthy host is available + caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4") + pool[1].setHealthy(false) + h = ipHash.Select(pool, req, nil) + if h != pool[2] { + t.Error("Expected ip hash policy host to be the third host.") + } + + caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.2") + h = ipHash.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected ip hash policy host to be the first host.") + } + pool[1].setHealthy(true) + + caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.3") + pool[2].setHealthy(false) + h = ipHash.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected ip hash policy host to be the first host.") + } + caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4") + h = ipHash.Select(pool, req, nil) + if h != pool[1] { + t.Error("Expected ip hash policy host to be the second host.") + } + + // We should be able to resize the host pool and still be able to predict + // where a req will be routed with the same IP's used above + pool = UpstreamPool{ + {Host: new(Host), Dial: "0.0.0.2"}, + {Host: new(Host), Dial: "0.0.0.3"}, + } + caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.1:80") + h = ipHash.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected ip hash policy host to be the first host.") + } + caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.2:80") + h = ipHash.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected ip hash policy host to be the first host.") + } + caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.3:80") + h = ipHash.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected ip hash policy host to be the first host.") + } + caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4:80") + h = ipHash.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected ip hash policy host to be the first host.") + } + + // We should get nil when there are no healthy hosts + pool[0].setHealthy(false) + pool[1].setHealthy(false) + h = ipHash.Select(pool, req, nil) + if h != nil { + t.Error("Expected ip hash policy host to be nil.") + } + + // Reproduce #4135 + pool = UpstreamPool{ + {Host: new(Host)}, + {Host: new(Host)}, + {Host: new(Host)}, + {Host: new(Host)}, + {Host: new(Host)}, + {Host: new(Host)}, + {Host: new(Host)}, + {Host: new(Host)}, + {Host: new(Host)}, + } + pool[0].setHealthy(false) + pool[1].setHealthy(false) + pool[2].setHealthy(false) + pool[3].setHealthy(false) + pool[4].setHealthy(false) + pool[5].setHealthy(false) + pool[6].setHealthy(false) + pool[7].setHealthy(false) + pool[8].setHealthy(true) + + // We should get a result back when there is one healthy host left. + h = ipHash.Select(pool, req, nil) + if h == nil { + // If it is nil, it means we missed a host even though one is available + t.Error("Expected ip hash policy host to not be nil, but it is nil.") + } +} + +func TestFirstPolicy(t *testing.T) { + pool := testPool() + firstPolicy := FirstSelection{} + req := httptest.NewRequest(http.MethodGet, "/", nil) + + h := firstPolicy.Select(pool, req, nil) + if h != pool[0] { + t.Error("Expected first policy host to be the first host.") + } + + pool[0].setHealthy(false) + h = firstPolicy.Select(pool, req, nil) + if h != pool[1] { + t.Error("Expected first policy host to be the second host.") + } +} + +func TestQueryHashPolicy(t *testing.T) { + ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + queryPolicy := QueryHashSelection{Key: "foo"} + if err := queryPolicy.Provision(ctx); err != nil { + t.Errorf("Provision error: %v", err) + t.FailNow() + } + + pool := testPool() + + request := httptest.NewRequest(http.MethodGet, "/?foo=1", nil) + h := queryPolicy.Select(pool, request, nil) + if h != pool[0] { + t.Error("Expected query policy host to be the first host.") + } + + request = httptest.NewRequest(http.MethodGet, "/?foo=100000", nil) + h = queryPolicy.Select(pool, request, nil) + if h != pool[1] { + t.Error("Expected query policy host to be the second host.") + } + + request = httptest.NewRequest(http.MethodGet, "/?foo=1", nil) + pool[0].setHealthy(false) + h = queryPolicy.Select(pool, request, nil) + if h != pool[2] { + t.Error("Expected query policy host to be the third host.") + } + + request = httptest.NewRequest(http.MethodGet, "/?foo=100000", nil) + h = queryPolicy.Select(pool, request, nil) + if h != pool[1] { + t.Error("Expected query policy host to be the second host.") + } + + // We should be able to resize the host pool and still be able to predict + // where a request will be routed with the same query used above + pool = UpstreamPool{ + {Host: new(Host)}, + {Host: new(Host)}, + } + + request = httptest.NewRequest(http.MethodGet, "/?foo=1", nil) + h = queryPolicy.Select(pool, request, nil) + if h != pool[0] { + t.Error("Expected query policy host to be the first host.") + } + + pool[0].setHealthy(false) + h = queryPolicy.Select(pool, request, nil) + if h != pool[1] { + t.Error("Expected query policy host to be the second host.") + } + + request = httptest.NewRequest(http.MethodGet, "/?foo=4", nil) + h = queryPolicy.Select(pool, request, nil) + if h != pool[1] { + t.Error("Expected query policy host to be the second host.") + } + + pool[0].setHealthy(false) + pool[1].setHealthy(false) + h = queryPolicy.Select(pool, request, nil) + if h != nil { + t.Error("Expected query policy policy host to be nil.") + } + + request = httptest.NewRequest(http.MethodGet, "/?foo=aa11&foo=bb22", nil) + pool = testPool() + h = queryPolicy.Select(pool, request, nil) + if h != pool[0] { + t.Error("Expected query policy host to be the first host.") + } +} + +func TestURIHashPolicy(t *testing.T) { + pool := testPool() + uriPolicy := URIHashSelection{} + + request := httptest.NewRequest(http.MethodGet, "/test", nil) + h := uriPolicy.Select(pool, request, nil) + if h != pool[2] { + t.Error("Expected uri policy host to be the third host.") + } + + pool[2].setHealthy(false) + h = uriPolicy.Select(pool, request, nil) + if h != pool[0] { + t.Error("Expected uri policy host to be the first host.") + } + + request = httptest.NewRequest(http.MethodGet, "/test_2", nil) + h = uriPolicy.Select(pool, request, nil) + if h != pool[0] { + t.Error("Expected uri policy host to be the first host.") + } + + // We should be able to resize the host pool and still be able to predict + // where a request will be routed with the same URI's used above + pool = UpstreamPool{ + {Host: new(Host)}, + {Host: new(Host)}, + } + + request = httptest.NewRequest(http.MethodGet, "/test", nil) + h = uriPolicy.Select(pool, request, nil) + if h != pool[0] { + t.Error("Expected uri policy host to be the first host.") + } + + pool[0].setHealthy(false) + h = uriPolicy.Select(pool, request, nil) + if h != pool[1] { + t.Error("Expected uri policy host to be the first host.") + } + + request = httptest.NewRequest(http.MethodGet, "/test_2", nil) + h = uriPolicy.Select(pool, request, nil) + if h != pool[1] { + t.Error("Expected uri policy host to be the second host.") + } + + pool[0].setHealthy(false) + pool[1].setHealthy(false) + h = uriPolicy.Select(pool, request, nil) + if h != nil { + t.Error("Expected uri policy policy host to be nil.") + } +} + +func TestLeastRequests(t *testing.T) { + pool := testPool() + pool[0].Dial = "localhost:8080" + pool[1].Dial = "localhost:8081" + pool[2].Dial = "localhost:8082" + pool[0].setHealthy(true) + pool[1].setHealthy(true) + pool[2].setHealthy(true) + pool[0].countRequest(10) + pool[1].countRequest(20) + pool[2].countRequest(30) + + result := leastRequests(pool) + + if result == nil { + t.Error("Least request should not return nil") + } + + if result != pool[0] { + t.Error("Least request should return pool[0]") + } +} + +func TestRandomChoicePolicy(t *testing.T) { + pool := testPool() + pool[0].Dial = "localhost:8080" + pool[1].Dial = "localhost:8081" + pool[2].Dial = "localhost:8082" + pool[0].setHealthy(false) + pool[1].setHealthy(true) + pool[2].setHealthy(true) + pool[0].countRequest(10) + pool[1].countRequest(20) + pool[2].countRequest(30) + + request := httptest.NewRequest(http.MethodGet, "/test", nil) + randomChoicePolicy := RandomChoiceSelection{Choose: 2} + + h := randomChoicePolicy.Select(pool, request, nil) + + if h == nil { + t.Error("RandomChoicePolicy should not return nil") + } + + if h == pool[0] { + t.Error("RandomChoicePolicy should not choose pool[0]") + } +} + +func TestCookieHashPolicy(t *testing.T) { + ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + cookieHashPolicy := CookieHashSelection{} + if err := cookieHashPolicy.Provision(ctx); err != nil { + t.Errorf("Provision error: %v", err) + t.FailNow() + } + + pool := testPool() + pool[0].Dial = "localhost:8080" + pool[1].Dial = "localhost:8081" + pool[2].Dial = "localhost:8082" + pool[0].setHealthy(true) + pool[1].setHealthy(false) + pool[2].setHealthy(false) + request := httptest.NewRequest(http.MethodGet, "/test", nil) + w := httptest.NewRecorder() + + h := cookieHashPolicy.Select(pool, request, w) + cookieServer1 := w.Result().Cookies()[0] + if cookieServer1 == nil { + t.Fatal("cookieHashPolicy should set a cookie") + } + if cookieServer1.Name != "lb" { + t.Error("cookieHashPolicy should set a cookie with name lb") + } + if cookieServer1.Secure { + t.Error("cookieHashPolicy should set cookie Secure attribute to false when request is not secure") + } + if h != pool[0] { + t.Error("Expected cookieHashPolicy host to be the first only available host.") + } + pool[1].setHealthy(true) + pool[2].setHealthy(true) + request = httptest.NewRequest(http.MethodGet, "/test", nil) + w = httptest.NewRecorder() + request.AddCookie(cookieServer1) + h = cookieHashPolicy.Select(pool, request, w) + if h != pool[0] { + t.Error("Expected cookieHashPolicy host to stick to the first host (matching cookie).") + } + s := w.Result().Cookies() + if len(s) != 0 { + t.Error("Expected cookieHashPolicy to not set a new cookie.") + } + pool[0].setHealthy(false) + request = httptest.NewRequest(http.MethodGet, "/test", nil) + w = httptest.NewRecorder() + request.AddCookie(cookieServer1) + h = cookieHashPolicy.Select(pool, request, w) + if h == pool[0] { + t.Error("Expected cookieHashPolicy to select a new host.") + } + if w.Result().Cookies() == nil { + t.Error("Expected cookieHashPolicy to set a new cookie.") + } +} + +func TestCookieHashPolicyWithSecureRequest(t *testing.T) { + ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + cookieHashPolicy := CookieHashSelection{} + if err := cookieHashPolicy.Provision(ctx); err != nil { + t.Errorf("Provision error: %v", err) + t.FailNow() + } + + pool := testPool() + pool[0].Dial = "localhost:8080" + pool[1].Dial = "localhost:8081" + pool[2].Dial = "localhost:8082" + pool[0].setHealthy(true) + pool[1].setHealthy(false) + pool[2].setHealthy(false) + + // Create a test server that serves HTTPS requests + ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + h := cookieHashPolicy.Select(pool, r, w) + if h != pool[0] { + t.Error("Expected cookieHashPolicy host to be the first only available host.") + } + })) + defer ts.Close() + + // Make a new HTTPS request to the test server + client := ts.Client() + request, err := http.NewRequest(http.MethodGet, ts.URL+"/test", nil) + if err != nil { + t.Fatal(err) + } + response, err := client.Do(request) + if err != nil { + t.Fatal(err) + } + + // Check if the cookie set is Secure and has SameSiteNone mode + cookies := response.Cookies() + if len(cookies) == 0 { + t.Fatal("Expected a cookie to be set") + } + cookie := cookies[0] + if !cookie.Secure { + t.Error("Expected cookie Secure attribute to be true when request is secure") + } + if cookie.SameSite != http.SameSiteNoneMode { + t.Error("Expected cookie SameSite attribute to be None when request is secure") + } +} + +func TestCookieHashPolicyWithFirstFallback(t *testing.T) { + ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + cookieHashPolicy := CookieHashSelection{ + FallbackRaw: caddyconfig.JSONModuleObject(FirstSelection{}, "policy", "first", nil), + } + if err := cookieHashPolicy.Provision(ctx); err != nil { + t.Errorf("Provision error: %v", err) + t.FailNow() + } + + pool := testPool() + pool[0].Dial = "localhost:8080" + pool[1].Dial = "localhost:8081" + pool[2].Dial = "localhost:8082" + pool[0].setHealthy(true) + pool[1].setHealthy(true) + pool[2].setHealthy(true) + request := httptest.NewRequest(http.MethodGet, "/test", nil) + w := httptest.NewRecorder() + + h := cookieHashPolicy.Select(pool, request, w) + cookieServer1 := w.Result().Cookies()[0] + if cookieServer1 == nil { + t.Fatal("cookieHashPolicy should set a cookie") + } + if cookieServer1.Name != "lb" { + t.Error("cookieHashPolicy should set a cookie with name lb") + } + if h != pool[0] { + t.Errorf("Expected cookieHashPolicy host to be the first only available host, got %s", h) + } + request = httptest.NewRequest(http.MethodGet, "/test", nil) + w = httptest.NewRecorder() + request.AddCookie(cookieServer1) + h = cookieHashPolicy.Select(pool, request, w) + if h != pool[0] { + t.Errorf("Expected cookieHashPolicy host to stick to the first host (matching cookie), got %s", h) + } + s := w.Result().Cookies() + if len(s) != 0 { + t.Error("Expected cookieHashPolicy to not set a new cookie.") + } + pool[0].setHealthy(false) + request = httptest.NewRequest(http.MethodGet, "/test", nil) + w = httptest.NewRecorder() + request.AddCookie(cookieServer1) + h = cookieHashPolicy.Select(pool, request, w) + if h != pool[1] { + t.Errorf("Expected cookieHashPolicy to select the next first available host, got %s", h) + } + if w.Result().Cookies() == nil { + t.Error("Expected cookieHashPolicy to set a new cookie.") + } +} diff --git a/modules/caddyhttp/reverseproxy/streaming.go b/modules/caddyhttp/reverseproxy/streaming.go new file mode 100644 index 00000000000..d697eb402d1 --- /dev/null +++ b/modules/caddyhttp/reverseproxy/streaming.go @@ -0,0 +1,664 @@ +// 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. + +// Most of the code in this file was initially borrowed from the Go +// standard library and modified; It had this copyright notice: +// Copyright 2011 The Go Authors + +package reverseproxy + +import ( + "bufio" + "context" + "errors" + "fmt" + "io" + weakrand "math/rand" + "mime" + "net/http" + "sync" + "time" + "unsafe" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "golang.org/x/net/http/httpguts" + + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +type h2ReadWriteCloser struct { + io.ReadCloser + http.ResponseWriter +} + +func (rwc h2ReadWriteCloser) Write(p []byte) (n int, err error) { + n, err = rwc.ResponseWriter.Write(p) + if err != nil { + return 0, err + } + + //nolint:bodyclose + err = http.NewResponseController(rwc.ResponseWriter).Flush() + if err != nil { + return 0, err + } + return n, nil +} + +func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup, rw http.ResponseWriter, req *http.Request, res *http.Response) { + reqUpType := upgradeType(req.Header) + resUpType := upgradeType(res.Header) + + // Taken from https://github.com/golang/go/commit/5c489514bc5e61ad9b5b07bd7d8ec65d66a0512a + // We know reqUpType is ASCII, it's checked by the caller. + if !asciiIsPrint(resUpType) { + if c := logger.Check(zapcore.DebugLevel, "backend tried to switch to invalid protocol"); c != nil { + c.Write(zap.String("backend_upgrade", resUpType)) + } + return + } + if !asciiEqualFold(reqUpType, resUpType) { + if c := logger.Check(zapcore.DebugLevel, "backend tried to switch to unexpected protocol via Upgrade header"); c != nil { + c.Write( + zap.String("backend_upgrade", resUpType), + zap.String("requested_upgrade", reqUpType), + ) + } + return + } + + backConn, ok := res.Body.(io.ReadWriteCloser) + if !ok { + logger.Error("internal error: 101 switching protocols response with non-writable body") + return + } + + // write header first, response headers should not be counted in size + // like the rest of handler chain. + copyHeader(rw.Header(), res.Header) + normalizeWebsocketHeaders(rw.Header()) + + var ( + conn io.ReadWriteCloser + brw *bufio.ReadWriter + ) + // websocket over http2, assuming backend doesn't support this, the request will be modified to http1.1 upgrade + // TODO: once we can reliably detect backend support this, it can be removed for those backends + if body, ok := caddyhttp.GetVar(req.Context(), "h2_websocket_body").(io.ReadCloser); ok { + req.Body = body + rw.Header().Del("Upgrade") + rw.Header().Del("Connection") + delete(rw.Header(), "Sec-WebSocket-Accept") + rw.WriteHeader(http.StatusOK) + + if c := logger.Check(zap.DebugLevel, "upgrading connection"); c != nil { + c.Write(zap.Int("http_version", 2)) + } + + //nolint:bodyclose + flushErr := http.NewResponseController(rw).Flush() + if flushErr != nil { + if c := h.logger.Check(zap.ErrorLevel, "failed to flush http2 websocket response"); c != nil { + c.Write(zap.Error(flushErr)) + } + return + } + conn = h2ReadWriteCloser{req.Body, rw} + // bufio is not needed, use minimal buffer + brw = bufio.NewReadWriter(bufio.NewReaderSize(conn, 1), bufio.NewWriterSize(conn, 1)) + } else { + rw.WriteHeader(res.StatusCode) + + if c := logger.Check(zap.DebugLevel, "upgrading connection"); c != nil { + c.Write(zap.Int("http_version", req.ProtoMajor)) + } + + var hijackErr error + //nolint:bodyclose + conn, brw, hijackErr = http.NewResponseController(rw).Hijack() + if errors.Is(hijackErr, http.ErrNotSupported) { + if c := h.logger.Check(zap.ErrorLevel, "can't switch protocols using non-Hijacker ResponseWriter"); c != nil { + c.Write(zap.String("type", fmt.Sprintf("%T", rw))) + } + return + } + + if hijackErr != nil { + if c := h.logger.Check(zap.ErrorLevel, "hijack failed on protocol switch"); c != nil { + c.Write(zap.Error(hijackErr)) + } + return + } + } + + // adopted from https://github.com/golang/go/commit/8bcf2834afdf6a1f7937390903a41518715ef6f5 + backConnCloseCh := make(chan struct{}) + go func() { + // Ensure that the cancelation of a request closes the backend. + // See issue https://golang.org/issue/35559. + select { + case <-req.Context().Done(): + case <-backConnCloseCh: + } + backConn.Close() + }() + defer close(backConnCloseCh) + + start := time.Now() + defer func() { + conn.Close() + if c := logger.Check(zapcore.DebugLevel, "connection closed"); c != nil { + c.Write(zap.Duration("duration", time.Since(start))) + } + }() + + if err := brw.Flush(); err != nil { + if c := logger.Check(zapcore.DebugLevel, "response flush"); c != nil { + c.Write(zap.Error(err)) + } + return + } + + // There may be buffered data in the *bufio.Reader + // see: https://github.com/caddyserver/caddy/issues/6273 + if buffered := brw.Reader.Buffered(); buffered > 0 { + data, _ := brw.Peek(buffered) + _, err := backConn.Write(data) + if err != nil { + if c := logger.Check(zapcore.DebugLevel, "backConn write failed"); c != nil { + c.Write(zap.Error(err)) + } + return + } + } + + // Ensure the hijacked client connection, and the new connection established + // with the backend, are both closed in the event of a server shutdown. This + // is done by registering them. We also try to gracefully close connections + // we recognize as websockets. + // We need to make sure the client connection messages (i.e. to upstream) + // are masked, so we need to know whether the connection is considered the + // server or the client side of the proxy. + gracefulClose := func(conn io.ReadWriteCloser, isClient bool) func() error { + if isWebsocket(req) { + return func() error { + return writeCloseControl(conn, isClient) + } + } + return nil + } + deleteFrontConn := h.registerConnection(conn, gracefulClose(conn, false)) + deleteBackConn := h.registerConnection(backConn, gracefulClose(backConn, true)) + defer deleteFrontConn() + defer deleteBackConn() + + spc := switchProtocolCopier{user: conn, backend: backConn, wg: wg} + + // setup the timeout if requested + var timeoutc <-chan time.Time + if h.StreamTimeout > 0 { + timer := time.NewTimer(time.Duration(h.StreamTimeout)) + defer timer.Stop() + timeoutc = timer.C + } + + errc := make(chan error, 1) + wg.Add(2) + go spc.copyToBackend(errc) + go spc.copyFromBackend(errc) + select { + case err := <-errc: + if c := logger.Check(zapcore.DebugLevel, "streaming error"); c != nil { + c.Write(zap.Error(err)) + } + case time := <-timeoutc: + if c := logger.Check(zapcore.DebugLevel, "stream timed out"); c != nil { + c.Write(zap.Time("timeout", time)) + } + } +} + +// flushInterval returns the p.FlushInterval value, conditionally +// overriding its value for a specific request/response. +func (h Handler) flushInterval(req *http.Request, res *http.Response) time.Duration { + resCTHeader := res.Header.Get("Content-Type") + resCT, _, err := mime.ParseMediaType(resCTHeader) + + // For Server-Sent Events responses, flush immediately. + // The MIME type is defined in https://www.w3.org/TR/eventsource/#text-event-stream + if err == nil && resCT == "text/event-stream" { + return -1 // negative means immediately + } + + // We might have the case of streaming for which Content-Length might be unset. + if res.ContentLength == -1 { + return -1 + } + + // for h2 and h2c upstream streaming data to client (issues #3556 and #3606) + if h.isBidirectionalStream(req, res) { + return -1 + } + + return time.Duration(h.FlushInterval) +} + +// isBidirectionalStream returns whether we should work in bi-directional stream mode. +// +// See https://github.com/caddyserver/caddy/pull/3620 for discussion of nuances. +func (h Handler) isBidirectionalStream(req *http.Request, res *http.Response) bool { + // We have to check the encoding here; only flush headers with identity encoding. + // Non-identity encoding might combine with "encode" directive, and in that case, + // if body size larger than enc.MinLength, upper level encode handle might have + // Content-Encoding header to write. + // (see https://github.com/caddyserver/caddy/issues/3606 for use case) + ae := req.Header.Get("Accept-Encoding") + + return req.ProtoMajor == 2 && + res.ProtoMajor == 2 && + res.ContentLength == -1 && + (ae == "identity" || ae == "") +} + +func (h Handler) copyResponse(dst http.ResponseWriter, src io.Reader, flushInterval time.Duration, logger *zap.Logger) error { + var w io.Writer = dst + + if flushInterval != 0 { + var mlwLogger *zap.Logger + if h.VerboseLogs { + mlwLogger = logger.Named("max_latency_writer") + } else { + mlwLogger = zap.NewNop() + } + mlw := &maxLatencyWriter{ + dst: dst, + //nolint:bodyclose + flush: http.NewResponseController(dst).Flush, + latency: flushInterval, + logger: mlwLogger, + } + defer mlw.stop() + + // set up initial timer so headers get flushed even if body writes are delayed + mlw.flushPending = true + mlw.t = time.AfterFunc(flushInterval, mlw.delayedFlush) + + w = mlw + } + + buf := streamingBufPool.Get().(*[]byte) + defer streamingBufPool.Put(buf) + + var copyLogger *zap.Logger + if h.VerboseLogs { + copyLogger = logger + } else { + copyLogger = zap.NewNop() + } + + _, err := h.copyBuffer(w, src, *buf, copyLogger) + return err +} + +// copyBuffer returns any write errors or non-EOF read errors, and the amount +// of bytes written. +func (h Handler) copyBuffer(dst io.Writer, src io.Reader, buf []byte, logger *zap.Logger) (int64, error) { + if len(buf) == 0 { + buf = make([]byte, defaultBufferSize) + } + var written int64 + for { + logger.Debug("waiting to read from upstream") + nr, rerr := src.Read(buf) + logger := logger.With(zap.Int("read", nr)) + if c := logger.Check(zapcore.DebugLevel, "read from upstream"); c != nil { + c.Write(zap.Error(rerr)) + } + if rerr != nil && rerr != io.EOF && rerr != context.Canceled { + // TODO: this could be useful to know (indeed, it revealed an error in our + // fastcgi PoC earlier; but it's this single error report here that necessitates + // a function separate from io.CopyBuffer, since io.CopyBuffer does not distinguish + // between read or write errors; in a reverse proxy situation, write errors are not + // something we need to report to the client, but read errors are a problem on our + // end for sure. so we need to decide what we want.) + // p.logf("copyBuffer: ReverseProxy read error during body copy: %v", rerr) + if c := logger.Check(zapcore.ErrorLevel, "reading from backend"); c != nil { + c.Write(zap.Error(rerr)) + } + } + if nr > 0 { + logger.Debug("writing to downstream") + nw, werr := dst.Write(buf[:nr]) + if nw > 0 { + written += int64(nw) + } + if c := logger.Check(zapcore.DebugLevel, "wrote to downstream"); c != nil { + c.Write( + zap.Int("written", nw), + zap.Int64("written_total", written), + zap.Error(werr), + ) + } + if werr != nil { + return written, fmt.Errorf("writing: %w", werr) + } + if nr != nw { + return written, io.ErrShortWrite + } + } + if rerr != nil { + if rerr == io.EOF { + return written, nil + } + return written, fmt.Errorf("reading: %w", rerr) + } + } +} + +// registerConnection holds onto conn so it can be closed in the event +// of a server shutdown. This is useful because hijacked connections or +// connections dialed to backends don't close when server is shut down. +// The caller should call the returned delete() function when the +// connection is done to remove it from memory. +func (h *Handler) registerConnection(conn io.ReadWriteCloser, gracefulClose func() error) (del func()) { + h.connectionsMu.Lock() + h.connections[conn] = openConnection{conn, gracefulClose} + h.connectionsMu.Unlock() + return func() { + h.connectionsMu.Lock() + delete(h.connections, conn) + // if there is no connection left before the connections close timer fires + if len(h.connections) == 0 && h.connectionsCloseTimer != nil { + // we release the timer that holds the reference to Handler + if (*h.connectionsCloseTimer).Stop() { + h.logger.Debug("stopped streaming connections close timer - all connections are already closed") + } + h.connectionsCloseTimer = nil + } + h.connectionsMu.Unlock() + } +} + +// closeConnections immediately closes all hijacked connections (both to client and backend). +func (h *Handler) closeConnections() error { + var err error + h.connectionsMu.Lock() + defer h.connectionsMu.Unlock() + + for _, oc := range h.connections { + if oc.gracefulClose != nil { + // this is potentially blocking while we have the lock on the connections + // map, but that should be OK since the server has in theory shut down + // and we are no longer using the connections map + gracefulErr := oc.gracefulClose() + if gracefulErr != nil && err == nil { + err = gracefulErr + } + } + closeErr := oc.conn.Close() + if closeErr != nil && err == nil { + err = closeErr + } + } + return err +} + +// cleanupConnections closes hijacked connections. +// Depending on the value of StreamCloseDelay it does that either immediately +// or sets up a timer that will do that later. +func (h *Handler) cleanupConnections() error { + if h.StreamCloseDelay == 0 { + return h.closeConnections() + } + + h.connectionsMu.Lock() + defer h.connectionsMu.Unlock() + // the handler is shut down, no new connection can appear, + // so we can skip setting up the timer when there are no connections + if len(h.connections) > 0 { + delay := time.Duration(h.StreamCloseDelay) + h.connectionsCloseTimer = time.AfterFunc(delay, func() { + if c := h.logger.Check(zapcore.DebugLevel, "closing streaming connections after delay"); c != nil { + c.Write(zap.Duration("delay", delay)) + } + err := h.closeConnections() + if err != nil { + if c := h.logger.Check(zapcore.ErrorLevel, "failed to closed connections after delay"); c != nil { + c.Write( + zap.Error(err), + zap.Duration("delay", delay), + ) + } + } + }) + } + return nil +} + +// writeCloseControl sends a best-effort Close control message to the given +// WebSocket connection. Thanks to @pascaldekloe who provided inspiration +// from his simple implementation of this I was able to learn from at: +// github.com/pascaldekloe/websocket. Further work for handling masking +// taken from github.com/gorilla/websocket. +func writeCloseControl(conn io.Writer, isClient bool) error { + // Sources: + // https://github.com/pascaldekloe/websocket/blob/32050af67a5d/websocket.go#L119 + // https://github.com/gorilla/websocket/blob/v1.5.0/conn.go#L413 + + // For now, we're not using a reason. We might later, though. + // The code handling the reason is left in + var reason string // max 123 bytes (control frame payload limit is 125; status code takes 2) + + const closeMessage = 8 + const finalBit = 1 << 7 // Frame header byte 0 bits from Section 5.2 of RFC 6455 + const maskBit = 1 << 7 // Frame header byte 1 bits from Section 5.2 of RFC 6455 + const goingAwayUpper uint8 = 1001 >> 8 + const goingAwayLower uint8 = 1001 & 0xff + + b0 := byte(closeMessage) | finalBit + b1 := byte(len(reason) + 2) + if isClient { + b1 |= maskBit + } + + buf := make([]byte, 0, 127) + buf = append(buf, b0, b1) + msgLength := 4 + len(reason) + + // Both branches below append the "going away" code and reason + appendMessage := func(buf []byte) []byte { + buf = append(buf, goingAwayUpper, goingAwayLower) + buf = append(buf, []byte(reason)...) + return buf + } + + // When we're the client, we need to mask the message as per + // https://www.rfc-editor.org/rfc/rfc6455#section-5.3 + if isClient { + key := newMaskKey() + buf = append(buf, key[:]...) + msgLength += len(key) + buf = appendMessage(buf) + maskBytes(key, 0, buf[2+len(key):]) + } else { + buf = appendMessage(buf) + } + + // simply best-effort, but return error for logging purposes + // TODO: we might need to ensure we are the exclusive writer by this point (io.Copy is stopped)? + _, err := conn.Write(buf[:msgLength]) + return err +} + +// Copied from https://github.com/gorilla/websocket/blob/v1.5.0/mask.go +func maskBytes(key [4]byte, pos int, b []byte) int { + // Mask one byte at a time for small buffers. + if len(b) < 2*wordSize { + for i := range b { + b[i] ^= key[pos&3] + pos++ + } + return pos & 3 + } + + // Mask one byte at a time to word boundary. + if n := int(uintptr(unsafe.Pointer(&b[0]))) % wordSize; n != 0 { + n = wordSize - n + for i := range b[:n] { + b[i] ^= key[pos&3] + pos++ + } + b = b[n:] + } + + // Create aligned word size key. + var k [wordSize]byte + for i := range k { + k[i] = key[(pos+i)&3] + } + kw := *(*uintptr)(unsafe.Pointer(&k)) + + // Mask one word at a time. + n := (len(b) / wordSize) * wordSize + for i := 0; i < n; i += wordSize { + *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(i))) ^= kw + } + + // Mask one byte at a time for remaining bytes. + b = b[n:] + for i := range b { + b[i] ^= key[pos&3] + pos++ + } + + return pos & 3 +} + +// Copied from https://github.com/gorilla/websocket/blob/v1.5.0/conn.go#L184 +func newMaskKey() [4]byte { + n := weakrand.Uint32() + return [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)} +} + +// isWebsocket returns true if r looks to be an upgrade request for WebSockets. +// It is a fairly naive check. +func isWebsocket(r *http.Request) bool { + return httpguts.HeaderValuesContainsToken(r.Header["Connection"], "upgrade") && + httpguts.HeaderValuesContainsToken(r.Header["Upgrade"], "websocket") +} + +// openConnection maps an open connection to +// an optional function for graceful close. +type openConnection struct { + conn io.ReadWriteCloser + gracefulClose func() error +} + +type maxLatencyWriter struct { + dst io.Writer + flush func() error + latency time.Duration // non-zero; negative means to flush immediately + + mu sync.Mutex // protects t, flushPending, and dst.Flush + t *time.Timer + flushPending bool + logger *zap.Logger +} + +func (m *maxLatencyWriter) Write(p []byte) (n int, err error) { + m.mu.Lock() + defer m.mu.Unlock() + n, err = m.dst.Write(p) + if c := m.logger.Check(zapcore.DebugLevel, "wrote bytes"); c != nil { + c.Write(zap.Int("n", n), zap.Error(err)) + } + if m.latency < 0 { + m.logger.Debug("flushing immediately") + //nolint:errcheck + m.flush() + return + } + if m.flushPending { + m.logger.Debug("delayed flush already pending") + return + } + if m.t == nil { + m.t = time.AfterFunc(m.latency, m.delayedFlush) + } else { + m.t.Reset(m.latency) + } + if c := m.logger.Check(zapcore.DebugLevel, "timer set for delayed flush"); c != nil { + c.Write(zap.Duration("duration", m.latency)) + } + m.flushPending = true + return +} + +func (m *maxLatencyWriter) delayedFlush() { + m.mu.Lock() + defer m.mu.Unlock() + if !m.flushPending { // if stop was called but AfterFunc already started this goroutine + m.logger.Debug("delayed flush is not pending") + return + } + m.logger.Debug("delayed flush") + //nolint:errcheck + m.flush() + m.flushPending = false +} + +func (m *maxLatencyWriter) stop() { + m.mu.Lock() + defer m.mu.Unlock() + m.flushPending = false + if m.t != nil { + m.t.Stop() + } +} + +// switchProtocolCopier exists so goroutines proxying data back and +// forth have nice names in stacks. +type switchProtocolCopier struct { + user, backend io.ReadWriteCloser + wg *sync.WaitGroup +} + +func (c switchProtocolCopier) copyFromBackend(errc chan<- error) { + _, err := io.Copy(c.user, c.backend) + errc <- err + c.wg.Done() +} + +func (c switchProtocolCopier) copyToBackend(errc chan<- error) { + _, err := io.Copy(c.backend, c.user) + errc <- err + c.wg.Done() +} + +var streamingBufPool = sync.Pool{ + New: func() any { + // The Pool's New function should generally only return pointer + // types, since a pointer can be put into the return interface + // value without an allocation + // - (from the package docs) + b := make([]byte, defaultBufferSize) + return &b + }, +} + +const ( + defaultBufferSize = 32 * 1024 + wordSize = int(unsafe.Sizeof(uintptr(0))) +) diff --git a/modules/caddyhttp/reverseproxy/streaming_test.go b/modules/caddyhttp/reverseproxy/streaming_test.go new file mode 100644 index 00000000000..3f6da2ffa37 --- /dev/null +++ b/modules/caddyhttp/reverseproxy/streaming_test.go @@ -0,0 +1,36 @@ +package reverseproxy + +import ( + "bytes" + "net/http/httptest" + "strings" + "testing" + + "github.com/caddyserver/caddy/v2" +) + +func TestHandlerCopyResponse(t *testing.T) { + h := Handler{} + testdata := []string{ + "", + strings.Repeat("a", defaultBufferSize), + strings.Repeat("123456789 123456789 123456789 12", 3000), + } + + dst := bytes.NewBuffer(nil) + recorder := httptest.NewRecorder() + recorder.Body = dst + + for _, d := range testdata { + src := bytes.NewBuffer([]byte(d)) + dst.Reset() + err := h.copyResponse(recorder, src, 0, caddy.Log()) + if err != nil { + t.Errorf("failed with error: %v", err) + } + out := dst.String() + if out != d { + t.Errorf("bad read: got %q", out) + } + } +} diff --git a/modules/caddyhttp/reverseproxy/upstreams.go b/modules/caddyhttp/reverseproxy/upstreams.go new file mode 100644 index 00000000000..aa59dc41b2f --- /dev/null +++ b/modules/caddyhttp/reverseproxy/upstreams.go @@ -0,0 +1,553 @@ +package reverseproxy + +import ( + "context" + "encoding/json" + "fmt" + weakrand "math/rand" + "net" + "net/http" + "strconv" + "sync" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" +) + +func init() { + caddy.RegisterModule(SRVUpstreams{}) + caddy.RegisterModule(AUpstreams{}) + caddy.RegisterModule(MultiUpstreams{}) +} + +// SRVUpstreams provides upstreams from SRV lookups. +// The lookup DNS name can be configured either by +// its individual parts (that is, specifying the +// service, protocol, and name separately) to form +// the standard "_service._proto.name" domain, or +// the domain can be specified directly in name by +// leaving service and proto empty. See RFC 2782. +// +// Lookups are cached and refreshed at the configured +// refresh interval. +// +// Returned upstreams are sorted by priority and weight. +type SRVUpstreams struct { + // The service label. + Service string `json:"service,omitempty"` + + // The protocol label; either tcp or udp. + Proto string `json:"proto,omitempty"` + + // The name label; or, if service and proto are + // empty, the entire domain name to look up. + Name string `json:"name,omitempty"` + + // The interval at which to refresh the SRV lookup. + // Results are cached between lookups. Default: 1m + Refresh caddy.Duration `json:"refresh,omitempty"` + + // If > 0 and there is an error with the lookup, + // continue to use the cached results for up to + // this long before trying again, (even though they + // are stale) instead of returning an error to the + // client. Default: 0s. + GracePeriod caddy.Duration `json:"grace_period,omitempty"` + + // Configures the DNS resolver used to resolve the + // SRV address to SRV records. + Resolver *UpstreamResolver `json:"resolver,omitempty"` + + // If Resolver is configured, how long to wait before + // timing out trying to connect to the DNS server. + DialTimeout caddy.Duration `json:"dial_timeout,omitempty"` + + // If Resolver is configured, how long to wait before + // spawning an RFC 6555 Fast Fallback connection. + // A negative value disables this. + FallbackDelay caddy.Duration `json:"dial_fallback_delay,omitempty"` + + resolver *net.Resolver + + logger *zap.Logger +} + +// CaddyModule returns the Caddy module information. +func (SRVUpstreams) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.reverse_proxy.upstreams.srv", + New: func() caddy.Module { return new(SRVUpstreams) }, + } +} + +func (su *SRVUpstreams) Provision(ctx caddy.Context) error { + su.logger = ctx.Logger() + if su.Refresh == 0 { + su.Refresh = caddy.Duration(time.Minute) + } + + if su.Resolver != nil { + err := su.Resolver.ParseAddresses() + if err != nil { + return err + } + d := &net.Dialer{ + Timeout: time.Duration(su.DialTimeout), + FallbackDelay: time.Duration(su.FallbackDelay), + } + su.resolver = &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, _, _ string) (net.Conn, error) { + //nolint:gosec + addr := su.Resolver.netAddrs[weakrand.Intn(len(su.Resolver.netAddrs))] + return d.DialContext(ctx, addr.Network, addr.JoinHostPort(0)) + }, + } + } + if su.resolver == nil { + su.resolver = net.DefaultResolver + } + + return nil +} + +func (su SRVUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) { + suAddr, service, proto, name := su.expandedAddr(r) + + // first, use a cheap read-lock to return a cached result quickly + srvsMu.RLock() + cached := srvs[suAddr] + srvsMu.RUnlock() + if cached.isFresh() { + return allNew(cached.upstreams), nil + } + + // otherwise, obtain a write-lock to update the cached value + srvsMu.Lock() + defer srvsMu.Unlock() + + // check to see if it's still stale, since we're now in a different + // lock from when we first checked freshness; another goroutine might + // have refreshed it in the meantime before we re-obtained our lock + cached = srvs[suAddr] + if cached.isFresh() { + return allNew(cached.upstreams), nil + } + + if c := su.logger.Check(zapcore.DebugLevel, "refreshing SRV upstreams"); c != nil { + c.Write( + zap.String("service", service), + zap.String("proto", proto), + zap.String("name", name), + ) + } + + _, records, err := su.resolver.LookupSRV(r.Context(), service, proto, name) + if err != nil { + // From LookupSRV docs: "If the response contains invalid names, those records are filtered + // out and an error will be returned alongside the remaining results, if any." Thus, we + // only return an error if no records were also returned. + if len(records) == 0 { + if su.GracePeriod > 0 { + if c := su.logger.Check(zapcore.ErrorLevel, "SRV lookup failed; using previously cached"); c != nil { + c.Write(zap.Error(err)) + } + cached.freshness = time.Now().Add(time.Duration(su.GracePeriod) - time.Duration(su.Refresh)) + srvs[suAddr] = cached + return allNew(cached.upstreams), nil + } + return nil, err + } + if c := su.logger.Check(zapcore.WarnLevel, "SRV records filtered"); c != nil { + c.Write(zap.Error(err)) + } + } + + upstreams := make([]Upstream, len(records)) + for i, rec := range records { + if c := su.logger.Check(zapcore.DebugLevel, "discovered SRV record"); c != nil { + c.Write( + zap.String("target", rec.Target), + zap.Uint16("port", rec.Port), + zap.Uint16("priority", rec.Priority), + zap.Uint16("weight", rec.Weight), + ) + } + addr := net.JoinHostPort(rec.Target, strconv.Itoa(int(rec.Port))) + upstreams[i] = Upstream{Dial: addr} + } + + // before adding a new one to the cache (as opposed to replacing stale one), make room if cache is full + if cached.freshness.IsZero() && len(srvs) >= 100 { + for randomKey := range srvs { + delete(srvs, randomKey) + break + } + } + + srvs[suAddr] = srvLookup{ + srvUpstreams: su, + freshness: time.Now(), + upstreams: upstreams, + } + + return allNew(upstreams), nil +} + +func (su SRVUpstreams) String() string { + if su.Service == "" && su.Proto == "" { + return su.Name + } + return su.formattedAddr(su.Service, su.Proto, su.Name) +} + +// expandedAddr expands placeholders in the configured SRV domain labels. +// The return values are: addr, the RFC 2782 representation of the SRV domain; +// service, the service; proto, the protocol; and name, the name. +// If su.Service and su.Proto are empty, name will be returned as addr instead. +func (su SRVUpstreams) expandedAddr(r *http.Request) (addr, service, proto, name string) { + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + name = repl.ReplaceAll(su.Name, "") + if su.Service == "" && su.Proto == "" { + addr = name + return + } + service = repl.ReplaceAll(su.Service, "") + proto = repl.ReplaceAll(su.Proto, "") + addr = su.formattedAddr(service, proto, name) + return +} + +// formattedAddr the RFC 2782 representation of the SRV domain, in +// the form "_service._proto.name". +func (SRVUpstreams) formattedAddr(service, proto, name string) string { + return fmt.Sprintf("_%s._%s.%s", service, proto, name) +} + +type srvLookup struct { + srvUpstreams SRVUpstreams + freshness time.Time + upstreams []Upstream +} + +func (sl srvLookup) isFresh() bool { + return time.Since(sl.freshness) < time.Duration(sl.srvUpstreams.Refresh) +} + +type IPVersions struct { + IPv4 *bool `json:"ipv4,omitempty"` + IPv6 *bool `json:"ipv6,omitempty"` +} + +func resolveIpVersion(versions *IPVersions) string { + resolveIpv4 := versions == nil || (versions.IPv4 == nil && versions.IPv6 == nil) || (versions.IPv4 != nil && *versions.IPv4) + resolveIpv6 := versions == nil || (versions.IPv6 == nil && versions.IPv4 == nil) || (versions.IPv6 != nil && *versions.IPv6) + switch { + case resolveIpv4 && !resolveIpv6: + return "ip4" + case !resolveIpv4 && resolveIpv6: + return "ip6" + default: + return "ip" + } +} + +// AUpstreams provides upstreams from A/AAAA lookups. +// Results are cached and refreshed at the configured +// refresh interval. +type AUpstreams struct { + // The domain name to look up. + Name string `json:"name,omitempty"` + + // The port to use with the upstreams. Default: 80 + Port string `json:"port,omitempty"` + + // The interval at which to refresh the A lookup. + // Results are cached between lookups. Default: 1m + Refresh caddy.Duration `json:"refresh,omitempty"` + + // Configures the DNS resolver used to resolve the + // domain name to A records. + Resolver *UpstreamResolver `json:"resolver,omitempty"` + + // If Resolver is configured, how long to wait before + // timing out trying to connect to the DNS server. + DialTimeout caddy.Duration `json:"dial_timeout,omitempty"` + + // If Resolver is configured, how long to wait before + // spawning an RFC 6555 Fast Fallback connection. + // A negative value disables this. + FallbackDelay caddy.Duration `json:"dial_fallback_delay,omitempty"` + + // The IP versions to resolve for. By default, both + // "ipv4" and "ipv6" will be enabled, which + // correspond to A and AAAA records respectively. + Versions *IPVersions `json:"versions,omitempty"` + + resolver *net.Resolver + + logger *zap.Logger +} + +// CaddyModule returns the Caddy module information. +func (AUpstreams) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.reverse_proxy.upstreams.a", + New: func() caddy.Module { return new(AUpstreams) }, + } +} + +func (au *AUpstreams) Provision(ctx caddy.Context) error { + au.logger = ctx.Logger() + if au.Refresh == 0 { + au.Refresh = caddy.Duration(time.Minute) + } + if au.Port == "" { + au.Port = "80" + } + + if au.Resolver != nil { + err := au.Resolver.ParseAddresses() + if err != nil { + return err + } + d := &net.Dialer{ + Timeout: time.Duration(au.DialTimeout), + FallbackDelay: time.Duration(au.FallbackDelay), + } + au.resolver = &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, _, _ string) (net.Conn, error) { + //nolint:gosec + addr := au.Resolver.netAddrs[weakrand.Intn(len(au.Resolver.netAddrs))] + return d.DialContext(ctx, addr.Network, addr.JoinHostPort(0)) + }, + } + } + if au.resolver == nil { + au.resolver = net.DefaultResolver + } + + return nil +} + +func (au AUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) { + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + + // Map ipVersion early, so we can use it as part of the cache-key. + // This should be fairly inexpensive and comes and the upside of + // allowing the same dynamic upstream (name + port combination) + // to be used multiple times with different ip versions. + // + // It also forced a cache-miss if a previously cached dynamic + // upstream changes its ip version, e.g. after a config reload, + // while keeping the cache-invalidation as simple as it currently is. + ipVersion := resolveIpVersion(au.Versions) + + auStr := repl.ReplaceAll(au.String()+ipVersion, "") + + // first, use a cheap read-lock to return a cached result quickly + aAaaaMu.RLock() + cached := aAaaa[auStr] + aAaaaMu.RUnlock() + if cached.isFresh() { + return allNew(cached.upstreams), nil + } + + // otherwise, obtain a write-lock to update the cached value + aAaaaMu.Lock() + defer aAaaaMu.Unlock() + + // check to see if it's still stale, since we're now in a different + // lock from when we first checked freshness; another goroutine might + // have refreshed it in the meantime before we re-obtained our lock + cached = aAaaa[auStr] + if cached.isFresh() { + return allNew(cached.upstreams), nil + } + + name := repl.ReplaceAll(au.Name, "") + port := repl.ReplaceAll(au.Port, "") + + if c := au.logger.Check(zapcore.DebugLevel, "refreshing A upstreams"); c != nil { + c.Write( + zap.String("version", ipVersion), + zap.String("name", name), + zap.String("port", port), + ) + } + + ips, err := au.resolver.LookupIP(r.Context(), ipVersion, name) + if err != nil { + return nil, err + } + + upstreams := make([]Upstream, len(ips)) + for i, ip := range ips { + if c := au.logger.Check(zapcore.DebugLevel, "discovered A record"); c != nil { + c.Write(zap.String("ip", ip.String())) + } + upstreams[i] = Upstream{ + Dial: net.JoinHostPort(ip.String(), port), + } + } + + // before adding a new one to the cache (as opposed to replacing stale one), make room if cache is full + if cached.freshness.IsZero() && len(aAaaa) >= 100 { + for randomKey := range aAaaa { + delete(aAaaa, randomKey) + break + } + } + + aAaaa[auStr] = aLookup{ + aUpstreams: au, + freshness: time.Now(), + upstreams: upstreams, + } + + return allNew(upstreams), nil +} + +func (au AUpstreams) String() string { return net.JoinHostPort(au.Name, au.Port) } + +type aLookup struct { + aUpstreams AUpstreams + freshness time.Time + upstreams []Upstream +} + +func (al aLookup) isFresh() bool { + return time.Since(al.freshness) < time.Duration(al.aUpstreams.Refresh) +} + +// MultiUpstreams is a single dynamic upstream source that +// aggregates the results of multiple dynamic upstream sources. +// All configured sources will be queried in order, with their +// results appended to the end of the list. Errors returned +// from individual sources will be logged and the next source +// will continue to be invoked. +// +// This module makes it easy to implement redundant cluster +// failovers, especially in conjunction with the `first` load +// balancing policy: if the first source returns an error or +// no upstreams, the second source's upstreams will be used +// naturally. +type MultiUpstreams struct { + // The list of upstream source modules to get upstreams from. + // They will be queried in order, with their results appended + // in the order they are returned. + SourcesRaw []json.RawMessage `json:"sources,omitempty" caddy:"namespace=http.reverse_proxy.upstreams inline_key=source"` + sources []UpstreamSource + + logger *zap.Logger +} + +// CaddyModule returns the Caddy module information. +func (MultiUpstreams) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.reverse_proxy.upstreams.multi", + New: func() caddy.Module { return new(MultiUpstreams) }, + } +} + +func (mu *MultiUpstreams) Provision(ctx caddy.Context) error { + mu.logger = ctx.Logger() + + if mu.SourcesRaw != nil { + mod, err := ctx.LoadModule(mu, "SourcesRaw") + if err != nil { + return fmt.Errorf("loading upstream source modules: %v", err) + } + for _, src := range mod.([]any) { + mu.sources = append(mu.sources, src.(UpstreamSource)) + } + } + + return nil +} + +func (mu MultiUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) { + var upstreams []*Upstream + + for i, src := range mu.sources { + select { + case <-r.Context().Done(): + return upstreams, context.Canceled + default: + } + + up, err := src.GetUpstreams(r) + if err != nil { + if c := mu.logger.Check(zapcore.ErrorLevel, "upstream source returned error"); c != nil { + c.Write( + zap.Int("source_idx", i), + zap.Error(err), + ) + } + } else if len(up) == 0 { + if c := mu.logger.Check(zapcore.WarnLevel, "upstream source returned 0 upstreams"); c != nil { + c.Write(zap.Int("source_idx", i)) + } + } else { + upstreams = append(upstreams, up...) + } + } + + return upstreams, nil +} + +// UpstreamResolver holds the set of addresses of DNS resolvers of +// upstream addresses +type UpstreamResolver struct { + // The addresses of DNS resolvers to use when looking up the addresses of proxy upstreams. + // It accepts [network addresses](/docs/conventions#network-addresses) + // with port range of only 1. If the host is an IP address, it will be dialed directly to resolve the upstream server. + // If the host is not an IP address, the addresses are resolved using the [name resolution convention](https://golang.org/pkg/net/#hdr-Name_Resolution) of the Go standard library. + // If the array contains more than 1 resolver address, one is chosen at random. + Addresses []string `json:"addresses,omitempty"` + netAddrs []caddy.NetworkAddress +} + +// ParseAddresses parses all the configured network addresses +// and ensures they're ready to be used. +func (u *UpstreamResolver) ParseAddresses() error { + for _, v := range u.Addresses { + addr, err := caddy.ParseNetworkAddressWithDefaults(v, "udp", 53) + if err != nil { + return err + } + if addr.PortRangeSize() != 1 { + return fmt.Errorf("resolver address must have exactly one address; cannot call %v", addr) + } + u.netAddrs = append(u.netAddrs, addr) + } + return nil +} + +func allNew(upstreams []Upstream) []*Upstream { + results := make([]*Upstream, len(upstreams)) + for i := range upstreams { + results[i] = &Upstream{Dial: upstreams[i].Dial} + } + return results +} + +var ( + srvs = make(map[string]srvLookup) + srvsMu sync.RWMutex + + aAaaa = make(map[string]aLookup) + aAaaaMu sync.RWMutex +) + +// Interface guards +var ( + _ caddy.Provisioner = (*SRVUpstreams)(nil) + _ UpstreamSource = (*SRVUpstreams)(nil) + _ caddy.Provisioner = (*AUpstreams)(nil) + _ UpstreamSource = (*AUpstreams)(nil) +) diff --git a/modules/caddyhttp/reverseproxy/upstreams_test.go b/modules/caddyhttp/reverseproxy/upstreams_test.go new file mode 100644 index 00000000000..48e2d2a6310 --- /dev/null +++ b/modules/caddyhttp/reverseproxy/upstreams_test.go @@ -0,0 +1,56 @@ +package reverseproxy + +import "testing" + +func TestResolveIpVersion(t *testing.T) { + falseBool := false + trueBool := true + tests := []struct { + Versions *IPVersions + expectedIpVersion string + }{ + { + Versions: &IPVersions{IPv4: &trueBool}, + expectedIpVersion: "ip4", + }, + { + Versions: &IPVersions{IPv4: &falseBool}, + expectedIpVersion: "ip", + }, + { + Versions: &IPVersions{IPv4: &trueBool, IPv6: &falseBool}, + expectedIpVersion: "ip4", + }, + { + Versions: &IPVersions{IPv6: &trueBool}, + expectedIpVersion: "ip6", + }, + { + Versions: &IPVersions{IPv6: &falseBool}, + expectedIpVersion: "ip", + }, + { + Versions: &IPVersions{IPv6: &trueBool, IPv4: &falseBool}, + expectedIpVersion: "ip6", + }, + { + Versions: &IPVersions{}, + expectedIpVersion: "ip", + }, + { + Versions: &IPVersions{IPv4: &trueBool, IPv6: &trueBool}, + expectedIpVersion: "ip", + }, + { + Versions: &IPVersions{IPv4: &falseBool, IPv6: &falseBool}, + expectedIpVersion: "ip", + }, + } + for _, test := range tests { + ipVersion := resolveIpVersion(test.Versions) + if ipVersion != test.expectedIpVersion { + t.Errorf("resolveIpVersion(): Expected %s got %s", test.expectedIpVersion, ipVersion) + } + } + +} diff --git a/modules/caddyhttp/rewrite/caddyfile.go b/modules/caddyhttp/rewrite/caddyfile.go new file mode 100644 index 00000000000..5f9b97adfbd --- /dev/null +++ b/modules/caddyhttp/rewrite/caddyfile.go @@ -0,0 +1,293 @@ +// 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 rewrite + +import ( + "encoding/json" + "strconv" + "strings" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func init() { + httpcaddyfile.RegisterDirective("rewrite", parseCaddyfileRewrite) + httpcaddyfile.RegisterHandlerDirective("method", parseCaddyfileMethod) + httpcaddyfile.RegisterHandlerDirective("uri", parseCaddyfileURI) + httpcaddyfile.RegisterDirective("handle_path", parseCaddyfileHandlePath) +} + +// parseCaddyfileRewrite sets up a basic rewrite handler from Caddyfile tokens. Syntax: +// +// rewrite [] +// +// Only URI components which are given in will be set in the resulting URI. +// See the docs for the rewrite handler for more information. +func parseCaddyfileRewrite(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) { + h.Next() // consume directive name + + // count the tokens to determine what to do + argsCount := h.CountRemainingArgs() + if argsCount == 0 { + return nil, h.Errf("too few arguments; must have at least a rewrite URI") + } + if argsCount > 2 { + return nil, h.Errf("too many arguments; should only be a matcher and a URI") + } + + // with only one arg, assume it's a rewrite URI with no matcher token + if argsCount == 1 { + if !h.NextArg() { + return nil, h.ArgErr() + } + return h.NewRoute(nil, Rewrite{URI: h.Val()}), nil + } + + // parse the matcher token into a matcher set + userMatcherSet, err := h.ExtractMatcherSet() + if err != nil { + return nil, err + } + h.Next() // consume directive name again, matcher parsing does a reset + h.Next() // advance to the rewrite URI + + return h.NewRoute(userMatcherSet, Rewrite{URI: h.Val()}), nil +} + +// parseCaddyfileMethod sets up a basic method rewrite handler from Caddyfile tokens. Syntax: +// +// method [] +func parseCaddyfileMethod(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + h.Next() // consume directive name + if !h.NextArg() { + return nil, h.ArgErr() + } + if h.NextArg() { + return nil, h.ArgErr() + } + return Rewrite{Method: h.Val()}, nil +} + +// parseCaddyfileURI sets up a handler for manipulating (but not "rewriting") the +// URI from Caddyfile tokens. Syntax: +// +// uri [] strip_prefix|strip_suffix|replace|path_regexp [ []] +// +// If strip_prefix or strip_suffix are used, then will be stripped +// only if it is the beginning or the end, respectively, of the URI path. If +// replace is used, then will be replaced with across +// the whole URI, up to times (or unlimited if unspecified). If +// path_regexp is used, then regular expression replacements will be performed +// on the path portion of the URI (and a limit cannot be set). +func parseCaddyfileURI(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + h.Next() // consume directive name + + args := h.RemainingArgs() + if len(args) < 1 { + return nil, h.ArgErr() + } + + var rewr Rewrite + + switch args[0] { + case "strip_prefix": + if len(args) != 2 { + return nil, h.ArgErr() + } + rewr.StripPathPrefix = args[1] + + case "strip_suffix": + if len(args) != 2 { + return nil, h.ArgErr() + } + rewr.StripPathSuffix = args[1] + + case "replace": + var find, replace, lim string + switch len(args) { + case 4: + lim = args[3] + fallthrough + case 3: + find = args[1] + replace = args[2] + default: + return nil, h.ArgErr() + } + + var limInt int + if lim != "" { + var err error + limInt, err = strconv.Atoi(lim) + if err != nil { + return nil, h.Errf("limit must be an integer; invalid: %v", err) + } + } + + rewr.URISubstring = append(rewr.URISubstring, substrReplacer{ + Find: find, + Replace: replace, + Limit: limInt, + }) + + case "path_regexp": + if len(args) != 3 { + return nil, h.ArgErr() + } + find, replace := args[1], args[2] + rewr.PathRegexp = append(rewr.PathRegexp, ®exReplacer{ + Find: find, + Replace: replace, + }) + + case "query": + if len(args) > 4 { + return nil, h.ArgErr() + } + rewr.Query = &queryOps{} + var hasArgs bool + if len(args) > 1 { + hasArgs = true + err := applyQueryOps(h, rewr.Query, args[1:]) + if err != nil { + return nil, err + } + } + + for h.NextBlock(0) { + if hasArgs { + return nil, h.Err("Cannot specify uri query rewrites in both argument and block") + } + queryArgs := []string{h.Val()} + queryArgs = append(queryArgs, h.RemainingArgs()...) + err := applyQueryOps(h, rewr.Query, queryArgs) + if err != nil { + return nil, err + } + } + + default: + return nil, h.Errf("unrecognized URI manipulation '%s'", args[0]) + } + return rewr, nil +} + +func applyQueryOps(h httpcaddyfile.Helper, qo *queryOps, args []string) error { + key := args[0] + switch { + case strings.HasPrefix(key, "-"): + if len(args) != 1 { + return h.ArgErr() + } + qo.Delete = append(qo.Delete, strings.TrimLeft(key, "-")) + + case strings.HasPrefix(key, "+"): + if len(args) != 2 { + return h.ArgErr() + } + param := strings.TrimLeft(key, "+") + qo.Add = append(qo.Add, queryOpsArguments{Key: param, Val: args[1]}) + + case strings.Contains(key, ">"): + if len(args) != 1 { + return h.ArgErr() + } + renameValKey := strings.Split(key, ">") + qo.Rename = append(qo.Rename, queryOpsArguments{Key: renameValKey[0], Val: renameValKey[1]}) + + case len(args) == 3: + qo.Replace = append(qo.Replace, &queryOpsReplacement{Key: key, SearchRegexp: args[1], Replace: args[2]}) + + default: + if len(args) != 2 { + return h.ArgErr() + } + qo.Set = append(qo.Set, queryOpsArguments{Key: key, Val: args[1]}) + } + return nil +} + +// parseCaddyfileHandlePath parses the handle_path directive. Syntax: +// +// handle_path [] { +// +// } +// +// Only path matchers (with a `/` prefix) are supported as this is a shortcut +// for the handle directive with a strip_prefix rewrite. +func parseCaddyfileHandlePath(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) { + h.Next() // consume directive name + + // there must be a path matcher + if !h.NextArg() { + return nil, h.ArgErr() + } + + // read the prefix to strip + path := h.Val() + if !strings.HasPrefix(path, "/") { + return nil, h.Errf("path matcher must begin with '/', got %s", path) + } + + // we only want to strip what comes before the '/' if + // the user specified it (e.g. /api/* should only strip /api) + var stripPath string + if strings.HasSuffix(path, "/*") { + stripPath = path[:len(path)-2] + } else if strings.HasSuffix(path, "*") { + stripPath = path[:len(path)-1] + } else { + stripPath = path + } + + // the ParseSegmentAsSubroute function expects the cursor + // to be at the token just before the block opening, + // so we need to rewind because we already read past it + h.Reset() + h.Next() + + // parse the block contents as a subroute handler + handler, err := httpcaddyfile.ParseSegmentAsSubroute(h) + if err != nil { + return nil, err + } + subroute, ok := handler.(*caddyhttp.Subroute) + if !ok { + return nil, h.Errf("segment was not parsed as a subroute") + } + + // make a matcher on the path and everything below it + pathMatcher := caddy.ModuleMap{ + "path": h.JSON(caddyhttp.MatchPath{path}), + } + + // build a route with a rewrite handler to strip the path prefix + route := caddyhttp.Route{ + HandlersRaw: []json.RawMessage{ + caddyconfig.JSONModuleObject(Rewrite{ + StripPathPrefix: stripPath, + }, "handler", "rewrite", nil), + }, + } + + // prepend the route to the subroute + subroute.Routes = append([]caddyhttp.Route{route}, subroute.Routes...) + + // build and return a route from the subroute + return h.NewRoute(pathMatcher, subroute), nil +} diff --git a/modules/caddyhttp/rewrite/rewrite.go b/modules/caddyhttp/rewrite/rewrite.go new file mode 100644 index 00000000000..31ebfb4307e --- /dev/null +++ b/modules/caddyhttp/rewrite/rewrite.go @@ -0,0 +1,625 @@ +// 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 rewrite + +import ( + "fmt" + "net/http" + "net/url" + "regexp" + "strconv" + "strings" + + "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func init() { + caddy.RegisterModule(Rewrite{}) +} + +// Rewrite is a middleware which can rewrite/mutate HTTP requests. +// +// The Method and URI properties are "setters" (the request URI +// will be overwritten with the given values). Other properties are +// "modifiers" (they modify existing values in a differentiable +// way). It is atypical to combine the use of setters and +// modifiers in a single rewrite. +// +// To ensure consistent behavior, prefix and suffix stripping is +// performed in the URL-decoded (unescaped, normalized) space by +// default except for the specific bytes where an escape sequence +// is used in the prefix or suffix pattern. +// +// For all modifiers, paths are cleaned before being modified so that +// multiple, consecutive slashes are collapsed into a single slash, +// and dot elements are resolved and removed. In the special case +// of a prefix, suffix, or substring containing "//" (repeated slashes), +// slashes will not be merged while cleaning the path so that +// the rewrite can be interpreted literally. +type Rewrite struct { + // Changes the request's HTTP verb. + Method string `json:"method,omitempty"` + + // Changes the request's URI, which consists of path and query string. + // Only components of the URI that are specified will be changed. + // For example, a value of "/foo.html" or "foo.html" will only change + // the path and will preserve any existing query string. Similarly, a + // value of "?a=b" will only change the query string and will not affect + // the path. Both can also be changed: "/foo?a=b" - this sets both the + // path and query string at the same time. + // + // You can also use placeholders. For example, to preserve the existing + // query string, you might use: "?{http.request.uri.query}&a=b". Any + // key-value pairs you add to the query string will not overwrite + // existing values (individual pairs are append-only). + // + // To clear the query string, explicitly set an empty one: "?" + URI string `json:"uri,omitempty"` + + // Strips the given prefix from the beginning of the URI path. + // The prefix should be written in normalized (unescaped) form, + // but if an escaping (`%xx`) is used, the path will be required + // to have that same escape at that position in order to match. + StripPathPrefix string `json:"strip_path_prefix,omitempty"` + + // Strips the given suffix from the end of the URI path. + // The suffix should be written in normalized (unescaped) form, + // but if an escaping (`%xx`) is used, the path will be required + // to have that same escape at that position in order to match. + StripPathSuffix string `json:"strip_path_suffix,omitempty"` + + // Performs substring replacements on the URI. + URISubstring []substrReplacer `json:"uri_substring,omitempty"` + + // Performs regular expression replacements on the URI path. + PathRegexp []*regexReplacer `json:"path_regexp,omitempty"` + + // Mutates the query string of the URI. + Query *queryOps `json:"query,omitempty"` + + logger *zap.Logger +} + +// CaddyModule returns the Caddy module information. +func (Rewrite) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.rewrite", + New: func() caddy.Module { return new(Rewrite) }, + } +} + +// Provision sets up rewr. +func (rewr *Rewrite) Provision(ctx caddy.Context) error { + rewr.logger = ctx.Logger() + + for i, rep := range rewr.PathRegexp { + if rep.Find == "" { + return fmt.Errorf("path_regexp find cannot be empty") + } + re, err := regexp.Compile(rep.Find) + if err != nil { + return fmt.Errorf("compiling regular expression %d: %v", i, err) + } + rep.re = re + } + if rewr.Query != nil { + for _, replacementOp := range rewr.Query.Replace { + err := replacementOp.Provision(ctx) + if err != nil { + return fmt.Errorf("compiling regular expression %s in query rewrite replace operation: %v", replacementOp.SearchRegexp, err) + } + } + } + + return nil +} + +func (rewr Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + const message = "rewrote request" + + c := rewr.logger.Check(zap.DebugLevel, message) + if c == nil { + rewr.Rewrite(r, repl) + return next.ServeHTTP(w, r) + } + + changed := rewr.Rewrite(r, repl) + + if changed { + c.Write( + zap.Object("request", caddyhttp.LoggableHTTPRequest{Request: r}), + zap.String("method", r.Method), + zap.String("uri", r.RequestURI), + ) + } + + return next.ServeHTTP(w, r) +} + +// rewrite performs the rewrites on r using repl, which should +// have been obtained from r, but is passed in for efficiency. +// It returns true if any changes were made to r. +func (rewr Rewrite) Rewrite(r *http.Request, repl *caddy.Replacer) bool { + oldMethod := r.Method + oldURI := r.RequestURI + + // method + if rewr.Method != "" { + r.Method = strings.ToUpper(repl.ReplaceAll(rewr.Method, "")) + } + + // uri (path, query string and... fragment, because why not) + if uri := rewr.URI; uri != "" { + // find the bounds of each part of the URI that exist + pathStart, qsStart, fragStart := -1, -1, -1 + pathEnd, qsEnd := -1, -1 + loop: + for i, ch := range uri { + switch { + case ch == '?' && qsStart < 0: + pathEnd, qsStart = i, i+1 + case ch == '#' && fragStart < 0: // everything after fragment is fragment (very clear in RFC 3986 section 4.2) + if qsStart < 0 { + pathEnd = i + } else { + qsEnd = i + } + fragStart = i + 1 + break loop + case pathStart < 0 && qsStart < 0: + pathStart = i + } + } + if pathStart >= 0 && pathEnd < 0 { + pathEnd = len(uri) + } + if qsStart >= 0 && qsEnd < 0 { + qsEnd = len(uri) + } + + // isolate the three main components of the URI + var path, query, frag string + if pathStart > -1 { + path = uri[pathStart:pathEnd] + } + if qsStart > -1 { + query = uri[qsStart:qsEnd] + } + if fragStart > -1 { + frag = uri[fragStart:] + } + + // build components which are specified, and store them + // in a temporary variable so that they all read the + // same version of the URI + var newPath, newQuery, newFrag string + + if path != "" { + // replace the `path` placeholder to escaped path + pathPlaceholder := "{http.request.uri.path}" + if strings.Contains(path, pathPlaceholder) { + path = strings.ReplaceAll(path, pathPlaceholder, r.URL.EscapedPath()) + } + + newPath = repl.ReplaceAll(path, "") + } + + // before continuing, we need to check if a query string + // snuck into the path component during replacements + if before, after, found := strings.Cut(newPath, "?"); found { + // recompute; new path contains a query string + var injectedQuery string + newPath, injectedQuery = before, after + // don't overwrite explicitly-configured query string + if query == "" { + query = injectedQuery + } + } + + if query != "" { + newQuery = buildQueryString(query, repl) + } + if frag != "" { + newFrag = repl.ReplaceAll(frag, "") + } + + // update the URI with the new components + // only after building them + if pathStart >= 0 { + if path, err := url.PathUnescape(newPath); err != nil { + r.URL.Path = newPath + } else { + r.URL.Path = path + } + } + if qsStart >= 0 { + r.URL.RawQuery = newQuery + } + if fragStart >= 0 { + r.URL.Fragment = newFrag + } + } + + // strip path prefix or suffix + if rewr.StripPathPrefix != "" { + prefix := repl.ReplaceAll(rewr.StripPathPrefix, "") + if !strings.HasPrefix(prefix, "/") { + prefix = "/" + prefix + } + mergeSlashes := !strings.Contains(prefix, "//") + changePath(r, func(escapedPath string) string { + escapedPath = caddyhttp.CleanPath(escapedPath, mergeSlashes) + return trimPathPrefix(escapedPath, prefix) + }) + } + if rewr.StripPathSuffix != "" { + suffix := repl.ReplaceAll(rewr.StripPathSuffix, "") + mergeSlashes := !strings.Contains(suffix, "//") + changePath(r, func(escapedPath string) string { + escapedPath = caddyhttp.CleanPath(escapedPath, mergeSlashes) + return reverse(trimPathPrefix(reverse(escapedPath), reverse(suffix))) + }) + } + + // substring replacements in URI + for _, rep := range rewr.URISubstring { + rep.do(r, repl) + } + + // regular expression replacements on the path + for _, rep := range rewr.PathRegexp { + rep.do(r, repl) + } + + // apply query operations + if rewr.Query != nil { + rewr.Query.do(r, repl) + } + + // update the encoded copy of the URI + r.RequestURI = r.URL.RequestURI() + + // return true if anything changed + return r.Method != oldMethod || r.RequestURI != oldURI +} + +// buildQueryString takes an input query string and +// performs replacements on each component, returning +// the resulting query string. This function appends +// duplicate keys rather than replaces. +func buildQueryString(qs string, repl *caddy.Replacer) string { + var sb strings.Builder + + // first component must be key, which is the same + // as if we just wrote a value in previous iteration + wroteVal := true + + for len(qs) > 0 { + // determine the end of this component, which will be at + // the next equal sign or ampersand, whichever comes first + nextEq, nextAmp := strings.Index(qs, "="), strings.Index(qs, "&") + ampIsNext := nextAmp >= 0 && (nextAmp < nextEq || nextEq < 0) + end := len(qs) // assume no delimiter remains... + if ampIsNext { + end = nextAmp // ...unless ampersand is first... + } else if nextEq >= 0 && (nextEq < nextAmp || nextAmp < 0) { + end = nextEq // ...or unless equal is first. + } + + // consume the component and write the result + comp := qs[:end] + comp, _ = repl.ReplaceFunc(comp, func(name string, val any) (any, error) { + if name == "http.request.uri.query" && wroteVal { + return val, nil // already escaped + } + var valStr string + switch v := val.(type) { + case string: + valStr = v + case fmt.Stringer: + valStr = v.String() + case int: + valStr = strconv.Itoa(v) + default: + valStr = fmt.Sprintf("%+v", v) + } + return url.QueryEscape(valStr), nil + }) + if end < len(qs) { + end++ // consume delimiter + } + qs = qs[end:] + + // if previous iteration wrote a value, + // that means we are writing a key + if wroteVal { + if sb.Len() > 0 && len(comp) > 0 { + sb.WriteRune('&') + } + } else { + sb.WriteRune('=') + } + sb.WriteString(comp) + + // remember for the next iteration that we just wrote a value, + // which means the next iteration MUST write a key + wroteVal = ampIsNext + } + + return sb.String() +} + +// trimPathPrefix is like strings.TrimPrefix, but customized for advanced URI +// path prefix matching. The string prefix will be trimmed from the beginning +// of escapedPath if escapedPath starts with prefix. Rather than a naive 1:1 +// comparison of each byte to determine if escapedPath starts with prefix, +// both strings are iterated in lock-step, and if prefix has a '%' encoding +// at a particular position, escapedPath must also have the same encoding +// representation for that character. In other words, if the prefix string +// uses the escaped form for a character, escapedPath must literally use the +// same escape at that position. Otherwise, all character comparisons are +// performed in normalized/unescaped space. +func trimPathPrefix(escapedPath, prefix string) string { + var iPath, iPrefix int + for { + if iPath >= len(escapedPath) || iPrefix >= len(prefix) { + break + } + + prefixCh := prefix[iPrefix] + ch := string(escapedPath[iPath]) + + if ch == "%" && prefixCh != '%' && len(escapedPath) >= iPath+3 { + var err error + ch, err = url.PathUnescape(escapedPath[iPath : iPath+3]) + if err != nil { + // should be impossible unless EscapedPath() is returning invalid values! + return escapedPath + } + iPath += 2 + } + + // prefix comparisons are case-insensitive to consistency with + // path matcher, which is case-insensitive for good reasons + if !strings.EqualFold(ch, string(prefixCh)) { + return escapedPath + } + + iPath++ + iPrefix++ + } + + // if we iterated through the entire prefix, we found it, so trim it + if iPath >= len(prefix) { + return escapedPath[iPath:] + } + + // otherwise we did not find the prefix + return escapedPath +} + +func reverse(s string) string { + r := []rune(s) + for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 { + r[i], r[j] = r[j], r[i] + } + return string(r) +} + +// substrReplacer describes either a simple and fast substring replacement. +type substrReplacer struct { + // A substring to find. Supports placeholders. + Find string `json:"find,omitempty"` + + // The substring to replace with. Supports placeholders. + Replace string `json:"replace,omitempty"` + + // Maximum number of replacements per string. + // Set to <= 0 for no limit (default). + Limit int `json:"limit,omitempty"` +} + +// do performs the substring replacement on r. +func (rep substrReplacer) do(r *http.Request, repl *caddy.Replacer) { + if rep.Find == "" { + return + } + + lim := rep.Limit + if lim == 0 { + lim = -1 + } + + find := repl.ReplaceAll(rep.Find, "") + replace := repl.ReplaceAll(rep.Replace, "") + + mergeSlashes := !strings.Contains(rep.Find, "//") + + changePath(r, func(pathOrRawPath string) string { + return strings.Replace(caddyhttp.CleanPath(pathOrRawPath, mergeSlashes), find, replace, lim) + }) + + r.URL.RawQuery = strings.Replace(r.URL.RawQuery, find, replace, lim) +} + +// regexReplacer describes a replacement using a regular expression. +type regexReplacer struct { + // The regular expression to find. + Find string `json:"find,omitempty"` + + // The substring to replace with. Supports placeholders and + // regular expression capture groups. + Replace string `json:"replace,omitempty"` + + re *regexp.Regexp +} + +func (rep regexReplacer) do(r *http.Request, repl *caddy.Replacer) { + if rep.Find == "" || rep.re == nil { + return + } + replace := repl.ReplaceAll(rep.Replace, "") + changePath(r, func(pathOrRawPath string) string { + return rep.re.ReplaceAllString(pathOrRawPath, replace) + }) +} + +func changePath(req *http.Request, newVal func(pathOrRawPath string) string) { + req.URL.RawPath = newVal(req.URL.EscapedPath()) + if p, err := url.PathUnescape(req.URL.RawPath); err == nil && p != "" { + req.URL.Path = p + } else { + req.URL.Path = newVal(req.URL.Path) + } + // RawPath is only set if it's different from the normalized Path (std lib) + if req.URL.RawPath == req.URL.Path { + req.URL.RawPath = "" + } +} + +// queryOps describes the operations to perform on query keys: add, set, rename and delete. +type queryOps struct { + // Renames a query key from Key to Val, without affecting the value. + Rename []queryOpsArguments `json:"rename,omitempty"` + + // Sets query parameters; overwrites a query key with the given value. + Set []queryOpsArguments `json:"set,omitempty"` + + // Adds query parameters; does not overwrite an existing query field, + // and only appends an additional value for that key if any already exist. + Add []queryOpsArguments `json:"add,omitempty"` + + // Replaces query parameters. + Replace []*queryOpsReplacement `json:"replace,omitempty"` + + // Deletes a given query key by name. + Delete []string `json:"delete,omitempty"` +} + +// Provision compiles the query replace operation regex. +func (replacement *queryOpsReplacement) Provision(_ caddy.Context) error { + if replacement.SearchRegexp != "" { + re, err := regexp.Compile(replacement.SearchRegexp) + if err != nil { + return fmt.Errorf("replacement for query field '%s': %v", replacement.Key, err) + } + replacement.re = re + } + return nil +} + +func (q *queryOps) do(r *http.Request, repl *caddy.Replacer) { + query := r.URL.Query() + for _, renameParam := range q.Rename { + key := repl.ReplaceAll(renameParam.Key, "") + val := repl.ReplaceAll(renameParam.Val, "") + if key == "" || val == "" { + continue + } + query[val] = query[key] + delete(query, key) + } + + for _, setParam := range q.Set { + key := repl.ReplaceAll(setParam.Key, "") + if key == "" { + continue + } + val := repl.ReplaceAll(setParam.Val, "") + query[key] = []string{val} + } + + for _, addParam := range q.Add { + key := repl.ReplaceAll(addParam.Key, "") + if key == "" { + continue + } + val := repl.ReplaceAll(addParam.Val, "") + query[key] = append(query[key], val) + } + + for _, replaceParam := range q.Replace { + key := repl.ReplaceAll(replaceParam.Key, "") + search := repl.ReplaceKnown(replaceParam.Search, "") + replace := repl.ReplaceKnown(replaceParam.Replace, "") + + // replace all query keys... + if key == "*" { + for fieldName, vals := range query { + for i := range vals { + if replaceParam.re != nil { + query[fieldName][i] = replaceParam.re.ReplaceAllString(query[fieldName][i], replace) + } else { + query[fieldName][i] = strings.ReplaceAll(query[fieldName][i], search, replace) + } + } + } + continue + } + + for fieldName, vals := range query { + for i := range vals { + if replaceParam.re != nil { + query[fieldName][i] = replaceParam.re.ReplaceAllString(query[fieldName][i], replace) + } else { + query[fieldName][i] = strings.ReplaceAll(query[fieldName][i], search, replace) + } + } + } + } + + for _, deleteParam := range q.Delete { + param := repl.ReplaceAll(deleteParam, "") + if param == "" { + continue + } + delete(query, param) + } + + r.URL.RawQuery = query.Encode() +} + +type queryOpsArguments struct { + // A key in the query string. Note that query string keys may appear multiple times. + Key string `json:"key,omitempty"` + + // The value for the given operation; for add and set, this is + // simply the value of the query, and for rename this is the + // query key to rename to. + Val string `json:"val,omitempty"` +} + +type queryOpsReplacement struct { + // The key to replace in the query string. + Key string `json:"key,omitempty"` + + // The substring to search for. + Search string `json:"search,omitempty"` + + // The regular expression to search with. + SearchRegexp string `json:"search_regexp,omitempty"` + + // The string with which to replace matches. + Replace string `json:"replace,omitempty"` + + re *regexp.Regexp +} + +// Interface guard +var _ caddyhttp.MiddlewareHandler = (*Rewrite)(nil) diff --git a/modules/caddyhttp/rewrite/rewrite_test.go b/modules/caddyhttp/rewrite/rewrite_test.go new file mode 100644 index 00000000000..81360baee2a --- /dev/null +++ b/modules/caddyhttp/rewrite/rewrite_test.go @@ -0,0 +1,424 @@ +// 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 rewrite + +import ( + "net/http" + "regexp" + "testing" + + "github.com/caddyserver/caddy/v2" +) + +func TestRewrite(t *testing.T) { + repl := caddy.NewReplacer() + + for i, tc := range []struct { + input, expect *http.Request + rule Rewrite + }{ + { + input: newRequest(t, "GET", "/"), + expect: newRequest(t, "GET", "/"), + }, + { + rule: Rewrite{Method: "GET", URI: "/"}, + input: newRequest(t, "GET", "/"), + expect: newRequest(t, "GET", "/"), + }, + { + rule: Rewrite{Method: "POST"}, + input: newRequest(t, "GET", "/"), + expect: newRequest(t, "POST", "/"), + }, + + { + rule: Rewrite{URI: "/foo"}, + input: newRequest(t, "GET", "/"), + expect: newRequest(t, "GET", "/foo"), + }, + { + rule: Rewrite{URI: "/foo"}, + input: newRequest(t, "GET", "/bar"), + expect: newRequest(t, "GET", "/foo"), + }, + { + rule: Rewrite{URI: "foo"}, + input: newRequest(t, "GET", "/"), + expect: newRequest(t, "GET", "foo"), + }, + { + rule: Rewrite{URI: "{http.request.uri}"}, + input: newRequest(t, "GET", "/bar%3Fbaz?c=d"), + expect: newRequest(t, "GET", "/bar%3Fbaz?c=d"), + }, + { + rule: Rewrite{URI: "{http.request.uri.path}"}, + input: newRequest(t, "GET", "/bar%3Fbaz"), + expect: newRequest(t, "GET", "/bar%3Fbaz"), + }, + { + rule: Rewrite{URI: "/foo{http.request.uri.path}"}, + input: newRequest(t, "GET", "/bar"), + expect: newRequest(t, "GET", "/foo/bar"), + }, + { + rule: Rewrite{URI: "/index.php?p={http.request.uri.path}"}, + input: newRequest(t, "GET", "/foo/bar"), + expect: newRequest(t, "GET", "/index.php?p=%2Ffoo%2Fbar"), + }, + { + rule: Rewrite{URI: "?a=b&{http.request.uri.query}"}, + input: newRequest(t, "GET", "/"), + expect: newRequest(t, "GET", "/?a=b"), + }, + { + rule: Rewrite{URI: "/?c=d"}, + input: newRequest(t, "GET", "/"), + expect: newRequest(t, "GET", "/?c=d"), + }, + { + rule: Rewrite{URI: "/?c=d"}, + input: newRequest(t, "GET", "/?a=b"), + expect: newRequest(t, "GET", "/?c=d"), + }, + { + rule: Rewrite{URI: "?c=d"}, + input: newRequest(t, "GET", "/foo"), + expect: newRequest(t, "GET", "/foo?c=d"), + }, + { + rule: Rewrite{URI: "/?c=d"}, + input: newRequest(t, "GET", "/foo"), + expect: newRequest(t, "GET", "/?c=d"), + }, + { + rule: Rewrite{URI: "/?{http.request.uri.query}&c=d"}, + input: newRequest(t, "GET", "/"), + expect: newRequest(t, "GET", "/?c=d"), + }, + { + rule: Rewrite{URI: "/foo?{http.request.uri.query}&c=d"}, + input: newRequest(t, "GET", "/"), + expect: newRequest(t, "GET", "/foo?c=d"), + }, + { + rule: Rewrite{URI: "?{http.request.uri.query}&c=d"}, + input: newRequest(t, "GET", "/foo"), + expect: newRequest(t, "GET", "/foo?c=d"), + }, + { + rule: Rewrite{URI: "{http.request.uri.path}?{http.request.uri.query}&c=d"}, + input: newRequest(t, "GET", "/foo"), + expect: newRequest(t, "GET", "/foo?c=d"), + }, + { + rule: Rewrite{URI: "{http.request.uri.path}?{http.request.uri.query}&c=d"}, + input: newRequest(t, "GET", "/foo"), + expect: newRequest(t, "GET", "/foo?c=d"), + }, + { + rule: Rewrite{URI: "/index.php?{http.request.uri.query}&c=d"}, + input: newRequest(t, "GET", "/foo"), + expect: newRequest(t, "GET", "/index.php?c=d"), + }, + { + rule: Rewrite{URI: "?a=b&c=d"}, + input: newRequest(t, "GET", "/foo"), + expect: newRequest(t, "GET", "/foo?a=b&c=d"), + }, + { + rule: Rewrite{URI: "/index.php?{http.request.uri.query}&c=d"}, + input: newRequest(t, "GET", "/?a=b"), + expect: newRequest(t, "GET", "/index.php?a=b&c=d"), + }, + { + rule: Rewrite{URI: "/index.php?c=d&{http.request.uri.query}"}, + input: newRequest(t, "GET", "/?a=b"), + expect: newRequest(t, "GET", "/index.php?c=d&a=b"), + }, + { + rule: Rewrite{URI: "/index.php?{http.request.uri.query}&p={http.request.uri.path}"}, + input: newRequest(t, "GET", "/foo/bar?a=b"), + expect: newRequest(t, "GET", "/index.php?a=b&p=%2Ffoo%2Fbar"), + }, + { + rule: Rewrite{URI: "{http.request.uri.path}?"}, + input: newRequest(t, "GET", "/foo/bar?a=b&c=d"), + expect: newRequest(t, "GET", "/foo/bar"), + }, + { + rule: Rewrite{URI: "?qs={http.request.uri.query}"}, + input: newRequest(t, "GET", "/foo?a=b&c=d"), + expect: newRequest(t, "GET", "/foo?qs=a%3Db%26c%3Dd"), + }, + { + rule: Rewrite{URI: "/foo?{http.request.uri.query}#frag"}, + input: newRequest(t, "GET", "/foo/bar?a=b"), + expect: newRequest(t, "GET", "/foo?a=b#frag"), + }, + { + rule: Rewrite{URI: "/foo{http.request.uri}"}, + input: newRequest(t, "GET", "/bar?a=b"), + expect: newRequest(t, "GET", "/foo/bar?a=b"), + }, + { + rule: Rewrite{URI: "/foo{http.request.uri}"}, + input: newRequest(t, "GET", "/bar"), + expect: newRequest(t, "GET", "/foo/bar"), + }, + { + rule: Rewrite{URI: "/foo{http.request.uri}?c=d"}, + input: newRequest(t, "GET", "/bar?a=b"), + expect: newRequest(t, "GET", "/foo/bar?c=d"), + }, + { + rule: Rewrite{URI: "/foo{http.request.uri}?{http.request.uri.query}&c=d"}, + input: newRequest(t, "GET", "/bar?a=b"), + expect: newRequest(t, "GET", "/foo/bar?a=b&c=d"), + }, + { + rule: Rewrite{URI: "{http.request.uri}"}, + input: newRequest(t, "GET", "/bar?a=b"), + expect: newRequest(t, "GET", "/bar?a=b"), + }, + { + rule: Rewrite{URI: "{http.request.uri.path}bar?c=d"}, + input: newRequest(t, "GET", "/foo/?a=b"), + expect: newRequest(t, "GET", "/foo/bar?c=d"), + }, + { + rule: Rewrite{URI: "/i{http.request.uri}"}, + input: newRequest(t, "GET", "/%C2%B7%E2%88%B5.png"), + expect: newRequest(t, "GET", "/i/%C2%B7%E2%88%B5.png"), + }, + { + rule: Rewrite{URI: "/i{http.request.uri}"}, + input: newRequest(t, "GET", "/·∵.png?a=b"), + expect: newRequest(t, "GET", "/i/%C2%B7%E2%88%B5.png?a=b"), + }, + { + rule: Rewrite{URI: "/i{http.request.uri}"}, + input: newRequest(t, "GET", "/%C2%B7%E2%88%B5.png?a=b"), + expect: newRequest(t, "GET", "/i/%C2%B7%E2%88%B5.png?a=b"), + }, + { + rule: Rewrite{URI: "/bar#?"}, + input: newRequest(t, "GET", "/foo#fragFirst?c=d"), // not a valid query string (is part of fragment) + expect: newRequest(t, "GET", "/bar#?"), // I think this is right? but who knows; std lib drops fragment when parsing + }, + { + rule: Rewrite{URI: "/bar"}, + input: newRequest(t, "GET", "/foo#fragFirst?c=d"), + expect: newRequest(t, "GET", "/bar#fragFirst?c=d"), + }, + + { + rule: Rewrite{StripPathPrefix: "/prefix"}, + input: newRequest(t, "GET", "/foo/bar"), + expect: newRequest(t, "GET", "/foo/bar"), + }, + { + rule: Rewrite{StripPathPrefix: "/prefix"}, + input: newRequest(t, "GET", "/prefix/foo/bar"), + expect: newRequest(t, "GET", "/foo/bar"), + }, + { + rule: Rewrite{StripPathPrefix: "prefix"}, + input: newRequest(t, "GET", "/prefix/foo/bar"), + expect: newRequest(t, "GET", "/foo/bar"), + }, + { + rule: Rewrite{StripPathPrefix: "/prefix"}, + input: newRequest(t, "GET", "/prefix"), + expect: newRequest(t, "GET", ""), + }, + { + rule: Rewrite{StripPathPrefix: "/prefix"}, + input: newRequest(t, "GET", "/"), + expect: newRequest(t, "GET", "/"), + }, + { + rule: Rewrite{StripPathPrefix: "/prefix"}, + input: newRequest(t, "GET", "/prefix/foo%2Fbar"), + expect: newRequest(t, "GET", "/foo%2Fbar"), + }, + { + rule: Rewrite{StripPathPrefix: "/prefix"}, + input: newRequest(t, "GET", "/foo/prefix/bar"), + expect: newRequest(t, "GET", "/foo/prefix/bar"), + }, + { + rule: Rewrite{StripPathPrefix: "//prefix"}, + // scheme and host needed for URL parser to succeed in setting up test + input: newRequest(t, "GET", "http://host//prefix/foo/bar"), + expect: newRequest(t, "GET", "http://host/foo/bar"), + }, + { + rule: Rewrite{StripPathPrefix: "//prefix"}, + input: newRequest(t, "GET", "/prefix/foo/bar"), + expect: newRequest(t, "GET", "/prefix/foo/bar"), + }, + { + rule: Rewrite{StripPathPrefix: "/a%2Fb/c"}, + input: newRequest(t, "GET", "/a%2Fb/c/d"), + expect: newRequest(t, "GET", "/d"), + }, + { + rule: Rewrite{StripPathPrefix: "/a%2Fb/c"}, + input: newRequest(t, "GET", "/a%2fb/c/d"), + expect: newRequest(t, "GET", "/d"), + }, + { + rule: Rewrite{StripPathPrefix: "/a/b/c"}, + input: newRequest(t, "GET", "/a%2Fb/c/d"), + expect: newRequest(t, "GET", "/d"), + }, + { + rule: Rewrite{StripPathPrefix: "/a%2Fb/c"}, + input: newRequest(t, "GET", "/a/b/c/d"), + expect: newRequest(t, "GET", "/a/b/c/d"), + }, + { + rule: Rewrite{StripPathPrefix: "//a%2Fb/c"}, + input: newRequest(t, "GET", "/a/b/c/d"), + expect: newRequest(t, "GET", "/a/b/c/d"), + }, + + { + rule: Rewrite{StripPathSuffix: "/suffix"}, + input: newRequest(t, "GET", "/foo/bar"), + expect: newRequest(t, "GET", "/foo/bar"), + }, + { + rule: Rewrite{StripPathSuffix: "suffix"}, + input: newRequest(t, "GET", "/foo/bar/suffix"), + expect: newRequest(t, "GET", "/foo/bar/"), + }, + { + rule: Rewrite{StripPathSuffix: "suffix"}, + input: newRequest(t, "GET", "/foo%2Fbar/suffix"), + expect: newRequest(t, "GET", "/foo%2Fbar/"), + }, + { + rule: Rewrite{StripPathSuffix: "%2fsuffix"}, + input: newRequest(t, "GET", "/foo%2Fbar%2fsuffix"), + expect: newRequest(t, "GET", "/foo%2Fbar"), + }, + { + rule: Rewrite{StripPathSuffix: "/suffix"}, + input: newRequest(t, "GET", "/foo/suffix/bar"), + expect: newRequest(t, "GET", "/foo/suffix/bar"), + }, + + { + rule: Rewrite{URISubstring: []substrReplacer{{Find: "findme", Replace: "replaced"}}}, + input: newRequest(t, "GET", "/foo/bar"), + expect: newRequest(t, "GET", "/foo/bar"), + }, + { + rule: Rewrite{URISubstring: []substrReplacer{{Find: "findme", Replace: "replaced"}}}, + input: newRequest(t, "GET", "/foo/findme/bar"), + expect: newRequest(t, "GET", "/foo/replaced/bar"), + }, + { + rule: Rewrite{URISubstring: []substrReplacer{{Find: "findme", Replace: "replaced"}}}, + input: newRequest(t, "GET", "/foo/findme%2Fbar"), + expect: newRequest(t, "GET", "/foo/replaced%2Fbar"), + }, + + { + rule: Rewrite{PathRegexp: []*regexReplacer{{Find: "/{2,}", Replace: "/"}}}, + input: newRequest(t, "GET", "/foo//bar///baz?a=b//c"), + expect: newRequest(t, "GET", "/foo/bar/baz?a=b//c"), + }, + } { + // copy the original input just enough so that we can + // compare it after the rewrite to see if it changed + urlCopy := *tc.input.URL + originalInput := &http.Request{ + Method: tc.input.Method, + RequestURI: tc.input.RequestURI, + URL: &urlCopy, + } + + // populate the replacer just enough for our tests + repl.Set("http.request.uri", tc.input.RequestURI) + repl.Set("http.request.uri.path", tc.input.URL.Path) + repl.Set("http.request.uri.query", tc.input.URL.RawQuery) + + // we can't directly call Provision() without a valid caddy.Context + // (TODO: fix that) so here we ad-hoc compile the regex + for _, rep := range tc.rule.PathRegexp { + re, err := regexp.Compile(rep.Find) + if err != nil { + t.Fatal(err) + } + rep.re = re + } + + changed := tc.rule.Rewrite(tc.input, repl) + + if expected, actual := !reqEqual(originalInput, tc.input), changed; expected != actual { + t.Errorf("Test %d: Expected changed=%t but was %t", i, expected, actual) + } + if expected, actual := tc.expect.Method, tc.input.Method; expected != actual { + t.Errorf("Test %d: Expected Method='%s' but got '%s'", i, expected, actual) + } + if expected, actual := tc.expect.RequestURI, tc.input.RequestURI; expected != actual { + t.Errorf("Test %d: Expected RequestURI='%s' but got '%s'", i, expected, actual) + } + if expected, actual := tc.expect.URL.String(), tc.input.URL.String(); expected != actual { + t.Errorf("Test %d: Expected URL='%s' but got '%s'", i, expected, actual) + } + if expected, actual := tc.expect.URL.RequestURI(), tc.input.URL.RequestURI(); expected != actual { + t.Errorf("Test %d: Expected URL.RequestURI()='%s' but got '%s'", i, expected, actual) + } + if expected, actual := tc.expect.URL.Fragment, tc.input.URL.Fragment; expected != actual { + t.Errorf("Test %d: Expected URL.Fragment='%s' but got '%s'", i, expected, actual) + } + } +} + +func newRequest(t *testing.T, method, uri string) *http.Request { + req, err := http.NewRequest(method, uri, nil) + if err != nil { + t.Fatalf("error creating request: %v", err) + } + req.RequestURI = req.URL.RequestURI() // simulate incoming request + return req +} + +// reqEqual if r1 and r2 are equal enough for our purposes. +func reqEqual(r1, r2 *http.Request) bool { + if r1.Method != r2.Method { + return false + } + if r1.RequestURI != r2.RequestURI { + return false + } + if (r1.URL == nil && r2.URL != nil) || (r1.URL != nil && r2.URL == nil) { + return false + } + if r1.URL == nil && r2.URL == nil { + return true + } + return r1.URL.Scheme == r2.URL.Scheme && + r1.URL.Host == r2.URL.Host && + r1.URL.Path == r2.URL.Path && + r1.URL.RawPath == r2.URL.RawPath && + r1.URL.RawQuery == r2.URL.RawQuery && + r1.URL.Fragment == r2.URL.Fragment +} diff --git a/modules/caddyhttp/routes.go b/modules/caddyhttp/routes.go new file mode 100644 index 00000000000..ccb5f251547 --- /dev/null +++ b/modules/caddyhttp/routes.go @@ -0,0 +1,464 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/caddyserver/caddy/v2" +) + +// Route consists of a set of rules for matching HTTP requests, +// a list of handlers to execute, and optional flow control +// parameters which customize the handling of HTTP requests +// in a highly flexible and performant manner. +type Route struct { + // Group is an optional name for a group to which this + // route belongs. Grouping a route makes it mutually + // exclusive with others in its group; if a route belongs + // to a group, only the first matching route in that group + // will be executed. + Group string `json:"group,omitempty"` + + // The matcher sets which will be used to qualify this + // route for a request (essentially the "if" statement + // of this route). Each matcher set is OR'ed, but matchers + // within a set are AND'ed together. + MatcherSetsRaw RawMatcherSets `json:"match,omitempty" caddy:"namespace=http.matchers"` + + // The list of handlers for this route. Upon matching a request, they are chained + // together in a middleware fashion: requests flow from the first handler to the last + // (top of the list to the bottom), with the possibility that any handler could stop + // the chain and/or return an error. Responses flow back through the chain (bottom of + // the list to the top) as they are written out to the client. + // + // Not all handlers call the next handler in the chain. For example, the reverse_proxy + // handler always sends a request upstream or returns an error. Thus, configuring + // handlers after reverse_proxy in the same route is illogical, since they would never + // be executed. You will want to put handlers which originate the response at the very + // end of your route(s). The documentation for a module should state whether it invokes + // the next handler, but sometimes it is common sense. + // + // Some handlers manipulate the response. Remember that requests flow down the list, and + // responses flow up the list. + // + // For example, if you wanted to use both `templates` and `encode` handlers, you would + // need to put `templates` after `encode` in your route, because responses flow up. + // Thus, `templates` will be able to parse and execute the plain-text response as a + // template, and then return it up to the `encode` handler which will then compress it + // into a binary format. + // + // If `templates` came before `encode`, then `encode` would write a compressed, + // binary-encoded response to `templates` which would not be able to parse the response + // properly. + // + // The correct order, then, is this: + // + // [ + // {"handler": "encode"}, + // {"handler": "templates"}, + // {"handler": "file_server"} + // ] + // + // The request flows ⬇️ DOWN (`encode` -> `templates` -> `file_server`). + // + // 1. First, `encode` will choose how to `encode` the response and wrap the response. + // 2. Then, `templates` will wrap the response with a buffer. + // 3. Finally, `file_server` will originate the content from a file. + // + // The response flows ⬆️ UP (`file_server` -> `templates` -> `encode`): + // + // 1. First, `file_server` will write the file to the response. + // 2. That write will be buffered and then executed by `templates`. + // 3. Lastly, the write from `templates` will flow into `encode` which will compress the stream. + // + // If you think of routes in this way, it will be easy and even fun to solve the puzzle of writing correct routes. + HandlersRaw []json.RawMessage `json:"handle,omitempty" caddy:"namespace=http.handlers inline_key=handler"` + + // If true, no more routes will be executed after this one. + Terminal bool `json:"terminal,omitempty"` + + // decoded values + MatcherSets MatcherSets `json:"-"` + Handlers []MiddlewareHandler `json:"-"` + + middleware []Middleware +} + +// Empty returns true if the route has all zero/default values. +func (r Route) Empty() bool { + return len(r.MatcherSetsRaw) == 0 && + len(r.MatcherSets) == 0 && + len(r.HandlersRaw) == 0 && + len(r.Handlers) == 0 && + !r.Terminal && + r.Group == "" +} + +func (r Route) String() string { + handlersRaw := "[" + for _, hr := range r.HandlersRaw { + handlersRaw += " " + string(hr) + } + handlersRaw += "]" + + return fmt.Sprintf(`{Group:"%s" MatcherSetsRaw:%s HandlersRaw:%s Terminal:%t}`, + r.Group, r.MatcherSetsRaw, handlersRaw, r.Terminal) +} + +// Provision sets up both the matchers and handlers in the route. +func (r *Route) Provision(ctx caddy.Context, metrics *Metrics) error { + err := r.ProvisionMatchers(ctx) + if err != nil { + return err + } + return r.ProvisionHandlers(ctx, metrics) +} + +// ProvisionMatchers sets up all the matchers by loading the +// matcher modules. Only call this method directly if you need +// to set up matchers and handlers separately without having +// to provision a second time; otherwise use Provision instead. +func (r *Route) ProvisionMatchers(ctx caddy.Context) error { + // matchers + matchersIface, err := ctx.LoadModule(r, "MatcherSetsRaw") + if err != nil { + return fmt.Errorf("loading matcher modules: %v", err) + } + err = r.MatcherSets.FromInterface(matchersIface) + if err != nil { + return err + } + return nil +} + +// ProvisionHandlers sets up all the handlers by loading the +// handler modules. Only call this method directly if you need +// to set up matchers and handlers separately without having +// to provision a second time; otherwise use Provision instead. +func (r *Route) ProvisionHandlers(ctx caddy.Context, metrics *Metrics) error { + handlersIface, err := ctx.LoadModule(r, "HandlersRaw") + if err != nil { + return fmt.Errorf("loading handler modules: %v", err) + } + for _, handler := range handlersIface.([]any) { + r.Handlers = append(r.Handlers, handler.(MiddlewareHandler)) + } + + // Make ProvisionHandlers idempotent by clearing the middleware field + r.middleware = []Middleware{} + + // pre-compile the middleware handler chain + for _, midhandler := range r.Handlers { + r.middleware = append(r.middleware, wrapMiddleware(ctx, midhandler, metrics)) + } + return nil +} + +// Compile prepares a middleware chain from the route list. +// This should only be done once during the request, just +// before the middleware chain is executed. +func (r Route) Compile(next Handler) Handler { + return wrapRoute(r)(next) +} + +// RouteList is a list of server routes that can +// create a middleware chain. +type RouteList []Route + +// Provision sets up both the matchers and handlers in the routes. +func (routes RouteList) Provision(ctx caddy.Context) error { + err := routes.ProvisionMatchers(ctx) + if err != nil { + return err + } + return routes.ProvisionHandlers(ctx, nil) +} + +// ProvisionMatchers sets up all the matchers by loading the +// matcher modules. Only call this method directly if you need +// to set up matchers and handlers separately without having +// to provision a second time; otherwise use Provision instead. +func (routes RouteList) ProvisionMatchers(ctx caddy.Context) error { + for i := range routes { + err := routes[i].ProvisionMatchers(ctx) + if err != nil { + return fmt.Errorf("route %d: %v", i, err) + } + } + return nil +} + +// ProvisionHandlers sets up all the handlers by loading the +// handler modules. Only call this method directly if you need +// to set up matchers and handlers separately without having +// to provision a second time; otherwise use Provision instead. +func (routes RouteList) ProvisionHandlers(ctx caddy.Context, metrics *Metrics) error { + for i := range routes { + err := routes[i].ProvisionHandlers(ctx, metrics) + if err != nil { + return fmt.Errorf("route %d: %v", i, err) + } + } + return nil +} + +// Compile prepares a middleware chain from the route list. +// This should only be done either once during provisioning +// for top-level routes, or on each request just before the +// middleware chain is executed for subroutes. +func (routes RouteList) Compile(next Handler) Handler { + mid := make([]Middleware, 0, len(routes)) + for _, route := range routes { + mid = append(mid, wrapRoute(route)) + } + stack := next + for i := len(mid) - 1; i >= 0; i-- { + stack = mid[i](stack) + } + return stack +} + +// wrapRoute wraps route with a middleware and handler so that it can +// be chained in and defer evaluation of its matchers to request-time. +// Like wrapMiddleware, it is vital that this wrapping takes place in +// its own stack frame so as to not overwrite the reference to the +// intended route by looping and changing the reference each time. +func wrapRoute(route Route) Middleware { + return func(next Handler) Handler { + return HandlerFunc(func(rw http.ResponseWriter, req *http.Request) error { + // TODO: Update this comment, it seems we've moved the copy into the handler? + // copy the next handler (it's an interface, so it's just + // a very lightweight copy of a pointer); this is important + // because this is a closure to the func below, which + // re-assigns the value as it compiles the middleware stack; + // if we don't make this copy, we'd affect the underlying + // pointer for all future request (yikes); we could + // alternatively solve this by moving the func below out of + // this closure and into a standalone package-level func, + // but I just thought this made more sense + nextCopy := next + + // route must match at least one of the matcher sets + matches, err := route.MatcherSets.AnyMatchWithError(req) + if err != nil { + // allow matchers the opportunity to short circuit + // the request and trigger the error handling chain + return err + } + if !matches { + // call the next handler, and skip this one, + // since the matcher didn't match + return nextCopy.ServeHTTP(rw, req) + } + + // if route is part of a group, ensure only the + // first matching route in the group is applied + if route.Group != "" { + groups := req.Context().Value(routeGroupCtxKey).(map[string]struct{}) + + if _, ok := groups[route.Group]; ok { + // this group has already been + // satisfied by a matching route + return nextCopy.ServeHTTP(rw, req) + } + + // this matching route satisfies the group + groups[route.Group] = struct{}{} + } + + // make terminal routes terminate + if route.Terminal { + if _, ok := req.Context().Value(ErrorCtxKey).(error); ok { + nextCopy = errorEmptyHandler + } else { + nextCopy = emptyHandler + } + } + + // compile this route's handler stack + for i := len(route.middleware) - 1; i >= 0; i-- { + nextCopy = route.middleware[i](nextCopy) + } + + return nextCopy.ServeHTTP(rw, req) + }) + } +} + +// wrapMiddleware wraps mh such that it can be correctly +// appended to a list of middleware in preparation for +// compiling into a handler chain. We can't do this inline +// inside a loop, because it relies on a reference to mh +// not changing until the execution of its handler (which +// is deferred by multiple func closures). In other words, +// we need to pull this particular MiddlewareHandler +// pointer into its own stack frame to preserve it so it +// won't be overwritten in future loop iterations. +func wrapMiddleware(ctx caddy.Context, mh MiddlewareHandler, metrics *Metrics) Middleware { + handlerToUse := mh + if metrics != nil { + // wrap the middleware with metrics instrumentation + handlerToUse = newMetricsInstrumentedHandler(ctx, caddy.GetModuleName(mh), mh, metrics) + } + + return func(next Handler) Handler { + // copy the next handler (it's an interface, so it's + // just a very lightweight copy of a pointer); this + // is a safeguard against the handler changing the + // value, which could affect future requests (yikes) + nextCopy := next + + return HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { + // EXPERIMENTAL: Trace each module that gets invoked + if server, ok := r.Context().Value(ServerCtxKey).(*Server); ok && server != nil { + server.logTrace(handlerToUse) + } + return handlerToUse.ServeHTTP(w, r, nextCopy) + }) + } +} + +// MatcherSet is a set of matchers which +// must all match in order for the request +// to be matched successfully. +type MatcherSet []any + +// Match returns true if the request matches all +// matchers in mset or if there are no matchers. +func (mset MatcherSet) Match(r *http.Request) bool { + for _, m := range mset { + if me, ok := m.(RequestMatcherWithError); ok { + match, _ := me.MatchWithError(r) + if !match { + return false + } + continue + } + if me, ok := m.(RequestMatcher); ok { + if !me.Match(r) { + return false + } + continue + } + return false + } + return true +} + +// MatchWithError returns true if r matches m. +func (mset MatcherSet) MatchWithError(r *http.Request) (bool, error) { + for _, m := range mset { + if me, ok := m.(RequestMatcherWithError); ok { + match, err := me.MatchWithError(r) + if err != nil || !match { + return match, err + } + continue + } + if me, ok := m.(RequestMatcher); ok { + if !me.Match(r) { + // for backwards compatibility + err, ok := GetVar(r.Context(), MatcherErrorVarKey).(error) + if ok { + // clear out the error from context since we've consumed it + SetVar(r.Context(), MatcherErrorVarKey, nil) + return false, err + } + return false, nil + } + continue + } + return false, fmt.Errorf("matcher is not a RequestMatcher or RequestMatcherWithError: %#v", m) + } + return true, nil +} + +// RawMatcherSets is a group of matcher sets +// in their raw, JSON form. +type RawMatcherSets []caddy.ModuleMap + +// MatcherSets is a group of matcher sets capable +// of checking whether a request matches any of +// the sets. +type MatcherSets []MatcherSet + +// AnyMatch returns true if req matches any of the +// matcher sets in ms or if there are no matchers, +// in which case the request always matches. +// +// Deprecated: Use AnyMatchWithError instead. +func (ms MatcherSets) AnyMatch(req *http.Request) bool { + for _, m := range ms { + match, err := m.MatchWithError(req) + if err != nil { + SetVar(req.Context(), MatcherErrorVarKey, err) + return false + } + if match { + return match + } + } + return len(ms) == 0 +} + +// AnyMatchWithError returns true if req matches any of the +// matcher sets in ms or if there are no matchers, in which +// case the request always matches. If any matcher returns +// an error, we cut short and return the error. +func (ms MatcherSets) AnyMatchWithError(req *http.Request) (bool, error) { + for _, m := range ms { + match, err := m.MatchWithError(req) + if err != nil || match { + return match, err + } + } + return len(ms) == 0, nil +} + +// FromInterface fills ms from an 'any' value obtained from LoadModule. +func (ms *MatcherSets) FromInterface(matcherSets any) error { + for _, matcherSetIfaces := range matcherSets.([]map[string]any) { + var matcherSet MatcherSet + for _, matcher := range matcherSetIfaces { + if m, ok := matcher.(RequestMatcherWithError); ok { + matcherSet = append(matcherSet, m) + continue + } + if m, ok := matcher.(RequestMatcher); ok { + matcherSet = append(matcherSet, m) + continue + } + return fmt.Errorf("decoded module is not a RequestMatcher or RequestMatcherWithError: %#v", matcher) + } + *ms = append(*ms, matcherSet) + } + return nil +} + +// TODO: Is this used? +func (ms MatcherSets) String() string { + result := "[" + for _, matcherSet := range ms { + for _, matcher := range matcherSet { + result += fmt.Sprintf(" %#v", matcher) + } + } + return result + " ]" +} + +var routeGroupCtxKey = caddy.CtxKey("route_group") diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go new file mode 100644 index 00000000000..a2b29d65831 --- /dev/null +++ b/modules/caddyhttp/server.go @@ -0,0 +1,1121 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "context" + "crypto/tls" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "net/netip" + "net/url" + "runtime" + "slices" + "strings" + "sync" + "time" + + "github.com/caddyserver/certmagic" + "github.com/quic-go/quic-go" + "github.com/quic-go/quic-go/http3" + "github.com/quic-go/quic-go/qlog" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyevents" + "github.com/caddyserver/caddy/v2/modules/caddytls" +) + +// Server describes an HTTP server. +type Server struct { + // Socket addresses to which to bind listeners. Accepts + // [network addresses](/docs/conventions#network-addresses) + // that may include port ranges. Listener addresses must + // be unique; they cannot be repeated across all defined + // servers. + Listen []string `json:"listen,omitempty"` + + // A list of listener wrapper modules, which can modify the behavior + // of the base listener. They are applied in the given order. + ListenerWrappersRaw []json.RawMessage `json:"listener_wrappers,omitempty" caddy:"namespace=caddy.listeners inline_key=wrapper"` + + // How long to allow a read from a client's upload. Setting this + // to a short, non-zero value can mitigate slowloris attacks, but + // may also affect legitimately slow clients. + ReadTimeout caddy.Duration `json:"read_timeout,omitempty"` + + // ReadHeaderTimeout is like ReadTimeout but for request headers. + // Default is 1 minute. + ReadHeaderTimeout caddy.Duration `json:"read_header_timeout,omitempty"` + + // WriteTimeout is how long to allow a write to a client. Note + // that setting this to a small value when serving large files + // may negatively affect legitimately slow clients. + WriteTimeout caddy.Duration `json:"write_timeout,omitempty"` + + // IdleTimeout is the maximum time to wait for the next request + // when keep-alives are enabled. If zero, a default timeout of + // 5m is applied to help avoid resource exhaustion. + IdleTimeout caddy.Duration `json:"idle_timeout,omitempty"` + + // KeepAliveInterval is the interval at which TCP keepalive packets + // are sent to keep the connection alive at the TCP layer when no other + // data is being transmitted. The default is 15s. + KeepAliveInterval caddy.Duration `json:"keepalive_interval,omitempty"` + + // MaxHeaderBytes is the maximum size to parse from a client's + // HTTP request headers. + MaxHeaderBytes int `json:"max_header_bytes,omitempty"` + + // Enable full-duplex communication for HTTP/1 requests. + // Only has an effect if Caddy was built with Go 1.21 or later. + // + // For HTTP/1 requests, the Go HTTP server by default consumes any + // unread portion of the request body before beginning to write the + // response, preventing handlers from concurrently reading from the + // request and writing the response. Enabling this option disables + // this behavior and permits handlers to continue to read from the + // request while concurrently writing the response. + // + // For HTTP/2 requests, the Go HTTP server always permits concurrent + // reads and responses, so this option has no effect. + // + // Test thoroughly with your HTTP clients, as some older clients may + // not support full-duplex HTTP/1 which can cause them to deadlock. + // See https://github.com/golang/go/issues/57786 for more info. + // + // TODO: This is an EXPERIMENTAL feature. Subject to change or removal. + EnableFullDuplex bool `json:"enable_full_duplex,omitempty"` + + // Routes describes how this server will handle requests. + // Routes are executed sequentially. First a route's matchers + // are evaluated, then its grouping. If it matches and has + // not been mutually-excluded by its grouping, then its + // handlers are executed sequentially. The sequence of invoked + // handlers comprises a compiled middleware chain that flows + // from each matching route and its handlers to the next. + // + // By default, all unrouted requests receive a 200 OK response + // to indicate the server is working. + Routes RouteList `json:"routes,omitempty"` + + // Errors is how this server will handle errors returned from any + // of the handlers in the primary routes. If the primary handler + // chain returns an error, the error along with its recommended + // status code are bubbled back up to the HTTP server which + // executes a separate error route, specified using this property. + // The error routes work exactly like the normal routes. + Errors *HTTPErrorConfig `json:"errors,omitempty"` + + // NamedRoutes describes a mapping of reusable routes that can be + // invoked by their name. This can be used to optimize memory usage + // when the same route is needed for many subroutes, by having + // the handlers and matchers be only provisioned once, but used from + // many places. These routes are not executed unless they are invoked + // from another route. + // + // EXPERIMENTAL: Subject to change or removal. + NamedRoutes map[string]*Route `json:"named_routes,omitempty"` + + // How to handle TLS connections. At least one policy is + // required to enable HTTPS on this server if automatic + // HTTPS is disabled or does not apply. + TLSConnPolicies caddytls.ConnectionPolicies `json:"tls_connection_policies,omitempty"` + + // AutoHTTPS configures or disables automatic HTTPS within this server. + // HTTPS is enabled automatically and by default when qualifying names + // are present in a Host matcher and/or when the server is listening + // only on the HTTPS port. + AutoHTTPS *AutoHTTPSConfig `json:"automatic_https,omitempty"` + + // If true, will require that a request's Host header match + // the value of the ServerName sent by the client's TLS + // ClientHello; often a necessary safeguard when using TLS + // client authentication. + StrictSNIHost *bool `json:"strict_sni_host,omitempty"` + + // A module which provides a source of IP ranges, from which + // requests should be trusted. By default, no proxies are + // trusted. + // + // On its own, this configuration will not do anything, + // but it can be used as a default set of ranges for + // handlers or matchers in routes to pick up, instead + // of needing to configure each of them. See the + // `reverse_proxy` handler for example, which uses this + // to trust sensitive incoming `X-Forwarded-*` headers. + TrustedProxiesRaw json.RawMessage `json:"trusted_proxies,omitempty" caddy:"namespace=http.ip_sources inline_key=source"` + + // The headers from which the client IP address could be + // read from. These will be considered in order, with the + // first good value being used as the client IP. + // By default, only `X-Forwarded-For` is considered. + // + // This depends on `trusted_proxies` being configured and + // the request being validated as coming from a trusted + // proxy, otherwise the client IP will be set to the direct + // remote IP address. + ClientIPHeaders []string `json:"client_ip_headers,omitempty"` + + // If greater than zero, enables strict ClientIPHeaders + // (default X-Forwarded-For) parsing. If enabled, the + // ClientIPHeaders will be parsed from right to left, and + // the first value that is both valid and doesn't match the + // trusted proxy list will be used as client IP. If zero, + // the ClientIPHeaders will be parsed from left to right, + // and the first value that is a valid IP address will be + // used as client IP. + // + // This depends on `trusted_proxies` being configured. + // This option is disabled by default. + TrustedProxiesStrict int `json:"trusted_proxies_strict,omitempty"` + + // Enables access logging and configures how access logs are handled + // in this server. To minimally enable access logs, simply set this + // to a non-null, empty struct. + Logs *ServerLogConfig `json:"logs,omitempty"` + + // Protocols specifies which HTTP protocols to enable. + // Supported values are: + // + // - `h1` (HTTP/1.1) + // - `h2` (HTTP/2) + // - `h2c` (cleartext HTTP/2) + // - `h3` (HTTP/3) + // + // If enabling `h2` or `h2c`, `h1` must also be enabled; + // this is due to current limitations in the Go standard + // library. + // + // HTTP/2 operates only over TLS (HTTPS). HTTP/3 opens + // a UDP socket to serve QUIC connections. + // + // H2C operates over plain TCP if the client supports it; + // however, because this is not implemented by the Go + // standard library, other server options are not compatible + // and will not be applied to H2C requests. Do not enable this + // only to achieve maximum client compatibility. In practice, + // very few clients implement H2C, and even fewer require it. + // Enabling H2C can be useful for serving/proxying gRPC + // if encryption is not possible or desired. + // + // We recommend for most users to simply let Caddy use the + // default settings. + // + // Default: `[h1 h2 h3]` + Protocols []string `json:"protocols,omitempty"` + + // ListenProtocols overrides Protocols for each parallel address in Listen. + // A nil value or element indicates that Protocols will be used instead. + ListenProtocols [][]string `json:"listen_protocols,omitempty"` + + // If set, metrics observations will be enabled. + // This setting is EXPERIMENTAL and subject to change. + // DEPRECATED: Use the app-level `metrics` field. + Metrics *Metrics `json:"metrics,omitempty"` + + name string + + primaryHandlerChain Handler + errorHandlerChain Handler + listenerWrappers []caddy.ListenerWrapper + listeners []net.Listener + + tlsApp *caddytls.TLS + events *caddyevents.App + logger *zap.Logger + accessLogger *zap.Logger + errorLogger *zap.Logger + traceLogger *zap.Logger + ctx caddy.Context + + server *http.Server + h3server *http3.Server + h2listeners []*http2Listener + addresses []caddy.NetworkAddress + + trustedProxies IPRangeSource + + shutdownAt time.Time + shutdownAtMu *sync.RWMutex + + // registered callback functions + connStateFuncs []func(net.Conn, http.ConnState) + connContextFuncs []func(ctx context.Context, c net.Conn) context.Context + onShutdownFuncs []func() + onStopFuncs []func(context.Context) error // TODO: Experimental (Nov. 2023) +} + +// ServeHTTP is the entry point for all HTTP requests. +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // If there are listener wrappers that process tls connections but don't return a *tls.Conn, this field will be nil. + // TODO: Can be removed if https://github.com/golang/go/pull/56110 is ever merged. + if r.TLS == nil { + // not all requests have a conn (like virtual requests) - see #5698 + if conn, ok := r.Context().Value(ConnCtxKey).(net.Conn); ok { + if csc, ok := conn.(connectionStateConn); ok { + r.TLS = new(tls.ConnectionState) + *r.TLS = csc.ConnectionState() + } + } + } + + w.Header().Set("Server", "Caddy") + + // advertise HTTP/3, if enabled + if s.h3server != nil { + if r.ProtoMajor < 3 { + err := s.h3server.SetQUICHeaders(w.Header()) + if err != nil { + if c := s.logger.Check(zapcore.ErrorLevel, "setting HTTP/3 Alt-Svc header"); c != nil { + c.Write(zap.Error(err)) + } + } + } + } + + // reject very long methods; probably a mistake or an attack + if len(r.Method) > 32 { + if s.shouldLogRequest(r) { + if c := s.accessLogger.Check(zapcore.DebugLevel, "rejecting request with long method"); c != nil { + c.Write( + zap.String("method_trunc", r.Method[:32]), + zap.String("remote_addr", r.RemoteAddr), + ) + } + } + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + + repl := caddy.NewReplacer() + r = PrepareRequest(r, repl, w, s) + + // enable full-duplex for HTTP/1, ensuring the entire + // request body gets consumed before writing the response + if s.EnableFullDuplex && r.ProtoMajor == 1 { + //nolint:bodyclose + err := http.NewResponseController(w).EnableFullDuplex() + if err != nil { + if c := s.logger.Check(zapcore.WarnLevel, "failed to enable full duplex"); c != nil { + c.Write(zap.Error(err)) + } + } + } + + // clone the request for logging purposes before + // it enters any handler chain; this is necessary + // to capture the original request in case it gets + // modified during handling + // cloning the request and using .WithLazy is considerably faster + // than using .With, which will JSON encode the request immediately + shouldLogCredentials := s.Logs != nil && s.Logs.ShouldLogCredentials + loggableReq := zap.Object("request", LoggableHTTPRequest{ + Request: r.Clone(r.Context()), + ShouldLogCredentials: shouldLogCredentials, + }) + errLog := s.errorLogger.WithLazy(loggableReq) + + var duration time.Duration + + if s.shouldLogRequest(r) { + wrec := NewResponseRecorder(w, nil, nil) + w = wrec + + // wrap the request body in a LengthReader + // so we can track the number of bytes read from it + var bodyReader *lengthReader + if r.Body != nil { + bodyReader = &lengthReader{Source: r.Body} + r.Body = bodyReader + + // should always be true, private interface can only be referenced in the same package + if setReadSizer, ok := wrec.(interface{ setReadSize(*int) }); ok { + setReadSizer.setReadSize(&bodyReader.Length) + } + } + + // capture the original version of the request + accLog := s.accessLogger.With(loggableReq) + + defer s.logRequest(accLog, r, wrec, &duration, repl, bodyReader, shouldLogCredentials) + } + + start := time.Now() + + // guarantee ACME HTTP challenges; handle them + // separately from any user-defined handlers + if s.tlsApp.HandleHTTPChallenge(w, r) { + duration = time.Since(start) + return + } + + // execute the primary handler chain + err := s.primaryHandlerChain.ServeHTTP(w, r) + duration = time.Since(start) + + // if no errors, we're done! + if err == nil { + return + } + + // restore original request before invoking error handler chain (issue #3717) + // TODO: this does not restore original headers, if modified (for efficiency) + origReq := r.Context().Value(OriginalRequestCtxKey).(http.Request) + r.Method = origReq.Method + r.RemoteAddr = origReq.RemoteAddr + r.RequestURI = origReq.RequestURI + cloneURL(origReq.URL, r.URL) + + // prepare the error log + errLog = errLog.With(zap.Duration("duration", duration)) + errLoggers := []*zap.Logger{errLog} + if s.Logs != nil { + errLoggers = s.Logs.wrapLogger(errLog, r) + } + + // get the values that will be used to log the error + errStatus, errMsg, errFields := errLogValues(err) + + // add HTTP error information to request context + r = s.Errors.WithError(r, err) + + var fields []zapcore.Field + if s.Errors != nil && len(s.Errors.Routes) > 0 { + // execute user-defined error handling route + err2 := s.errorHandlerChain.ServeHTTP(w, r) + if err2 == nil { + // user's error route handled the error response + // successfully, so now just log the error + for _, logger := range errLoggers { + if c := logger.Check(zapcore.DebugLevel, errMsg); c != nil { + if fields == nil { + fields = errFields() + } + c.Write(fields...) + } + } + } else { + // well... this is awkward + for _, logger := range errLoggers { + if c := logger.Check(zapcore.ErrorLevel, "error handling handler error"); c != nil { + if fields == nil { + fields = errFields() + fields = append([]zapcore.Field{ + zap.String("error", err2.Error()), + zap.Namespace("first_error"), + zap.String("msg", errMsg), + }, fields...) + } + c.Write(fields...) + } + } + if handlerErr, ok := err.(HandlerError); ok { + w.WriteHeader(handlerErr.StatusCode) + } else { + w.WriteHeader(http.StatusInternalServerError) + } + } + } else { + logLevel := zapcore.DebugLevel + if errStatus >= 500 { + logLevel = zapcore.ErrorLevel + } + + for _, logger := range errLoggers { + if c := logger.Check(logLevel, errMsg); c != nil { + if fields == nil { + fields = errFields() + } + c.Write(fields...) + } + } + w.WriteHeader(errStatus) + } +} + +// wrapPrimaryRoute wraps stack (a compiled middleware handler chain) +// in s.enforcementHandler which performs crucial security checks, etc. +func (s *Server) wrapPrimaryRoute(stack Handler) Handler { + return HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { + return s.enforcementHandler(w, r, stack) + }) +} + +// enforcementHandler is an implicit middleware which performs +// standard checks before executing the HTTP middleware chain. +func (s *Server) enforcementHandler(w http.ResponseWriter, r *http.Request, next Handler) error { + // enforce strict host matching, which ensures that the SNI + // value (if any), matches the Host header; essential for + // servers that rely on TLS ClientAuth sharing a listener + // with servers that do not; if not enforced, client could + // bypass by sending benign SNI then restricted Host header + if s.StrictSNIHost != nil && *s.StrictSNIHost && r.TLS != nil { + hostname, _, err := net.SplitHostPort(r.Host) + if err != nil { + hostname = r.Host // OK; probably lacked port + } + if !strings.EqualFold(r.TLS.ServerName, hostname) { + err := fmt.Errorf("strict host matching: TLS ServerName (%s) and HTTP Host (%s) values differ", + r.TLS.ServerName, hostname) + r.Close = true + return Error(http.StatusMisdirectedRequest, err) + } + } + return next.ServeHTTP(w, r) +} + +// listenersUseAnyPortOtherThan returns true if there are any +// listeners in s that use a port which is not otherPort. +func (s *Server) listenersUseAnyPortOtherThan(otherPort int) bool { + for _, lnAddr := range s.Listen { + laddrs, err := caddy.ParseNetworkAddress(lnAddr) + if err != nil { + continue + } + if uint(otherPort) > laddrs.EndPort || uint(otherPort) < laddrs.StartPort { + return true + } + } + return false +} + +// hasListenerAddress returns true if s has a listener +// at the given address fullAddr. Currently, fullAddr +// must represent exactly one socket address (port +// ranges are not supported) +func (s *Server) hasListenerAddress(fullAddr string) bool { + laddrs, err := caddy.ParseNetworkAddress(fullAddr) + if err != nil { + return false + } + if laddrs.PortRangeSize() != 1 { + return false // TODO: support port ranges + } + + for _, lnAddr := range s.Listen { + thisAddrs, err := caddy.ParseNetworkAddress(lnAddr) + if err != nil { + continue + } + if thisAddrs.Network != laddrs.Network { + continue + } + + // Apparently, Linux requires all bound ports to be distinct + // *regardless of host interface* even if the addresses are + // in fact different; binding "192.168.0.1:9000" and then + // ":9000" will fail for ":9000" because "address is already + // in use" even though it's not, and the same bindings work + // fine on macOS. I also found on Linux that listening on + // "[::]:9000" would fail with a similar error, except with + // the address "0.0.0.0:9000", as if deliberately ignoring + // that I specified the IPv6 interface explicitly. This seems + // to be a major bug in the Linux network stack and I don't + // know why it hasn't been fixed yet, so for now we have to + // special-case ourselves around Linux like a doting parent. + // The second issue seems very similar to a discussion here: + // https://github.com/nodejs/node/issues/9390 + // + // This is very easy to reproduce by creating an HTTP server + // that listens to both addresses or just one with a host + // interface; or for a more confusing reproduction, try + // listening on "127.0.0.1:80" and ":443" and you'll see + // the error, if you take away the GOOS condition below. + // + // So, an address is equivalent if the port is in the port + // range, and if not on Linux, the host is the same... sigh. + if (runtime.GOOS == "linux" || thisAddrs.Host == laddrs.Host) && + (laddrs.StartPort <= thisAddrs.EndPort) && + (laddrs.StartPort >= thisAddrs.StartPort) { + return true + } + } + return false +} + +func (s *Server) hasTLSClientAuth() bool { + return slices.ContainsFunc(s.TLSConnPolicies, func(cp *caddytls.ConnectionPolicy) bool { + return cp.ClientAuthentication != nil && cp.ClientAuthentication.Active() + }) +} + +// findLastRouteWithHostMatcher returns the index of the last route +// in the server which has a host matcher. Used during Automatic HTTPS +// to determine where to insert the HTTP->HTTPS redirect route, such +// that it is after any other host matcher but before any "catch-all" +// route without a host matcher. +func (s *Server) findLastRouteWithHostMatcher() int { + foundHostMatcher := false + lastIndex := len(s.Routes) + + for i, route := range s.Routes { + // since we want to break out of an inner loop, use a closure + // to allow us to use 'return' when we found a host matcher + found := (func() bool { + for _, sets := range route.MatcherSets { + for _, matcher := range sets { + switch matcher.(type) { + case *MatchHost: + foundHostMatcher = true + return true + } + } + } + return false + })() + + // if we found the host matcher, change the lastIndex to + // just after the current route + if found { + lastIndex = i + 1 + } + } + + // If we didn't actually find a host matcher, return 0 + // because that means every defined route was a "catch-all". + // See https://caddy.community/t/how-to-set-priority-in-caddyfile/13002/8 + if !foundHostMatcher { + return 0 + } + + return lastIndex +} + +// serveHTTP3 creates a QUIC listener, configures an HTTP/3 server if +// not already done, and then uses that server to serve HTTP/3 over +// the listener, with Server s as the handler. +func (s *Server) serveHTTP3(addr caddy.NetworkAddress, tlsCfg *tls.Config) error { + h3net, err := getHTTP3Network(addr.Network) + if err != nil { + return fmt.Errorf("starting HTTP/3 QUIC listener: %v", err) + } + addr.Network = h3net + h3ln, err := addr.ListenQUIC(s.ctx, 0, net.ListenConfig{}, tlsCfg) + if err != nil { + return fmt.Errorf("starting HTTP/3 QUIC listener: %v", err) + } + + // create HTTP/3 server if not done already + if s.h3server == nil { + s.h3server = &http3.Server{ + Handler: s, + TLSConfig: tlsCfg, + MaxHeaderBytes: s.MaxHeaderBytes, + QUICConfig: &quic.Config{ + Versions: []quic.Version{quic.Version1, quic.Version2}, + Tracer: qlog.DefaultConnectionTracer, + }, + IdleTimeout: time.Duration(s.IdleTimeout), + } + } + + //nolint:errcheck + go s.h3server.ServeListener(h3ln) + + return nil +} + +// configureServer applies/binds the registered callback functions to the server. +func (s *Server) configureServer(server *http.Server) { + for _, f := range s.connStateFuncs { + if server.ConnState != nil { + baseConnStateFunc := server.ConnState + server.ConnState = func(conn net.Conn, state http.ConnState) { + baseConnStateFunc(conn, state) + f(conn, state) + } + } else { + server.ConnState = f + } + } + + for _, f := range s.connContextFuncs { + if server.ConnContext != nil { + baseConnContextFunc := server.ConnContext + server.ConnContext = func(ctx context.Context, c net.Conn) context.Context { + return f(baseConnContextFunc(ctx, c), c) + } + } else { + server.ConnContext = f + } + } + + for _, f := range s.onShutdownFuncs { + server.RegisterOnShutdown(f) + } +} + +// RegisterConnState registers f to be invoked on s.ConnState. +func (s *Server) RegisterConnState(f func(net.Conn, http.ConnState)) { + s.connStateFuncs = append(s.connStateFuncs, f) +} + +// RegisterConnContext registers f to be invoked as part of s.ConnContext. +func (s *Server) RegisterConnContext(f func(ctx context.Context, c net.Conn) context.Context) { + s.connContextFuncs = append(s.connContextFuncs, f) +} + +// RegisterOnShutdown registers f to be invoked when the server begins to shut down. +func (s *Server) RegisterOnShutdown(f func()) { + s.onShutdownFuncs = append(s.onShutdownFuncs, f) +} + +// RegisterOnStop registers f to be invoked after the server has shut down completely. +// +// EXPERIMENTAL: Subject to change or removal. +func (s *Server) RegisterOnStop(f func(context.Context) error) { + s.onStopFuncs = append(s.onStopFuncs, f) +} + +// HTTPErrorConfig determines how to handle errors +// from the HTTP handlers. +type HTTPErrorConfig struct { + // The routes to evaluate after the primary handler + // chain returns an error. In an error route, extra + // placeholders are available: + // + // Placeholder | Description + // ------------|--------------- + // `{http.error.status_code}` | The recommended HTTP status code + // `{http.error.status_text}` | The status text associated with the recommended status code + // `{http.error.message}` | The error message + // `{http.error.trace}` | The origin of the error + // `{http.error.id}` | An identifier for this occurrence of the error + Routes RouteList `json:"routes,omitempty"` +} + +// WithError makes a shallow copy of r to add the error to its +// context, and sets placeholders on the request's replacer +// related to err. It returns the modified request which has +// the error information in its context and replacer. It +// overwrites any existing error values that are stored. +func (*HTTPErrorConfig) WithError(r *http.Request, err error) *http.Request { + // add the raw error value to the request context + // so it can be accessed by error handlers + c := context.WithValue(r.Context(), ErrorCtxKey, err) + r = r.WithContext(c) + + // add error values to the replacer + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + repl.Set("http.error", err) + if handlerErr, ok := err.(HandlerError); ok { + repl.Set("http.error.status_code", handlerErr.StatusCode) + repl.Set("http.error.status_text", http.StatusText(handlerErr.StatusCode)) + repl.Set("http.error.id", handlerErr.ID) + repl.Set("http.error.trace", handlerErr.Trace) + if handlerErr.Err != nil { + repl.Set("http.error.message", handlerErr.Err.Error()) + } else { + repl.Set("http.error.message", http.StatusText(handlerErr.StatusCode)) + } + } + + return r +} + +// shouldLogRequest returns true if this request should be logged. +func (s *Server) shouldLogRequest(r *http.Request) bool { + if s.accessLogger == nil || s.Logs == nil { + // logging is disabled + return false + } + + // strip off the port if any, logger names are host only + hostWithoutPort, _, err := net.SplitHostPort(r.Host) + if err != nil { + hostWithoutPort = r.Host + } + + if _, ok := s.Logs.LoggerNames[hostWithoutPort]; ok { + // this host is mapped to a particular logger name + return true + } + for _, dh := range s.Logs.SkipHosts { + // logging for this particular host is disabled + if certmagic.MatchWildcard(hostWithoutPort, dh) { + return false + } + } + // if configured, this host is not mapped and thus must not be logged + return !s.Logs.SkipUnmappedHosts +} + +// logTrace will log that this middleware handler is being invoked. +// It emits at DEBUG level. +func (s *Server) logTrace(mh MiddlewareHandler) { + if s.Logs == nil || !s.Logs.Trace { + return + } + if c := s.traceLogger.Check(zapcore.DebugLevel, caddy.GetModuleName(mh)); c != nil { + c.Write(zap.Any("module", mh)) + } +} + +// logRequest logs the request to access logs, unless skipped. +func (s *Server) logRequest( + accLog *zap.Logger, r *http.Request, wrec ResponseRecorder, duration *time.Duration, + repl *caddy.Replacer, bodyReader *lengthReader, shouldLogCredentials bool, +) { + // this request may be flagged as omitted from the logs + if skip, ok := GetVar(r.Context(), LogSkipVar).(bool); ok && skip { + return + } + + status := wrec.Status() + size := wrec.Size() + + repl.Set("http.response.status", status) // will be 0 if no response is written by us (Go will write 200 to client) + repl.Set("http.response.size", size) + repl.Set("http.response.duration", duration) + repl.Set("http.response.duration_ms", duration.Seconds()*1e3) // multiply seconds to preserve decimal (see #4666) + + loggers := []*zap.Logger{accLog} + if s.Logs != nil { + loggers = s.Logs.wrapLogger(accLog, r) + } + + message := "handled request" + if nop, ok := GetVar(r.Context(), "unhandled").(bool); ok && nop { + message = "NOP" + } + + logLevel := zapcore.InfoLevel + if status >= 500 { + logLevel = zapcore.ErrorLevel + } + + var fields []zapcore.Field + for _, logger := range loggers { + c := logger.Check(logLevel, message) + if c == nil { + continue + } + + if fields == nil { + userID, _ := repl.GetString("http.auth.user.id") + + reqBodyLength := 0 + if bodyReader != nil { + reqBodyLength = bodyReader.Length + } + + extra := r.Context().Value(ExtraLogFieldsCtxKey).(*ExtraLogFields) + + fieldCount := 6 + fields = make([]zapcore.Field, 0, fieldCount+len(extra.fields)) + fields = append(fields, + zap.Int("bytes_read", reqBodyLength), + zap.String("user_id", userID), + zap.Duration("duration", *duration), + zap.Int("size", size), + zap.Int("status", status), + zap.Object("resp_headers", LoggableHTTPHeader{ + Header: wrec.Header(), + ShouldLogCredentials: shouldLogCredentials, + }), + ) + fields = append(fields, extra.fields...) + } + + c.Write(fields...) + } +} + +// protocol returns true if the protocol proto is configured/enabled. +func (s *Server) protocol(proto string) bool { + if s.ListenProtocols == nil { + if slices.Contains(s.Protocols, proto) { + return true + } + } else { + for _, lnProtocols := range s.ListenProtocols { + for _, lnProtocol := range lnProtocols { + if lnProtocol == "" && slices.Contains(s.Protocols, proto) || lnProtocol == proto { + return true + } + } + } + } + + return false +} + +// Listeners returns the server's listeners. These are active listeners, +// so calling Accept() or Close() on them will probably break things. +// They are made available here for read-only purposes (e.g. Addr()) +// and for type-asserting for purposes where you know what you're doing. +// +// EXPERIMENTAL: Subject to change or removal. +func (s *Server) Listeners() []net.Listener { return s.listeners } + +// Name returns the server's name. +func (s *Server) Name() string { return s.name } + +// PrepareRequest fills the request r for use in a Caddy HTTP handler chain. w and s can +// be nil, but the handlers will lose response placeholders and access to the server. +func PrepareRequest(r *http.Request, repl *caddy.Replacer, w http.ResponseWriter, s *Server) *http.Request { + // set up the context for the request + ctx := context.WithValue(r.Context(), caddy.ReplacerCtxKey, repl) + ctx = context.WithValue(ctx, ServerCtxKey, s) + + trusted, clientIP := determineTrustedProxy(r, s) + ctx = context.WithValue(ctx, VarsCtxKey, map[string]any{ + TrustedProxyVarKey: trusted, + ClientIPVarKey: clientIP, + }) + + ctx = context.WithValue(ctx, routeGroupCtxKey, make(map[string]struct{})) + + var url2 url.URL // avoid letting this escape to the heap + ctx = context.WithValue(ctx, OriginalRequestCtxKey, originalRequest(r, &url2)) + + ctx = context.WithValue(ctx, ExtraLogFieldsCtxKey, new(ExtraLogFields)) + r = r.WithContext(ctx) + + // once the pointer to the request won't change + // anymore, finish setting up the replacer + addHTTPVarsToReplacer(repl, r, w) + + return r +} + +// originalRequest returns a partial, shallow copy of +// req, including: req.Method, deep copy of req.URL +// (into the urlCopy parameter, which should be on the +// stack), req.RequestURI, and req.RemoteAddr. Notably, +// headers are not copied. This function is designed to +// be very fast and efficient, and useful primarily for +// read-only/logging purposes. +func originalRequest(req *http.Request, urlCopy *url.URL) http.Request { + cloneURL(req.URL, urlCopy) + return http.Request{ + Method: req.Method, + RemoteAddr: req.RemoteAddr, + RequestURI: req.RequestURI, + URL: urlCopy, + } +} + +// determineTrustedProxy parses the remote IP address of +// the request, and determines (if the server configured it) +// if the client is a trusted proxy. If trusted, also returns +// the real client IP if possible. +func determineTrustedProxy(r *http.Request, s *Server) (bool, string) { + // If there's no server, then we can't check anything + if s == nil { + return false, "" + } + + // Parse the remote IP, ignore the error as non-fatal, + // but the remote IP is required to continue, so we + // just return early. This should probably never happen + // though, unless some other module manipulated the request's + // remote address and used an invalid value. + clientIP, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + return false, "" + } + + // Client IP may contain a zone if IPv6, so we need + // to pull that out before parsing the IP + clientIP, _, _ = strings.Cut(clientIP, "%") + ipAddr, err := netip.ParseAddr(clientIP) + if err != nil { + return false, "" + } + + // Check if the client is a trusted proxy + if s.trustedProxies == nil { + return false, ipAddr.String() + } + + if isTrustedClientIP(ipAddr, s.trustedProxies.GetIPRanges(r)) { + if s.TrustedProxiesStrict > 0 { + return true, strictUntrustedClientIp(r, s.ClientIPHeaders, s.trustedProxies.GetIPRanges(r), ipAddr.String()) + } + return true, trustedRealClientIP(r, s.ClientIPHeaders, ipAddr.String()) + } + + return false, ipAddr.String() +} + +// isTrustedClientIP returns true if the given IP address is +// in the list of trusted IP ranges. +func isTrustedClientIP(ipAddr netip.Addr, trusted []netip.Prefix) bool { + return slices.ContainsFunc(trusted, func(prefix netip.Prefix) bool { + return prefix.Contains(ipAddr) + }) +} + +// trustedRealClientIP finds the client IP from the request assuming it is +// from a trusted client. If there is no client IP headers, then the +// direct remote address is returned. If there are client IP headers, +// then the first value from those headers is used. +func trustedRealClientIP(r *http.Request, headers []string, clientIP string) string { + // Read all the values of the configured client IP headers, in order + var values []string + for _, field := range headers { + values = append(values, r.Header.Values(field)...) + } + + // If we don't have any values, then give up + if len(values) == 0 { + return clientIP + } + + // Since there can be many header values, we need to + // join them together before splitting to get the full list + allValues := strings.Split(strings.Join(values, ","), ",") + + // Get first valid left-most IP address + for _, part := range allValues { + // Some proxies may retain the port number, so split if possible + host, _, err := net.SplitHostPort(part) + if err != nil { + host = part + } + + // Remove any zone identifier from the IP address + host, _, _ = strings.Cut(strings.TrimSpace(host), "%") + + // Parse the IP address + ipAddr, err := netip.ParseAddr(host) + if err != nil { + continue + } + return ipAddr.String() + } + + // We didn't find a valid IP + return clientIP +} + +// strictUntrustedClientIp iterates through the list of client IP headers, +// parses them from right-to-left, and returns the first valid IP address +// that is untrusted. If no valid IP address is found, then the direct +// remote address is returned. +func strictUntrustedClientIp(r *http.Request, headers []string, trusted []netip.Prefix, clientIP string) string { + for _, headerName := range headers { + parts := strings.Split(strings.Join(r.Header.Values(headerName), ","), ",") + + for i := len(parts) - 1; i >= 0; i-- { + // Some proxies may retain the port number, so split if possible + host, _, err := net.SplitHostPort(parts[i]) + if err != nil { + host = parts[i] + } + + // Remove any zone identifier from the IP address + host, _, _ = strings.Cut(strings.TrimSpace(host), "%") + + // Parse the IP address + ipAddr, err := netip.ParseAddr(host) + if err != nil { + continue + } + if !isTrustedClientIP(ipAddr, trusted) { + return ipAddr.String() + } + } + } + + return clientIP +} + +// cloneURL makes a copy of r.URL and returns a +// new value that doesn't reference the original. +func cloneURL(from, to *url.URL) { + *to = *from + if from.User != nil { + userInfo := new(url.Userinfo) + *userInfo = *from.User + to.User = userInfo + } +} + +// lengthReader is an io.ReadCloser that keeps track of the +// number of bytes read from the request body. +type lengthReader struct { + Source io.ReadCloser + Length int +} + +func (r *lengthReader) Read(b []byte) (int, error) { + n, err := r.Source.Read(b) + r.Length += n + return n, err +} + +func (r *lengthReader) Close() error { + return r.Source.Close() +} + +// Context keys for HTTP request context values. +const ( + // For referencing the server instance + ServerCtxKey caddy.CtxKey = "server" + + // For the request's variable table + VarsCtxKey caddy.CtxKey = "vars" + + // For a partial copy of the unmodified request that + // originally came into the server's entry handler + OriginalRequestCtxKey caddy.CtxKey = "original_request" + + // For referencing underlying net.Conn + ConnCtxKey caddy.CtxKey = "conn" + + // For tracking whether the client is a trusted proxy + TrustedProxyVarKey string = "trusted_proxy" + + // For tracking the real client IP (affected by trusted_proxy) + ClientIPVarKey string = "client_ip" +) + +var networkTypesHTTP3 = map[string]string{ + "unixgram": "unixgram", + "udp": "udp", + "udp4": "udp4", + "udp6": "udp6", + "tcp": "udp", + "tcp4": "udp4", + "tcp6": "udp6", + "fdgram": "fdgram", +} + +// RegisterNetworkHTTP3 registers a mapping from non-HTTP/3 network to HTTP/3 +// network. This should be called during init() and will panic if the network +// type is standard, reserved, or already registered. +// +// EXPERIMENTAL: Subject to change. +func RegisterNetworkHTTP3(originalNetwork, h3Network string) { + if _, ok := networkTypesHTTP3[strings.ToLower(originalNetwork)]; ok { + panic("network type " + originalNetwork + " is already registered") + } + networkTypesHTTP3[originalNetwork] = h3Network +} + +func getHTTP3Network(originalNetwork string) (string, error) { + h3Network, ok := networkTypesHTTP3[strings.ToLower(originalNetwork)] + if !ok { + return "", fmt.Errorf("network '%s' cannot handle HTTP/3 connections", originalNetwork) + } + return h3Network, nil +} diff --git a/modules/caddyhttp/server_test.go b/modules/caddyhttp/server_test.go new file mode 100644 index 00000000000..53f35368f93 --- /dev/null +++ b/modules/caddyhttp/server_test.go @@ -0,0 +1,473 @@ +package caddyhttp + +import ( + "bytes" + "context" + "io" + "net/http" + "net/http/httptest" + "net/netip" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type writeFunc func(p []byte) (int, error) + +type nopSyncer writeFunc + +func (n nopSyncer) Write(p []byte) (int, error) { + return n(p) +} + +func (n nopSyncer) Sync() error { + return nil +} + +// testLogger returns a logger and a buffer to which the logger writes. The +// buffer can be read for asserting log output. +func testLogger(wf writeFunc) *zap.Logger { + ws := nopSyncer(wf) + encoderCfg := zapcore.EncoderConfig{ + MessageKey: "msg", + LevelKey: "level", + NameKey: "logger", + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeDuration: zapcore.StringDurationEncoder, + } + core := zapcore.NewCore(zapcore.NewJSONEncoder(encoderCfg), ws, zap.DebugLevel) + + return zap.New(core) +} + +func TestServer_LogRequest(t *testing.T) { + s := &Server{} + + ctx := context.Background() + ctx = context.WithValue(ctx, ExtraLogFieldsCtxKey, new(ExtraLogFields)) + req := httptest.NewRequest(http.MethodGet, "/", nil).WithContext(ctx) + rec := httptest.NewRecorder() + wrec := NewResponseRecorder(rec, nil, nil) + + duration := 50 * time.Millisecond + repl := NewTestReplacer(req) + bodyReader := &lengthReader{Source: req.Body} + shouldLogCredentials := false + + buf := bytes.Buffer{} + accLog := testLogger(buf.Write) + s.logRequest(accLog, req, wrec, &duration, repl, bodyReader, shouldLogCredentials) + + assert.JSONEq(t, `{ + "msg":"handled request", "level":"info", "bytes_read":0, + "duration":"50ms", "resp_headers": {}, "size":0, + "status":0, "user_id":"" + }`, buf.String()) +} + +func TestServer_LogRequest_WithTrace(t *testing.T) { + s := &Server{} + + extra := new(ExtraLogFields) + ctx := context.WithValue(context.Background(), ExtraLogFieldsCtxKey, extra) + extra.Add(zap.String("traceID", "1234567890abcdef")) + extra.Add(zap.String("spanID", "12345678")) + + req := httptest.NewRequest(http.MethodGet, "/", nil).WithContext(ctx) + rec := httptest.NewRecorder() + wrec := NewResponseRecorder(rec, nil, nil) + + duration := 50 * time.Millisecond + repl := NewTestReplacer(req) + bodyReader := &lengthReader{Source: req.Body} + shouldLogCredentials := false + + buf := bytes.Buffer{} + accLog := testLogger(buf.Write) + s.logRequest(accLog, req, wrec, &duration, repl, bodyReader, shouldLogCredentials) + + assert.JSONEq(t, `{ + "msg":"handled request", "level":"info", "bytes_read":0, + "duration":"50ms", "resp_headers": {}, "size":0, + "status":0, "user_id":"", + "traceID":"1234567890abcdef", + "spanID":"12345678" + }`, buf.String()) +} + +func BenchmarkServer_LogRequest(b *testing.B) { + s := &Server{} + + extra := new(ExtraLogFields) + ctx := context.WithValue(context.Background(), ExtraLogFieldsCtxKey, extra) + + req := httptest.NewRequest(http.MethodGet, "/", nil).WithContext(ctx) + rec := httptest.NewRecorder() + wrec := NewResponseRecorder(rec, nil, nil) + + duration := 50 * time.Millisecond + repl := NewTestReplacer(req) + bodyReader := &lengthReader{Source: req.Body} + + buf := io.Discard + accLog := testLogger(buf.Write) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + s.logRequest(accLog, req, wrec, &duration, repl, bodyReader, false) + } +} + +func BenchmarkServer_LogRequest_NopLogger(b *testing.B) { + s := &Server{} + + extra := new(ExtraLogFields) + ctx := context.WithValue(context.Background(), ExtraLogFieldsCtxKey, extra) + + req := httptest.NewRequest(http.MethodGet, "/", nil).WithContext(ctx) + rec := httptest.NewRecorder() + wrec := NewResponseRecorder(rec, nil, nil) + + duration := 50 * time.Millisecond + repl := NewTestReplacer(req) + bodyReader := &lengthReader{Source: req.Body} + + accLog := zap.NewNop() + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + s.logRequest(accLog, req, wrec, &duration, repl, bodyReader, false) + } +} + +func BenchmarkServer_LogRequest_WithTrace(b *testing.B) { + s := &Server{} + + extra := new(ExtraLogFields) + ctx := context.WithValue(context.Background(), ExtraLogFieldsCtxKey, extra) + extra.Add(zap.String("traceID", "1234567890abcdef")) + extra.Add(zap.String("spanID", "12345678")) + + req := httptest.NewRequest(http.MethodGet, "/", nil).WithContext(ctx) + rec := httptest.NewRecorder() + wrec := NewResponseRecorder(rec, nil, nil) + + duration := 50 * time.Millisecond + repl := NewTestReplacer(req) + bodyReader := &lengthReader{Source: req.Body} + + buf := io.Discard + accLog := testLogger(buf.Write) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + s.logRequest(accLog, req, wrec, &duration, repl, bodyReader, false) + } +} +func TestServer_TrustedRealClientIP_NoTrustedHeaders(t *testing.T) { + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "192.0.2.1:12345" + ip := trustedRealClientIP(req, []string{}, "192.0.2.1") + + assert.Equal(t, ip, "192.0.2.1") +} + +func TestServer_TrustedRealClientIP_OneTrustedHeaderEmpty(t *testing.T) { + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "192.0.2.1:12345" + ip := trustedRealClientIP(req, []string{"X-Forwarded-For"}, "192.0.2.1") + + assert.Equal(t, ip, "192.0.2.1") +} + +func TestServer_TrustedRealClientIP_OneTrustedHeaderInvalid(t *testing.T) { + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "192.0.2.1:12345" + req.Header.Set("X-Forwarded-For", "not, an, ip") + ip := trustedRealClientIP(req, []string{"X-Forwarded-For"}, "192.0.2.1") + + assert.Equal(t, ip, "192.0.2.1") +} + +func TestServer_TrustedRealClientIP_OneTrustedHeaderValid(t *testing.T) { + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "192.0.2.1:12345" + req.Header.Set("X-Forwarded-For", "10.0.0.1") + ip := trustedRealClientIP(req, []string{"X-Forwarded-For"}, "192.0.2.1") + + assert.Equal(t, ip, "10.0.0.1") +} + +func TestServer_TrustedRealClientIP_OneTrustedHeaderValidArray(t *testing.T) { + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "192.0.2.1:12345" + req.Header.Set("X-Forwarded-For", "1.1.1.1, 2.2.2.2, 3.3.3.3") + ip := trustedRealClientIP(req, []string{"X-Forwarded-For"}, "192.0.2.1") + + assert.Equal(t, ip, "1.1.1.1") +} + +func TestServer_TrustedRealClientIP_IncludesPort(t *testing.T) { + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "192.0.2.1:12345" + req.Header.Set("X-Forwarded-For", "1.1.1.1:1234") + ip := trustedRealClientIP(req, []string{"X-Forwarded-For"}, "192.0.2.1") + + assert.Equal(t, ip, "1.1.1.1") +} + +func TestServer_TrustedRealClientIP_SkipsInvalidIps(t *testing.T) { + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "192.0.2.1:12345" + req.Header.Set("X-Forwarded-For", "not an ip, bad bad, 10.0.0.1") + ip := trustedRealClientIP(req, []string{"X-Forwarded-For"}, "192.0.2.1") + + assert.Equal(t, ip, "10.0.0.1") +} + +func TestServer_TrustedRealClientIP_MultipleTrustedHeaderValidArray(t *testing.T) { + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "192.0.2.1:12345" + req.Header.Set("Real-Client-IP", "1.1.1.1, 2.2.2.2, 3.3.3.3") + req.Header.Set("X-Forwarded-For", "3.3.3.3, 4.4.4.4") + ip1 := trustedRealClientIP(req, []string{"X-Forwarded-For", "Real-Client-IP"}, "192.0.2.1") + ip2 := trustedRealClientIP(req, []string{"Real-Client-IP", "X-Forwarded-For"}, "192.0.2.1") + ip3 := trustedRealClientIP(req, []string{"Missing-Header-IP", "Real-Client-IP", "X-Forwarded-For"}, "192.0.2.1") + + assert.Equal(t, ip1, "3.3.3.3") + assert.Equal(t, ip2, "1.1.1.1") + assert.Equal(t, ip3, "1.1.1.1") +} + +func TestServer_DetermineTrustedProxy_NoConfig(t *testing.T) { + server := &Server{} + + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "192.0.2.1:12345" + + trusted, clientIP := determineTrustedProxy(req, server) + + assert.False(t, trusted) + assert.Equal(t, clientIP, "192.0.2.1") +} + +func TestServer_DetermineTrustedProxy_NoConfigIpv6(t *testing.T) { + server := &Server{} + + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "[::1]:12345" + + trusted, clientIP := determineTrustedProxy(req, server) + + assert.False(t, trusted) + assert.Equal(t, clientIP, "::1") +} + +func TestServer_DetermineTrustedProxy_NoConfigIpv6Zones(t *testing.T) { + server := &Server{} + + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "[::1%eth2]:12345" + + trusted, clientIP := determineTrustedProxy(req, server) + + assert.False(t, trusted) + assert.Equal(t, clientIP, "::1") +} + +func TestServer_DetermineTrustedProxy_TrustedLoopback(t *testing.T) { + loopbackPrefix, _ := netip.ParsePrefix("127.0.0.1/8") + + server := &Server{ + trustedProxies: &StaticIPRange{ + ranges: []netip.Prefix{loopbackPrefix}, + }, + ClientIPHeaders: []string{"X-Forwarded-For"}, + } + + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "127.0.0.1:12345" + req.Header.Set("X-Forwarded-For", "31.40.0.10") + + trusted, clientIP := determineTrustedProxy(req, server) + + assert.True(t, trusted) + assert.Equal(t, clientIP, "31.40.0.10") +} + +func TestServer_DetermineTrustedProxy_UntrustedPrefix(t *testing.T) { + loopbackPrefix, _ := netip.ParsePrefix("127.0.0.1/8") + + server := &Server{ + trustedProxies: &StaticIPRange{ + ranges: []netip.Prefix{loopbackPrefix}, + }, + ClientIPHeaders: []string{"X-Forwarded-For"}, + } + + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "10.0.0.1:12345" + req.Header.Set("X-Forwarded-For", "31.40.0.10") + + trusted, clientIP := determineTrustedProxy(req, server) + + assert.False(t, trusted) + assert.Equal(t, clientIP, "10.0.0.1") +} + +func TestServer_DetermineTrustedProxy_MultipleTrustedPrefixes(t *testing.T) { + loopbackPrefix, _ := netip.ParsePrefix("127.0.0.1/8") + localPrivatePrefix, _ := netip.ParsePrefix("10.0.0.0/8") + + server := &Server{ + trustedProxies: &StaticIPRange{ + ranges: []netip.Prefix{loopbackPrefix, localPrivatePrefix}, + }, + ClientIPHeaders: []string{"X-Forwarded-For"}, + } + + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "10.0.0.1:12345" + req.Header.Set("X-Forwarded-For", "31.40.0.10") + + trusted, clientIP := determineTrustedProxy(req, server) + + assert.True(t, trusted) + assert.Equal(t, clientIP, "31.40.0.10") +} + +func TestServer_DetermineTrustedProxy_MultipleTrustedClientHeaders(t *testing.T) { + loopbackPrefix, _ := netip.ParsePrefix("127.0.0.1/8") + localPrivatePrefix, _ := netip.ParsePrefix("10.0.0.0/8") + + server := &Server{ + trustedProxies: &StaticIPRange{ + ranges: []netip.Prefix{loopbackPrefix, localPrivatePrefix}, + }, + ClientIPHeaders: []string{"CF-Connecting-IP", "X-Forwarded-For"}, + } + + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "10.0.0.1:12345" + req.Header.Set("CF-Connecting-IP", "1.1.1.1, 2.2.2.2") + req.Header.Set("X-Forwarded-For", "3.3.3.3, 4.4.4.4") + + trusted, clientIP := determineTrustedProxy(req, server) + + assert.True(t, trusted) + assert.Equal(t, clientIP, "1.1.1.1") +} + +func TestServer_DetermineTrustedProxy_MatchLeftMostValidIp(t *testing.T) { + localPrivatePrefix, _ := netip.ParsePrefix("10.0.0.0/8") + + server := &Server{ + trustedProxies: &StaticIPRange{ + ranges: []netip.Prefix{localPrivatePrefix}, + }, + ClientIPHeaders: []string{"X-Forwarded-For"}, + } + + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "10.0.0.1:12345" + req.Header.Set("X-Forwarded-For", "30.30.30.30, 45.54.45.54, 10.0.0.1") + + trusted, clientIP := determineTrustedProxy(req, server) + + assert.True(t, trusted) + assert.Equal(t, clientIP, "30.30.30.30") +} + +func TestServer_DetermineTrustedProxy_MatchRightMostUntrusted(t *testing.T) { + localPrivatePrefix, _ := netip.ParsePrefix("10.0.0.0/8") + + server := &Server{ + trustedProxies: &StaticIPRange{ + ranges: []netip.Prefix{localPrivatePrefix}, + }, + ClientIPHeaders: []string{"X-Forwarded-For"}, + TrustedProxiesStrict: 1, + } + + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "10.0.0.1:12345" + req.Header.Set("X-Forwarded-For", "30.30.30.30, 45.54.45.54, 10.0.0.1") + + trusted, clientIP := determineTrustedProxy(req, server) + + assert.True(t, trusted) + assert.Equal(t, clientIP, "45.54.45.54") +} + +func TestServer_DetermineTrustedProxy_MatchRightMostUntrustedSkippingEmpty(t *testing.T) { + localPrivatePrefix, _ := netip.ParsePrefix("10.0.0.0/8") + + server := &Server{ + trustedProxies: &StaticIPRange{ + ranges: []netip.Prefix{localPrivatePrefix}, + }, + ClientIPHeaders: []string{"Missing-Header", "CF-Connecting-IP", "X-Forwarded-For"}, + TrustedProxiesStrict: 1, + } + + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "10.0.0.1:12345" + req.Header.Set("CF-Connecting-IP", "not a real IP") + req.Header.Set("X-Forwarded-For", "30.30.30.30, bad, 45.54.45.54, not real") + + trusted, clientIP := determineTrustedProxy(req, server) + + assert.True(t, trusted) + assert.Equal(t, clientIP, "45.54.45.54") +} + +func TestServer_DetermineTrustedProxy_MatchRightMostUntrustedSkippingTrusted(t *testing.T) { + localPrivatePrefix, _ := netip.ParsePrefix("10.0.0.0/8") + + server := &Server{ + trustedProxies: &StaticIPRange{ + ranges: []netip.Prefix{localPrivatePrefix}, + }, + ClientIPHeaders: []string{"CF-Connecting-IP", "X-Forwarded-For"}, + TrustedProxiesStrict: 1, + } + + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "10.0.0.1:12345" + req.Header.Set("CF-Connecting-IP", "10.0.0.1, 10.0.0.2, 10.0.0.3") + req.Header.Set("X-Forwarded-For", "30.30.30.30, 45.54.45.54, 10.0.0.4") + + trusted, clientIP := determineTrustedProxy(req, server) + + assert.True(t, trusted) + assert.Equal(t, clientIP, "45.54.45.54") +} + +func TestServer_DetermineTrustedProxy_MatchRightMostUntrustedFirst(t *testing.T) { + localPrivatePrefix, _ := netip.ParsePrefix("10.0.0.0/8") + + server := &Server{ + trustedProxies: &StaticIPRange{ + ranges: []netip.Prefix{localPrivatePrefix}, + }, + ClientIPHeaders: []string{"CF-Connecting-IP", "X-Forwarded-For"}, + TrustedProxiesStrict: 1, + } + + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "10.0.0.1:12345" + req.Header.Set("CF-Connecting-IP", "10.0.0.1, 90.100.110.120, 10.0.0.2, 10.0.0.3") + req.Header.Set("X-Forwarded-For", "30.30.30.30, 45.54.45.54, 10.0.0.4") + + trusted, clientIP := determineTrustedProxy(req, server) + + assert.True(t, trusted) + assert.Equal(t, clientIP, "90.100.110.120") +} diff --git a/modules/caddyhttp/standard/imports.go b/modules/caddyhttp/standard/imports.go new file mode 100644 index 00000000000..6617941c65d --- /dev/null +++ b/modules/caddyhttp/standard/imports.go @@ -0,0 +1,25 @@ +package standard + +import ( + // standard Caddy HTTP app modules + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp" + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/caddyauth" + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode" + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode/brotli" + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode/gzip" + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode/zstd" + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/fileserver" + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/headers" + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/intercept" + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/logging" + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/map" + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/proxyprotocol" + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/push" + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/requestbody" + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy" + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy/fastcgi" + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy/forwardauth" + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite" + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/templates" + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/tracing" +) diff --git a/modules/caddyhttp/staticerror.go b/modules/caddyhttp/staticerror.go new file mode 100644 index 00000000000..aeb31140667 --- /dev/null +++ b/modules/caddyhttp/staticerror.go @@ -0,0 +1,115 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func init() { + caddy.RegisterModule(StaticError{}) +} + +// StaticError implements a simple handler that returns an error. +// This handler returns an error value, but does not write a response. +// This is useful when you want the server to act as if an error +// occurred; for example, to invoke your custom error handling logic. +// +// Since this handler does not write a response, the error information +// is for use by the server to know how to handle the error. +type StaticError struct { + // The error message. Optional. Default is no error message. + Error string `json:"error,omitempty"` + + // The recommended HTTP status code. Can be either an integer or a + // string if placeholders are needed. Optional. Default is 500. + StatusCode WeakString `json:"status_code,omitempty"` +} + +// CaddyModule returns the Caddy module information. +func (StaticError) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.error", + New: func() caddy.Module { return new(StaticError) }, + } +} + +// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax: +// +// error [] | [] { +// message +// } +// +// If there is just one argument (other than the matcher), it is considered +// to be a status code if it's a valid positive integer of 3 digits. +func (e *StaticError) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume directive name + args := d.RemainingArgs() + switch len(args) { + case 1: + if len(args[0]) == 3 { + if num, err := strconv.Atoi(args[0]); err == nil && num > 0 { + e.StatusCode = WeakString(args[0]) + break + } + } + e.Error = args[0] + case 2: + e.Error = args[0] + e.StatusCode = WeakString(args[1]) + default: + return d.ArgErr() + } + + for d.NextBlock(0) { + switch d.Val() { + case "message": + if e.Error != "" { + return d.Err("message already specified") + } + if !d.AllArgs(&e.Error) { + return d.ArgErr() + } + default: + return d.Errf("unrecognized subdirective '%s'", d.Val()) + } + } + return nil +} + +func (e StaticError) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error { + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + + statusCode := http.StatusInternalServerError + if codeStr := e.StatusCode.String(); codeStr != "" { + intVal, err := strconv.Atoi(repl.ReplaceAll(codeStr, "")) + if err != nil { + return Error(http.StatusInternalServerError, err) + } + statusCode = intVal + } + return Error(statusCode, fmt.Errorf("%s", repl.ReplaceKnown(e.Error, ""))) +} + +// Interface guard +var ( + _ MiddlewareHandler = (*StaticError)(nil) + _ caddyfile.Unmarshaler = (*StaticError)(nil) +) diff --git a/modules/caddyhttp/staticresp.go b/modules/caddyhttp/staticresp.go new file mode 100644 index 00000000000..1b93ede4b65 --- /dev/null +++ b/modules/caddyhttp/staticresp.go @@ -0,0 +1,462 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/textproto" + "os" + "strconv" + "strings" + "text/template" + "time" + + "github.com/spf13/cobra" + "go.uber.org/zap" + + caddycmd "github.com/caddyserver/caddy/v2/cmd" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func init() { + caddy.RegisterModule(StaticResponse{}) + caddycmd.RegisterCommand(caddycmd.Command{ + Name: "respond", + Usage: `[--status ] [--body ] [--listen ] [--access-log] [--debug] [--header "Field: value"] `, + Short: "Simple, hard-coded HTTP responses for development and testing", + Long: ` +Spins up a quick-and-clean HTTP server for development and testing purposes. + +With no options specified, this command listens on a random available port +and answers HTTP requests with an empty 200 response. The listen address can +be customized with the --listen flag and will always be printed to stdout. +If the listen address includes a port range, multiple servers will be started. + +If a final, unnamed argument is given, it will be treated as a status code +(same as the --status flag) if it is a 3-digit number. Otherwise, it is used +as the response body (same as the --body flag). The --status and --body flags +will always override this argument (for example, to write a body that +literally says "404" but with a status code of 200, do '--status 200 404'). + +A body may be given in 3 ways: a flag, a final (and unnamed) argument to +the command, or piped to stdin (if flag and argument are unset). Limited +template evaluation is supported on the body, with the following variables: + + {{.N}} The server number (useful if using a port range) + {{.Port}} The listener port + {{.Address}} The listener address + +(See the docs for the text/template package in the Go standard library for +information about using templates: https://pkg.go.dev/text/template) + +Access/request logging and more verbose debug logging can also be enabled. + +Response headers may be added using the --header flag for each header field. +`, + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("listen", "l", ":0", "The address to which to bind the listener") + cmd.Flags().IntP("status", "s", http.StatusOK, "The response status code") + cmd.Flags().StringP("body", "b", "", "The body of the HTTP response") + cmd.Flags().BoolP("access-log", "", false, "Enable the access log") + cmd.Flags().BoolP("debug", "v", false, "Enable more verbose debug-level logging") + cmd.Flags().StringSliceP("header", "H", []string{}, "Set a header on the response (format: \"Field: value\")") + cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdRespond) + }, + }) +} + +// StaticResponse implements a simple responder for static responses. +type StaticResponse struct { + // The HTTP status code to respond with. Can be an integer or, + // if needing to use a placeholder, a string. + // + // If the status code is 103 (Early Hints), the response headers + // will be written to the client immediately, the body will be + // ignored, and the next handler will be invoked. This behavior + // is EXPERIMENTAL while RFC 8297 is a draft, and may be changed + // or removed. + StatusCode WeakString `json:"status_code,omitempty"` + + // Header fields to set on the response; overwrites any existing + // header fields of the same names after normalization. + Headers http.Header `json:"headers,omitempty"` + + // The response body. If non-empty, the Content-Type header may + // be added automatically if it is not explicitly configured nor + // already set on the response; the default value is + // "text/plain; charset=utf-8" unless the body is a valid JSON object + // or array, in which case the value will be "application/json". + // Other than those common special cases the Content-Type header + // should be set explicitly if it is desired because MIME sniffing + // is disabled for safety. + Body string `json:"body,omitempty"` + + // If true, the server will close the client's connection + // after writing the response. + Close bool `json:"close,omitempty"` + + // Immediately and forcefully closes the connection without + // writing a response. Interrupts any other HTTP streams on + // the same connection. + Abort bool `json:"abort,omitempty"` +} + +// CaddyModule returns the Caddy module information. +func (StaticResponse) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.static_response", + New: func() caddy.Module { return new(StaticResponse) }, + } +} + +// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax: +// +// respond [] | [] { +// body +// close +// } +// +// If there is just one argument (other than the matcher), it is considered +// to be a status code if it's a valid positive integer of 3 digits. +func (s *StaticResponse) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume directive name + args := d.RemainingArgs() + switch len(args) { + case 1: + if len(args[0]) == 3 { + if num, err := strconv.Atoi(args[0]); err == nil && num > 0 { + s.StatusCode = WeakString(args[0]) + break + } + } + s.Body = args[0] + case 2: + s.Body = args[0] + s.StatusCode = WeakString(args[1]) + default: + return d.ArgErr() + } + + for d.NextBlock(0) { + switch d.Val() { + case "body": + if s.Body != "" { + return d.Err("body already specified") + } + if !d.AllArgs(&s.Body) { + return d.ArgErr() + } + case "close": + if s.Close { + return d.Err("close already specified") + } + s.Close = true + default: + return d.Errf("unrecognized subdirective '%s'", d.Val()) + } + } + return nil +} + +func (s StaticResponse) ServeHTTP(w http.ResponseWriter, r *http.Request, next Handler) error { + // close the connection immediately + if s.Abort { + panic(http.ErrAbortHandler) + } + + // close the connection after responding + if s.Close { + r.Close = true + w.Header().Set("Connection", "close") + } + + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + + // set all headers + for field, vals := range s.Headers { + field = textproto.CanonicalMIMEHeaderKey(repl.ReplaceAll(field, "")) + newVals := make([]string, len(vals)) + for i := range vals { + newVals[i] = repl.ReplaceAll(vals[i], "") + } + w.Header()[field] = newVals + } + + // implicitly set Content-Type header if we can do so safely + // (this allows templates handler to eval templates successfully + // or for clients to render JSON properly which is very common) + body := repl.ReplaceKnown(s.Body, "") + if body != "" && w.Header().Get("Content-Type") == "" { + content := strings.TrimSpace(body) + if len(content) > 2 && + (content[0] == '{' && content[len(content)-1] == '}' || + (content[0] == '[' && content[len(content)-1] == ']')) && + json.Valid([]byte(content)) { + w.Header().Set("Content-Type", "application/json") + } else { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + } + } + + // do not allow Go to sniff the content-type, for safety + if w.Header().Get("Content-Type") == "" { + w.Header()["Content-Type"] = nil + } + + // get the status code; if this handler exists in an error route, + // use the recommended status code as the default; otherwise 200 + statusCode := http.StatusOK + if reqErr, ok := r.Context().Value(ErrorCtxKey).(error); ok { + if handlerErr, ok := reqErr.(HandlerError); ok { + if handlerErr.StatusCode > 0 { + statusCode = handlerErr.StatusCode + } + } + } + if codeStr := s.StatusCode.String(); codeStr != "" { + intVal, err := strconv.Atoi(repl.ReplaceAll(codeStr, "")) + if err != nil { + return Error(http.StatusInternalServerError, err) + } + statusCode = intVal + } + + // write headers + w.WriteHeader(statusCode) + + // write response body + if statusCode != http.StatusEarlyHints && body != "" { + fmt.Fprint(w, body) + } + + // continue handling after Early Hints as they are not the final response + if statusCode == http.StatusEarlyHints { + return next.ServeHTTP(w, r) + } + + return nil +} + +func buildHTTPServer(i int, port uint, addr string, statusCode int, hdr http.Header, body string, accessLog bool) (*Server, error) { + var handlers []json.RawMessage + + // response body supports a basic template; evaluate it + tplCtx := struct { + N int // server number + Port uint // only the port + Address string // listener address + }{ + N: i, + Port: port, + Address: addr, + } + tpl, err := template.New("body").Parse(body) + if err != nil { + return nil, err + } + buf := new(bytes.Buffer) + err = tpl.Execute(buf, tplCtx) + if err != nil { + return nil, err + } + + // create route with handler + handler := StaticResponse{ + StatusCode: WeakString(fmt.Sprintf("%d", statusCode)), + Headers: hdr, + Body: buf.String(), + } + handlers = append(handlers, caddyconfig.JSONModuleObject(handler, "handler", "static_response", nil)) + route := Route{HandlersRaw: handlers} + + server := &Server{ + Listen: []string{addr}, + ReadHeaderTimeout: caddy.Duration(10 * time.Second), + IdleTimeout: caddy.Duration(30 * time.Second), + MaxHeaderBytes: 1024 * 10, + Routes: RouteList{route}, + AutoHTTPS: &AutoHTTPSConfig{DisableRedir: true}, + } + if accessLog { + server.Logs = new(ServerLogConfig) + } + + return server, nil +} + +func cmdRespond(fl caddycmd.Flags) (int, error) { + caddy.TrapSignals() + + // get flag values + listen := fl.String("listen") + statusCodeFl := fl.Int("status") + bodyFl := fl.String("body") + accessLog := fl.Bool("access-log") + debug := fl.Bool("debug") + arg := fl.Arg(0) + + if fl.NArg() > 1 { + return caddy.ExitCodeFailedStartup, fmt.Errorf("too many unflagged arguments") + } + + // prefer status and body from explicit flags + statusCode, body := statusCodeFl, bodyFl + + // figure out if status code was explicitly specified; this lets + // us set a non-zero value as the default but is a little hacky + var statusCodeFlagSpecified bool + for _, fl := range os.Args { + if fl == "--status" { + statusCodeFlagSpecified = true + break + } + } + + // try to determine what kind of parameter the unnamed argument is + if arg != "" { + // specifying body and status flags makes the argument redundant/unused + if bodyFl != "" && statusCodeFlagSpecified { + return caddy.ExitCodeFailedStartup, fmt.Errorf("unflagged argument \"%s\" is overridden by flags", arg) + } + + // if a valid 3-digit number, treat as status code; otherwise body + if argInt, err := strconv.Atoi(arg); err == nil && !statusCodeFlagSpecified { + if argInt >= 100 && argInt <= 999 { + statusCode = argInt + } + } else if body == "" { + body = arg + } + } + + // if we still need a body, see if stdin is being piped + if body == "" { + stdinInfo, err := os.Stdin.Stat() + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + if stdinInfo.Mode()&os.ModeNamedPipe != 0 { + bodyBytes, err := io.ReadAll(os.Stdin) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + body = string(bodyBytes) + } + } + + // build headers map + headers, err := fl.GetStringSlice("header") + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid header flag: %v", err) + } + hdr := make(http.Header) + for i, h := range headers { + key, val, found := strings.Cut(h, ":") + key, val = strings.TrimSpace(key), strings.TrimSpace(val) + if !found || key == "" || val == "" { + return caddy.ExitCodeFailedStartup, fmt.Errorf("header %d: invalid format \"%s\" (expecting \"Field: value\")", i, h) + } + hdr.Set(key, val) + } + + // build each HTTP server + httpApp := App{Servers: make(map[string]*Server)} + + // expand listen address, if more than one port + listenAddr, err := caddy.ParseNetworkAddress(listen) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + if !listenAddr.IsUnixNetwork() && !listenAddr.IsFdNetwork() { + listenAddrs := make([]string, 0, listenAddr.PortRangeSize()) + for offset := uint(0); offset < listenAddr.PortRangeSize(); offset++ { + listenAddrs = append(listenAddrs, listenAddr.JoinHostPort(offset)) + } + + for i, addr := range listenAddrs { + server, err := buildHTTPServer(i, listenAddr.StartPort+uint(i), addr, statusCode, hdr, body, accessLog) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + // save server + httpApp.Servers[fmt.Sprintf("static%d", i)] = server + } + } else { + server, err := buildHTTPServer(0, 0, listen, statusCode, hdr, body, accessLog) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + // save server + httpApp.Servers[fmt.Sprintf("static%d", 0)] = server + } + + // finish building the config + var false bool + cfg := &caddy.Config{ + Admin: &caddy.AdminConfig{ + Disabled: true, + Config: &caddy.ConfigSettings{ + Persist: &false, + }, + }, + AppsRaw: caddy.ModuleMap{ + "http": caddyconfig.JSON(httpApp, nil), + }, + } + if debug { + cfg.Logging = &caddy.Logging{ + Logs: map[string]*caddy.CustomLog{ + "default": {BaseLog: caddy.BaseLog{Level: zap.DebugLevel.CapitalString()}}, + }, + } + } + + // run it! + err = caddy.Run(cfg) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + // to print listener addresses, get the active HTTP app + loadedHTTPApp, err := caddy.ActiveContext().App("http") + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + // print each listener address + for _, srv := range loadedHTTPApp.(*App).Servers { + for _, ln := range srv.listeners { + fmt.Printf("Server address: %s\n", ln.Addr()) + } + } + + select {} +} + +// Interface guards +var ( + _ MiddlewareHandler = (*StaticResponse)(nil) + _ caddyfile.Unmarshaler = (*StaticResponse)(nil) +) diff --git a/modules/caddyhttp/staticresp_test.go b/modules/caddyhttp/staticresp_test.go new file mode 100644 index 00000000000..5844f43f354 --- /dev/null +++ b/modules/caddyhttp/staticresp_test.go @@ -0,0 +1,66 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/caddyserver/caddy/v2" +) + +func TestStaticResponseHandler(t *testing.T) { + r := fakeRequest() + w := httptest.NewRecorder() + + s := StaticResponse{ + StatusCode: WeakString(strconv.Itoa(http.StatusNotFound)), + Headers: http.Header{ + "X-Test": []string{"Testing"}, + }, + Body: "Text", + Close: true, + } + + err := s.ServeHTTP(w, r, nil) + if err != nil { + t.Errorf("did not expect an error, but got: %v", err) + } + + resp := w.Result() + respBody, _ := io.ReadAll(resp.Body) + + if resp.StatusCode != http.StatusNotFound { + t.Errorf("expected status %d but got %d", http.StatusNotFound, resp.StatusCode) + } + if resp.Header.Get("X-Test") != "Testing" { + t.Errorf("expected x-test header to be 'testing' but was '%s'", resp.Header.Get("X-Test")) + } + if string(respBody) != "Text" { + t.Errorf("expected body to be 'test' but was '%s'", respBody) + } +} + +func fakeRequest() *http.Request { + r, _ := http.NewRequest("GET", "/", nil) + repl := caddy.NewReplacer() + ctx := context.WithValue(r.Context(), caddy.ReplacerCtxKey, repl) + r = r.WithContext(ctx) + return r +} diff --git a/modules/caddyhttp/subroute.go b/modules/caddyhttp/subroute.go new file mode 100644 index 00000000000..2e80d88d4da --- /dev/null +++ b/modules/caddyhttp/subroute.go @@ -0,0 +1,87 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "fmt" + "net/http" + + "github.com/caddyserver/caddy/v2" +) + +func init() { + caddy.RegisterModule(Subroute{}) +} + +// Subroute implements a handler that compiles and executes routes. +// This is useful for a batch of routes that all inherit the same +// matchers, or for multiple routes that should be treated as a +// single route. +// +// You can also use subroutes to handle errors from its handlers. +// First the primary routes will be executed, and if they return an +// error, the errors routes will be executed; in that case, an error +// is only returned to the entry point at the server if there is an +// additional error returned from the errors routes. +type Subroute struct { + // The primary list of routes to compile and execute. + Routes RouteList `json:"routes,omitempty"` + + // If the primary routes return an error, error handling + // can be promoted to this configuration instead. + Errors *HTTPErrorConfig `json:"errors,omitempty"` +} + +// CaddyModule returns the Caddy module information. +func (Subroute) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.subroute", + New: func() caddy.Module { return new(Subroute) }, + } +} + +// Provision sets up subrouting. +func (sr *Subroute) Provision(ctx caddy.Context) error { + if sr.Routes != nil { + err := sr.Routes.Provision(ctx) + if err != nil { + return fmt.Errorf("setting up subroutes: %v", err) + } + if sr.Errors != nil { + err := sr.Errors.Routes.Provision(ctx) + if err != nil { + return fmt.Errorf("setting up error subroutes: %v", err) + } + } + } + return nil +} + +func (sr *Subroute) ServeHTTP(w http.ResponseWriter, r *http.Request, next Handler) error { + subroute := sr.Routes.Compile(next) + err := subroute.ServeHTTP(w, r) + if err != nil && sr.Errors != nil { + r = sr.Errors.WithError(r, err) + errRoute := sr.Errors.Routes.Compile(next) + return errRoute.ServeHTTP(w, r) + } + return err +} + +// Interface guards +var ( + _ caddy.Provisioner = (*Subroute)(nil) + _ MiddlewareHandler = (*Subroute)(nil) +) diff --git a/modules/caddyhttp/templates/caddyfile.go b/modules/caddyhttp/templates/caddyfile.go new file mode 100644 index 00000000000..d23493483d9 --- /dev/null +++ b/modules/caddyhttp/templates/caddyfile.go @@ -0,0 +1,81 @@ +// 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 templates + +import ( + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func init() { + httpcaddyfile.RegisterHandlerDirective("templates", parseCaddyfile) +} + +// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax: +// +// templates [] { +// mime +// between +// root +// } +func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + h.Next() // consume directive name + t := new(Templates) + for h.NextBlock(0) { + switch h.Val() { + case "mime": + t.MIMETypes = h.RemainingArgs() + if len(t.MIMETypes) == 0 { + return nil, h.ArgErr() + } + case "between": + t.Delimiters = h.RemainingArgs() + if len(t.Delimiters) != 2 { + return nil, h.ArgErr() + } + case "root": + if !h.Args(&t.FileRoot) { + return nil, h.ArgErr() + } + case "extensions": + if h.NextArg() { + return nil, h.ArgErr() + } + if t.ExtensionsRaw != nil { + return nil, h.Err("extensions already specified") + } + for nesting := h.Nesting(); h.NextBlock(nesting); { + extensionModuleName := h.Val() + modID := "http.handlers.templates.functions." + extensionModuleName + unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID) + if err != nil { + return nil, err + } + cf, ok := unm.(CustomFunctions) + if !ok { + return nil, h.Errf("module %s (%T) does not provide template functions", modID, unm) + } + if t.ExtensionsRaw == nil { + t.ExtensionsRaw = make(caddy.ModuleMap) + } + t.ExtensionsRaw[extensionModuleName] = caddyconfig.JSON(cf, nil) + } + } + } + return t, nil +} diff --git a/modules/caddyhttp/templates/frontmatter.go b/modules/caddyhttp/templates/frontmatter.go new file mode 100644 index 00000000000..fb62a41847f --- /dev/null +++ b/modules/caddyhttp/templates/frontmatter.go @@ -0,0 +1,128 @@ +package templates + +import ( + "encoding/json" + "fmt" + "strings" + "unicode" + + "github.com/BurntSushi/toml" + "gopkg.in/yaml.v3" +) + +func extractFrontMatter(input string) (map[string]any, string, error) { + // get the bounds of the first non-empty line + var firstLineStart, firstLineEnd int + lineEmpty := true + for i, b := range input { + if b == '\n' { + firstLineStart = firstLineEnd + if firstLineStart > 0 { + firstLineStart++ // skip newline character + } + firstLineEnd = i + if !lineEmpty { + break + } + continue + } + lineEmpty = lineEmpty && unicode.IsSpace(b) + } + firstLine := input[firstLineStart:firstLineEnd] + + // ensure residue windows carriage return byte is removed + firstLine = strings.TrimSpace(firstLine) + + // see what kind of front matter there is, if any + var closingFence []string + var fmParser func([]byte) (map[string]any, error) + for _, fmType := range supportedFrontMatterTypes { + if firstLine == fmType.FenceOpen { + closingFence = fmType.FenceClose + fmParser = fmType.ParseFunc + break + } + } + + if fmParser == nil { + // no recognized front matter; whole document is body + return nil, input, nil + } + + // find end of front matter + var fmEndFence string + fmEndFenceStart := -1 + for _, fence := range closingFence { + index := strings.Index(input[firstLineEnd:], "\n"+fence) + if index >= 0 { + fmEndFenceStart = index + fmEndFence = fence + break + } + } + if fmEndFenceStart < 0 { + return nil, "", fmt.Errorf("unterminated front matter") + } + fmEndFenceStart += firstLineEnd + 1 // add 1 to account for newline + + // extract and parse front matter + frontMatter := input[firstLineEnd:fmEndFenceStart] + fm, err := fmParser([]byte(frontMatter)) + if err != nil { + return nil, "", err + } + + // the rest is the body + body := input[fmEndFenceStart+len(fmEndFence):] + + return fm, body, nil +} + +func yamlFrontMatter(input []byte) (map[string]any, error) { + m := make(map[string]any) + err := yaml.Unmarshal(input, &m) + return m, err +} + +func tomlFrontMatter(input []byte) (map[string]any, error) { + m := make(map[string]any) + err := toml.Unmarshal(input, &m) + return m, err +} + +func jsonFrontMatter(input []byte) (map[string]any, error) { + input = append([]byte{'{'}, input...) + input = append(input, '}') + m := make(map[string]any) + err := json.Unmarshal(input, &m) + return m, err +} + +type parsedMarkdownDoc struct { + Meta map[string]any `json:"meta,omitempty"` + Body string `json:"body,omitempty"` +} + +type frontMatterType struct { + FenceOpen string + FenceClose []string + ParseFunc func(input []byte) (map[string]any, error) +} + +var supportedFrontMatterTypes = []frontMatterType{ + { + FenceOpen: "---", + FenceClose: []string{"---", "..."}, + ParseFunc: yamlFrontMatter, + }, + { + FenceOpen: "+++", + FenceClose: []string{"+++"}, + ParseFunc: tomlFrontMatter, + }, + { + FenceOpen: "{", + FenceClose: []string{"}"}, + ParseFunc: jsonFrontMatter, + }, +} diff --git a/modules/caddyhttp/templates/frontmatter_fuzz.go b/modules/caddyhttp/templates/frontmatter_fuzz.go new file mode 100644 index 00000000000..7af301312f8 --- /dev/null +++ b/modules/caddyhttp/templates/frontmatter_fuzz.go @@ -0,0 +1,25 @@ +// 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 templates + +func FuzzExtractFrontMatter(data []byte) int { + _, _, err := extractFrontMatter(string(data)) + if err != nil { + return 0 + } + return 1 +} diff --git a/modules/caddyhttp/templates/templates.go b/modules/caddyhttp/templates/templates.go new file mode 100644 index 00000000000..eb648865983 --- /dev/null +++ b/modules/caddyhttp/templates/templates.go @@ -0,0 +1,497 @@ +// 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 templates + +import ( + "bytes" + "errors" + "fmt" + "net/http" + "strconv" + "strings" + "text/template" + + "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func init() { + caddy.RegisterModule(Templates{}) +} + +// Templates is a middleware which executes response bodies as Go templates. +// The syntax is documented in the Go standard library's +// [text/template package](https://golang.org/pkg/text/template/). +// +// ⚠️ Template functions/actions are still experimental, so they are subject to change. +// +// Custom template functions can be registered by creating a plugin module under the `http.handlers.templates.functions.*` namespace that implements the `CustomFunctions` interface. +// +// [All Sprig functions](https://masterminds.github.io/sprig/) are supported. +// +// In addition to the standard functions and the Sprig library, Caddy adds +// extra functions and data that are available to a template: +// +// ##### `.Args` +// +// A slice of arguments passed to this page/context, for example +// as the result of a [`include`](#include). +// +// ``` +// {{index .Args 0}} // first argument +// ``` +// +// ##### `.Cookie` +// +// Gets the value of a cookie by name. +// +// ``` +// {{.Cookie "cookiename"}} +// ``` +// +// ##### `env` +// +// Gets an environment variable. +// +// ``` +// {{env "VAR_NAME"}} +// ``` +// +// ##### `placeholder` +// +// Gets an [placeholder variable](/docs/conventions#placeholders). +// The braces (`{}`) have to be omitted. +// +// ``` +// {{placeholder "http.request.uri.path"}} +// {{placeholder "http.error.status_code"}} +// ``` +// +// As a shortcut, `ph` is an alias for `placeholder`. +// +// ``` +// {{ph "http.request.method"}} +// ``` +// +// ##### `.Host` +// +// Returns the hostname portion (no port) of the Host header of the HTTP request. +// +// ``` +// {{.Host}} +// ``` +// +// ##### `httpInclude` +// +// Includes the contents of another file, and renders it in-place, +// by making a virtual HTTP request (also known as a sub-request). +// The URI path must exist on the same virtual server because the +// request does not use sockets; instead, the request is crafted in +// memory and the handler is invoked directly for increased efficiency. +// +// ``` +// {{httpInclude "/foo/bar?q=val"}} +// ``` +// +// ##### `import` +// +// Reads and returns the contents of another file, and parses it +// as a template, adding any template definitions to the template +// stack. If there are no definitions, the filepath will be the +// definition name. Any `{{ define }}` blocks will be accessible by +// `{{ template }}` or `{{ block }}`. Imports must happen before the +// template or block action is called. Note that the contents are +// NOT escaped, so you should only import trusted template files. +// +// **filename.html** +// ``` +// {{ define "main" }} +// content +// {{ end }} +// ``` +// +// **index.html** +// ``` +// {{ import "/path/to/filename.html" }} +// {{ template "main" }} +// ``` +// +// ##### `include` +// +// Includes the contents of another file, rendering it in-place. +// Optionally can pass key-value pairs as arguments to be accessed +// by the included file. Use [`.Args N`](#args) to access the N-th +// argument, 0-indexed. Note that the contents are NOT escaped, so +// you should only include trusted template files. +// +// ``` +// {{include "path/to/file.html"}} // no arguments +// {{include "path/to/file.html" "arg0" 1 "value 2"}} // with arguments +// ``` +// +// ##### `readFile` +// +// Reads and returns the contents of another file, as-is. +// Note that the contents are NOT escaped, so you should +// only read trusted files. +// +// ``` +// {{readFile "path/to/file.html"}} +// ``` +// +// ##### `listFiles` +// +// Returns a list of the files in the given directory, which is relative +// to the template context's file root. +// +// ``` +// {{listFiles "/mydir"}} +// ``` +// +// ##### `markdown` +// +// Renders the given Markdown text as HTML and returns it. This uses the +// [Goldmark](https://github.com/yuin/goldmark) library, +// which is CommonMark compliant. It also has these extensions +// enabled: GitHub Flavored Markdown, Footnote, and syntax +// highlighting provided by [Chroma](https://github.com/alecthomas/chroma). +// +// ``` +// {{markdown "My _markdown_ text"}} +// ``` +// +// ##### `.RemoteIP` +// +// Returns the connection's IP address. +// +// ``` +// {{.RemoteIP}} +// ``` +// +// ##### `.ClientIP` +// +// Returns the real client's IP address, if `trusted_proxies` was configured, +// otherwise returns the connection's IP address. +// +// ``` +// {{.ClientIP}} +// ``` +// +// ##### `.Req` +// +// Accesses the current HTTP request, which has various fields, including: +// +// - `.Method` - the method +// - `.URL` - the URL, which in turn has component fields (Scheme, Host, Path, etc.) +// - `.Header` - the header fields +// - `.Host` - the Host or :authority header of the request +// +// ``` +// {{.Req.Header.Get "User-Agent"}} +// ``` +// +// ##### `.OriginalReq` +// +// Like [`.Req`](#req), except it accesses the original HTTP +// request before rewrites or other internal modifications. +// +// ##### `.RespHeader.Add` +// +// Adds a header field to the HTTP response. +// +// ``` +// {{.RespHeader.Add "Field-Name" "val"}} +// ``` +// +// ##### `.RespHeader.Del` +// +// Deletes a header field on the HTTP response. +// +// ``` +// {{.RespHeader.Del "Field-Name"}} +// ``` +// +// ##### `.RespHeader.Set` +// +// Sets a header field on the HTTP response, replacing any existing value. +// +// ``` +// {{.RespHeader.Set "Field-Name" "val"}} +// ``` +// +// ##### `httpError` +// +// Returns an error with the given status code to the HTTP handler chain. +// +// ``` +// {{if not (fileExists $includedFile)}}{{httpError 404}}{{end}} +// ``` +// +// ##### `splitFrontMatter` +// +// Splits front matter out from the body. Front matter is metadata that +// appears at the very beginning of a file or string. Front matter can +// be in YAML, TOML, or JSON formats: +// +// **TOML** front matter starts and ends with `+++`: +// +// ```toml +// +++ +// template = "blog" +// title = "Blog Homepage" +// sitename = "A Caddy site" +// +++ +// ``` +// +// **YAML** is surrounded by `---`: +// +// ```yaml +// --- +// template: blog +// title: Blog Homepage +// sitename: A Caddy site +// --- +// ``` +// +// **JSON** is simply `{` and `}`: +// +// ```json +// { +// "template": "blog", +// "title": "Blog Homepage", +// "sitename": "A Caddy site" +// } +// ``` +// +// The resulting front matter will be made available like so: +// +// - `.Meta` to access the metadata fields, for example: `{{$parsed.Meta.title}}` +// - `.Body` to access the body after the front matter, for example: `{{markdown $parsed.Body}}` +// +// ##### `stripHTML` +// +// Removes HTML from a string. +// +// ``` +// {{stripHTML "Shows only text content"}} +// ``` +// +// ##### `humanize` +// +// Transforms size and time inputs to a human readable format. +// This uses the [go-humanize](https://github.com/dustin/go-humanize) library. +// +// The first argument must be a format type, and the last argument +// is the input, or the input can be piped in. The supported format +// types are: +// - **size** which turns an integer amount of bytes into a string like `2.3 MB` +// - **time** which turns a time string into a relative time string like `2 weeks ago` +// +// For the `time` format, the layout for parsing the input can be configured +// by appending a colon `:` followed by the desired time layout. You can +// find the documentation on time layouts [in Go's docs](https://pkg.go.dev/time#pkg-constants). +// The default time layout is `RFC1123Z`, i.e. `Mon, 02 Jan 2006 15:04:05 -0700`. +// +// ##### `pathEscape` +// +// Passes a string through `url.PathEscape`, replacing characters that have +// special meaning in URL path parameters (`?`, `&`, `%`). +// +// Useful e.g. to include filenames containing these characters in URL path +// parameters, or use them as an `img` element's `src` attribute. +// +// ``` +// {{pathEscape "50%_valid_filename?.jpg"}} +// ``` +// +// ``` +// {{humanize "size" "2048000"}} +// {{placeholder "http.response.header.Content-Length" | humanize "size"}} +// {{humanize "time" "Fri, 05 May 2022 15:04:05 +0200"}} +// {{humanize "time:2006-Jan-02" "2022-May-05"}} +// ``` +type Templates struct { + // The root path from which to load files. Required if template functions + // accessing the file system are used (such as include). Default is + // `{http.vars.root}` if set, or current working directory otherwise. + FileRoot string `json:"file_root,omitempty"` + + // The MIME types for which to render templates. It is important to use + // this if the route matchers do not exclude images or other binary files. + // Default is text/plain, text/markdown, and text/html. + MIMETypes []string `json:"mime_types,omitempty"` + + // The template action delimiters. If set, must be precisely two elements: + // the opening and closing delimiters. Default: `["{{", "}}"]` + Delimiters []string `json:"delimiters,omitempty"` + + // Extensions adds functions to the template's func map. These often + // act as components on web pages, for example. + ExtensionsRaw caddy.ModuleMap `json:"match,omitempty" caddy:"namespace=http.handlers.templates.functions"` + + customFuncs []template.FuncMap + logger *zap.Logger +} + +// CustomFunctions is the interface for registering custom template functions. +type CustomFunctions interface { + // CustomTemplateFunctions should return the mapping from custom function names to implementations. + CustomTemplateFunctions() template.FuncMap +} + +// CaddyModule returns the Caddy module information. +func (Templates) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.templates", + New: func() caddy.Module { return new(Templates) }, + } +} + +// Provision provisions t. +func (t *Templates) Provision(ctx caddy.Context) error { + t.logger = ctx.Logger() + mods, err := ctx.LoadModule(t, "ExtensionsRaw") + if err != nil { + return fmt.Errorf("loading template extensions: %v", err) + } + for _, modIface := range mods.(map[string]any) { + t.customFuncs = append(t.customFuncs, modIface.(CustomFunctions).CustomTemplateFunctions()) + } + + if t.MIMETypes == nil { + t.MIMETypes = defaultMIMETypes + } + if t.FileRoot == "" { + t.FileRoot = "{http.vars.root}" + } + return nil +} + +// Validate ensures t has a valid configuration. +func (t *Templates) Validate() error { + if len(t.Delimiters) != 0 && len(t.Delimiters) != 2 { + return fmt.Errorf("delimiters must consist of exactly two elements: opening and closing") + } + return nil +} + +func (t *Templates) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + buf := bufPool.Get().(*bytes.Buffer) + buf.Reset() + defer bufPool.Put(buf) + + // shouldBuf determines whether to execute templates on this response, + // since generally we will not want to execute for images or CSS, etc. + shouldBuf := func(status int, header http.Header) bool { + ct := header.Get("Content-Type") + for _, mt := range t.MIMETypes { + if strings.Contains(ct, mt) { + return true + } + } + return false + } + + rec := caddyhttp.NewResponseRecorder(w, buf, shouldBuf) + + err := next.ServeHTTP(rec, r) + if err != nil { + return err + } + if !rec.Buffered() { + return nil + } + + err = t.executeTemplate(rec, r) + if err != nil { + return err + } + + rec.Header().Set("Content-Length", strconv.Itoa(buf.Len())) + rec.Header().Del("Accept-Ranges") // we don't know ranges for dynamically-created content + rec.Header().Del("Last-Modified") // useless for dynamic content since it's always changing + + // we don't know a way to quickly generate etag for dynamic content, + // and weak etags still cause browsers to rely on it even after a + // refresh, so disable them until we find a better way to do this + rec.Header().Del("Etag") + + return rec.WriteResponse() +} + +// executeTemplate executes the template contained in wb.buf and replaces it with the results. +func (t *Templates) executeTemplate(rr caddyhttp.ResponseRecorder, r *http.Request) error { + var fs http.FileSystem + if t.FileRoot != "" { + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + fs = http.Dir(repl.ReplaceAll(t.FileRoot, ".")) + } + + ctx := &TemplateContext{ + Root: fs, + Req: r, + RespHeader: WrappedHeader{rr.Header()}, + config: t, + CustomFuncs: t.customFuncs, + } + + err := ctx.executeTemplateInBuffer(r.URL.Path, rr.Buffer()) + if err != nil { + // templates may return a custom HTTP error to be propagated to the client, + // otherwise for any other error we assume the template is broken + var handlerErr caddyhttp.HandlerError + if errors.As(err, &handlerErr) { + return handlerErr + } + return caddyhttp.Error(http.StatusInternalServerError, err) + } + + return nil +} + +// virtualResponseWriter is used in virtualized HTTP requests +// that templates may execute. +type virtualResponseWriter struct { + status int + header http.Header + body *bytes.Buffer +} + +func (vrw *virtualResponseWriter) Header() http.Header { + return vrw.header +} + +func (vrw *virtualResponseWriter) WriteHeader(statusCode int) { + vrw.status = statusCode +} + +func (vrw *virtualResponseWriter) Write(data []byte) (int, error) { + return vrw.body.Write(data) +} + +var defaultMIMETypes = []string{ + "text/html", + "text/plain", + "text/markdown", +} + +// Interface guards +var ( + _ caddy.Provisioner = (*Templates)(nil) + _ caddy.Validator = (*Templates)(nil) + _ caddyhttp.MiddlewareHandler = (*Templates)(nil) +) diff --git a/modules/caddyhttp/templates/tplcontext.go b/modules/caddyhttp/templates/tplcontext.go new file mode 100644 index 00000000000..1b1020f1b2c --- /dev/null +++ b/modules/caddyhttp/templates/tplcontext.go @@ -0,0 +1,589 @@ +// 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 templates + +import ( + "bytes" + "fmt" + "io" + "io/fs" + "net" + "net/http" + "net/url" + "os" + "path" + "reflect" + "strconv" + "strings" + "sync" + "text/template" + "time" + + "github.com/Masterminds/sprig/v3" + chromahtml "github.com/alecthomas/chroma/v2/formatters/html" + "github.com/dustin/go-humanize" + "github.com/yuin/goldmark" + highlighting "github.com/yuin/goldmark-highlighting/v2" + "github.com/yuin/goldmark/extension" + "github.com/yuin/goldmark/parser" + gmhtml "github.com/yuin/goldmark/renderer/html" + "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +// TemplateContext is the TemplateContext with which HTTP templates are executed. +type TemplateContext struct { + Root http.FileSystem + Req *http.Request + Args []any // defined by arguments to funcInclude + RespHeader WrappedHeader + CustomFuncs []template.FuncMap // functions added by plugins + + config *Templates + tpl *template.Template +} + +// NewTemplate returns a new template intended to be evaluated with this +// context, as it is initialized with configuration from this context. +func (c *TemplateContext) NewTemplate(tplName string) *template.Template { + c.tpl = template.New(tplName).Option("missingkey=zero") + + // customize delimiters, if applicable + if c.config != nil && len(c.config.Delimiters) == 2 { + c.tpl.Delims(c.config.Delimiters[0], c.config.Delimiters[1]) + } + + // add sprig library + c.tpl.Funcs(sprigFuncMap) + + // add all custom functions + for _, funcMap := range c.CustomFuncs { + c.tpl.Funcs(funcMap) + } + + // add our own library + c.tpl.Funcs(template.FuncMap{ + "include": c.funcInclude, + "readFile": c.funcReadFile, + "import": c.funcImport, + "httpInclude": c.funcHTTPInclude, + "stripHTML": c.funcStripHTML, + "markdown": c.funcMarkdown, + "splitFrontMatter": c.funcSplitFrontMatter, + "listFiles": c.funcListFiles, + "fileStat": c.funcFileStat, + "env": c.funcEnv, + "placeholder": c.funcPlaceholder, + "ph": c.funcPlaceholder, // shortcut + "fileExists": c.funcFileExists, + "httpError": c.funcHTTPError, + "humanize": c.funcHumanize, + "maybe": c.funcMaybe, + "pathEscape": url.PathEscape, + }) + return c.tpl +} + +// OriginalReq returns the original, unmodified, un-rewritten request as +// it originally came in over the wire. +func (c TemplateContext) OriginalReq() http.Request { + or, _ := c.Req.Context().Value(caddyhttp.OriginalRequestCtxKey).(http.Request) + return or +} + +// funcInclude returns the contents of filename relative to the site root and renders it in place. +// Note that included files are NOT escaped, so you should only include +// trusted files. If it is not trusted, be sure to use escaping functions +// in your template. +func (c TemplateContext) funcInclude(filename string, args ...any) (string, error) { + bodyBuf := bufPool.Get().(*bytes.Buffer) + bodyBuf.Reset() + defer bufPool.Put(bodyBuf) + + err := c.readFileToBuffer(filename, bodyBuf) + if err != nil { + return "", err + } + + c.Args = args + + err = c.executeTemplateInBuffer(filename, bodyBuf) + if err != nil { + return "", err + } + + return bodyBuf.String(), nil +} + +// funcReadFile returns the contents of a filename relative to the site root. +// Note that included files are NOT escaped, so you should only include +// trusted files. If it is not trusted, be sure to use escaping functions +// in your template. +func (c TemplateContext) funcReadFile(filename string) (string, error) { + bodyBuf := bufPool.Get().(*bytes.Buffer) + bodyBuf.Reset() + defer bufPool.Put(bodyBuf) + + err := c.readFileToBuffer(filename, bodyBuf) + if err != nil { + return "", err + } + + return bodyBuf.String(), nil +} + +// readFileToBuffer reads a file into a buffer +func (c TemplateContext) readFileToBuffer(filename string, bodyBuf *bytes.Buffer) error { + if c.Root == nil { + return fmt.Errorf("root file system not specified") + } + + file, err := c.Root.Open(filename) + if err != nil { + return err + } + defer file.Close() + + _, err = io.Copy(bodyBuf, file) + if err != nil { + return err + } + + return nil +} + +// funcHTTPInclude returns the body of a virtual (lightweight) request +// to the given URI on the same server. Note that included bodies +// are NOT escaped, so you should only include trusted resources. +// If it is not trusted, be sure to use escaping functions yourself. +func (c TemplateContext) funcHTTPInclude(uri string) (string, error) { + // prevent virtual request loops by counting how many levels + // deep we are; and if we get too deep, return an error + recursionCount := 1 + if numStr := c.Req.Header.Get(recursionPreventionHeader); numStr != "" { + num, err := strconv.Atoi(numStr) + if err != nil { + return "", fmt.Errorf("parsing %s: %v", recursionPreventionHeader, err) + } + if num >= 3 { + return "", fmt.Errorf("virtual request cycle") + } + recursionCount = num + 1 + } + + buf := bufPool.Get().(*bytes.Buffer) + buf.Reset() + defer bufPool.Put(buf) + + virtReq, err := http.NewRequest("GET", uri, nil) + if err != nil { + return "", err + } + virtReq.Host = c.Req.Host + virtReq.RemoteAddr = "127.0.0.1:10000" // https://github.com/caddyserver/caddy/issues/5835 + virtReq.Header = c.Req.Header.Clone() + virtReq.Header.Set("Accept-Encoding", "identity") // https://github.com/caddyserver/caddy/issues/4352 + virtReq.Trailer = c.Req.Trailer.Clone() + virtReq.Header.Set(recursionPreventionHeader, strconv.Itoa(recursionCount)) + + vrw := &virtualResponseWriter{body: buf, header: make(http.Header)} + server := c.Req.Context().Value(caddyhttp.ServerCtxKey).(http.Handler) + + server.ServeHTTP(vrw, virtReq) + if vrw.status >= 400 { + return "", fmt.Errorf("http %d", vrw.status) + } + + err = c.executeTemplateInBuffer(uri, buf) + if err != nil { + return "", err + } + + return buf.String(), nil +} + +// funcImport parses the filename into the current template stack. The imported +// file will be rendered within the current template by calling {{ block }} or +// {{ template }} from the standard template library. If the imported file has +// no {{ define }} blocks, the name of the import will be the path +func (c *TemplateContext) funcImport(filename string) (string, error) { + bodyBuf := bufPool.Get().(*bytes.Buffer) + bodyBuf.Reset() + defer bufPool.Put(bodyBuf) + + err := c.readFileToBuffer(filename, bodyBuf) + if err != nil { + return "", err + } + + _, err = c.tpl.Parse(bodyBuf.String()) + if err != nil { + return "", err + } + return "", nil +} + +func (c *TemplateContext) executeTemplateInBuffer(tplName string, buf *bytes.Buffer) error { + c.NewTemplate(tplName) + + _, err := c.tpl.Parse(buf.String()) + if err != nil { + return err + } + + buf.Reset() // reuse buffer for output + + return c.tpl.Execute(buf, c) +} + +func (c TemplateContext) funcPlaceholder(name string) string { + repl := c.Req.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + + // For safety, we don't want to allow the file placeholder in + // templates because it could be used to read arbitrary files + // if the template contents were not trusted. + repl = repl.WithoutFile() + + value, _ := repl.GetString(name) + return value +} + +func (TemplateContext) funcEnv(varName string) string { + return os.Getenv(varName) +} + +// Cookie gets the value of a cookie with name. +func (c TemplateContext) Cookie(name string) string { + cookies := c.Req.Cookies() + for _, cookie := range cookies { + if cookie.Name == name { + return cookie.Value + } + } + return "" +} + +// RemoteIP gets the IP address of the connection's remote IP. +func (c TemplateContext) RemoteIP() string { + ip, _, err := net.SplitHostPort(c.Req.RemoteAddr) + if err != nil { + return c.Req.RemoteAddr + } + return ip +} + +// ClientIP gets the IP address of the real client making the request +// if the request is trusted (see trusted_proxies), otherwise returns +// the connection's remote IP. +func (c TemplateContext) ClientIP() string { + address := caddyhttp.GetVar(c.Req.Context(), caddyhttp.ClientIPVarKey).(string) + clientIP, _, err := net.SplitHostPort(address) + if err != nil { + clientIP = address // no port + } + return clientIP +} + +// Host returns the hostname portion of the Host header +// from the HTTP request. +func (c TemplateContext) Host() (string, error) { + host, _, err := net.SplitHostPort(c.Req.Host) + if err != nil { + if !strings.Contains(c.Req.Host, ":") { + // common with sites served on the default port 80 + return c.Req.Host, nil + } + return "", err + } + return host, nil +} + +// funcStripHTML returns s without HTML tags. It is fairly naive +// but works with most valid HTML inputs. +func (TemplateContext) funcStripHTML(s string) string { + var buf bytes.Buffer + var inTag, inQuotes bool + var tagStart int + for i, ch := range s { + if inTag { + if ch == '>' && !inQuotes { + inTag = false + } else if ch == '<' && !inQuotes { + // false start + buf.WriteString(s[tagStart:i]) + tagStart = i + } else if ch == '"' { + inQuotes = !inQuotes + } + continue + } + if ch == '<' { + inTag = true + tagStart = i + continue + } + buf.WriteRune(ch) + } + if inTag { + // false start + buf.WriteString(s[tagStart:]) + } + return buf.String() +} + +// funcMarkdown renders the markdown body as HTML. The resulting +// HTML is NOT escaped so that it can be rendered as HTML. +func (TemplateContext) funcMarkdown(input any) (string, error) { + inputStr := caddy.ToString(input) + + md := goldmark.New( + goldmark.WithExtensions( + extension.GFM, + extension.Footnote, + highlighting.NewHighlighting( + highlighting.WithFormatOptions( + chromahtml.WithClasses(true), + ), + ), + ), + goldmark.WithParserOptions( + parser.WithAutoHeadingID(), + ), + goldmark.WithRendererOptions( + gmhtml.WithUnsafe(), // TODO: this is not awesome, maybe should be configurable? + ), + ) + + buf := bufPool.Get().(*bytes.Buffer) + buf.Reset() + defer bufPool.Put(buf) + + err := md.Convert([]byte(inputStr), buf) + if err != nil { + return "", err + } + + return buf.String(), nil +} + +// splitFrontMatter parses front matter out from the beginning of input, +// and returns the separated key-value pairs and the body/content. input +// must be a "stringy" value. +func (TemplateContext) funcSplitFrontMatter(input any) (parsedMarkdownDoc, error) { + meta, body, err := extractFrontMatter(caddy.ToString(input)) + if err != nil { + return parsedMarkdownDoc{}, err + } + return parsedMarkdownDoc{Meta: meta, Body: body}, nil +} + +// funcListFiles reads and returns a slice of names from the given +// directory relative to the root of c. +func (c TemplateContext) funcListFiles(name string) ([]string, error) { + if c.Root == nil { + return nil, fmt.Errorf("root file system not specified") + } + + dir, err := c.Root.Open(path.Clean(name)) + if err != nil { + return nil, err + } + defer dir.Close() + + stat, err := dir.Stat() + if err != nil { + return nil, err + } + if !stat.IsDir() { + return nil, fmt.Errorf("%v is not a directory", name) + } + + dirInfo, err := dir.Readdir(0) + if err != nil { + return nil, err + } + + names := make([]string, len(dirInfo)) + for i, fileInfo := range dirInfo { + names[i] = fileInfo.Name() + } + + return names, nil +} + +// funcFileExists returns true if filename can be opened successfully. +func (c TemplateContext) funcFileExists(filename string) (bool, error) { + if c.Root == nil { + return false, fmt.Errorf("root file system not specified") + } + file, err := c.Root.Open(filename) + if err == nil { + file.Close() + return true, nil + } + return false, nil +} + +// funcFileStat returns Stat of a filename +func (c TemplateContext) funcFileStat(filename string) (fs.FileInfo, error) { + if c.Root == nil { + return nil, fmt.Errorf("root file system not specified") + } + + file, err := c.Root.Open(path.Clean(filename)) + if err != nil { + return nil, err + } + defer file.Close() + + return file.Stat() +} + +// funcHTTPError returns a structured HTTP handler error. EXPERIMENTAL; SUBJECT TO CHANGE. +// Example usage: `{{if not (fileExists $includeFile)}}{{httpError 404}}{{end}}` +func (c TemplateContext) funcHTTPError(statusCode int) (bool, error) { + // Delete some headers that may have been set by the underlying + // handler (such as file_server) which may break the error response. + c.RespHeader.Header.Del("Content-Length") + c.RespHeader.Header.Del("Content-Type") + c.RespHeader.Header.Del("Etag") + c.RespHeader.Header.Del("Last-Modified") + c.RespHeader.Header.Del("Accept-Ranges") + + return false, caddyhttp.Error(statusCode, nil) +} + +// funcHumanize transforms size and time inputs to a human readable format. +// +// Size inputs are expected to be integers, and are formatted as a +// byte size, such as "83 MB". +// +// Time inputs are parsed using the given layout (default layout is RFC1123Z) +// and are formatted as a relative time, such as "2 weeks ago". +// See https://pkg.go.dev/time#pkg-constants for time layout docs. +func (c TemplateContext) funcHumanize(formatType, data string) (string, error) { + // The format type can optionally be followed + // by a colon to provide arguments for the format + parts := strings.Split(formatType, ":") + + switch parts[0] { + case "size": + dataint, dataerr := strconv.ParseUint(data, 10, 64) + if dataerr != nil { + return "", fmt.Errorf("humanize: size cannot be parsed: %s", dataerr.Error()) + } + return humanize.Bytes(dataint), nil + + case "time": + timelayout := time.RFC1123Z + if len(parts) > 1 { + timelayout = parts[1] + } + + dataint, dataerr := time.Parse(timelayout, data) + if dataerr != nil { + return "", fmt.Errorf("humanize: time cannot be parsed: %s", dataerr.Error()) + } + return humanize.Time(dataint), nil + } + + return "", fmt.Errorf("no know function was given") +} + +// funcMaybe invokes the plugged-in function named functionName if it is plugged in +// (is a module in the 'http.handlers.templates.functions' namespace). If it is not +// available, a log message is emitted. +// +// The first argument is the function name, and the rest of the arguments are +// passed on to the actual function. +// +// This function is useful for executing templates that use components that may be +// considered as optional in some cases (like during local development) where you do +// not want to require everyone to have a custom Caddy build to be able to execute +// your template. +// +// NOTE: This function is EXPERIMENTAL and subject to change or removal. +func (c TemplateContext) funcMaybe(functionName string, args ...any) (any, error) { + for _, funcMap := range c.CustomFuncs { + if fn, ok := funcMap[functionName]; ok { + val := reflect.ValueOf(fn) + if val.Kind() != reflect.Func { + continue + } + argVals := make([]reflect.Value, len(args)) + for i, arg := range args { + argVals[i] = reflect.ValueOf(arg) + } + returnVals := val.Call(argVals) + switch len(returnVals) { + case 0: + return "", nil + case 1: + return returnVals[0].Interface(), nil + case 2: + var err error + if !returnVals[1].IsNil() { + err = returnVals[1].Interface().(error) + } + return returnVals[0].Interface(), err + default: + return nil, fmt.Errorf("maybe %s: invalid number of return values: %d", functionName, len(returnVals)) + } + } + } + c.config.logger.Named("maybe").Warn("template function could not be found; ignoring invocation", zap.String("name", functionName)) + return "", nil +} + +// WrappedHeader wraps niladic functions so that they +// can be used in templates. (Template functions must +// return a value.) +type WrappedHeader struct{ http.Header } + +// Add adds a header field value, appending val to +// existing values for that field. It returns an +// empty string. +func (h WrappedHeader) Add(field, val string) string { + h.Header.Add(field, val) + return "" +} + +// Set sets a header field value, overwriting any +// other values for that field. It returns an +// empty string. +func (h WrappedHeader) Set(field, val string) string { + h.Header.Set(field, val) + return "" +} + +// Del deletes a header field. It returns an empty string. +func (h WrappedHeader) Del(field string) string { + h.Header.Del(field) + return "" +} + +var bufPool = sync.Pool{ + New: func() any { + return new(bytes.Buffer) + }, +} + +// at time of writing, sprig.FuncMap() makes a copy, thus +// involves iterating the whole map, so do it just once +var sprigFuncMap = sprig.TxtFuncMap() + +const recursionPreventionHeader = "Caddy-Templates-Include" diff --git a/modules/caddyhttp/templates/tplcontext_test.go b/modules/caddyhttp/templates/tplcontext_test.go new file mode 100644 index 00000000000..67ebbac7031 --- /dev/null +++ b/modules/caddyhttp/templates/tplcontext_test.go @@ -0,0 +1,680 @@ +// 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 templates + +import ( + "bytes" + "context" + "errors" + "fmt" + "io/fs" + "net/http" + "os" + "path/filepath" + "reflect" + "sort" + "strings" + "testing" + "time" + + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +type handle struct{} + +func (h *handle) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("Accept-Encoding") == "identity" { + w.Write([]byte("good contents")) + } else { + w.Write([]byte("bad cause Accept-Encoding: " + r.Header.Get("Accept-Encoding"))) + } +} + +func TestHTTPInclude(t *testing.T) { + tplContext := getContextOrFail(t) + for i, test := range []struct { + uri string + handler *handle + expect string + }{ + { + uri: "https://example.com/foo/bar", + handler: &handle{}, + expect: "good contents", + }, + } { + ctx := context.WithValue(tplContext.Req.Context(), caddyhttp.ServerCtxKey, test.handler) + tplContext.Req = tplContext.Req.WithContext(ctx) + tplContext.Req.Header.Add("Accept-Encoding", "gzip") + result, err := tplContext.funcHTTPInclude(test.uri) + if result != test.expect { + t.Errorf("Test %d: expected '%s' but got '%s'", i, test.expect, result) + } + if err != nil { + t.Errorf("Test %d: got error: %v", i, result) + } + } +} + +func TestMarkdown(t *testing.T) { + tplContext := getContextOrFail(t) + + for i, test := range []struct { + body string + expect string + }{ + { + body: "- str1\n- str2\n", + expect: "
      \n
    • str1
    • \n
    • str2
    • \n
    \n", + }, + } { + result, err := tplContext.funcMarkdown(test.body) + if result != test.expect { + t.Errorf("Test %d: expected '%s' but got '%s'", i, test.expect, result) + } + if err != nil { + t.Errorf("Test %d: got error: %v", i, result) + } + } +} + +func TestCookie(t *testing.T) { + for i, test := range []struct { + cookie *http.Cookie + cookieName string + expect string + }{ + { + // happy path + cookie: &http.Cookie{Name: "cookieName", Value: "cookieValue"}, + cookieName: "cookieName", + expect: "cookieValue", + }, + { + // try to get a non-existing cookie + cookie: &http.Cookie{Name: "cookieName", Value: "cookieValue"}, + cookieName: "notExisting", + expect: "", + }, + { + // partial name match + cookie: &http.Cookie{Name: "cookie", Value: "cookieValue"}, + cookieName: "cook", + expect: "", + }, + { + // cookie with optional fields + cookie: &http.Cookie{Name: "cookie", Value: "cookieValue", Path: "/path", Domain: "https://localhost", Expires: time.Now().Add(10 * time.Minute), MaxAge: 120}, + cookieName: "cookie", + expect: "cookieValue", + }, + } { + tplContext := getContextOrFail(t) + tplContext.Req.AddCookie(test.cookie) + actual := tplContext.Cookie(test.cookieName) + if actual != test.expect { + t.Errorf("Test %d: Expected cookie value '%s' but got '%s' for cookie with name '%s'", + i, test.expect, actual, test.cookieName) + } + } +} + +func TestImport(t *testing.T) { + for i, test := range []struct { + fileContent string + fileName string + shouldErr bool + expect string + }{ + { + // file exists, template is defined + fileContent: `{{ define "imported" }}text{{end}}`, + fileName: "file1", + shouldErr: false, + expect: `"imported"`, + }, + { + // file does not exit + fileContent: "", + fileName: "", + shouldErr: true, + }, + } { + tplContext := getContextOrFail(t) + var absFilePath string + + // create files for test case + if test.fileName != "" { + absFilePath := filepath.Join(fmt.Sprintf("%s", tplContext.Root), test.fileName) + if err := os.WriteFile(absFilePath, []byte(test.fileContent), os.ModePerm); err != nil { + os.Remove(absFilePath) + t.Fatalf("Test %d: Expected no error creating file, got: '%s'", i, err.Error()) + } + } + + // perform test + tplContext.NewTemplate("parent") + actual, err := tplContext.funcImport(test.fileName) + templateWasDefined := strings.Contains(tplContext.tpl.DefinedTemplates(), test.expect) + if err != nil { + if !test.shouldErr { + t.Errorf("Test %d: Expected no error, got: '%s'", i, err) + } + } else if test.shouldErr { + t.Errorf("Test %d: Expected error but had none", i) + } else if !templateWasDefined && actual != "" { + // template should be defined, return value should be an empty string + t.Errorf("Test %d: Expected template %s to be define but got %s", i, test.expect, tplContext.tpl.DefinedTemplates()) + } + + if absFilePath != "" { + if err := os.Remove(absFilePath); err != nil && !errors.Is(err, fs.ErrNotExist) { + t.Fatalf("Test %d: Expected no error removing temporary test file, got: %v", i, err) + } + } + } +} + +func TestNestedInclude(t *testing.T) { + for i, test := range []struct { + child string + childFile string + parent string + parentFile string + shouldErr bool + expect string + child2 string + child2File string + }{ + { + // include in parent + child: `{{ include "file1" }}`, + childFile: "file0", + parent: `{{ $content := "file2" }}{{ $p := include $content}}`, + parentFile: "file1", + shouldErr: false, + expect: ``, + child2: `This shouldn't show`, + child2File: "file2", + }, + } { + context := getContextOrFail(t) + var absFilePath string + var absFilePath0 string + var absFilePath1 string + var buf *bytes.Buffer + var err error + + // create files and for test case + if test.parentFile != "" { + absFilePath = filepath.Join(fmt.Sprintf("%s", context.Root), test.parentFile) + if err := os.WriteFile(absFilePath, []byte(test.parent), os.ModePerm); err != nil { + os.Remove(absFilePath) + t.Fatalf("Test %d: Expected no error creating file, got: '%s'", i, err.Error()) + } + } + if test.childFile != "" { + absFilePath0 = filepath.Join(fmt.Sprintf("%s", context.Root), test.childFile) + if err := os.WriteFile(absFilePath0, []byte(test.child), os.ModePerm); err != nil { + os.Remove(absFilePath0) + t.Fatalf("Test %d: Expected no error creating file, got: '%s'", i, err.Error()) + } + } + if test.child2File != "" { + absFilePath1 = filepath.Join(fmt.Sprintf("%s", context.Root), test.child2File) + if err := os.WriteFile(absFilePath1, []byte(test.child2), os.ModePerm); err != nil { + os.Remove(absFilePath0) + t.Fatalf("Test %d: Expected no error creating file, got: '%s'", i, err.Error()) + } + } + + buf = bufPool.Get().(*bytes.Buffer) + buf.Reset() + defer bufPool.Put(buf) + buf.WriteString(test.child) + err = context.executeTemplateInBuffer(test.childFile, buf) + + if err != nil { + if !test.shouldErr { + t.Errorf("Test %d: Expected no error, got: '%s'", i, err) + } + } else if test.shouldErr { + t.Errorf("Test %d: Expected error but had none", i) + } else if buf.String() != test.expect { + // + t.Errorf("Test %d: Expected '%s' but got '%s'", i, test.expect, buf.String()) + } + + if absFilePath != "" { + if err := os.Remove(absFilePath); err != nil && !errors.Is(err, fs.ErrNotExist) { + t.Fatalf("Test %d: Expected no error removing temporary test file, got: %v", i, err) + } + } + if absFilePath0 != "" { + if err := os.Remove(absFilePath0); err != nil && !errors.Is(err, fs.ErrNotExist) { + t.Fatalf("Test %d: Expected no error removing temporary test file, got: %v", i, err) + } + } + if absFilePath1 != "" { + if err := os.Remove(absFilePath1); err != nil && !errors.Is(err, fs.ErrNotExist) { + t.Fatalf("Test %d: Expected no error removing temporary test file, got: %v", i, err) + } + } + } +} + +func TestInclude(t *testing.T) { + for i, test := range []struct { + fileContent string + fileName string + shouldErr bool + expect string + args string + }{ + { + // file exists, content is text only + fileContent: "text", + fileName: "file1", + shouldErr: false, + expect: "text", + }, + { + // file exists, content is template + fileContent: "{{ if . }}text{{ end }}", + fileName: "file1", + shouldErr: false, + expect: "text", + }, + { + // file does not exit + fileContent: "", + fileName: "", + shouldErr: true, + }, + { + // args + fileContent: "{{ index .Args 0 }}", + fileName: "file1", + shouldErr: false, + args: "text", + expect: "text", + }, + { + // args, reference arg out of range + fileContent: "{{ index .Args 1 }}", + fileName: "file1", + shouldErr: true, + args: "text", + }, + } { + tplContext := getContextOrFail(t) + var absFilePath string + + // create files for test case + if test.fileName != "" { + absFilePath := filepath.Join(fmt.Sprintf("%s", tplContext.Root), test.fileName) + if err := os.WriteFile(absFilePath, []byte(test.fileContent), os.ModePerm); err != nil { + os.Remove(absFilePath) + t.Fatalf("Test %d: Expected no error creating file, got: '%s'", i, err.Error()) + } + } + + // perform test + actual, err := tplContext.funcInclude(test.fileName, test.args) + if err != nil { + if !test.shouldErr { + t.Errorf("Test %d: Expected no error, got: '%s'", i, err) + } + } else if test.shouldErr { + t.Errorf("Test %d: Expected error but had none", i) + } else if actual != test.expect { + t.Errorf("Test %d: Expected %s but got %s", i, test.expect, actual) + } + + if absFilePath != "" { + if err := os.Remove(absFilePath); err != nil && !errors.Is(err, fs.ErrNotExist) { + t.Fatalf("Test %d: Expected no error removing temporary test file, got: %v", i, err) + } + } + } +} + +func TestCookieMultipleCookies(t *testing.T) { + tplContext := getContextOrFail(t) + + cookieNameBase, cookieValueBase := "cookieName", "cookieValue" + + for i := 0; i < 10; i++ { + tplContext.Req.AddCookie(&http.Cookie{ + Name: fmt.Sprintf("%s%d", cookieNameBase, i), + Value: fmt.Sprintf("%s%d", cookieValueBase, i), + }) + } + + for i := 0; i < 10; i++ { + expectedCookieVal := fmt.Sprintf("%s%d", cookieValueBase, i) + actualCookieVal := tplContext.Cookie(fmt.Sprintf("%s%d", cookieNameBase, i)) + if actualCookieVal != expectedCookieVal { + t.Errorf("Expected cookie value %s, found %s", expectedCookieVal, actualCookieVal) + } + } +} + +func TestIP(t *testing.T) { + tplContext := getContextOrFail(t) + for i, test := range []struct { + inputRemoteAddr string + expect string + }{ + {"1.1.1.1:1111", "1.1.1.1"}, + {"1.1.1.1", "1.1.1.1"}, + {"[::1]:11", "::1"}, + {"[2001:db8:a0b:12f0::1]", "[2001:db8:a0b:12f0::1]"}, + {`[fe80:1::3%eth0]:44`, `fe80:1::3%eth0`}, + } { + tplContext.Req.RemoteAddr = test.inputRemoteAddr + if actual := tplContext.RemoteIP(); actual != test.expect { + t.Errorf("Test %d: Expected %s but got %s", i, test.expect, actual) + } + } +} + +func TestStripHTML(t *testing.T) { + tplContext := getContextOrFail(t) + + for i, test := range []struct { + input string + expect string + }{ + { + // no tags + input: `h1`, + expect: `h1`, + }, + { + // happy path + input: `

    h1

    `, + expect: `h1`, + }, + { + // tag in quotes + input: `">h1`, + expect: `h1`, + }, + { + // multiple tags + input: `

    h1

    `, + expect: `h1`, + }, + { + // tags not closed + input: `hi`, + expect: ` 0 && !reflect.DeepEqual(test.fileNames, actual) { + t.Errorf("Test %d: Expected files %v, got: %v", + i, test.fileNames, actual) + } + } + } + + if dirPath != "" { + if err := os.RemoveAll(dirPath); err != nil && !errors.Is(err, fs.ErrNotExist) { + t.Fatalf("Test %d: Expected no error removing temporary test directory, got: %v", i, err) + } + } + } +} + +func TestSplitFrontMatter(t *testing.T) { + tplContext := getContextOrFail(t) + + for i, test := range []struct { + input string + expect string + body string + }{ + { + // yaml with windows newline + input: "---\r\ntitle: Welcome\r\n---\r\n# Test\\r\\n", + expect: `Welcome`, + body: "\r\n# Test\\r\\n", + }, + { + // yaml + input: `--- +title: Welcome +--- +### Test`, + expect: `Welcome`, + body: "\n### Test", + }, + { + // yaml with dots for closer + input: `--- +title: Welcome +... +### Test`, + expect: `Welcome`, + body: "\n### Test", + }, + { + // yaml with non-fence '...' line after closing fence (i.e. first matching closing fence should be used) + input: `--- +title: Welcome +--- +### Test +... +yeah`, + expect: `Welcome`, + body: "\n### Test\n...\nyeah", + }, + { + // toml + input: `+++ +title = "Welcome" ++++ +### Test`, + expect: `Welcome`, + body: "\n### Test", + }, + { + // json + input: `{ + "title": "Welcome" +} +### Test`, + expect: `Welcome`, + body: "\n### Test", + }, + } { + result, _ := tplContext.funcSplitFrontMatter(test.input) + if result.Meta["title"] != test.expect { + t.Errorf("Test %d: Expected %s, found %s. Input was SplitFrontMatter(%s)", i, test.expect, result.Meta["title"], test.input) + } + if result.Body != test.body { + t.Errorf("Test %d: Expected body %s, found %s. Input was SplitFrontMatter(%s)", i, test.body, result.Body, test.input) + } + } +} + +func TestHumanize(t *testing.T) { + tplContext := getContextOrFail(t) + for i, test := range []struct { + format string + inputData string + expect string + errorCase bool + verifyErr func(actual_string, substring string) bool + }{ + { + format: "size", + inputData: "2048000", + expect: "2.0 MB", + errorCase: false, + verifyErr: strings.Contains, + }, + { + format: "time", + inputData: "Fri, 05 May 2022 15:04:05 +0200", + expect: "ago", + errorCase: false, + verifyErr: strings.HasSuffix, + }, + { + format: "time:2006-Jan-02", + inputData: "2022-May-05", + expect: "ago", + errorCase: false, + verifyErr: strings.HasSuffix, + }, + { + format: "time", + inputData: "Fri, 05 May 2022 15:04:05 GMT+0200", + expect: "error:", + errorCase: true, + verifyErr: strings.HasPrefix, + }, + } { + if actual, err := tplContext.funcHumanize(test.format, test.inputData); !test.verifyErr(actual, test.expect) { + if !test.errorCase { + t.Errorf("Test %d: Expected '%s' but got '%s'", i, test.expect, actual) + if err != nil { + t.Errorf("Test %d: error: %s", i, err.Error()) + } + } + } + } +} + +func getContextOrFail(t *testing.T) TemplateContext { + tplContext, err := initTestContext() + t.Cleanup(func() { + os.RemoveAll(string(tplContext.Root.(http.Dir))) + }) + if err != nil { + t.Fatalf("failed to prepare test context: %v", err) + } + return tplContext +} + +func initTestContext() (TemplateContext, error) { + body := bytes.NewBufferString("request body") + request, err := http.NewRequest("GET", "https://example.com/foo/bar", body) + if err != nil { + return TemplateContext{}, err + } + tmpDir, err := os.MkdirTemp(os.TempDir(), "caddy") + if err != nil { + return TemplateContext{}, err + } + return TemplateContext{ + Root: http.Dir(tmpDir), + Req: request, + RespHeader: WrappedHeader{make(http.Header)}, + }, nil +} diff --git a/modules/caddyhttp/tracing/module.go b/modules/caddyhttp/tracing/module.go new file mode 100644 index 00000000000..85fd630020e --- /dev/null +++ b/modules/caddyhttp/tracing/module.go @@ -0,0 +1,119 @@ +package tracing + +import ( + "fmt" + "net/http" + + "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func init() { + caddy.RegisterModule(Tracing{}) + httpcaddyfile.RegisterHandlerDirective("tracing", parseCaddyfile) +} + +// Tracing implements an HTTP handler that adds support for distributed tracing, +// using OpenTelemetry. This module is responsible for the injection and +// propagation of the trace context. Configure this module via environment +// variables (see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md). +// Some values can be overwritten in the configuration file. +type Tracing struct { + // SpanName is a span name. It should follow the naming guidelines here: + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#span + SpanName string `json:"span"` + + // otel implements opentelemetry related logic. + otel openTelemetryWrapper + + logger *zap.Logger +} + +// CaddyModule returns the Caddy module information. +func (Tracing) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.tracing", + New: func() caddy.Module { return new(Tracing) }, + } +} + +// Provision implements caddy.Provisioner. +func (ot *Tracing) Provision(ctx caddy.Context) error { + ot.logger = ctx.Logger() + + var err error + ot.otel, err = newOpenTelemetryWrapper(ctx, ot.SpanName) + + return err +} + +// Cleanup implements caddy.CleanerUpper and closes any idle connections. It +// calls Shutdown method for a trace provider https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#shutdown. +func (ot *Tracing) Cleanup() error { + if err := ot.otel.cleanup(ot.logger); err != nil { + return fmt.Errorf("tracerProvider shutdown: %w", err) + } + return nil +} + +// ServeHTTP implements caddyhttp.MiddlewareHandler. +func (ot *Tracing) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + return ot.otel.ServeHTTP(w, r, next) +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax: +// +// tracing { +// [span ] +// } +func (ot *Tracing) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + setParameter := func(d *caddyfile.Dispenser, val *string) error { + if d.NextArg() { + *val = d.Val() + } else { + return d.ArgErr() + } + if d.NextArg() { + return d.ArgErr() + } + return nil + } + + // paramsMap is a mapping between "string" parameter from the Caddyfile and its destination within the module + paramsMap := map[string]*string{ + "span": &ot.SpanName, + } + + d.Next() // consume directive name + if d.NextArg() { + return d.ArgErr() + } + + for d.NextBlock(0) { + if dst, ok := paramsMap[d.Val()]; ok { + if err := setParameter(d, dst); err != nil { + return err + } + } else { + return d.ArgErr() + } + } + return nil +} + +func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + var m Tracing + err := m.UnmarshalCaddyfile(h.Dispenser) + return &m, err +} + +// Interface guards +var ( + _ caddy.Provisioner = (*Tracing)(nil) + _ caddyhttp.MiddlewareHandler = (*Tracing)(nil) + _ caddyfile.Unmarshaler = (*Tracing)(nil) +) diff --git a/modules/caddyhttp/tracing/module_test.go b/modules/caddyhttp/tracing/module_test.go new file mode 100644 index 00000000000..2a775fc18b2 --- /dev/null +++ b/modules/caddyhttp/tracing/module_test.go @@ -0,0 +1,190 @@ +package tracing + +import ( + "context" + "errors" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func TestTracing_UnmarshalCaddyfile(t *testing.T) { + tests := []struct { + name string + spanName string + d *caddyfile.Dispenser + wantErr bool + }{ + { + name: "Full config", + spanName: "my-span", + d: caddyfile.NewTestDispenser(` +tracing { + span my-span +}`), + wantErr: false, + }, + { + name: "Only span name in the config", + spanName: "my-span", + d: caddyfile.NewTestDispenser(` +tracing { + span my-span +}`), + wantErr: false, + }, + { + name: "Empty config", + d: caddyfile.NewTestDispenser(` +tracing { +}`), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ot := &Tracing{} + if err := ot.UnmarshalCaddyfile(tt.d); (err != nil) != tt.wantErr { + t.Errorf("UnmarshalCaddyfile() error = %v, wantErrType %v", err, tt.wantErr) + } + + if ot.SpanName != tt.spanName { + t.Errorf("UnmarshalCaddyfile() SpanName = %v, want SpanName %v", ot.SpanName, tt.spanName) + } + }) + } +} + +func TestTracing_UnmarshalCaddyfile_Error(t *testing.T) { + tests := []struct { + name string + d *caddyfile.Dispenser + wantErr bool + }{ + { + name: "Unknown parameter", + d: caddyfile.NewTestDispenser(` + tracing { + foo bar + }`), + wantErr: true, + }, + { + name: "Missed argument", + d: caddyfile.NewTestDispenser(` +tracing { + span +}`), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ot := &Tracing{} + if err := ot.UnmarshalCaddyfile(tt.d); (err != nil) != tt.wantErr { + t.Errorf("UnmarshalCaddyfile() error = %v, wantErrType %v", err, tt.wantErr) + } + }) + } +} + +func TestTracing_ServeHTTP_Propagation_Without_Initial_Headers(t *testing.T) { + ot := &Tracing{ + SpanName: "mySpan", + } + + req := createRequestWithContext("GET", "https://example.com/foo") + w := httptest.NewRecorder() + + var handler caddyhttp.HandlerFunc = func(writer http.ResponseWriter, request *http.Request) error { + traceparent := request.Header.Get("Traceparent") + if traceparent == "" || strings.HasPrefix(traceparent, "00-00000000000000000000000000000000-0000000000000000") { + t.Errorf("Invalid traceparent: %v", traceparent) + } + + return nil + } + + ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + + if err := ot.Provision(ctx); err != nil { + t.Errorf("Provision error: %v", err) + t.FailNow() + } + + if err := ot.ServeHTTP(w, req, handler); err != nil { + t.Errorf("ServeHTTP error: %v", err) + } +} + +func TestTracing_ServeHTTP_Propagation_With_Initial_Headers(t *testing.T) { + ot := &Tracing{ + SpanName: "mySpan", + } + + req := createRequestWithContext("GET", "https://example.com/foo") + req.Header.Set("traceparent", "00-11111111111111111111111111111111-1111111111111111-01") + w := httptest.NewRecorder() + + var handler caddyhttp.HandlerFunc = func(writer http.ResponseWriter, request *http.Request) error { + traceparent := request.Header.Get("Traceparent") + if !strings.HasPrefix(traceparent, "00-11111111111111111111111111111111") { + t.Errorf("Invalid traceparent: %v", traceparent) + } + + return nil + } + + ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + + if err := ot.Provision(ctx); err != nil { + t.Errorf("Provision error: %v", err) + t.FailNow() + } + + if err := ot.ServeHTTP(w, req, handler); err != nil { + t.Errorf("ServeHTTP error: %v", err) + } +} + +func TestTracing_ServeHTTP_Next_Error(t *testing.T) { + ot := &Tracing{ + SpanName: "mySpan", + } + + req := createRequestWithContext("GET", "https://example.com/foo") + w := httptest.NewRecorder() + + expectErr := errors.New("test error") + + var handler caddyhttp.HandlerFunc = func(writer http.ResponseWriter, request *http.Request) error { + return expectErr + } + + ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + + if err := ot.Provision(ctx); err != nil { + t.Errorf("Provision error: %v", err) + t.FailNow() + } + + if err := ot.ServeHTTP(w, req, handler); err == nil || !errors.Is(err, expectErr) { + t.Errorf("expected error, got: %v", err) + } +} + +func createRequestWithContext(method string, url string) *http.Request { + r, _ := http.NewRequest(method, url, nil) + repl := caddy.NewReplacer() + ctx := context.WithValue(r.Context(), caddy.ReplacerCtxKey, repl) + r = r.WithContext(ctx) + return r +} diff --git a/modules/caddyhttp/tracing/tracer.go b/modules/caddyhttp/tracing/tracer.go new file mode 100644 index 00000000000..261952aa65e --- /dev/null +++ b/modules/caddyhttp/tracing/tracer.go @@ -0,0 +1,136 @@ +package tracing + +import ( + "context" + "fmt" + "net/http" + + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/contrib/propagators/autoprop" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.17.0" + "go.opentelemetry.io/otel/trace" + "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +const ( + webEngineName = "Caddy" + defaultSpanName = "handler" + nextCallCtxKey caddy.CtxKey = "nextCall" +) + +// nextCall store the next handler, and the error value return on calling it (if any) +type nextCall struct { + next caddyhttp.Handler + err error +} + +// openTelemetryWrapper is responsible for the tracing injection, extraction and propagation. +type openTelemetryWrapper struct { + propagators propagation.TextMapPropagator + + handler http.Handler + + spanName string +} + +// newOpenTelemetryWrapper is responsible for the openTelemetryWrapper initialization using provided configuration. +func newOpenTelemetryWrapper( + ctx context.Context, + spanName string, +) (openTelemetryWrapper, error) { + if spanName == "" { + spanName = defaultSpanName + } + + ot := openTelemetryWrapper{ + spanName: spanName, + } + + version, _ := caddy.Version() + res, err := ot.newResource(webEngineName, version) + if err != nil { + return ot, fmt.Errorf("creating resource error: %w", err) + } + + traceExporter, err := otlptracegrpc.New(ctx) + if err != nil { + return ot, fmt.Errorf("creating trace exporter error: %w", err) + } + + ot.propagators = autoprop.NewTextMapPropagator() + + tracerProvider := globalTracerProvider.getTracerProvider( + sdktrace.WithBatcher(traceExporter), + sdktrace.WithResource(res), + ) + + ot.handler = otelhttp.NewHandler(http.HandlerFunc(ot.serveHTTP), + ot.spanName, + otelhttp.WithTracerProvider(tracerProvider), + otelhttp.WithPropagators(ot.propagators), + otelhttp.WithSpanNameFormatter(ot.spanNameFormatter), + ) + + return ot, nil +} + +// serveHTTP injects a tracing context and call the next handler. +func (ot *openTelemetryWrapper) serveHTTP(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + ot.propagators.Inject(ctx, propagation.HeaderCarrier(r.Header)) + spanCtx := trace.SpanContextFromContext(ctx) + if spanCtx.IsValid() { + traceID := spanCtx.TraceID().String() + spanID := spanCtx.SpanID().String() + // Add a trace_id placeholder, accessible via `{http.vars.trace_id}`. + caddyhttp.SetVar(ctx, "trace_id", traceID) + // Add a span_id placeholder, accessible via `{http.vars.span_id}`. + caddyhttp.SetVar(ctx, "span_id", spanID) + // Add the traceID and spanID to the log fields for the request. + if extra, ok := ctx.Value(caddyhttp.ExtraLogFieldsCtxKey).(*caddyhttp.ExtraLogFields); ok { + extra.Add(zap.String("traceID", traceID)) + extra.Add(zap.String("spanID", spanID)) + } + } + next := ctx.Value(nextCallCtxKey).(*nextCall) + next.err = next.next.ServeHTTP(w, r) +} + +// ServeHTTP propagates call to the by wrapped by `otelhttp` next handler. +func (ot *openTelemetryWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + n := &nextCall{ + next: next, + err: nil, + } + ot.handler.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), nextCallCtxKey, n))) + + return n.err +} + +// cleanup flush all remaining data and shutdown a tracerProvider +func (ot *openTelemetryWrapper) cleanup(logger *zap.Logger) error { + return globalTracerProvider.cleanupTracerProvider(logger) +} + +// newResource creates a resource that describe current handler instance and merge it with a default attributes value. +func (ot *openTelemetryWrapper) newResource( + webEngineName, + webEngineVersion string, +) (*resource.Resource, error) { + return resource.Merge(resource.Default(), resource.NewSchemaless( + semconv.WebEngineName(webEngineName), + semconv.WebEngineVersion(webEngineVersion), + )) +} + +// spanNameFormatter performs the replacement of placeholders in the span name +func (ot *openTelemetryWrapper) spanNameFormatter(operation string, r *http.Request) string { + return r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer).ReplaceAll(operation, "") +} diff --git a/modules/caddyhttp/tracing/tracer_test.go b/modules/caddyhttp/tracing/tracer_test.go new file mode 100644 index 00000000000..36a32ff46e0 --- /dev/null +++ b/modules/caddyhttp/tracing/tracer_test.go @@ -0,0 +1,27 @@ +package tracing + +import ( + "context" + "testing" + + "github.com/caddyserver/caddy/v2" +) + +func TestOpenTelemetryWrapper_newOpenTelemetryWrapper(t *testing.T) { + ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + + var otw openTelemetryWrapper + var err error + + if otw, err = newOpenTelemetryWrapper(ctx, + "", + ); err != nil { + t.Errorf("newOpenTelemetryWrapper() error = %v", err) + t.FailNow() + } + + if otw.propagators == nil { + t.Errorf("Propagators should not be empty") + } +} diff --git a/modules/caddyhttp/tracing/tracerprovider.go b/modules/caddyhttp/tracing/tracerprovider.go new file mode 100644 index 00000000000..0a376697471 --- /dev/null +++ b/modules/caddyhttp/tracing/tracerprovider.go @@ -0,0 +1,66 @@ +package tracing + +import ( + "context" + "fmt" + "sync" + + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// globalTracerProvider stores global tracer provider and is responsible for graceful shutdown when nobody is using it. +var globalTracerProvider = &tracerProvider{} + +type tracerProvider struct { + mu sync.Mutex + tracerProvider *sdktrace.TracerProvider + tracerProvidersCounter int +} + +// getTracerProvider create or return an existing global TracerProvider +func (t *tracerProvider) getTracerProvider(opts ...sdktrace.TracerProviderOption) *sdktrace.TracerProvider { + t.mu.Lock() + defer t.mu.Unlock() + + t.tracerProvidersCounter++ + + if t.tracerProvider == nil { + t.tracerProvider = sdktrace.NewTracerProvider( + opts..., + ) + } + + return t.tracerProvider +} + +// cleanupTracerProvider gracefully shutdown a TracerProvider +func (t *tracerProvider) cleanupTracerProvider(logger *zap.Logger) error { + t.mu.Lock() + defer t.mu.Unlock() + + if t.tracerProvidersCounter > 0 { + t.tracerProvidersCounter-- + } + + if t.tracerProvidersCounter == 0 { + if t.tracerProvider != nil { + // tracerProvider.ForceFlush SHOULD be invoked according to https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#forceflush + if err := t.tracerProvider.ForceFlush(context.Background()); err != nil { + if c := logger.Check(zapcore.ErrorLevel, "forcing flush"); c != nil { + c.Write(zap.Error(err)) + } + } + + // tracerProvider.Shutdown MUST be invoked according to https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#shutdown + if err := t.tracerProvider.Shutdown(context.Background()); err != nil { + return fmt.Errorf("tracerProvider shutdown error: %w", err) + } + } + + t.tracerProvider = nil + } + + return nil +} diff --git a/modules/caddyhttp/tracing/tracerprovider_test.go b/modules/caddyhttp/tracing/tracerprovider_test.go new file mode 100644 index 00000000000..5a5df0a23f2 --- /dev/null +++ b/modules/caddyhttp/tracing/tracerprovider_test.go @@ -0,0 +1,42 @@ +package tracing + +import ( + "testing" + + "go.uber.org/zap" +) + +func Test_tracersProvider_getTracerProvider(t *testing.T) { + tp := tracerProvider{} + + tp.getTracerProvider() + tp.getTracerProvider() + + if tp.tracerProvider == nil { + t.Errorf("There should be tracer provider") + } + + if tp.tracerProvidersCounter != 2 { + t.Errorf("Tracer providers counter should equal to 2") + } +} + +func Test_tracersProvider_cleanupTracerProvider(t *testing.T) { + tp := tracerProvider{} + + tp.getTracerProvider() + tp.getTracerProvider() + + err := tp.cleanupTracerProvider(zap.NewNop()) + if err != nil { + t.Errorf("There should be no error: %v", err) + } + + if tp.tracerProvider == nil { + t.Errorf("There should be tracer provider") + } + + if tp.tracerProvidersCounter != 1 { + t.Errorf("Tracer providers counter should equal to 1") + } +} diff --git a/modules/caddyhttp/vars.go b/modules/caddyhttp/vars.go new file mode 100644 index 00000000000..7ab891fc046 --- /dev/null +++ b/modules/caddyhttp/vars.go @@ -0,0 +1,456 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddyhttp + +import ( + "context" + "fmt" + "net/http" + "reflect" + "strings" + + "github.com/google/cel-go/cel" + "github.com/google/cel-go/common/types/ref" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func init() { + caddy.RegisterModule(VarsMiddleware{}) + caddy.RegisterModule(VarsMatcher{}) + caddy.RegisterModule(MatchVarsRE{}) +} + +// VarsMiddleware is an HTTP middleware which sets variables to +// have values that can be used in the HTTP request handler +// chain. The primary way to access variables is with placeholders, +// which have the form: `{http.vars.variable_name}`, or with +// the `vars` and `vars_regexp` request matchers. +// +// The key is the variable name, and the value is the value of the +// variable. Both the name and value may use or contain placeholders. +type VarsMiddleware map[string]any + +// CaddyModule returns the Caddy module information. +func (VarsMiddleware) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.vars", + New: func() caddy.Module { return new(VarsMiddleware) }, + } +} + +func (m VarsMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next Handler) error { + vars := r.Context().Value(VarsCtxKey).(map[string]any) + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + for k, v := range m { + keyExpanded := repl.ReplaceAll(k, "") + if valStr, ok := v.(string); ok { + v = repl.ReplaceAll(valStr, "") + } + vars[keyExpanded] = v + + // Special case: the user ID is in the replacer, pulled from there + // for access logs. Allow users to override it with the vars handler. + if keyExpanded == "http.auth.user.id" { + repl.Set(keyExpanded, v) + } + } + return next.ServeHTTP(w, r) +} + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. Syntax: +// +// vars [ ] { +// +// ... +// } +func (m *VarsMiddleware) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume directive name + + if *m == nil { + *m = make(VarsMiddleware) + } + + nextVar := func(headerLine bool) error { + if headerLine { + // header line is optional + if !d.NextArg() { + return nil + } + } + varName := d.Val() + + if !d.NextArg() { + return d.ArgErr() + } + varValue := d.ScalarVal() + + (*m)[varName] = varValue + + if d.NextArg() { + return d.ArgErr() + } + return nil + } + + if err := nextVar(true); err != nil { + return err + } + for d.NextBlock(0) { + if err := nextVar(false); err != nil { + return err + } + } + + return nil +} + +// VarsMatcher is an HTTP request matcher which can match +// requests based on variables in the context or placeholder +// values. The key is the placeholder or name of the variable, +// and the values are possible values the variable can be in +// order to match (logical OR'ed). +// +// If the key is surrounded by `{ }` it is assumed to be a +// placeholder. Otherwise, it will be considered a variable +// name. +// +// Placeholders in the keys are not expanded, but +// placeholders in the values are. +type VarsMatcher map[string][]string + +// CaddyModule returns the Caddy module information. +func (VarsMatcher) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.matchers.vars", + New: func() caddy.Module { return new(VarsMatcher) }, + } +} + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. +func (m *VarsMatcher) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + if *m == nil { + *m = make(map[string][]string) + } + // iterate to merge multiple matchers into one + for d.Next() { + var field string + if !d.Args(&field) { + return d.Errf("malformed vars matcher: expected field name") + } + vals := d.RemainingArgs() + if len(vals) == 0 { + return d.Errf("malformed vars matcher: expected at least one value to match against") + } + (*m)[field] = append((*m)[field], vals...) + if d.NextBlock(0) { + return d.Err("malformed vars matcher: blocks are not supported") + } + } + return nil +} + +// Match matches a request based on variables in the context, +// or placeholders if the key is not a variable. +func (m VarsMatcher) Match(r *http.Request) bool { + match, _ := m.MatchWithError(r) + return match +} + +// MatchWithError returns true if r matches m. +func (m VarsMatcher) MatchWithError(r *http.Request) (bool, error) { + if len(m) == 0 { + return true, nil + } + + vars := r.Context().Value(VarsCtxKey).(map[string]any) + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + + for key, vals := range m { + var varValue any + if strings.HasPrefix(key, "{") && + strings.HasSuffix(key, "}") && + strings.Count(key, "{") == 1 { + varValue, _ = repl.Get(strings.Trim(key, "{}")) + } else { + varValue = vars[key] + } + + // see if any of the values given in the matcher match the actual value + for _, v := range vals { + matcherValExpanded := repl.ReplaceAll(v, "") + var varStr string + switch vv := varValue.(type) { + case string: + varStr = vv + case fmt.Stringer: + varStr = vv.String() + case error: + varStr = vv.Error() + case nil: + varStr = "" + default: + varStr = fmt.Sprintf("%v", vv) + } + if varStr == matcherValExpanded { + return true, nil + } + } + } + return false, nil +} + +// CELLibrary produces options that expose this matcher for use in CEL +// expression matchers. +// +// Example: +// +// expression vars({'{magic_number}': ['3', '5']}) +// expression vars({'{foo}': 'single_value'}) +func (VarsMatcher) CELLibrary(_ caddy.Context) (cel.Library, error) { + return CELMatcherImpl( + "vars", + "vars_matcher_request_map", + []*cel.Type{CELTypeJSON}, + func(data ref.Val) (RequestMatcherWithError, error) { + mapStrListStr, err := CELValueToMapStrList(data) + if err != nil { + return nil, err + } + return VarsMatcher(mapStrListStr), nil + }, + ) +} + +// MatchVarsRE matches the value of the context variables by a given regular expression. +// +// Upon a match, it adds placeholders to the request: `{http.regexp.name.capture_group}` +// where `name` is the regular expression's name, and `capture_group` is either +// the named or positional capture group from the expression itself. If no name +// is given, then the placeholder omits the name: `{http.regexp.capture_group}` +// (potentially leading to collisions). +type MatchVarsRE map[string]*MatchRegexp + +// CaddyModule returns the Caddy module information. +func (MatchVarsRE) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.matchers.vars_regexp", + New: func() caddy.Module { return new(MatchVarsRE) }, + } +} + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. +func (m *MatchVarsRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + if *m == nil { + *m = make(map[string]*MatchRegexp) + } + // iterate to merge multiple matchers into one + for d.Next() { + var first, second, third string + if !d.Args(&first, &second) { + return d.ArgErr() + } + + var name, field, val string + if d.Args(&third) { + name = first + field = second + val = third + } else { + field = first + val = second + } + + // Default to the named matcher's name, if no regexp name is provided + if name == "" { + name = d.GetContextString(caddyfile.MatcherNameCtxKey) + } + + (*m)[field] = &MatchRegexp{Pattern: val, Name: name} + if d.NextBlock(0) { + return d.Err("malformed vars_regexp matcher: blocks are not supported") + } + } + return nil +} + +// Provision compiles m's regular expressions. +func (m MatchVarsRE) Provision(ctx caddy.Context) error { + for _, rm := range m { + err := rm.Provision(ctx) + if err != nil { + return err + } + } + return nil +} + +// Match returns true if r matches m. +func (m MatchVarsRE) Match(r *http.Request) bool { + match, _ := m.MatchWithError(r) + return match +} + +// MatchWithError returns true if r matches m. +func (m MatchVarsRE) MatchWithError(r *http.Request) (bool, error) { + vars := r.Context().Value(VarsCtxKey).(map[string]any) + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + for key, val := range m { + var varValue any + if strings.HasPrefix(key, "{") && + strings.HasSuffix(key, "}") && + strings.Count(key, "{") == 1 { + varValue, _ = repl.Get(strings.Trim(key, "{}")) + } else { + varValue = vars[key] + } + + var varStr string + switch vv := varValue.(type) { + case string: + varStr = vv + case fmt.Stringer: + varStr = vv.String() + case error: + varStr = vv.Error() + case nil: + varStr = "" + default: + varStr = fmt.Sprintf("%v", vv) + } + + valExpanded := repl.ReplaceAll(varStr, "") + if match := val.Match(valExpanded, repl); match { + return match, nil + } + } + return false, nil +} + +// CELLibrary produces options that expose this matcher for use in CEL +// expression matchers. +// +// Example: +// +// expression vars_regexp('foo', '{magic_number}', '[0-9]+') +// expression vars_regexp('{magic_number}', '[0-9]+') +func (MatchVarsRE) CELLibrary(ctx caddy.Context) (cel.Library, error) { + unnamedPattern, err := CELMatcherImpl( + "vars_regexp", + "vars_regexp_request_string_string", + []*cel.Type{cel.StringType, cel.StringType}, + func(data ref.Val) (RequestMatcherWithError, error) { + refStringList := reflect.TypeOf([]string{}) + params, err := data.ConvertToNative(refStringList) + if err != nil { + return nil, err + } + strParams := params.([]string) + matcher := MatchVarsRE{} + matcher[strParams[0]] = &MatchRegexp{ + Pattern: strParams[1], + Name: ctx.Value(MatcherNameCtxKey).(string), + } + err = matcher.Provision(ctx) + return matcher, err + }, + ) + if err != nil { + return nil, err + } + namedPattern, err := CELMatcherImpl( + "vars_regexp", + "vars_regexp_request_string_string_string", + []*cel.Type{cel.StringType, cel.StringType, cel.StringType}, + func(data ref.Val) (RequestMatcherWithError, error) { + refStringList := reflect.TypeOf([]string{}) + params, err := data.ConvertToNative(refStringList) + if err != nil { + return nil, err + } + strParams := params.([]string) + name := strParams[0] + if name == "" { + name = ctx.Value(MatcherNameCtxKey).(string) + } + matcher := MatchVarsRE{} + matcher[strParams[1]] = &MatchRegexp{ + Pattern: strParams[2], + Name: name, + } + err = matcher.Provision(ctx) + return matcher, err + }, + ) + if err != nil { + return nil, err + } + envOpts := append(unnamedPattern.CompileOptions(), namedPattern.CompileOptions()...) + prgOpts := append(unnamedPattern.ProgramOptions(), namedPattern.ProgramOptions()...) + return NewMatcherCELLibrary(envOpts, prgOpts), nil +} + +// Validate validates m's regular expressions. +func (m MatchVarsRE) Validate() error { + for _, rm := range m { + err := rm.Validate() + if err != nil { + return err + } + } + return nil +} + +// GetVar gets a value out of the context's variable table by key. +// If the key does not exist, the return value will be nil. +func GetVar(ctx context.Context, key string) any { + varMap, ok := ctx.Value(VarsCtxKey).(map[string]any) + if !ok { + return nil + } + return varMap[key] +} + +// SetVar sets a value in the context's variable table with +// the given key. It overwrites any previous value with the +// same key. +// +// If the value is nil (note: non-nil interface with nil +// underlying value does not count) and the key exists in +// the table, the key+value will be deleted from the table. +func SetVar(ctx context.Context, key string, value any) { + varMap, ok := ctx.Value(VarsCtxKey).(map[string]any) + if !ok { + return + } + if value == nil { + if _, ok := varMap[key]; ok { + delete(varMap, key) + return + } + } + varMap[key] = value +} + +// Interface guards +var ( + _ MiddlewareHandler = (*VarsMiddleware)(nil) + _ caddyfile.Unmarshaler = (*VarsMiddleware)(nil) + _ RequestMatcherWithError = (*VarsMatcher)(nil) + _ caddyfile.Unmarshaler = (*VarsMatcher)(nil) + _ RequestMatcherWithError = (*MatchVarsRE)(nil) + _ caddyfile.Unmarshaler = (*MatchVarsRE)(nil) +) diff --git a/modules/caddypki/acmeserver/acmeserver.go b/modules/caddypki/acmeserver/acmeserver.go new file mode 100644 index 00000000000..aeb4eab8ec3 --- /dev/null +++ b/modules/caddypki/acmeserver/acmeserver.go @@ -0,0 +1,355 @@ +// 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 acmeserver + +import ( + "context" + "fmt" + weakrand "math/rand" + "net" + "net/http" + "os" + "path/filepath" + "regexp" + "strings" + "time" + + "github.com/go-chi/chi/v5" + "github.com/smallstep/certificates/acme" + "github.com/smallstep/certificates/acme/api" + acmeNoSQL "github.com/smallstep/certificates/acme/db/nosql" + "github.com/smallstep/certificates/authority" + "github.com/smallstep/certificates/authority/provisioner" + "github.com/smallstep/certificates/db" + "github.com/smallstep/nosql" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddypki" +) + +func init() { + caddy.RegisterModule(Handler{}) +} + +// Handler is an ACME server handler. +type Handler struct { + // The ID of the CA to use for signing. This refers to + // the ID given to the CA in the `pki` app. If omitted, + // the default ID is "local". + CA string `json:"ca,omitempty"` + + // The lifetime for issued certificates + Lifetime caddy.Duration `json:"lifetime,omitempty"` + + // The hostname or IP address by which ACME clients + // will access the server. This is used to populate + // the ACME directory endpoint. If not set, the Host + // header of the request will be used. + // COMPATIBILITY NOTE / TODO: This property may go away in the + // future. Do not rely on this property long-term; check release notes. + Host string `json:"host,omitempty"` + + // The path prefix under which to serve all ACME + // endpoints. All other requests will not be served + // by this handler and will be passed through to + // the next one. Default: "/acme/". + // COMPATIBILITY NOTE / TODO: This property may go away in the + // future, as it is currently only required due to + // limitations in the underlying library. Do not rely + // on this property long-term; check release notes. + PathPrefix string `json:"path_prefix,omitempty"` + + // If true, the CA's root will be the issuer instead of + // the intermediate. This is NOT recommended and should + // only be used when devices/clients do not properly + // validate certificate chains. EXPERIMENTAL: Might be + // changed or removed in the future. + SignWithRoot bool `json:"sign_with_root,omitempty"` + + // The addresses of DNS resolvers to use when looking up + // the TXT records for solving DNS challenges. + // It accepts [network addresses](/docs/conventions#network-addresses) + // with port range of only 1. If the host is an IP address, + // it will be dialed directly to resolve the upstream server. + // If the host is not an IP address, the addresses are resolved + // using the [name resolution convention](https://golang.org/pkg/net/#hdr-Name_Resolution) + // of the Go standard library. If the array contains more + // than 1 resolver address, one is chosen at random. + Resolvers []string `json:"resolvers,omitempty"` + + // Specify the set of enabled ACME challenges. An empty or absent value + // means all challenges are enabled. Accepted values are: + // "http-01", "dns-01", "tls-alpn-01" + Challenges ACMEChallenges `json:"challenges,omitempty" ` + + // The policy to use for issuing certificates + Policy *Policy `json:"policy,omitempty"` + + logger *zap.Logger + resolvers []caddy.NetworkAddress + ctx caddy.Context + + acmeDB acme.DB + acmeAuth *authority.Authority + acmeClient acme.Client + acmeLinker acme.Linker + acmeEndpoints http.Handler +} + +// CaddyModule returns the Caddy module information. +func (Handler) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.acme_server", + New: func() caddy.Module { return new(Handler) }, + } +} + +// Provision sets up the ACME server handler. +func (ash *Handler) Provision(ctx caddy.Context) error { + ash.ctx = ctx + ash.logger = ctx.Logger() + + // set some defaults + if ash.CA == "" { + ash.CA = caddypki.DefaultCAID + } + if ash.PathPrefix == "" { + ash.PathPrefix = defaultPathPrefix + } + if ash.Lifetime == 0 { + ash.Lifetime = caddy.Duration(12 * time.Hour) + } + if len(ash.Challenges) > 0 { + if err := ash.Challenges.validate(); err != nil { + return err + } + } + + // get a reference to the configured CA + appModule, err := ctx.App("pki") + if err != nil { + return err + } + pkiApp := appModule.(*caddypki.PKI) + ca, err := pkiApp.GetCA(ctx, ash.CA) + if err != nil { + return err + } + + // make sure leaf cert lifetime is less than the intermediate cert lifetime. this check only + // applies for caddy-managed intermediate certificates + if ca.Intermediate == nil && ash.Lifetime >= ca.IntermediateLifetime { + return fmt.Errorf("certificate lifetime (%s) should be less than intermediate certificate lifetime (%s)", time.Duration(ash.Lifetime), time.Duration(ca.IntermediateLifetime)) + } + + database, err := ash.openDatabase() + if err != nil { + return err + } + + authorityConfig := caddypki.AuthorityConfig{ + SignWithRoot: ash.SignWithRoot, + AuthConfig: &authority.AuthConfig{ + Provisioners: provisioner.List{ + &provisioner.ACME{ + Name: ash.CA, + Challenges: ash.Challenges.toSmallstepType(), + Options: &provisioner.Options{ + X509: ash.Policy.normalizeRules(), + }, + Type: provisioner.TypeACME.String(), + Claims: &provisioner.Claims{ + MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute}, + MaxTLSDur: &provisioner.Duration{Duration: 24 * time.Hour * 365}, + DefaultTLSDur: &provisioner.Duration{Duration: time.Duration(ash.Lifetime)}, + }, + }, + }, + }, + DB: database, + } + + ash.acmeAuth, err = ca.NewAuthority(authorityConfig) + if err != nil { + return err + } + + ash.acmeDB, err = acmeNoSQL.New(ash.acmeAuth.GetDatabase().(nosql.DB)) + if err != nil { + return fmt.Errorf("configuring ACME DB: %v", err) + } + + ash.acmeClient, err = ash.makeClient() + if err != nil { + return err + } + + ash.acmeLinker = acme.NewLinker( + ash.Host, + strings.Trim(ash.PathPrefix, "/"), + ) + + // extract its http.Handler so we can use it directly + r := chi.NewRouter() + r.Route(ash.PathPrefix, func(r chi.Router) { + api.Route(r) + }) + ash.acmeEndpoints = r + + return nil +} + +func (ash Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + if strings.HasPrefix(r.URL.Path, ash.PathPrefix) { + acmeCtx := acme.NewContext( + r.Context(), + ash.acmeDB, + ash.acmeClient, + ash.acmeLinker, + nil, + ) + acmeCtx = authority.NewContext(acmeCtx, ash.acmeAuth) + r = r.WithContext(acmeCtx) + + ash.acmeEndpoints.ServeHTTP(w, r) + return nil + } + return next.ServeHTTP(w, r) +} + +func (ash Handler) getDatabaseKey() string { + key := ash.CA + key = strings.ToLower(key) + key = strings.TrimSpace(key) + return keyCleaner.ReplaceAllLiteralString(key, "") +} + +// Cleanup implements caddy.CleanerUpper and closes any idle databases. +func (ash Handler) Cleanup() error { + key := ash.getDatabaseKey() + deleted, err := databasePool.Delete(key) + if deleted { + if c := ash.logger.Check(zapcore.DebugLevel, "unloading unused CA database"); c != nil { + c.Write(zap.String("db_key", key)) + } + } + if err != nil { + if c := ash.logger.Check(zapcore.ErrorLevel, "closing CA database"); c != nil { + c.Write(zap.String("db_key", key), zap.Error(err)) + } + } + return err +} + +func (ash Handler) openDatabase() (*db.AuthDB, error) { + key := ash.getDatabaseKey() + database, loaded, err := databasePool.LoadOrNew(key, func() (caddy.Destructor, error) { + dbFolder := filepath.Join(caddy.AppDataDir(), "acme_server", key) + dbPath := filepath.Join(dbFolder, "db") + + err := os.MkdirAll(dbFolder, 0o755) + if err != nil { + return nil, fmt.Errorf("making folder for CA database: %v", err) + } + + dbConfig := &db.Config{ + Type: "bbolt", + DataSource: dbPath, + } + database, err := db.New(dbConfig) + return databaseCloser{&database}, err + }) + + if loaded { + if c := ash.logger.Check(zapcore.DebugLevel, "loaded preexisting CA database"); c != nil { + c.Write(zap.String("db_key", key)) + } + } + + return database.(databaseCloser).DB, err +} + +// makeClient creates an ACME client which will use a custom +// resolver instead of net.DefaultResolver. +func (ash Handler) makeClient() (acme.Client, error) { + for _, v := range ash.Resolvers { + addr, err := caddy.ParseNetworkAddressWithDefaults(v, "udp", 53) + if err != nil { + return nil, err + } + if addr.PortRangeSize() != 1 { + return nil, fmt.Errorf("resolver address must have exactly one address; cannot call %v", addr) + } + ash.resolvers = append(ash.resolvers, addr) + } + + var resolver *net.Resolver + if len(ash.resolvers) != 0 { + dialer := &net.Dialer{ + Timeout: 2 * time.Second, + } + resolver = &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + //nolint:gosec + addr := ash.resolvers[weakrand.Intn(len(ash.resolvers))] + return dialer.DialContext(ctx, addr.Network, addr.JoinHostPort(0)) + }, + } + } else { + resolver = net.DefaultResolver + } + + return resolverClient{ + Client: acme.NewClient(), + resolver: resolver, + ctx: ash.ctx, + }, nil +} + +type resolverClient struct { + acme.Client + + resolver *net.Resolver + ctx context.Context +} + +func (c resolverClient) LookupTxt(name string) ([]string, error) { + return c.resolver.LookupTXT(c.ctx, name) +} + +const defaultPathPrefix = "/acme/" + +var ( + keyCleaner = regexp.MustCompile(`[^\w.-_]`) + databasePool = caddy.NewUsagePool() +) + +type databaseCloser struct { + DB *db.AuthDB +} + +func (closer databaseCloser) Destruct() error { + return (*closer.DB).Shutdown() +} + +// Interface guards +var ( + _ caddyhttp.MiddlewareHandler = (*Handler)(nil) + _ caddy.Provisioner = (*Handler)(nil) +) diff --git a/modules/caddypki/acmeserver/caddyfile.go b/modules/caddypki/acmeserver/caddyfile.go new file mode 100644 index 00000000000..c4d11112861 --- /dev/null +++ b/modules/caddypki/acmeserver/caddyfile.go @@ -0,0 +1,160 @@ +// 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 acmeserver + +import ( + "time" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddypki" +) + +func init() { + httpcaddyfile.RegisterDirective("acme_server", parseACMEServer) +} + +// parseACMEServer sets up an ACME server handler from Caddyfile tokens. +// +// acme_server [] { +// ca +// lifetime +// resolvers +// challenges +// allow_wildcard_names +// allow { +// domains +// ip_ranges +// } +// deny { +// domains +// ip_ranges +// } +// sign_with_root +// } +func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) { + h.Next() // consume directive name + matcherSet, err := h.ExtractMatcherSet() + if err != nil { + return nil, err + } + h.Next() // consume the directive name again (matcher parsing resets) + + // no inline args allowed + if h.NextArg() { + return nil, h.ArgErr() + } + + var acmeServer Handler + var ca *caddypki.CA + + for h.NextBlock(0) { + switch h.Val() { + case "ca": + if !h.AllArgs(&acmeServer.CA) { + return nil, h.ArgErr() + } + if ca == nil { + ca = new(caddypki.CA) + } + ca.ID = acmeServer.CA + case "lifetime": + if !h.NextArg() { + return nil, h.ArgErr() + } + + dur, err := caddy.ParseDuration(h.Val()) + if err != nil { + return nil, err + } + if d := time.Duration(ca.IntermediateLifetime); d > 0 && dur > d { + return nil, h.Errf("certificate lifetime (%s) exceeds intermediate certificate lifetime (%s)", dur, d) + } + acmeServer.Lifetime = caddy.Duration(dur) + case "resolvers": + acmeServer.Resolvers = h.RemainingArgs() + if len(acmeServer.Resolvers) == 0 { + return nil, h.Errf("must specify at least one resolver address") + } + case "challenges": + acmeServer.Challenges = append(acmeServer.Challenges, stringToChallenges(h.RemainingArgs())...) + case "allow_wildcard_names": + if acmeServer.Policy == nil { + acmeServer.Policy = &Policy{} + } + acmeServer.Policy.AllowWildcardNames = true + case "allow": + r := &RuleSet{} + for h.Next() { + for h.NextBlock(h.Nesting() - 1) { + if h.CountRemainingArgs() == 0 { + return nil, h.ArgErr() // TODO: + } + switch h.Val() { + case "domains": + r.Domains = append(r.Domains, h.RemainingArgs()...) + case "ip_ranges": + r.IPRanges = append(r.IPRanges, h.RemainingArgs()...) + default: + return nil, h.Errf("unrecognized 'allow' subdirective: %s", h.Val()) + } + } + } + if acmeServer.Policy == nil { + acmeServer.Policy = &Policy{} + } + acmeServer.Policy.Allow = r + case "deny": + r := &RuleSet{} + for h.Next() { + for h.NextBlock(h.Nesting() - 1) { + if h.CountRemainingArgs() == 0 { + return nil, h.ArgErr() // TODO: + } + switch h.Val() { + case "domains": + r.Domains = append(r.Domains, h.RemainingArgs()...) + case "ip_ranges": + r.IPRanges = append(r.IPRanges, h.RemainingArgs()...) + default: + return nil, h.Errf("unrecognized 'deny' subdirective: %s", h.Val()) + } + } + } + if acmeServer.Policy == nil { + acmeServer.Policy = &Policy{} + } + acmeServer.Policy.Deny = r + case "sign_with_root": + if h.NextArg() { + return nil, h.ArgErr() + } + acmeServer.SignWithRoot = true + default: + return nil, h.Errf("unrecognized ACME server directive: %s", h.Val()) + } + } + + configVals := h.NewRoute(matcherSet, acmeServer) + + if ca == nil { + return configVals, nil + } + + return append(configVals, httpcaddyfile.ConfigValue{ + Class: "pki.ca", + Value: ca, + }), nil +} diff --git a/modules/caddypki/acmeserver/challenges.go b/modules/caddypki/acmeserver/challenges.go new file mode 100644 index 00000000000..728a7492813 --- /dev/null +++ b/modules/caddypki/acmeserver/challenges.go @@ -0,0 +1,77 @@ +package acmeserver + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/smallstep/certificates/authority/provisioner" +) + +// ACMEChallenge is an opaque string that represents supported ACME challenges. +type ACMEChallenge string + +const ( + HTTP_01 ACMEChallenge = "http-01" + DNS_01 ACMEChallenge = "dns-01" + TLS_ALPN_01 ACMEChallenge = "tls-alpn-01" +) + +// validate checks if the given challenge is supported. +func (c ACMEChallenge) validate() error { + switch c { + case HTTP_01, DNS_01, TLS_ALPN_01: + return nil + default: + return fmt.Errorf("acme challenge %q is not supported", c) + } +} + +// The unmarshaller first marshals the value into a string. Then it +// trims any space around it and lowercase it for normaliztion. The +// method does not and should not validate the value within accepted enums. +func (c *ACMEChallenge) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + *c = ACMEChallenge(strings.ToLower(strings.TrimSpace(s))) + return nil +} + +// String returns a string representation of the challenge. +func (c ACMEChallenge) String() string { + return strings.ToLower(string(c)) +} + +// ACMEChallenges is a list of ACME challenges. +type ACMEChallenges []ACMEChallenge + +// validate checks if the given challenges are supported. +func (c ACMEChallenges) validate() error { + for _, ch := range c { + if err := ch.validate(); err != nil { + return err + } + } + return nil +} + +func (c ACMEChallenges) toSmallstepType() []provisioner.ACMEChallenge { + if len(c) == 0 { + return nil + } + ac := make([]provisioner.ACMEChallenge, len(c)) + for i, ch := range c { + ac[i] = provisioner.ACMEChallenge(ch) + } + return ac +} + +func stringToChallenges(chs []string) ACMEChallenges { + challenges := make(ACMEChallenges, len(chs)) + for i, ch := range chs { + challenges[i] = ACMEChallenge(ch) + } + return challenges +} diff --git a/modules/caddypki/acmeserver/policy.go b/modules/caddypki/acmeserver/policy.go new file mode 100644 index 00000000000..96137e6e642 --- /dev/null +++ b/modules/caddypki/acmeserver/policy.go @@ -0,0 +1,83 @@ +package acmeserver + +import ( + "github.com/smallstep/certificates/authority/policy" + "github.com/smallstep/certificates/authority/provisioner" +) + +// Policy defines the criteria for the ACME server +// of when to issue a certificate. Refer to the +// [Certificate Issuance Policy](https://smallstep.com/docs/step-ca/policies/) +// on Smallstep website for the evaluation criteria. +type Policy struct { + // If a rule set is configured to allow a certain type of name, + // all other types of names are automatically denied. + Allow *RuleSet `json:"allow,omitempty"` + + // If a rule set is configured to deny a certain type of name, + // all other types of names are still allowed. + Deny *RuleSet `json:"deny,omitempty"` + + // If set to true, the ACME server will allow issuing wildcard certificates. + AllowWildcardNames bool `json:"allow_wildcard_names,omitempty"` +} + +// RuleSet is the specific set of SAN criteria for a certificate +// to be issued or denied. +type RuleSet struct { + // Domains is a list of DNS domains that are allowed to be issued. + // It can be in the form of FQDN for specific domain name, or + // a wildcard domain name format, e.g. *.example.com, to allow + // sub-domains of a domain. + Domains []string `json:"domains,omitempty"` + + // IP ranges in the form of CIDR notation or specific IP addresses + // to be approved or denied for certificates. Non-CIDR IP addresses + // are matched exactly. + IPRanges []string `json:"ip_ranges,omitempty"` +} + +// normalizeAllowRules returns `nil` if policy is nil, the `Allow` rule is `nil`, +// or all rules within the `Allow` rule are empty. Otherwise, it returns the X509NameOptions +// with the content of the `Allow` rule. +func (p *Policy) normalizeAllowRules() *policy.X509NameOptions { + if (p == nil) || (p.Allow == nil) || (len(p.Allow.Domains) == 0 && len(p.Allow.IPRanges) == 0) { + return nil + } + return &policy.X509NameOptions{ + DNSDomains: p.Allow.Domains, + IPRanges: p.Allow.IPRanges, + } +} + +// normalizeDenyRules returns `nil` if policy is nil, the `Deny` rule is `nil`, +// or all rules within the `Deny` rule are empty. Otherwise, it returns the X509NameOptions +// with the content of the `Deny` rule. +func (p *Policy) normalizeDenyRules() *policy.X509NameOptions { + if (p == nil) || (p.Deny == nil) || (len(p.Deny.Domains) == 0 && len(p.Deny.IPRanges) == 0) { + return nil + } + return &policy.X509NameOptions{ + DNSDomains: p.Deny.Domains, + IPRanges: p.Deny.IPRanges, + } +} + +// normalizeRules returns `nil` if policy is nil, the `Allow` and `Deny` rules are `nil`, +func (p *Policy) normalizeRules() *provisioner.X509Options { + if p == nil { + return nil + } + + allow := p.normalizeAllowRules() + deny := p.normalizeDenyRules() + if allow == nil && deny == nil && !p.AllowWildcardNames { + return nil + } + + return &provisioner.X509Options{ + AllowedNames: allow, + DeniedNames: deny, + AllowWildcardNames: p.AllowWildcardNames, + } +} diff --git a/modules/caddypki/acmeserver/policy_test.go b/modules/caddypki/acmeserver/policy_test.go new file mode 100644 index 00000000000..02d7856d970 --- /dev/null +++ b/modules/caddypki/acmeserver/policy_test.go @@ -0,0 +1,176 @@ +package acmeserver + +import ( + "reflect" + "testing" + + "github.com/smallstep/certificates/authority/policy" + "github.com/smallstep/certificates/authority/provisioner" +) + +func TestPolicyNormalizeAllowRules(t *testing.T) { + type fields struct { + Allow *RuleSet + Deny *RuleSet + AllowWildcardNames bool + } + tests := []struct { + name string + fields fields + want *policy.X509NameOptions + }{ + { + name: "providing no rules results in 'nil'", + fields: fields{}, + want: nil, + }, + { + name: "providing 'nil' Allow rules results in 'nil', regardless of Deny rules", + fields: fields{ + Allow: nil, + Deny: &RuleSet{}, + AllowWildcardNames: true, + }, + want: nil, + }, + { + name: "providing empty Allow rules results in 'nil', regardless of Deny rules", + fields: fields{ + Allow: &RuleSet{ + Domains: []string{}, + IPRanges: []string{}, + }, + }, + want: nil, + }, + { + name: "rules configured in Allow are returned in X509NameOptions", + fields: fields{ + Allow: &RuleSet{ + Domains: []string{"example.com"}, + IPRanges: []string{"127.0.0.1/32"}, + }, + }, + want: &policy.X509NameOptions{ + DNSDomains: []string{"example.com"}, + IPRanges: []string{"127.0.0.1/32"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &Policy{ + Allow: tt.fields.Allow, + Deny: tt.fields.Deny, + AllowWildcardNames: tt.fields.AllowWildcardNames, + } + if got := p.normalizeAllowRules(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Policy.normalizeAllowRules() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestPolicy_normalizeDenyRules(t *testing.T) { + type fields struct { + Allow *RuleSet + Deny *RuleSet + AllowWildcardNames bool + } + tests := []struct { + name string + fields fields + want *policy.X509NameOptions + }{ + { + name: "providing no rules results in 'nil'", + fields: fields{}, + want: nil, + }, + { + name: "providing 'nil' Deny rules results in 'nil', regardless of Allow rules", + fields: fields{ + Deny: nil, + Allow: &RuleSet{}, + AllowWildcardNames: true, + }, + want: nil, + }, + { + name: "providing empty Deny rules results in 'nil', regardless of Allow rules", + fields: fields{ + Deny: &RuleSet{ + Domains: []string{}, + IPRanges: []string{}, + }, + }, + want: nil, + }, + { + name: "rules configured in Deny are returned in X509NameOptions", + fields: fields{ + Deny: &RuleSet{ + Domains: []string{"example.com"}, + IPRanges: []string{"127.0.0.1/32"}, + }, + }, + want: &policy.X509NameOptions{ + DNSDomains: []string{"example.com"}, + IPRanges: []string{"127.0.0.1/32"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &Policy{ + Allow: tt.fields.Allow, + Deny: tt.fields.Deny, + AllowWildcardNames: tt.fields.AllowWildcardNames, + } + if got := p.normalizeDenyRules(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Policy.normalizeDenyRules() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestPolicy_normalizeRules(t *testing.T) { + tests := []struct { + name string + policy *Policy + want *provisioner.X509Options + }{ + { + name: "'nil' policy results in 'nil' options", + policy: nil, + want: nil, + }, + { + name: "'nil' Allow/Deny rules and disallowing wildcard names result in 'nil' X509Options", + policy: &Policy{ + Allow: nil, + Deny: nil, + AllowWildcardNames: false, + }, + want: nil, + }, + { + name: "'nil' Allow/Deny rules and allowing wildcard names result in 'nil' Allow/Deny rules in X509Options but allowing wildcard names in X509Options", + policy: &Policy{ + Allow: nil, + Deny: nil, + AllowWildcardNames: true, + }, + want: &provisioner.X509Options{ + AllowWildcardNames: true, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.policy.normalizeRules(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Policy.normalizeRules() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/modules/caddypki/adminapi.go b/modules/caddypki/adminapi.go new file mode 100644 index 00000000000..c454f645854 --- /dev/null +++ b/modules/caddypki/adminapi.go @@ -0,0 +1,249 @@ +// Copyright 2020 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 caddypki + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + + "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2" +) + +func init() { + caddy.RegisterModule(adminAPI{}) +} + +// adminAPI is a module that serves PKI endpoints to retrieve +// information about the CAs being managed by Caddy. +type adminAPI struct { + ctx caddy.Context + log *zap.Logger + pkiApp *PKI +} + +// CaddyModule returns the Caddy module information. +func (adminAPI) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "admin.api.pki", + New: func() caddy.Module { return new(adminAPI) }, + } +} + +// Provision sets up the adminAPI module. +func (a *adminAPI) Provision(ctx caddy.Context) error { + a.ctx = ctx + a.log = ctx.Logger(a) // TODO: passing in 'a' is a hack until the admin API is officially extensible (see #5032) + + // Avoid initializing PKI if it wasn't configured. + // We intentionally ignore the error since it's not + // fatal if the PKI app is not explicitly configured. + pkiApp, err := ctx.AppIfConfigured("pki") + if err == nil { + a.pkiApp = pkiApp.(*PKI) + } + + return nil +} + +// Routes returns the admin routes for the PKI app. +func (a *adminAPI) Routes() []caddy.AdminRoute { + return []caddy.AdminRoute{ + { + Pattern: adminPKIEndpointBase, + Handler: caddy.AdminHandlerFunc(a.handleAPIEndpoints), + }, + } +} + +// handleAPIEndpoints routes API requests within adminPKIEndpointBase. +func (a *adminAPI) handleAPIEndpoints(w http.ResponseWriter, r *http.Request) error { + uri := strings.TrimPrefix(r.URL.Path, "/pki/") + parts := strings.Split(uri, "/") + switch { + case len(parts) == 2 && parts[0] == "ca" && parts[1] != "": + return a.handleCAInfo(w, r) + case len(parts) == 3 && parts[0] == "ca" && parts[1] != "" && parts[2] == "certificates": + return a.handleCACerts(w, r) + } + return caddy.APIError{ + HTTPStatus: http.StatusNotFound, + Err: fmt.Errorf("resource not found: %v", r.URL.Path), + } +} + +// handleCAInfo returns information about a particular +// CA by its ID. If the CA ID is the default, then the CA will be +// provisioned if it has not already been. Other CA IDs will return an +// error if they have not been previously provisioned. +func (a *adminAPI) handleCAInfo(w http.ResponseWriter, r *http.Request) error { + if r.Method != http.MethodGet { + return caddy.APIError{ + HTTPStatus: http.StatusMethodNotAllowed, + Err: fmt.Errorf("method not allowed: %v", r.Method), + } + } + + ca, err := a.getCAFromAPIRequestPath(r) + if err != nil { + return err + } + + rootCert, interCert, err := rootAndIntermediatePEM(ca) + if err != nil { + return caddy.APIError{ + HTTPStatus: http.StatusInternalServerError, + Err: fmt.Errorf("failed to get root and intermediate cert for CA %s: %v", ca.ID, err), + } + } + + repl := ca.newReplacer() + + response := caInfo{ + ID: ca.ID, + Name: ca.Name, + RootCN: repl.ReplaceAll(ca.RootCommonName, ""), + IntermediateCN: repl.ReplaceAll(ca.IntermediateCommonName, ""), + RootCert: string(rootCert), + IntermediateCert: string(interCert), + } + + encoded, err := json.Marshal(response) + if err != nil { + return caddy.APIError{ + HTTPStatus: http.StatusInternalServerError, + Err: err, + } + } + + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(encoded) + + return nil +} + +// handleCACerts returns the certificate chain for a particular +// CA by its ID. If the CA ID is the default, then the CA will be +// provisioned if it has not already been. Other CA IDs will return an +// error if they have not been previously provisioned. +func (a *adminAPI) handleCACerts(w http.ResponseWriter, r *http.Request) error { + if r.Method != http.MethodGet { + return caddy.APIError{ + HTTPStatus: http.StatusMethodNotAllowed, + Err: fmt.Errorf("method not allowed: %v", r.Method), + } + } + + ca, err := a.getCAFromAPIRequestPath(r) + if err != nil { + return err + } + + rootCert, interCert, err := rootAndIntermediatePEM(ca) + if err != nil { + return caddy.APIError{ + HTTPStatus: http.StatusInternalServerError, + Err: fmt.Errorf("failed to get root and intermediate cert for CA %s: %v", ca.ID, err), + } + } + + w.Header().Set("Content-Type", "application/pem-certificate-chain") + _, err = w.Write(interCert) + if err == nil { + _, _ = w.Write(rootCert) + } + + return nil +} + +func (a *adminAPI) getCAFromAPIRequestPath(r *http.Request) (*CA, error) { + // Grab the CA ID from the request path, it should be the 4th segment (/pki/ca/) + id := strings.Split(r.URL.Path, "/")[3] + if id == "" { + return nil, caddy.APIError{ + HTTPStatus: http.StatusBadRequest, + Err: fmt.Errorf("missing CA in path"), + } + } + + // Find the CA by ID, if PKI is configured + var ca *CA + var ok bool + if a.pkiApp != nil { + ca, ok = a.pkiApp.CAs[id] + } + + // If we didn't find the CA, and PKI is not configured + // then we'll either error out if the CA ID is not the + // default. If the CA ID is the default, then we'll + // provision it, because the user probably aims to + // change their config to enable PKI immediately after + // if they actually requested the local CA ID. + if !ok { + if id != DefaultCAID { + return nil, caddy.APIError{ + HTTPStatus: http.StatusNotFound, + Err: fmt.Errorf("no certificate authority configured with id: %s", id), + } + } + + // Provision the default CA, which generates and stores a root + // certificate in storage, if one doesn't already exist. + ca = new(CA) + err := ca.Provision(a.ctx, id, a.log) + if err != nil { + return nil, caddy.APIError{ + HTTPStatus: http.StatusInternalServerError, + Err: fmt.Errorf("failed to provision CA %s, %w", id, err), + } + } + } + + return ca, nil +} + +func rootAndIntermediatePEM(ca *CA) (root, inter []byte, err error) { + root, err = pemEncodeCert(ca.RootCertificate().Raw) + if err != nil { + return + } + inter, err = pemEncodeCert(ca.IntermediateCertificate().Raw) + if err != nil { + return + } + return +} + +// caInfo is the response structure for the CA info API endpoint. +type caInfo struct { + ID string `json:"id"` + Name string `json:"name"` + RootCN string `json:"root_common_name"` + IntermediateCN string `json:"intermediate_common_name"` + RootCert string `json:"root_certificate"` + IntermediateCert string `json:"intermediate_certificate"` +} + +// adminPKIEndpointBase is the base admin endpoint under which all PKI admin endpoints exist. +const adminPKIEndpointBase = "/pki/" + +// Interface guards +var ( + _ caddy.AdminRouter = (*adminAPI)(nil) + _ caddy.Provisioner = (*adminAPI)(nil) +) diff --git a/modules/caddypki/ca.go b/modules/caddypki/ca.go new file mode 100644 index 00000000000..6c48da6f9a2 --- /dev/null +++ b/modules/caddypki/ca.go @@ -0,0 +1,444 @@ +// 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 caddypki + +import ( + "crypto" + "crypto/x509" + "encoding/json" + "errors" + "fmt" + "io/fs" + "path" + "sync" + "time" + + "github.com/caddyserver/certmagic" + "github.com/smallstep/certificates/authority" + "github.com/smallstep/certificates/db" + "github.com/smallstep/truststore" + "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2" +) + +// CA describes a certificate authority, which consists of +// root/signing certificates and various settings pertaining +// to the issuance of certificates and trusting them. +type CA struct { + // The user-facing name of the certificate authority. + Name string `json:"name,omitempty"` + + // The name to put in the CommonName field of the + // root certificate. + RootCommonName string `json:"root_common_name,omitempty"` + + // The name to put in the CommonName field of the + // intermediate certificates. + IntermediateCommonName string `json:"intermediate_common_name,omitempty"` + + // The lifetime for the intermediate certificates + IntermediateLifetime caddy.Duration `json:"intermediate_lifetime,omitempty"` + + // Whether Caddy will attempt to install the CA's root + // into the system trust store, as well as into Java + // and Mozilla Firefox trust stores. Default: true. + InstallTrust *bool `json:"install_trust,omitempty"` + + // The root certificate to use; if null, one will be generated. + Root *KeyPair `json:"root,omitempty"` + + // The intermediate (signing) certificate; if null, one will be generated. + Intermediate *KeyPair `json:"intermediate,omitempty"` + + // Optionally configure a separate storage module associated with this + // issuer, instead of using Caddy's global/default-configured storage. + // This can be useful if you want to keep your signing keys in a + // separate location from your leaf certificates. + StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"` + + // The unique config-facing ID of the certificate authority. + // Since the ID is set in JSON config via object key, this + // field is exported only for purposes of config generation + // and module provisioning. + ID string `json:"-"` + + storage certmagic.Storage + root, inter *x509.Certificate + interKey any // TODO: should we just store these as crypto.Signer? + mu *sync.RWMutex + + rootCertPath string // mainly used for logging purposes if trusting + log *zap.Logger + ctx caddy.Context +} + +// Provision sets up the CA. +func (ca *CA) Provision(ctx caddy.Context, id string, log *zap.Logger) error { + ca.mu = new(sync.RWMutex) + ca.log = log.Named("ca." + id) + ca.ctx = ctx + + if id == "" { + return fmt.Errorf("CA ID is required (use 'local' for the default CA)") + } + ca.mu.Lock() + ca.ID = id + ca.mu.Unlock() + + if ca.StorageRaw != nil { + val, err := ctx.LoadModule(ca, "StorageRaw") + if err != nil { + return fmt.Errorf("loading storage module: %v", err) + } + cmStorage, err := val.(caddy.StorageConverter).CertMagicStorage() + if err != nil { + return fmt.Errorf("creating storage configuration: %v", err) + } + ca.storage = cmStorage + } + if ca.storage == nil { + ca.storage = ctx.Storage() + } + + if ca.Name == "" { + ca.Name = defaultCAName + } + if ca.RootCommonName == "" { + ca.RootCommonName = defaultRootCommonName + } + if ca.IntermediateCommonName == "" { + ca.IntermediateCommonName = defaultIntermediateCommonName + } + if ca.IntermediateLifetime == 0 { + ca.IntermediateLifetime = caddy.Duration(defaultIntermediateLifetime) + } else if time.Duration(ca.IntermediateLifetime) >= defaultRootLifetime { + return fmt.Errorf("intermediate certificate lifetime must be less than root certificate lifetime (%s)", defaultRootLifetime) + } + + // load the certs and key that will be used for signing + var rootCert, interCert *x509.Certificate + var rootKey, interKey crypto.Signer + var err error + if ca.Root != nil { + if ca.Root.Format == "" || ca.Root.Format == "pem_file" { + ca.rootCertPath = ca.Root.Certificate + } + rootCert, rootKey, err = ca.Root.Load() + } else { + ca.rootCertPath = "storage:" + ca.storageKeyRootCert() + rootCert, rootKey, err = ca.loadOrGenRoot() + } + if err != nil { + return err + } + if ca.Intermediate != nil { + interCert, interKey, err = ca.Intermediate.Load() + } else { + interCert, interKey, err = ca.loadOrGenIntermediate(rootCert, rootKey) + } + if err != nil { + return err + } + + ca.mu.Lock() + ca.root, ca.inter, ca.interKey = rootCert, interCert, interKey + ca.mu.Unlock() + + return nil +} + +// RootCertificate returns the CA's root certificate (public key). +func (ca CA) RootCertificate() *x509.Certificate { + ca.mu.RLock() + defer ca.mu.RUnlock() + return ca.root +} + +// RootKey returns the CA's root private key. Since the root key is +// not cached in memory long-term, it needs to be loaded from storage, +// which could yield an error. +func (ca CA) RootKey() (any, error) { + _, rootKey, err := ca.loadOrGenRoot() + return rootKey, err +} + +// IntermediateCertificate returns the CA's intermediate +// certificate (public key). +func (ca CA) IntermediateCertificate() *x509.Certificate { + ca.mu.RLock() + defer ca.mu.RUnlock() + return ca.inter +} + +// IntermediateKey returns the CA's intermediate private key. +func (ca CA) IntermediateKey() any { + ca.mu.RLock() + defer ca.mu.RUnlock() + return ca.interKey +} + +// NewAuthority returns a new Smallstep-powered signing authority for this CA. +// Note that we receive *CA (a pointer) in this method to ensure the closure within it, which +// executes at a later time, always has the only copy of the CA so it can access the latest, +// renewed certificates since NewAuthority was called. See #4517 and #4669. +func (ca *CA) NewAuthority(authorityConfig AuthorityConfig) (*authority.Authority, error) { + // get the root certificate and the issuer cert+key + rootCert := ca.RootCertificate() + + // set up the signer; cert/key which signs the leaf certs + var signerOption authority.Option + if authorityConfig.SignWithRoot { + // if we're signing with root, we can just pass the + // cert/key directly, since it's unlikely to expire + // while Caddy is running (long lifetime) + var issuerCert *x509.Certificate + var issuerKey any + issuerCert = rootCert + var err error + issuerKey, err = ca.RootKey() + if err != nil { + return nil, fmt.Errorf("loading signing key: %v", err) + } + signerOption = authority.WithX509Signer(issuerCert, issuerKey.(crypto.Signer)) + } else { + // if we're signing with intermediate, we need to make + // sure it's always fresh, because the intermediate may + // renew while Caddy is running (medium lifetime) + signerOption = authority.WithX509SignerFunc(func() ([]*x509.Certificate, crypto.Signer, error) { + issuerCert := ca.IntermediateCertificate() + issuerKey := ca.IntermediateKey().(crypto.Signer) + ca.log.Debug("using intermediate signer", + zap.String("serial", issuerCert.SerialNumber.String()), + zap.String("not_before", issuerCert.NotBefore.String()), + zap.String("not_after", issuerCert.NotAfter.String())) + return []*x509.Certificate{issuerCert}, issuerKey, nil + }) + } + + opts := []authority.Option{ + authority.WithConfig(&authority.Config{ + AuthorityConfig: authorityConfig.AuthConfig, + }), + signerOption, + authority.WithX509RootCerts(rootCert), + } + + // Add a database if we have one + if authorityConfig.DB != nil { + opts = append(opts, authority.WithDatabase(*authorityConfig.DB)) + } + auth, err := authority.NewEmbedded(opts...) + if err != nil { + return nil, fmt.Errorf("initializing certificate authority: %v", err) + } + + return auth, nil +} + +func (ca CA) loadOrGenRoot() (rootCert *x509.Certificate, rootKey crypto.Signer, err error) { + if ca.Root != nil { + return ca.Root.Load() + } + rootCertPEM, err := ca.storage.Load(ca.ctx, ca.storageKeyRootCert()) + if err != nil { + if !errors.Is(err, fs.ErrNotExist) { + return nil, nil, fmt.Errorf("loading root cert: %v", err) + } + + // TODO: should we require that all or none of the assets are required before overwriting anything? + rootCert, rootKey, err = ca.genRoot() + if err != nil { + return nil, nil, fmt.Errorf("generating root: %v", err) + } + } + + if rootCert == nil { + rootCert, err = pemDecodeSingleCert(rootCertPEM) + if err != nil { + return nil, nil, fmt.Errorf("parsing root certificate PEM: %v", err) + } + } + if rootKey == nil { + rootKeyPEM, err := ca.storage.Load(ca.ctx, ca.storageKeyRootKey()) + if err != nil { + return nil, nil, fmt.Errorf("loading root key: %v", err) + } + rootKey, err = certmagic.PEMDecodePrivateKey(rootKeyPEM) + if err != nil { + return nil, nil, fmt.Errorf("decoding root key: %v", err) + } + } + + return rootCert, rootKey, nil +} + +func (ca CA) genRoot() (rootCert *x509.Certificate, rootKey crypto.Signer, err error) { + repl := ca.newReplacer() + + rootCert, rootKey, err = generateRoot(repl.ReplaceAll(ca.RootCommonName, "")) + if err != nil { + return nil, nil, fmt.Errorf("generating CA root: %v", err) + } + rootCertPEM, err := pemEncodeCert(rootCert.Raw) + if err != nil { + return nil, nil, fmt.Errorf("encoding root certificate: %v", err) + } + err = ca.storage.Store(ca.ctx, ca.storageKeyRootCert(), rootCertPEM) + if err != nil { + return nil, nil, fmt.Errorf("saving root certificate: %v", err) + } + rootKeyPEM, err := certmagic.PEMEncodePrivateKey(rootKey) + if err != nil { + return nil, nil, fmt.Errorf("encoding root key: %v", err) + } + err = ca.storage.Store(ca.ctx, ca.storageKeyRootKey(), rootKeyPEM) + if err != nil { + return nil, nil, fmt.Errorf("saving root key: %v", err) + } + + return rootCert, rootKey, nil +} + +func (ca CA) loadOrGenIntermediate(rootCert *x509.Certificate, rootKey crypto.Signer) (interCert *x509.Certificate, interKey crypto.Signer, err error) { + interCertPEM, err := ca.storage.Load(ca.ctx, ca.storageKeyIntermediateCert()) + if err != nil { + if !errors.Is(err, fs.ErrNotExist) { + return nil, nil, fmt.Errorf("loading intermediate cert: %v", err) + } + + // TODO: should we require that all or none of the assets are required before overwriting anything? + interCert, interKey, err = ca.genIntermediate(rootCert, rootKey) + if err != nil { + return nil, nil, fmt.Errorf("generating new intermediate cert: %v", err) + } + } + + if interCert == nil { + interCert, err = pemDecodeSingleCert(interCertPEM) + if err != nil { + return nil, nil, fmt.Errorf("decoding intermediate certificate PEM: %v", err) + } + } + + if interKey == nil { + interKeyPEM, err := ca.storage.Load(ca.ctx, ca.storageKeyIntermediateKey()) + if err != nil { + return nil, nil, fmt.Errorf("loading intermediate key: %v", err) + } + interKey, err = certmagic.PEMDecodePrivateKey(interKeyPEM) + if err != nil { + return nil, nil, fmt.Errorf("decoding intermediate key: %v", err) + } + } + + return interCert, interKey, nil +} + +func (ca CA) genIntermediate(rootCert *x509.Certificate, rootKey crypto.Signer) (interCert *x509.Certificate, interKey crypto.Signer, err error) { + repl := ca.newReplacer() + + interCert, interKey, err = generateIntermediate(repl.ReplaceAll(ca.IntermediateCommonName, ""), rootCert, rootKey, time.Duration(ca.IntermediateLifetime)) + if err != nil { + return nil, nil, fmt.Errorf("generating CA intermediate: %v", err) + } + interCertPEM, err := pemEncodeCert(interCert.Raw) + if err != nil { + return nil, nil, fmt.Errorf("encoding intermediate certificate: %v", err) + } + err = ca.storage.Store(ca.ctx, ca.storageKeyIntermediateCert(), interCertPEM) + if err != nil { + return nil, nil, fmt.Errorf("saving intermediate certificate: %v", err) + } + interKeyPEM, err := certmagic.PEMEncodePrivateKey(interKey) + if err != nil { + return nil, nil, fmt.Errorf("encoding intermediate key: %v", err) + } + err = ca.storage.Store(ca.ctx, ca.storageKeyIntermediateKey(), interKeyPEM) + if err != nil { + return nil, nil, fmt.Errorf("saving intermediate key: %v", err) + } + + return interCert, interKey, nil +} + +func (ca CA) storageKeyCAPrefix() string { + return path.Join("pki", "authorities", certmagic.StorageKeys.Safe(ca.ID)) +} + +func (ca CA) storageKeyRootCert() string { + return path.Join(ca.storageKeyCAPrefix(), "root.crt") +} + +func (ca CA) storageKeyRootKey() string { + return path.Join(ca.storageKeyCAPrefix(), "root.key") +} + +func (ca CA) storageKeyIntermediateCert() string { + return path.Join(ca.storageKeyCAPrefix(), "intermediate.crt") +} + +func (ca CA) storageKeyIntermediateKey() string { + return path.Join(ca.storageKeyCAPrefix(), "intermediate.key") +} + +func (ca CA) newReplacer() *caddy.Replacer { + repl := caddy.NewReplacer() + repl.Set("pki.ca.name", ca.Name) + return repl +} + +// installRoot installs this CA's root certificate into the +// local trust store(s) if it is not already trusted. The CA +// must already be provisioned. +func (ca CA) installRoot() error { + // avoid password prompt if already trusted + if trusted(ca.root) { + ca.log.Info("root certificate is already trusted by system", + zap.String("path", ca.rootCertPath)) + return nil + } + + ca.log.Warn("installing root certificate (you might be prompted for password)", + zap.String("path", ca.rootCertPath)) + + return truststore.Install(ca.root, + truststore.WithDebug(), + truststore.WithFirefox(), + truststore.WithJava(), + ) +} + +// AuthorityConfig is used to help a CA configure +// the underlying signing authority. +type AuthorityConfig struct { + SignWithRoot bool + + // TODO: should we just embed the underlying authority.Config struct type? + DB *db.AuthDB + AuthConfig *authority.AuthConfig +} + +const ( + // DefaultCAID is the default CA ID. + DefaultCAID = "local" + + defaultCAName = "Caddy Local Authority" + defaultRootCommonName = "{pki.ca.name} - {time.now.year} ECC Root" + defaultIntermediateCommonName = "{pki.ca.name} - ECC Intermediate" + + defaultRootLifetime = 24 * time.Hour * 30 * 12 * 10 + defaultIntermediateLifetime = 24 * time.Hour * 7 +) diff --git a/modules/caddypki/certificates.go b/modules/caddypki/certificates.go new file mode 100644 index 00000000000..e300429382f --- /dev/null +++ b/modules/caddypki/certificates.go @@ -0,0 +1,68 @@ +// 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 caddypki + +import ( + "crypto" + "crypto/x509" + "time" + + "go.step.sm/crypto/keyutil" + "go.step.sm/crypto/x509util" +) + +func generateRoot(commonName string) (*x509.Certificate, crypto.Signer, error) { + template, signer, err := newCert(commonName, x509util.DefaultRootTemplate, defaultRootLifetime) + if err != nil { + return nil, nil, err + } + root, err := x509util.CreateCertificate(template, template, signer.Public(), signer) + if err != nil { + return nil, nil, err + } + return root, signer, nil +} + +func generateIntermediate(commonName string, rootCrt *x509.Certificate, rootKey crypto.Signer, lifetime time.Duration) (*x509.Certificate, crypto.Signer, error) { + template, signer, err := newCert(commonName, x509util.DefaultIntermediateTemplate, lifetime) + if err != nil { + return nil, nil, err + } + intermediate, err := x509util.CreateCertificate(template, rootCrt, signer.Public(), rootKey) + if err != nil { + return nil, nil, err + } + return intermediate, signer, nil +} + +func newCert(commonName, templateName string, lifetime time.Duration) (cert *x509.Certificate, signer crypto.Signer, err error) { + signer, err = keyutil.GenerateDefaultSigner() + if err != nil { + return nil, nil, err + } + csr, err := x509util.CreateCertificateRequest(commonName, []string{}, signer) + if err != nil { + return nil, nil, err + } + template, err := x509util.NewCertificate(csr, x509util.WithTemplate(templateName, x509util.CreateTemplateData(commonName, []string{}))) + if err != nil { + return nil, nil, err + } + + cert = template.GetCertificate() + cert.NotBefore = time.Now().Truncate(time.Second) + cert.NotAfter = cert.NotBefore.Add(lifetime) + return cert, signer, nil +} diff --git a/modules/caddypki/command.go b/modules/caddypki/command.go new file mode 100644 index 00000000000..b7fa1bb7ce1 --- /dev/null +++ b/modules/caddypki/command.go @@ -0,0 +1,235 @@ +// 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 caddypki + +import ( + "crypto/x509" + "encoding/json" + "encoding/pem" + "fmt" + "net/http" + "os" + "path" + + "github.com/smallstep/truststore" + "github.com/spf13/cobra" + + caddycmd "github.com/caddyserver/caddy/v2/cmd" + + "github.com/caddyserver/caddy/v2" +) + +func init() { + caddycmd.RegisterCommand(caddycmd.Command{ + Name: "trust", + Usage: "[--ca ] [--address ] [--config [--adapter ]]", + Short: "Installs a CA certificate into local trust stores", + Long: ` +Adds a root certificate into the local trust stores. + +Caddy will attempt to install its root certificates into the local +trust stores automatically when they are first generated, but it +might fail if Caddy doesn't have the appropriate permissions to +write to the trust store. This command is necessary to pre-install +the certificates before using them, if the server process runs as an +unprivileged user (such as via systemd). + +By default, this command installs the root certificate for Caddy's +default CA (i.e. 'local'). You may specify the ID of another CA +with the --ca flag. + +This command will attempt to connect to Caddy's admin API running at +'` + caddy.DefaultAdminListen + `' to fetch the root certificate. You may +explicitly specify the --address, or use the --config flag to load +the admin address from your config, if not using the default.`, + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("ca", "", "", "The ID of the CA to trust (defaults to 'local')") + cmd.Flags().StringP("address", "", "", "Address of the administration API listener (if --config is not used)") + cmd.Flags().StringP("config", "c", "", "Configuration file (if --address is not used)") + cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply (if --config is used)") + cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdTrust) + }, + }) + + caddycmd.RegisterCommand(caddycmd.Command{ + Name: "untrust", + Usage: "[--cert ] | [[--ca ] [--address ] [--config [--adapter ]]]", + Short: "Untrusts a locally-trusted CA certificate", + Long: ` +Untrusts a root certificate from the local trust store(s). + +This command uninstalls trust; it does not necessarily delete the +root certificate from trust stores entirely. Thus, repeatedly +trusting and untrusting new certificates can fill up trust databases. + +This command does not delete or modify certificate files from Caddy's +configured storage. + +This command can be used in one of two ways. Either by specifying +which certificate to untrust by a direct path to the certificate +file with the --cert flag, or by fetching the root certificate for +the CA from the admin API (default behaviour). + +If the admin API is used, then the CA defaults to 'local'. You may +specify the ID of another CA with the --ca flag. By default, this +will attempt to connect to the Caddy's admin API running at +'` + caddy.DefaultAdminListen + `' to fetch the root certificate. +You may explicitly specify the --address, or use the --config flag +to load the admin address from your config, if not using the default.`, + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("cert", "p", "", "The path to the CA certificate to untrust") + cmd.Flags().StringP("ca", "", "", "The ID of the CA to untrust (defaults to 'local')") + cmd.Flags().StringP("address", "", "", "Address of the administration API listener (if --config is not used)") + cmd.Flags().StringP("config", "c", "", "Configuration file (if --address is not used)") + cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply (if --config is used)") + cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdUntrust) + }, + }) +} + +func cmdTrust(fl caddycmd.Flags) (int, error) { + caID := fl.String("ca") + addrFlag := fl.String("address") + configFlag := fl.String("config") + configAdapterFlag := fl.String("adapter") + + // Prepare the URI to the admin endpoint + if caID == "" { + caID = DefaultCAID + } + + // Determine where we're sending the request to get the CA info + adminAddr, err := caddycmd.DetermineAdminAPIAddress(addrFlag, nil, configFlag, configAdapterFlag) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err) + } + + // Fetch the root cert from the admin API + rootCert, err := rootCertFromAdmin(adminAddr, caID) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + // Set up the CA struct; we only need to fill in the root + // because we're only using it to make use of the installRoot() + // function. Also needs a logger for warnings, and a "cert path" + // for the root cert; since we're loading from the API and we + // don't know the actual storage path via this flow, we'll just + // pass through the admin API address instead. + ca := CA{ + log: caddy.Log(), + root: rootCert, + rootCertPath: adminAddr + path.Join(adminPKIEndpointBase, "ca", caID), + } + + // Install the cert! + err = ca.installRoot() + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + return caddy.ExitCodeSuccess, nil +} + +func cmdUntrust(fl caddycmd.Flags) (int, error) { + certFile := fl.String("cert") + caID := fl.String("ca") + addrFlag := fl.String("address") + configFlag := fl.String("config") + configAdapterFlag := fl.String("adapter") + + if certFile != "" && (caID != "" || addrFlag != "" || configFlag != "") { + return caddy.ExitCodeFailedStartup, fmt.Errorf("conflicting command line arguments, cannot use --cert with other flags") + } + + // If a file was specified, try to uninstall the cert matching that file + if certFile != "" { + // Sanity check, make sure cert file exists first + _, err := os.Stat(certFile) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("accessing certificate file: %v", err) + } + + // Uninstall the file! + err = truststore.UninstallFile(certFile, + truststore.WithDebug(), + truststore.WithFirefox(), + truststore.WithJava()) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("failed to uninstall certificate file: %v", err) + } + + return caddy.ExitCodeSuccess, nil + } + + // Prepare the URI to the admin endpoint + if caID == "" { + caID = DefaultCAID + } + + // Determine where we're sending the request to get the CA info + adminAddr, err := caddycmd.DetermineAdminAPIAddress(addrFlag, nil, configFlag, configAdapterFlag) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err) + } + + // Fetch the root cert from the admin API + rootCert, err := rootCertFromAdmin(adminAddr, caID) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + // Uninstall the cert! + err = truststore.Uninstall(rootCert, + truststore.WithDebug(), + truststore.WithFirefox(), + truststore.WithJava()) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("failed to uninstall certificate file: %v", err) + } + + return caddy.ExitCodeSuccess, nil +} + +// rootCertFromAdmin makes the API request to fetch the root certificate for the named CA via admin API. +func rootCertFromAdmin(adminAddr string, caID string) (*x509.Certificate, error) { + uri := path.Join(adminPKIEndpointBase, "ca", caID) + + // Make the request to fetch the CA info + resp, err := caddycmd.AdminAPIRequest(adminAddr, http.MethodGet, uri, make(http.Header), nil) + if err != nil { + return nil, fmt.Errorf("requesting CA info: %v", err) + } + defer resp.Body.Close() + + // Decode the response + caInfo := new(caInfo) + err = json.NewDecoder(resp.Body).Decode(caInfo) + if err != nil { + return nil, fmt.Errorf("failed to decode JSON response: %v", err) + } + + // Decode the root cert + rootBlock, _ := pem.Decode([]byte(caInfo.RootCert)) + if rootBlock == nil { + return nil, fmt.Errorf("failed to decode root certificate: %v", err) + } + rootCert, err := x509.ParseCertificate(rootBlock.Bytes) + if err != nil { + return nil, fmt.Errorf("failed to parse root certificate: %v", err) + } + + return rootCert, nil +} diff --git a/modules/caddypki/crypto.go b/modules/caddypki/crypto.go new file mode 100644 index 00000000000..324a4fcfafb --- /dev/null +++ b/modules/caddypki/crypto.go @@ -0,0 +1,103 @@ +// 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 caddypki + +import ( + "bytes" + "crypto" + "crypto/x509" + "encoding/pem" + "fmt" + "os" + + "github.com/caddyserver/certmagic" +) + +func pemDecodeSingleCert(pemDER []byte) (*x509.Certificate, error) { + pemBlock, remaining := pem.Decode(pemDER) + if pemBlock == nil { + return nil, fmt.Errorf("no PEM block found") + } + if len(remaining) > 0 { + return nil, fmt.Errorf("input contained more than a single PEM block") + } + if pemBlock.Type != "CERTIFICATE" { + return nil, fmt.Errorf("expected PEM block type to be CERTIFICATE, but got '%s'", pemBlock.Type) + } + return x509.ParseCertificate(pemBlock.Bytes) +} + +func pemEncodeCert(der []byte) ([]byte, error) { + return pemEncode("CERTIFICATE", der) +} + +func pemEncode(blockType string, b []byte) ([]byte, error) { + var buf bytes.Buffer + err := pem.Encode(&buf, &pem.Block{Type: blockType, Bytes: b}) + return buf.Bytes(), err +} + +func trusted(cert *x509.Certificate) bool { + chains, err := cert.Verify(x509.VerifyOptions{}) + return len(chains) > 0 && err == nil +} + +// KeyPair represents a public-private key pair, where the +// public key is also called a certificate. +type KeyPair struct { + // The certificate. By default, this should be the path to + // a PEM file unless format is something else. + Certificate string `json:"certificate,omitempty"` + + // The private key. By default, this should be the path to + // a PEM file unless format is something else. + PrivateKey string `json:"private_key,omitempty"` + + // The format in which the certificate and private + // key are provided. Default: pem_file + Format string `json:"format,omitempty"` +} + +// Load loads the certificate and key. +func (kp KeyPair) Load() (*x509.Certificate, crypto.Signer, error) { + switch kp.Format { + case "", "pem_file": + certData, err := os.ReadFile(kp.Certificate) + if err != nil { + return nil, nil, err + } + cert, err := pemDecodeSingleCert(certData) + if err != nil { + return nil, nil, err + } + + var key crypto.Signer + if kp.PrivateKey != "" { + keyData, err := os.ReadFile(kp.PrivateKey) + if err != nil { + return nil, nil, err + } + key, err = certmagic.PEMDecodePrivateKey(keyData) + if err != nil { + return nil, nil, err + } + } + + return cert, key, nil + + default: + return nil, nil, fmt.Errorf("unsupported format: %s", kp.Format) + } +} diff --git a/modules/caddypki/maintain.go b/modules/caddypki/maintain.go new file mode 100644 index 00000000000..31e453ff927 --- /dev/null +++ b/modules/caddypki/maintain.go @@ -0,0 +1,107 @@ +// 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 caddypki + +import ( + "crypto/x509" + "fmt" + "log" + "runtime/debug" + "time" + + "go.uber.org/zap" +) + +func (p *PKI) maintenance() { + defer func() { + if err := recover(); err != nil { + log.Printf("[PANIC] PKI maintenance: %v\n%s", err, debug.Stack()) + } + }() + + ticker := time.NewTicker(10 * time.Minute) // TODO: make configurable + defer ticker.Stop() + + for { + select { + case <-ticker.C: + p.renewCerts() + case <-p.ctx.Done(): + return + } + } +} + +func (p *PKI) renewCerts() { + for _, ca := range p.CAs { + err := p.renewCertsForCA(ca) + if err != nil { + p.log.Error("renewing intermediate certificates", + zap.Error(err), + zap.String("ca", ca.ID)) + } + } +} + +func (p *PKI) renewCertsForCA(ca *CA) error { + ca.mu.Lock() + defer ca.mu.Unlock() + + log := p.log.With(zap.String("ca", ca.ID)) + + // only maintain the root if it's not manually provided in the config + if ca.Root == nil { + if needsRenewal(ca.root) { + // TODO: implement root renewal (use same key) + log.Warn("root certificate expiring soon (FIXME: ROOT RENEWAL NOT YET IMPLEMENTED)", + zap.Duration("time_remaining", time.Until(ca.inter.NotAfter)), + ) + } + } + + // only maintain the intermediate if it's not manually provided in the config + if ca.Intermediate == nil { + if needsRenewal(ca.inter) { + log.Info("intermediate expires soon; renewing", + zap.Duration("time_remaining", time.Until(ca.inter.NotAfter)), + ) + + rootCert, rootKey, err := ca.loadOrGenRoot() + if err != nil { + return fmt.Errorf("loading root key: %v", err) + } + interCert, interKey, err := ca.genIntermediate(rootCert, rootKey) + if err != nil { + return fmt.Errorf("generating new certificate: %v", err) + } + ca.inter, ca.interKey = interCert, interKey + + log.Info("renewed intermediate", + zap.Time("new_expiration", ca.inter.NotAfter), + ) + } + } + + return nil +} + +func needsRenewal(cert *x509.Certificate) bool { + lifetime := cert.NotAfter.Sub(cert.NotBefore) + renewalWindow := time.Duration(float64(lifetime) * renewalWindowRatio) + renewalWindowStart := cert.NotAfter.Add(-renewalWindow) + return time.Now().After(renewalWindowStart) +} + +const renewalWindowRatio = 0.2 // TODO: make configurable diff --git a/modules/caddypki/pki.go b/modules/caddypki/pki.go new file mode 100644 index 00000000000..9f974a956bb --- /dev/null +++ b/modules/caddypki/pki.go @@ -0,0 +1,149 @@ +// 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 caddypki + +import ( + "fmt" + + "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2" +) + +func init() { + caddy.RegisterModule(PKI{}) +} + +// PKI provides Public Key Infrastructure facilities for Caddy. +// +// This app can define certificate authorities (CAs) which are capable +// of signing certificates. Other modules can be configured to use +// the CAs defined by this app for issuing certificates or getting +// key information needed for establishing trust. +type PKI struct { + // The certificate authorities to manage. Each CA is keyed by an + // ID that is used to uniquely identify it from other CAs. + // At runtime, the GetCA() method should be used instead to ensure + // the default CA is provisioned if it hadn't already been. + // The default CA ID is "local". + CAs map[string]*CA `json:"certificate_authorities,omitempty"` + + ctx caddy.Context + log *zap.Logger +} + +// CaddyModule returns the Caddy module information. +func (PKI) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "pki", + New: func() caddy.Module { return new(PKI) }, + } +} + +// Provision sets up the configuration for the PKI app. +func (p *PKI) Provision(ctx caddy.Context) error { + p.ctx = ctx + p.log = ctx.Logger() + + for caID, ca := range p.CAs { + err := ca.Provision(ctx, caID, p.log) + if err != nil { + return fmt.Errorf("provisioning CA '%s': %v", caID, err) + } + } + + // if this app is initialized at all, ensure there's at + // least a default CA that can be used: the standard CA + // which is used implicitly for signing local-use certs + if len(p.CAs) == 0 { + err := p.ProvisionDefaultCA(ctx) + if err != nil { + return fmt.Errorf("provisioning CA '%s': %v", DefaultCAID, err) + } + } + + return nil +} + +// ProvisionDefaultCA sets up the default CA. +func (p *PKI) ProvisionDefaultCA(ctx caddy.Context) error { + if p.CAs == nil { + p.CAs = make(map[string]*CA) + } + + p.CAs[DefaultCAID] = new(CA) + return p.CAs[DefaultCAID].Provision(ctx, DefaultCAID, p.log) +} + +// Start starts the PKI app. +func (p *PKI) Start() error { + // install roots to trust store, if not disabled + for _, ca := range p.CAs { + if ca.InstallTrust != nil && !*ca.InstallTrust { + ca.log.Info("root certificate trust store installation disabled; unconfigured clients may show warnings", + zap.String("path", ca.rootCertPath)) + continue + } + + if err := ca.installRoot(); err != nil { + // could be some system dependencies that are missing; + // shouldn't totally prevent startup, but we should log it + ca.log.Error("failed to install root certificate", + zap.Error(err), + zap.String("certificate_file", ca.rootCertPath)) + } + } + + // see if root/intermediates need renewal... + p.renewCerts() + + // ...and keep them renewed + go p.maintenance() + + return nil +} + +// Stop stops the PKI app. +func (p *PKI) Stop() error { + return nil +} + +// GetCA retrieves a CA by ID. If the ID is the default +// CA ID, and it hasn't been provisioned yet, it will +// be provisioned. +func (p *PKI) GetCA(ctx caddy.Context, id string) (*CA, error) { + ca, ok := p.CAs[id] + if !ok { + // for anything other than the default CA ID, error out if it wasn't configured + if id != DefaultCAID { + return nil, fmt.Errorf("no certificate authority configured with id: %s", id) + } + + // for the default CA ID, provision it, because we want it to "just work" + err := p.ProvisionDefaultCA(ctx) + if err != nil { + return nil, fmt.Errorf("failed to provision default CA: %s", err) + } + ca = p.CAs[id] + } + + return ca, nil +} + +// Interface guards +var ( + _ caddy.Provisioner = (*PKI)(nil) + _ caddy.App = (*PKI)(nil) +) diff --git a/modules/caddytls/acmeissuer.go b/modules/caddytls/acmeissuer.go new file mode 100644 index 00000000000..2fe5eec97d6 --- /dev/null +++ b/modules/caddytls/acmeissuer.go @@ -0,0 +1,683 @@ +// 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 caddytls + +import ( + "context" + "crypto/x509" + "encoding/json" + "fmt" + "net/http" + "net/url" + "os" + "strconv" + "strings" + "time" + + "github.com/caddyserver/certmagic" + "github.com/caddyserver/zerossl" + "github.com/mholt/acmez/v3/acme" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func init() { + caddy.RegisterModule(ACMEIssuer{}) +} + +// ACMEIssuer manages certificates using the ACME protocol (RFC 8555). +type ACMEIssuer struct { + // The URL to the CA's ACME directory endpoint. Default: + // https://acme-v02.api.letsencrypt.org/directory + CA string `json:"ca,omitempty"` + + // The URL to the test CA's ACME directory endpoint. + // This endpoint is only used during retries if there + // is a failure using the primary CA. Default: + // https://acme-staging-v02.api.letsencrypt.org/directory + TestCA string `json:"test_ca,omitempty"` + + // Your email address, so the CA can contact you if necessary. + // Not required, but strongly recommended to provide one so + // you can be reached if there is a problem. Your email is + // not sent to any Caddy mothership or used for any purpose + // other than ACME transactions. + Email string `json:"email,omitempty"` + + // Optionally select an ACME profile to use for certificate + // orders. Must be a profile name offered by the ACME server, + // which are listed at its directory endpoint. + // + // EXPERIMENTAL: Subject to change. + // See https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/ + Profile string `json:"profile,omitempty"` + + // If you have an existing account with the ACME server, put + // the private key here in PEM format. The ACME client will + // look up your account information with this key first before + // trying to create a new one. You can use placeholders here, + // for example if you have it in an environment variable. + AccountKey string `json:"account_key,omitempty"` + + // If using an ACME CA that requires an external account + // binding, specify the CA-provided credentials here. + ExternalAccount *acme.EAB `json:"external_account,omitempty"` + + // Time to wait before timing out an ACME operation. + // Default: 0 (no timeout) + ACMETimeout caddy.Duration `json:"acme_timeout,omitempty"` + + // Configures the various ACME challenge types. + Challenges *ChallengesConfig `json:"challenges,omitempty"` + + // An array of files of CA certificates to accept when connecting to the + // ACME CA. Generally, you should only use this if the ACME CA endpoint + // is internal or for development/testing purposes. + TrustedRootsPEMFiles []string `json:"trusted_roots_pem_files,omitempty"` + + // Preferences for selecting alternate certificate chains, if offered + // by the CA. By default, the first offered chain will be selected. + // If configured, the chains may be sorted and the first matching chain + // will be selected. + PreferredChains *ChainPreference `json:"preferred_chains,omitempty"` + + // The validity period to ask the CA to issue a certificate for. + // Default: 0 (CA chooses lifetime). + // This value is used to compute the "notAfter" field of the ACME order; + // therefore the system must have a reasonably synchronized clock. + // NOTE: Not all CAs support this. Check with your CA's ACME + // documentation to see if this is allowed and what values may + // be used. EXPERIMENTAL: Subject to change. + CertificateLifetime caddy.Duration `json:"certificate_lifetime,omitempty"` + + rootPool *x509.CertPool + logger *zap.Logger + + template certmagic.ACMEIssuer // set at Provision + magic *certmagic.Config // set at PreCheck + issuer *certmagic.ACMEIssuer // set at PreCheck; result of template + magic +} + +// CaddyModule returns the Caddy module information. +func (ACMEIssuer) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.issuance.acme", + New: func() caddy.Module { return new(ACMEIssuer) }, + } +} + +// Provision sets up iss. +func (iss *ACMEIssuer) Provision(ctx caddy.Context) error { + iss.logger = ctx.Logger() + + repl := caddy.NewReplacer() + + // expand email address, if non-empty + if iss.Email != "" { + email, err := repl.ReplaceOrErr(iss.Email, true, true) + if err != nil { + return fmt.Errorf("expanding email address '%s': %v", iss.Email, err) + } + iss.Email = email + } + + // expand account key, if non-empty + if iss.AccountKey != "" { + accountKey, err := repl.ReplaceOrErr(iss.AccountKey, true, true) + if err != nil { + return fmt.Errorf("expanding account key PEM '%s': %v", iss.AccountKey, err) + } + iss.AccountKey = accountKey + } + + // DNS providers + if iss.Challenges != nil && iss.Challenges.DNS != nil && iss.Challenges.DNS.ProviderRaw != nil { + val, err := ctx.LoadModule(iss.Challenges.DNS, "ProviderRaw") + if err != nil { + return fmt.Errorf("loading DNS provider module: %v", err) + } + iss.Challenges.DNS.solver = &certmagic.DNS01Solver{ + DNSManager: certmagic.DNSManager{ + DNSProvider: val.(certmagic.DNSProvider), + TTL: time.Duration(iss.Challenges.DNS.TTL), + PropagationDelay: time.Duration(iss.Challenges.DNS.PropagationDelay), + PropagationTimeout: time.Duration(iss.Challenges.DNS.PropagationTimeout), + Resolvers: iss.Challenges.DNS.Resolvers, + OverrideDomain: iss.Challenges.DNS.OverrideDomain, + }, + } + } + + // add any custom CAs to trust store + if len(iss.TrustedRootsPEMFiles) > 0 { + iss.rootPool = x509.NewCertPool() + for _, pemFile := range iss.TrustedRootsPEMFiles { + pemData, err := os.ReadFile(pemFile) + if err != nil { + return fmt.Errorf("loading trusted root CA's PEM file: %s: %v", pemFile, err) + } + if !iss.rootPool.AppendCertsFromPEM(pemData) { + return fmt.Errorf("unable to add %s to trust pool: %v", pemFile, err) + } + } + } + + var err error + iss.template, err = iss.makeIssuerTemplate() + if err != nil { + return err + } + + return nil +} + +func (iss *ACMEIssuer) makeIssuerTemplate() (certmagic.ACMEIssuer, error) { + template := certmagic.ACMEIssuer{ + CA: iss.CA, + TestCA: iss.TestCA, + Email: iss.Email, + Profile: iss.Profile, + AccountKeyPEM: iss.AccountKey, + CertObtainTimeout: time.Duration(iss.ACMETimeout), + TrustedRoots: iss.rootPool, + ExternalAccount: iss.ExternalAccount, + NotAfter: time.Duration(iss.CertificateLifetime), + Logger: iss.logger, + } + + if iss.Challenges != nil { + if iss.Challenges.HTTP != nil { + template.DisableHTTPChallenge = iss.Challenges.HTTP.Disabled + template.AltHTTPPort = iss.Challenges.HTTP.AlternatePort + } + if iss.Challenges.TLSALPN != nil { + template.DisableTLSALPNChallenge = iss.Challenges.TLSALPN.Disabled + template.AltTLSALPNPort = iss.Challenges.TLSALPN.AlternatePort + } + if iss.Challenges.DNS != nil { + template.DNS01Solver = iss.Challenges.DNS.solver + } + template.ListenHost = iss.Challenges.BindHost + } + + if iss.PreferredChains != nil { + template.PreferredChains = certmagic.ChainPreference{ + Smallest: iss.PreferredChains.Smallest, + AnyCommonName: iss.PreferredChains.AnyCommonName, + RootCommonName: iss.PreferredChains.RootCommonName, + } + } + + // ZeroSSL requires EAB, but we can generate that automatically (requires an email address be configured) + if strings.HasPrefix(iss.CA, "https://acme.zerossl.com/") { + template.NewAccountFunc = func(ctx context.Context, acmeIss *certmagic.ACMEIssuer, acct acme.Account) (acme.Account, error) { + if acmeIss.ExternalAccount != nil { + return acct, nil + } + var err error + acmeIss.ExternalAccount, acct, err = iss.generateZeroSSLEABCredentials(ctx, acct) + return acct, err + } + } + + return template, nil +} + +// SetConfig sets the associated certmagic config for this issuer. +// This is required because ACME needs values from the config in +// order to solve the challenges during issuance. This implements +// the ConfigSetter interface. +func (iss *ACMEIssuer) SetConfig(cfg *certmagic.Config) { + iss.magic = cfg + iss.issuer = certmagic.NewACMEIssuer(cfg, iss.template) +} + +// PreCheck implements the certmagic.PreChecker interface. +func (iss *ACMEIssuer) PreCheck(ctx context.Context, names []string, interactive bool) error { + return iss.issuer.PreCheck(ctx, names, interactive) +} + +// Issue obtains a certificate for the given csr. +func (iss *ACMEIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest) (*certmagic.IssuedCertificate, error) { + return iss.issuer.Issue(ctx, csr) +} + +// IssuerKey returns the unique issuer key for the configured CA endpoint. +func (iss *ACMEIssuer) IssuerKey() string { + return iss.issuer.IssuerKey() +} + +// Revoke revokes the given certificate. +func (iss *ACMEIssuer) Revoke(ctx context.Context, cert certmagic.CertificateResource, reason int) error { + return iss.issuer.Revoke(ctx, cert, reason) +} + +// GetACMEIssuer returns iss. This is useful when other types embed ACMEIssuer, because +// type-asserting them to *ACMEIssuer will fail, but type-asserting them to an interface +// with only this method will succeed, and will still allow the embedded ACMEIssuer +// to be accessed and manipulated. +func (iss *ACMEIssuer) GetACMEIssuer() *ACMEIssuer { return iss } + +// GetRenewalInfo wraps the underlying GetRenewalInfo method and satisfies +// the CertMagic interface for ARI support. +func (iss *ACMEIssuer) GetRenewalInfo(ctx context.Context, cert certmagic.Certificate) (acme.RenewalInfo, error) { + return iss.issuer.GetRenewalInfo(ctx, cert) +} + +// generateZeroSSLEABCredentials generates ZeroSSL EAB credentials for the primary contact email +// on the issuer. It should only be usedif the CA endpoint is ZeroSSL. An email address is required. +func (iss *ACMEIssuer) generateZeroSSLEABCredentials(ctx context.Context, acct acme.Account) (*acme.EAB, acme.Account, error) { + if strings.TrimSpace(iss.Email) == "" { + return nil, acme.Account{}, fmt.Errorf("your email address is required to use ZeroSSL's ACME endpoint") + } + + if len(acct.Contact) == 0 { + // we borrow the email from config or the default email, so ensure it's saved with the account + acct.Contact = []string{"mailto:" + iss.Email} + } + + endpoint := zerossl.BaseURL + "/acme/eab-credentials-email" + form := url.Values{"email": []string{iss.Email}} + body := strings.NewReader(form.Encode()) + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, body) + if err != nil { + return nil, acct, fmt.Errorf("forming request: %v", err) + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("User-Agent", certmagic.UserAgent) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, acct, fmt.Errorf("performing EAB credentials request: %v", err) + } + defer resp.Body.Close() + + var result struct { + Success bool `json:"success"` + Error struct { + Code int `json:"code"` + Type string `json:"type"` + } `json:"error"` + EABKID string `json:"eab_kid"` + EABHMACKey string `json:"eab_hmac_key"` + } + err = json.NewDecoder(resp.Body).Decode(&result) + if err != nil { + return nil, acct, fmt.Errorf("decoding API response: %v", err) + } + if result.Error.Code != 0 { + // do this check first because ZeroSSL's API returns 200 on errors + return nil, acct, fmt.Errorf("failed getting EAB credentials: HTTP %d: %s (code %d)", + resp.StatusCode, result.Error.Type, result.Error.Code) + } + if resp.StatusCode != http.StatusOK { + return nil, acct, fmt.Errorf("failed getting EAB credentials: HTTP %d", resp.StatusCode) + } + + if c := iss.logger.Check(zapcore.InfoLevel, "generated EAB credentials"); c != nil { + c.Write(zap.String("key_id", result.EABKID)) + } + + return &acme.EAB{ + KeyID: result.EABKID, + MACKey: result.EABHMACKey, + }, acct, nil +} + +// UnmarshalCaddyfile deserializes Caddyfile tokens into iss. +// +// ... acme [] { +// dir +// test_dir +// email +// profile +// timeout +// disable_http_challenge +// disable_tlsalpn_challenge +// alt_http_port +// alt_tlsalpn_port +// eab +// trusted_roots +// dns [] +// propagation_delay +// propagation_timeout +// resolvers +// dns_ttl +// dns_challenge_override_domain +// preferred_chains [smallest] { +// root_common_name +// any_common_name +// } +// } +func (iss *ACMEIssuer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume issuer name + + if d.NextArg() { + iss.CA = d.Val() + if d.NextArg() { + return d.ArgErr() + } + } + + for d.NextBlock(0) { + switch d.Val() { + case "lifetime": + var lifetimeStr string + if !d.AllArgs(&lifetimeStr) { + return d.ArgErr() + } + lifetime, err := caddy.ParseDuration(lifetimeStr) + if err != nil { + return d.Errf("invalid lifetime %s: %v", lifetimeStr, err) + } + if lifetime < 0 { + return d.Errf("lifetime must be >= 0: %s", lifetime) + } + iss.CertificateLifetime = caddy.Duration(lifetime) + + case "dir": + if iss.CA != "" { + return d.Errf("directory is already specified: %s", iss.CA) + } + if !d.AllArgs(&iss.CA) { + return d.ArgErr() + } + + case "test_dir": + if !d.AllArgs(&iss.TestCA) { + return d.ArgErr() + } + + case "email": + if !d.AllArgs(&iss.Email) { + return d.ArgErr() + } + + case "profile": + if !d.AllArgs(&iss.Profile) { + return d.ArgErr() + } + + case "timeout": + var timeoutStr string + if !d.AllArgs(&timeoutStr) { + return d.ArgErr() + } + timeout, err := caddy.ParseDuration(timeoutStr) + if err != nil { + return d.Errf("invalid timeout duration %s: %v", timeoutStr, err) + } + iss.ACMETimeout = caddy.Duration(timeout) + + case "disable_http_challenge": + if d.NextArg() { + return d.ArgErr() + } + if iss.Challenges == nil { + iss.Challenges = new(ChallengesConfig) + } + if iss.Challenges.HTTP == nil { + iss.Challenges.HTTP = new(HTTPChallengeConfig) + } + iss.Challenges.HTTP.Disabled = true + + case "disable_tlsalpn_challenge": + if d.NextArg() { + return d.ArgErr() + } + if iss.Challenges == nil { + iss.Challenges = new(ChallengesConfig) + } + if iss.Challenges.TLSALPN == nil { + iss.Challenges.TLSALPN = new(TLSALPNChallengeConfig) + } + iss.Challenges.TLSALPN.Disabled = true + + case "alt_http_port": + if !d.NextArg() { + return d.ArgErr() + } + port, err := strconv.Atoi(d.Val()) + if err != nil { + return d.Errf("invalid port %s: %v", d.Val(), err) + } + if iss.Challenges == nil { + iss.Challenges = new(ChallengesConfig) + } + if iss.Challenges.HTTP == nil { + iss.Challenges.HTTP = new(HTTPChallengeConfig) + } + iss.Challenges.HTTP.AlternatePort = port + + case "alt_tlsalpn_port": + if !d.NextArg() { + return d.ArgErr() + } + port, err := strconv.Atoi(d.Val()) + if err != nil { + return d.Errf("invalid port %s: %v", d.Val(), err) + } + if iss.Challenges == nil { + iss.Challenges = new(ChallengesConfig) + } + if iss.Challenges.TLSALPN == nil { + iss.Challenges.TLSALPN = new(TLSALPNChallengeConfig) + } + iss.Challenges.TLSALPN.AlternatePort = port + + case "eab": + iss.ExternalAccount = new(acme.EAB) + if !d.AllArgs(&iss.ExternalAccount.KeyID, &iss.ExternalAccount.MACKey) { + return d.ArgErr() + } + + case "trusted_roots": + iss.TrustedRootsPEMFiles = d.RemainingArgs() + + case "dns": + if !d.NextArg() { + return d.ArgErr() + } + provName := d.Val() + if iss.Challenges == nil { + iss.Challenges = new(ChallengesConfig) + } + if iss.Challenges.DNS == nil { + iss.Challenges.DNS = new(DNSChallengeConfig) + } + unm, err := caddyfile.UnmarshalModule(d, "dns.providers."+provName) + if err != nil { + return err + } + iss.Challenges.DNS.ProviderRaw = caddyconfig.JSONModuleObject(unm, "name", provName, nil) + + case "propagation_delay": + if !d.NextArg() { + return d.ArgErr() + } + delayStr := d.Val() + delay, err := caddy.ParseDuration(delayStr) + if err != nil { + return d.Errf("invalid propagation_delay duration %s: %v", delayStr, err) + } + if iss.Challenges == nil { + iss.Challenges = new(ChallengesConfig) + } + if iss.Challenges.DNS == nil { + iss.Challenges.DNS = new(DNSChallengeConfig) + } + iss.Challenges.DNS.PropagationDelay = caddy.Duration(delay) + + case "propagation_timeout": + if !d.NextArg() { + return d.ArgErr() + } + timeoutStr := d.Val() + var timeout time.Duration + if timeoutStr == "-1" { + timeout = time.Duration(-1) + } else { + var err error + timeout, err = caddy.ParseDuration(timeoutStr) + if err != nil { + return d.Errf("invalid propagation_timeout duration %s: %v", timeoutStr, err) + } + } + if iss.Challenges == nil { + iss.Challenges = new(ChallengesConfig) + } + if iss.Challenges.DNS == nil { + iss.Challenges.DNS = new(DNSChallengeConfig) + } + iss.Challenges.DNS.PropagationTimeout = caddy.Duration(timeout) + + case "resolvers": + if iss.Challenges == nil { + iss.Challenges = new(ChallengesConfig) + } + if iss.Challenges.DNS == nil { + iss.Challenges.DNS = new(DNSChallengeConfig) + } + iss.Challenges.DNS.Resolvers = d.RemainingArgs() + if len(iss.Challenges.DNS.Resolvers) == 0 { + return d.ArgErr() + } + + case "dns_ttl": + if !d.NextArg() { + return d.ArgErr() + } + ttlStr := d.Val() + ttl, err := caddy.ParseDuration(ttlStr) + if err != nil { + return d.Errf("invalid dns_ttl duration %s: %v", ttlStr, err) + } + if iss.Challenges == nil { + iss.Challenges = new(ChallengesConfig) + } + if iss.Challenges.DNS == nil { + iss.Challenges.DNS = new(DNSChallengeConfig) + } + iss.Challenges.DNS.TTL = caddy.Duration(ttl) + + case "dns_challenge_override_domain": + arg := d.RemainingArgs() + if len(arg) != 1 { + return d.ArgErr() + } + if iss.Challenges == nil { + iss.Challenges = new(ChallengesConfig) + } + if iss.Challenges.DNS == nil { + iss.Challenges.DNS = new(DNSChallengeConfig) + } + iss.Challenges.DNS.OverrideDomain = arg[0] + + case "preferred_chains": + chainPref, err := ParseCaddyfilePreferredChainsOptions(d) + if err != nil { + return err + } + iss.PreferredChains = chainPref + + default: + return d.Errf("unrecognized ACME issuer property: %s", d.Val()) + } + } + return nil +} + +func ParseCaddyfilePreferredChainsOptions(d *caddyfile.Dispenser) (*ChainPreference, error) { + chainPref := new(ChainPreference) + if d.NextArg() { + smallestOpt := d.Val() + if smallestOpt == "smallest" { + trueBool := true + chainPref.Smallest = &trueBool + if d.NextArg() { // Only one argument allowed + return nil, d.ArgErr() + } + if d.NextBlock(d.Nesting()) { // Don't allow other options when smallest == true + return nil, d.Err("No more options are accepted when using the 'smallest' option") + } + } else { // Smallest option should always be 'smallest' or unset + return nil, d.Errf("Invalid argument '%s'", smallestOpt) + } + } + for nesting := d.Nesting(); d.NextBlock(nesting); { + switch d.Val() { + case "root_common_name": + rootCommonNameOpt := d.RemainingArgs() + chainPref.RootCommonName = rootCommonNameOpt + if rootCommonNameOpt == nil { + return nil, d.ArgErr() + } + if chainPref.AnyCommonName != nil { + return nil, d.Err("Can't set root_common_name when any_common_name is already set") + } + + case "any_common_name": + anyCommonNameOpt := d.RemainingArgs() + chainPref.AnyCommonName = anyCommonNameOpt + if anyCommonNameOpt == nil { + return nil, d.ArgErr() + } + if chainPref.RootCommonName != nil { + return nil, d.Err("Can't set any_common_name when root_common_name is already set") + } + + default: + return nil, d.Errf("Received unrecognized parameter '%s'", d.Val()) + } + } + + if chainPref.Smallest == nil && chainPref.RootCommonName == nil && chainPref.AnyCommonName == nil { + return nil, d.Err("No options for preferred_chains received") + } + + return chainPref, nil +} + +// ChainPreference describes the client's preferred certificate chain, +// useful if the CA offers alternate chains. The first matching chain +// will be selected. +type ChainPreference struct { + // Prefer chains with the fewest number of bytes. + Smallest *bool `json:"smallest,omitempty"` + + // Select first chain having a root with one of + // these common names. + RootCommonName []string `json:"root_common_name,omitempty"` + + // Select first chain that has any issuer with one + // of these common names. + AnyCommonName []string `json:"any_common_name,omitempty"` +} + +// Interface guards +var ( + _ certmagic.PreChecker = (*ACMEIssuer)(nil) + _ certmagic.Issuer = (*ACMEIssuer)(nil) + _ certmagic.Revoker = (*ACMEIssuer)(nil) + _ certmagic.RenewalInfoGetter = (*ACMEIssuer)(nil) + _ caddy.Provisioner = (*ACMEIssuer)(nil) + _ ConfigSetter = (*ACMEIssuer)(nil) + _ caddyfile.Unmarshaler = (*ACMEIssuer)(nil) +) diff --git a/modules/caddytls/automation.go b/modules/caddytls/automation.go new file mode 100644 index 00000000000..1bc86020da2 --- /dev/null +++ b/modules/caddytls/automation.go @@ -0,0 +1,523 @@ +// 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 caddytls + +import ( + "context" + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "net" + "slices" + "strings" + + "github.com/caddyserver/certmagic" + "github.com/mholt/acmez/v3" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" +) + +// AutomationConfig governs the automated management of TLS certificates. +type AutomationConfig struct { + // The list of automation policies. The first policy matching + // a certificate or subject name will be applied. + Policies []*AutomationPolicy `json:"policies,omitempty"` + + // On-Demand TLS defers certificate operations to the + // moment they are needed, e.g. during a TLS handshake. + // Useful when you don't know all the hostnames at + // config-time, or when you are not in control of the + // domain names you are managing certificates for. + // In 2015, Caddy became the first web server to + // implement this experimental technology. + // + // Note that this field does not enable on-demand TLS; + // it only configures it for when it is used. To enable + // it, create an automation policy with `on_demand`. + OnDemand *OnDemandConfig `json:"on_demand,omitempty"` + + // Caddy staples OCSP (and caches the response) for all + // qualifying certificates by default. This setting + // changes how often it scans responses for freshness, + // and updates them if they are getting stale. Default: 1h + OCSPCheckInterval caddy.Duration `json:"ocsp_interval,omitempty"` + + // Every so often, Caddy will scan all loaded, managed + // certificates for expiration. This setting changes how + // frequently the scan for expiring certificates is + // performed. Default: 10m + RenewCheckInterval caddy.Duration `json:"renew_interval,omitempty"` + + // How often to scan storage units for old or expired + // assets and remove them. These scans exert lots of + // reads (and list operations) on the storage module, so + // choose a longer interval for large deployments. + // Default: 24h + // + // Storage will always be cleaned when the process first + // starts. Then, a new cleaning will be started this + // duration after the previous cleaning started if the + // previous cleaning finished in less than half the time + // of this interval (otherwise next start will be skipped). + StorageCleanInterval caddy.Duration `json:"storage_clean_interval,omitempty"` + + defaultPublicAutomationPolicy *AutomationPolicy + defaultInternalAutomationPolicy *AutomationPolicy // only initialized if necessary +} + +// AutomationPolicy designates the policy for automating the +// management (obtaining, renewal, and revocation) of managed +// TLS certificates. +// +// An AutomationPolicy value is not valid until it has been +// provisioned; use the `AddAutomationPolicy()` method on the +// TLS app to properly provision a new policy. +type AutomationPolicy struct { + // Which subjects (hostnames or IP addresses) this policy applies to. + // + // This list is a filter, not a command. In other words, it is used + // only to filter whether this policy should apply to a subject that + // needs a certificate; it does NOT command the TLS app to manage a + // certificate for that subject. To have Caddy automate a certificate + // or specific subjects, use the "automate" certificate loader module + // of the TLS app. + SubjectsRaw []string `json:"subjects,omitempty"` + + // The modules that may issue certificates. Default: internal if all + // subjects do not qualify for public certificates; otherwise acme and + // zerossl. + IssuersRaw []json.RawMessage `json:"issuers,omitempty" caddy:"namespace=tls.issuance inline_key=module"` + + // Modules that can get a custom certificate to use for any + // given TLS handshake at handshake-time. Custom certificates + // can be useful if another entity is managing certificates + // and Caddy need only get it and serve it. Specifying a Manager + // enables on-demand TLS, i.e. it has the side-effect of setting + // the on_demand parameter to `true`. + // + // TODO: This is an EXPERIMENTAL feature. Subject to change or removal. + ManagersRaw []json.RawMessage `json:"get_certificate,omitempty" caddy:"namespace=tls.get_certificate inline_key=via"` + + // If true, certificates will be requested with MustStaple. Not all + // CAs support this, and there are potentially serious consequences + // of enabling this feature without proper threat modeling. + MustStaple bool `json:"must_staple,omitempty"` + + // How long before a certificate's expiration to try renewing it, + // as a function of its total lifetime. As a general and conservative + // rule, it is a good idea to renew a certificate when it has about + // 1/3 of its total lifetime remaining. This utilizes the majority + // of the certificate's lifetime while still saving time to + // troubleshoot problems. However, for extremely short-lived certs, + // you may wish to increase the ratio to ~1/2. + RenewalWindowRatio float64 `json:"renewal_window_ratio,omitempty"` + + // The type of key to generate for certificates. + // Supported values: `ed25519`, `p256`, `p384`, `rsa2048`, `rsa4096`. + KeyType string `json:"key_type,omitempty"` + + // Optionally configure a separate storage module associated with this + // manager, instead of using Caddy's global/default-configured storage. + StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"` + + // If true, certificates will be managed "on demand"; that is, during + // TLS handshakes or when needed, as opposed to at startup or config + // load. This enables On-Demand TLS for this policy. + OnDemand bool `json:"on_demand,omitempty"` + + // If true, private keys already existing in storage + // will be reused. Otherwise, a new key will be + // created for every new certificate to mitigate + // pinning and reduce the scope of key compromise. + // TEMPORARY: Key pinning is against industry best practices. + // This property will likely be removed in the future. + // Do not rely on it forever; watch the release notes. + ReusePrivateKeys bool `json:"reuse_private_keys,omitempty"` + + // Disables OCSP stapling. Disabling OCSP stapling puts clients at + // greater risk, reduces their privacy, and usually lowers client + // performance. It is NOT recommended to disable this unless you + // are able to justify the costs. + // EXPERIMENTAL. Subject to change. + DisableOCSPStapling bool `json:"disable_ocsp_stapling,omitempty"` + + // Overrides the URLs of OCSP responders embedded in certificates. + // Each key is a OCSP server URL to override, and its value is the + // replacement. An empty value will disable querying of that server. + // EXPERIMENTAL. Subject to change. + OCSPOverrides map[string]string `json:"ocsp_overrides,omitempty"` + + // Issuers and Managers store the decoded issuer and manager modules; + // they are only used to populate an underlying certmagic.Config's + // fields during provisioning so that the modules can survive a + // re-provisioning. + Issuers []certmagic.Issuer `json:"-"` + Managers []certmagic.Manager `json:"-"` + + subjects []string + magic *certmagic.Config + storage certmagic.Storage + + // Whether this policy had explicit managers configured directly on it. + hadExplicitManagers bool +} + +// Provision sets up ap and builds its underlying CertMagic config. +func (ap *AutomationPolicy) Provision(tlsApp *TLS) error { + // replace placeholders in subjects to allow environment variables + repl := caddy.NewReplacer() + subjects := make([]string, len(ap.SubjectsRaw)) + for i, sub := range ap.SubjectsRaw { + subjects[i] = repl.ReplaceAll(sub, "") + } + ap.subjects = subjects + + // policy-specific storage implementation + if ap.StorageRaw != nil { + val, err := tlsApp.ctx.LoadModule(ap, "StorageRaw") + if err != nil { + return fmt.Errorf("loading TLS storage module: %v", err) + } + cmStorage, err := val.(caddy.StorageConverter).CertMagicStorage() + if err != nil { + return fmt.Errorf("creating TLS storage configuration: %v", err) + } + ap.storage = cmStorage + } + + // we don't store loaded modules directly in the certmagic config since + // policy provisioning may happen more than once (during auto-HTTPS) and + // loading a module clears its config bytes; thus, load the module and + // store them on the policy before putting it on the config + + // load and provision any cert manager modules + if ap.ManagersRaw != nil { + ap.hadExplicitManagers = true + vals, err := tlsApp.ctx.LoadModule(ap, "ManagersRaw") + if err != nil { + return fmt.Errorf("loading external certificate manager modules: %v", err) + } + for _, getCertVal := range vals.([]any) { + ap.Managers = append(ap.Managers, getCertVal.(certmagic.Manager)) + } + } + + // load and provision any explicitly-configured issuer modules + if ap.IssuersRaw != nil { + val, err := tlsApp.ctx.LoadModule(ap, "IssuersRaw") + if err != nil { + return fmt.Errorf("loading TLS automation management module: %s", err) + } + for _, issVal := range val.([]any) { + ap.Issuers = append(ap.Issuers, issVal.(certmagic.Issuer)) + } + } + + issuers := ap.Issuers + if len(issuers) == 0 { + var err error + issuers, err = DefaultIssuersProvisioned(tlsApp.ctx) + if err != nil { + return err + } + } + + keyType := ap.KeyType + if keyType != "" { + var err error + keyType, err = caddy.NewReplacer().ReplaceOrErr(ap.KeyType, true, true) + if err != nil { + return fmt.Errorf("invalid key type %s: %s", ap.KeyType, err) + } + if _, ok := supportedCertKeyTypes[keyType]; !ok { + return fmt.Errorf("unrecognized key type: %s", keyType) + } + } + keySource := certmagic.StandardKeyGenerator{ + KeyType: supportedCertKeyTypes[keyType], + } + + storage := ap.storage + if storage == nil { + storage = tlsApp.ctx.Storage() + } + + // on-demand TLS + var ond *certmagic.OnDemandConfig + if ap.OnDemand || len(ap.Managers) > 0 { + // permission module is now required after a number of negligence cases that allowed abuse; + // but it may still be optional for explicit subjects (bounded, non-wildcard), for the + // internal issuer since it doesn't cause public PKI pressure on ACME servers; subtly, it + // is useful to allow on-demand TLS to be enabled so Managers can be used, but to still + // prevent issuance from Issuers (when Managers don't provide a certificate) if there's no + // permission module configured + noProtections := ap.isWildcardOrDefault() && !ap.onlyInternalIssuer() && (tlsApp.Automation == nil || tlsApp.Automation.OnDemand == nil || tlsApp.Automation.OnDemand.permission == nil) + failClosed := noProtections && !ap.hadExplicitManagers // don't allow on-demand issuance (other than implicit managers) if no managers have been explicitly configured + if noProtections { + if !ap.hadExplicitManagers { + // no managers, no explicitly-configured permission module, this is a config error + return fmt.Errorf("on-demand TLS cannot be enabled without a permission module to prevent abuse; please refer to documentation for details") + } + // allow on-demand to be enabled but only for the purpose of the Managers; issuance won't be allowed from Issuers + tlsApp.logger.Warn("on-demand TLS can only get certificates from the configured external manager(s) because no ask endpoint / permission module is specified") + } + ond = &certmagic.OnDemandConfig{ + DecisionFunc: func(ctx context.Context, name string) error { + if failClosed { + return fmt.Errorf("no permission module configured; certificates not allowed except from external Managers") + } + if tlsApp.Automation == nil || tlsApp.Automation.OnDemand == nil { + return nil + } + + // logging the remote IP can be useful for servers that want to count + // attempts from clients to detect patterns of abuse -- it should NOT be + // used solely for decision making, however + var remoteIP string + if hello, ok := ctx.Value(certmagic.ClientHelloInfoCtxKey).(*tls.ClientHelloInfo); ok && hello != nil { + if remote := hello.Conn.RemoteAddr(); remote != nil { + remoteIP, _, _ = net.SplitHostPort(remote.String()) + } + } + if c := tlsApp.logger.Check(zapcore.DebugLevel, "asking for permission for on-demand certificate"); c != nil { + c.Write( + zap.String("remote_ip", remoteIP), + zap.String("domain", name), + ) + } + + // ask the permission module if this cert is allowed + if err := tlsApp.Automation.OnDemand.permission.CertificateAllowed(ctx, name); err != nil { + // distinguish true errors from denials, because it's important to elevate actual errors + if errors.Is(err, ErrPermissionDenied) { + if c := tlsApp.logger.Check(zapcore.DebugLevel, "on-demand certificate issuance denied"); c != nil { + c.Write( + zap.String("domain", name), + zap.Error(err), + ) + } + } else { + if c := tlsApp.logger.Check(zapcore.ErrorLevel, "failed to get permission for on-demand certificate"); c != nil { + c.Write( + zap.String("domain", name), + zap.Error(err), + ) + } + } + return err + } + + return nil + }, + Managers: ap.Managers, + } + } + + template := certmagic.Config{ + MustStaple: ap.MustStaple, + RenewalWindowRatio: ap.RenewalWindowRatio, + KeySource: keySource, + OnEvent: tlsApp.onEvent, + OnDemand: ond, + ReusePrivateKeys: ap.ReusePrivateKeys, + OCSP: certmagic.OCSPConfig{ + DisableStapling: ap.DisableOCSPStapling, + ResponderOverrides: ap.OCSPOverrides, + }, + Storage: storage, + Issuers: issuers, + Logger: tlsApp.logger, + } + certCacheMu.RLock() + ap.magic = certmagic.New(certCache, template) + certCacheMu.RUnlock() + + // sometimes issuers may need the parent certmagic.Config in + // order to function properly (for example, ACMEIssuer needs + // access to the correct storage and cache so it can solve + // ACME challenges -- it's an annoying, inelegant circular + // dependency that I don't know how to resolve nicely!) + for _, issuer := range ap.magic.Issuers { + if annoying, ok := issuer.(ConfigSetter); ok { + annoying.SetConfig(ap.magic) + } + } + + return nil +} + +// Subjects returns the list of subjects with all placeholders replaced. +func (ap *AutomationPolicy) Subjects() []string { + return ap.subjects +} + +// AllInternalSubjects returns true if all the subjects on this policy are internal. +func (ap *AutomationPolicy) AllInternalSubjects() bool { + return !slices.ContainsFunc(ap.subjects, func(s string) bool { + return !certmagic.SubjectIsInternal(s) + }) +} + +func (ap *AutomationPolicy) onlyInternalIssuer() bool { + if len(ap.Issuers) != 1 { + return false + } + _, ok := ap.Issuers[0].(*InternalIssuer) + return ok +} + +// isWildcardOrDefault determines if the subjects include any wildcard domains, +// or is the "default" policy (i.e. no subjects) which is unbounded. +func (ap *AutomationPolicy) isWildcardOrDefault() bool { + isWildcardOrDefault := false + if len(ap.subjects) == 0 { + isWildcardOrDefault = true + } + for _, sub := range ap.subjects { + if strings.HasPrefix(sub, "*") { + isWildcardOrDefault = true + break + } + } + return isWildcardOrDefault +} + +// DefaultIssuers returns empty Issuers (not provisioned) to be used as defaults. +// This function is experimental and has no compatibility promises. +func DefaultIssuers(userEmail string) []certmagic.Issuer { + issuers := []certmagic.Issuer{new(ACMEIssuer)} + if strings.TrimSpace(userEmail) != "" { + issuers = append(issuers, &ACMEIssuer{ + CA: certmagic.ZeroSSLProductionCA, + Email: userEmail, + }) + } + return issuers +} + +// DefaultIssuersProvisioned returns empty but provisioned default Issuers from +// DefaultIssuers(). This function is experimental and has no compatibility promises. +func DefaultIssuersProvisioned(ctx caddy.Context) ([]certmagic.Issuer, error) { + issuers := DefaultIssuers("") + for i, iss := range issuers { + if prov, ok := iss.(caddy.Provisioner); ok { + err := prov.Provision(ctx) + if err != nil { + return nil, fmt.Errorf("provisioning default issuer %d: %T: %v", i, iss, err) + } + } + } + return issuers, nil +} + +// ChallengesConfig configures the ACME challenges. +type ChallengesConfig struct { + // HTTP configures the ACME HTTP challenge. This + // challenge is enabled and used automatically + // and by default. + HTTP *HTTPChallengeConfig `json:"http,omitempty"` + + // TLSALPN configures the ACME TLS-ALPN challenge. + // This challenge is enabled and used automatically + // and by default. + TLSALPN *TLSALPNChallengeConfig `json:"tls-alpn,omitempty"` + + // Configures the ACME DNS challenge. Because this + // challenge typically requires credentials for + // interfacing with a DNS provider, this challenge is + // not enabled by default. This is the only challenge + // type which does not require a direct connection + // to Caddy from an external server. + // + // NOTE: DNS providers are currently being upgraded, + // and this API is subject to change, but should be + // stabilized soon. + DNS *DNSChallengeConfig `json:"dns,omitempty"` + + // Optionally customize the host to which a listener + // is bound if required for solving a challenge. + BindHost string `json:"bind_host,omitempty"` +} + +// HTTPChallengeConfig configures the ACME HTTP challenge. +type HTTPChallengeConfig struct { + // If true, the HTTP challenge will be disabled. + Disabled bool `json:"disabled,omitempty"` + + // An alternate port on which to service this + // challenge. Note that the HTTP challenge port is + // hard-coded into the spec and cannot be changed, + // so you would have to forward packets from the + // standard HTTP challenge port to this one. + AlternatePort int `json:"alternate_port,omitempty"` +} + +// TLSALPNChallengeConfig configures the ACME TLS-ALPN challenge. +type TLSALPNChallengeConfig struct { + // If true, the TLS-ALPN challenge will be disabled. + Disabled bool `json:"disabled,omitempty"` + + // An alternate port on which to service this + // challenge. Note that the TLS-ALPN challenge port + // is hard-coded into the spec and cannot be changed, + // so you would have to forward packets from the + // standard TLS-ALPN challenge port to this one. + AlternatePort int `json:"alternate_port,omitempty"` +} + +// DNSChallengeConfig configures the ACME DNS challenge. +// +// NOTE: This API is still experimental and is subject to change. +type DNSChallengeConfig struct { + // The DNS provider module to use which will manage + // the DNS records relevant to the ACME challenge. + // Required. + ProviderRaw json.RawMessage `json:"provider,omitempty" caddy:"namespace=dns.providers inline_key=name"` + + // The TTL of the TXT record used for the DNS challenge. + TTL caddy.Duration `json:"ttl,omitempty"` + + // How long to wait before starting propagation checks. + // Default: 0 (no wait). + PropagationDelay caddy.Duration `json:"propagation_delay,omitempty"` + + // Maximum time to wait for temporary DNS record to appear. + // Set to -1 to disable propagation checks. + // Default: 2 minutes. + PropagationTimeout caddy.Duration `json:"propagation_timeout,omitempty"` + + // Custom DNS resolvers to prefer over system/built-in defaults. + // Often necessary to configure when using split-horizon DNS. + Resolvers []string `json:"resolvers,omitempty"` + + // Override the domain to use for the DNS challenge. This + // is to delegate the challenge to a different domain, + // e.g. one that updates faster or one with a provider API. + OverrideDomain string `json:"override_domain,omitempty"` + + solver acmez.Solver +} + +// ConfigSetter is implemented by certmagic.Issuers that +// need access to a parent certmagic.Config as part of +// their provisioning phase. For example, the ACMEIssuer +// requires a config so it can access storage and the +// cache to solve ACME challenges. +type ConfigSetter interface { + SetConfig(cfg *certmagic.Config) +} diff --git a/modules/caddytls/capools.go b/modules/caddytls/capools.go new file mode 100644 index 00000000000..c73bc4832fd --- /dev/null +++ b/modules/caddytls/capools.go @@ -0,0 +1,697 @@ +package caddytls + +import ( + "crypto/tls" + "crypto/x509" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "os" + "reflect" + + "github.com/caddyserver/certmagic" + + "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() { + caddy.RegisterModule(InlineCAPool{}) + caddy.RegisterModule(FileCAPool{}) + caddy.RegisterModule(PKIRootCAPool{}) + caddy.RegisterModule(PKIIntermediateCAPool{}) + caddy.RegisterModule(StoragePool{}) + caddy.RegisterModule(HTTPCertPool{}) +} + +// The interface to be implemented by all guest modules part of +// the namespace 'tls.ca_pool.source.' +type CA interface { + CertPool() *x509.CertPool +} + +// InlineCAPool is a certificate authority pool provider coming from +// a DER-encoded certificates in the config +type InlineCAPool struct { + // A list of base64 DER-encoded CA certificates + // against which to validate client certificates. + // Client certs which are not signed by any of + // these CAs will be rejected. + TrustedCACerts []string `json:"trusted_ca_certs,omitempty"` + + pool *x509.CertPool +} + +// CaddyModule implements caddy.Module. +func (icp InlineCAPool) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.ca_pool.source.inline", + New: func() caddy.Module { + return new(InlineCAPool) + }, + } +} + +// Provision implements caddy.Provisioner. +func (icp *InlineCAPool) Provision(ctx caddy.Context) error { + caPool := x509.NewCertPool() + for i, clientCAString := range icp.TrustedCACerts { + clientCA, err := decodeBase64DERCert(clientCAString) + if err != nil { + return fmt.Errorf("parsing certificate at index %d: %v", i, err) + } + caPool.AddCert(clientCA) + } + icp.pool = caPool + + return nil +} + +// Syntax: +// +// trust_pool inline { +// trust_der ... +// } +// +// The 'trust_der' directive can be specified multiple times. +func (icp *InlineCAPool) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume module name + if d.CountRemainingArgs() > 0 { + return d.ArgErr() + } + for d.NextBlock(0) { + switch d.Val() { + case "trust_der": + icp.TrustedCACerts = append(icp.TrustedCACerts, d.RemainingArgs()...) + default: + return d.Errf("unrecognized directive: %s", d.Val()) + } + } + if len(icp.TrustedCACerts) == 0 { + return d.Err("no certificates specified") + } + return nil +} + +// CertPool implements CA. +func (icp InlineCAPool) CertPool() *x509.CertPool { + return icp.pool +} + +// FileCAPool generates trusted root certificates pool from the designated DER and PEM file +type FileCAPool struct { + // TrustedCACertPEMFiles is a list of PEM file names + // from which to load certificates of trusted CAs. + // Client certificates which are not signed by any of + // these CA certificates will be rejected. + TrustedCACertPEMFiles []string `json:"pem_files,omitempty"` + + pool *x509.CertPool +} + +// CaddyModule implements caddy.Module. +func (FileCAPool) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.ca_pool.source.file", + New: func() caddy.Module { + return new(FileCAPool) + }, + } +} + +// Loads and decodes the DER and pem files to generate the certificate pool +func (f *FileCAPool) Provision(ctx caddy.Context) error { + caPool := x509.NewCertPool() + for _, pemFile := range f.TrustedCACertPEMFiles { + pemContents, err := os.ReadFile(pemFile) + if err != nil { + return fmt.Errorf("reading %s: %v", pemFile, err) + } + caPool.AppendCertsFromPEM(pemContents) + } + f.pool = caPool + return nil +} + +// Syntax: +// +// trust_pool file [...] { +// pem_file ... +// } +// +// The 'pem_file' directive can be specified multiple times. +func (fcap *FileCAPool) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume module name + fcap.TrustedCACertPEMFiles = append(fcap.TrustedCACertPEMFiles, d.RemainingArgs()...) + for d.NextBlock(0) { + switch d.Val() { + case "pem_file": + fcap.TrustedCACertPEMFiles = append(fcap.TrustedCACertPEMFiles, d.RemainingArgs()...) + default: + return d.Errf("unrecognized directive: %s", d.Val()) + } + } + if len(fcap.TrustedCACertPEMFiles) == 0 { + return d.Err("no certificates specified") + } + return nil +} + +func (f FileCAPool) CertPool() *x509.CertPool { + return f.pool +} + +// PKIRootCAPool extracts the trusted root certificates from Caddy's native 'pki' app +type PKIRootCAPool struct { + // List of the Authority names that are configured in the `pki` app whose root certificates are trusted + Authority []string `json:"authority,omitempty"` + + ca []*caddypki.CA + pool *x509.CertPool +} + +// CaddyModule implements caddy.Module. +func (PKIRootCAPool) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.ca_pool.source.pki_root", + New: func() caddy.Module { + return new(PKIRootCAPool) + }, + } +} + +// Loads the PKI app and load the root certificates into the certificate pool +func (p *PKIRootCAPool) Provision(ctx caddy.Context) error { + pkiApp, err := ctx.AppIfConfigured("pki") + if err != nil { + return fmt.Errorf("pki_root CA pool requires that a PKI app is configured: %v", err) + } + pki := pkiApp.(*caddypki.PKI) + for _, caID := range p.Authority { + c, err := pki.GetCA(ctx, caID) + if err != nil || c == nil { + return fmt.Errorf("getting CA %s: %v", caID, err) + } + p.ca = append(p.ca, c) + } + + caPool := x509.NewCertPool() + for _, ca := range p.ca { + caPool.AddCert(ca.RootCertificate()) + } + p.pool = caPool + + return nil +} + +// Syntax: +// +// trust_pool pki_root [...] { +// authority ... +// } +// +// The 'authority' directive can be specified multiple times. +func (pkir *PKIRootCAPool) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume module name + pkir.Authority = append(pkir.Authority, d.RemainingArgs()...) + for nesting := d.Nesting(); d.NextBlock(nesting); { + switch d.Val() { + case "authority": + pkir.Authority = append(pkir.Authority, d.RemainingArgs()...) + default: + return d.Errf("unrecognized directive: %s", d.Val()) + } + } + if len(pkir.Authority) == 0 { + return d.Err("no authorities specified") + } + return nil +} + +// return the certificate pool generated with root certificates from the PKI app +func (p PKIRootCAPool) CertPool() *x509.CertPool { + return p.pool +} + +// PKIIntermediateCAPool extracts the trusted intermediate certificates from Caddy's native 'pki' app +type PKIIntermediateCAPool struct { + // List of the Authority names that are configured in the `pki` app whose intermediate certificates are trusted + Authority []string `json:"authority,omitempty"` + + ca []*caddypki.CA + pool *x509.CertPool +} + +// CaddyModule implements caddy.Module. +func (PKIIntermediateCAPool) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.ca_pool.source.pki_intermediate", + New: func() caddy.Module { + return new(PKIIntermediateCAPool) + }, + } +} + +// Loads the PKI app and load the intermediate certificates into the certificate pool +func (p *PKIIntermediateCAPool) Provision(ctx caddy.Context) error { + pkiApp, err := ctx.AppIfConfigured("pki") + if err != nil { + return fmt.Errorf("pki_intermediate CA pool requires that a PKI app is configured: %v", err) + } + pki := pkiApp.(*caddypki.PKI) + for _, caID := range p.Authority { + c, err := pki.GetCA(ctx, caID) + if err != nil || c == nil { + return fmt.Errorf("getting CA %s: %v", caID, err) + } + p.ca = append(p.ca, c) + } + + caPool := x509.NewCertPool() + for _, ca := range p.ca { + caPool.AddCert(ca.IntermediateCertificate()) + } + p.pool = caPool + return nil +} + +// Syntax: +// +// trust_pool pki_intermediate [...] { +// authority ... +// } +// +// The 'authority' directive can be specified multiple times. +func (pic *PKIIntermediateCAPool) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume module name + pic.Authority = append(pic.Authority, d.RemainingArgs()...) + for nesting := d.Nesting(); d.NextBlock(nesting); { + switch d.Val() { + case "authority": + pic.Authority = append(pic.Authority, d.RemainingArgs()...) + default: + return d.Errf("unrecognized directive: %s", d.Val()) + } + } + if len(pic.Authority) == 0 { + return d.Err("no authorities specified") + } + return nil +} + +// return the certificate pool generated with intermediate certificates from the PKI app +func (p PKIIntermediateCAPool) CertPool() *x509.CertPool { + return p.pool +} + +// StoragePool extracts the trusted certificates root from Caddy storage +type StoragePool struct { + // The storage module where the trusted root certificates are stored. Absent + // explicit storage implies the use of Caddy default storage. + StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"` + + // The storage key/index to the location of the certificates + PEMKeys []string `json:"pem_keys,omitempty"` + + storage certmagic.Storage + pool *x509.CertPool +} + +// CaddyModule implements caddy.Module. +func (StoragePool) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.ca_pool.source.storage", + New: func() caddy.Module { + return new(StoragePool) + }, + } +} + +// Provision implements caddy.Provisioner. +func (ca *StoragePool) Provision(ctx caddy.Context) error { + if ca.StorageRaw != nil { + val, err := ctx.LoadModule(ca, "StorageRaw") + if err != nil { + return fmt.Errorf("loading storage module: %v", err) + } + cmStorage, err := val.(caddy.StorageConverter).CertMagicStorage() + if err != nil { + return fmt.Errorf("creating storage configuration: %v", err) + } + ca.storage = cmStorage + } + if ca.storage == nil { + ca.storage = ctx.Storage() + } + if len(ca.PEMKeys) == 0 { + return fmt.Errorf("no PEM keys specified") + } + caPool := x509.NewCertPool() + for _, caID := range ca.PEMKeys { + bs, err := ca.storage.Load(ctx, caID) + if err != nil { + return fmt.Errorf("error loading cert '%s' from storage: %s", caID, err) + } + if !caPool.AppendCertsFromPEM(bs) { + return fmt.Errorf("failed to add certificate '%s' to pool", caID) + } + } + ca.pool = caPool + + return nil +} + +// Syntax: +// +// trust_pool storage [...] { +// storage +// keys ... +// } +// +// The 'keys' directive can be specified multiple times. +// The'storage' directive is optional and defaults to the default storage module. +func (sp *StoragePool) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume module name + sp.PEMKeys = append(sp.PEMKeys, d.RemainingArgs()...) + for nesting := d.Nesting(); d.NextBlock(nesting); { + switch d.Val() { + case "storage": + if sp.StorageRaw != nil { + return d.Err("storage module already set") + } + if !d.NextArg() { + return d.ArgErr() + } + modStem := d.Val() + modID := "caddy.storage." + modStem + unm, err := caddyfile.UnmarshalModule(d, modID) + if err != nil { + return err + } + storage, ok := unm.(caddy.StorageConverter) + if !ok { + return d.Errf("module %s is not a caddy.StorageConverter", modID) + } + sp.StorageRaw = caddyconfig.JSONModuleObject(storage, "module", modStem, nil) + case "keys": + sp.PEMKeys = append(sp.PEMKeys, d.RemainingArgs()...) + default: + return d.Errf("unrecognized directive: %s", d.Val()) + } + } + return nil +} + +func (p StoragePool) CertPool() *x509.CertPool { + return p.pool +} + +// TLSConfig holds configuration related to the TLS configuration for the +// transport/client. +// copied from with minor modifications: modules/caddyhttp/reverseproxy/httptransport.go +type TLSConfig struct { + // Provides the guest module that provides the trusted certificate authority (CA) certificates + CARaw json.RawMessage `json:"ca,omitempty" caddy:"namespace=tls.ca_pool.source inline_key=provider"` + + // If true, TLS verification of server certificates will be disabled. + // This is insecure and may be removed in the future. Do not use this + // option except in testing or local development environments. + InsecureSkipVerify bool `json:"insecure_skip_verify,omitempty"` + + // The duration to allow a TLS handshake to a server. Default: No timeout. + HandshakeTimeout caddy.Duration `json:"handshake_timeout,omitempty"` + + // The server name used when verifying the certificate received in the TLS + // handshake. By default, this will use the upstream address' host part. + // You only need to override this if your upstream address does not match the + // certificate the upstream is likely to use. For example if the upstream + // address is an IP address, then you would need to configure this to the + // hostname being served by the upstream server. Currently, this does not + // support placeholders because the TLS config is not provisioned on each + // connection, so a static value must be used. + ServerName string `json:"server_name,omitempty"` + + // TLS renegotiation level. TLS renegotiation is the act of performing + // subsequent handshakes on a connection after the first. + // The level can be: + // - "never": (the default) disables renegotiation. + // - "once": allows a remote server to request renegotiation once per connection. + // - "freely": allows a remote server to repeatedly request renegotiation. + Renegotiation string `json:"renegotiation,omitempty"` +} + +func (t *TLSConfig) unmarshalCaddyfile(d *caddyfile.Dispenser) error { + for nesting := d.Nesting(); d.NextBlock(nesting); { + switch d.Val() { + case "ca": + if !d.NextArg() { + return d.ArgErr() + } + modStem := d.Val() + modID := "tls.ca_pool.source." + modStem + unm, err := caddyfile.UnmarshalModule(d, modID) + if err != nil { + return err + } + ca, ok := unm.(CA) + if !ok { + return d.Errf("module %s is not a caddytls.CA", modID) + } + t.CARaw = caddyconfig.JSONModuleObject(ca, "provider", modStem, nil) + case "insecure_skip_verify": + t.InsecureSkipVerify = true + case "handshake_timeout": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("bad timeout value '%s': %v", d.Val(), err) + } + t.HandshakeTimeout = caddy.Duration(dur) + case "server_name": + if !d.Args(&t.ServerName) { + return d.ArgErr() + } + case "renegotiation": + if !d.Args(&t.Renegotiation) { + return d.ArgErr() + } + switch t.Renegotiation { + case "never", "once", "freely": + continue + default: + t.Renegotiation = "" + return d.Errf("unrecognized renegotiation level: %s", t.Renegotiation) + } + default: + return d.Errf("unrecognized directive: %s", d.Val()) + } + } + return nil +} + +// MakeTLSClientConfig returns a tls.Config usable by a client to a backend. +// If there is no custom TLS configuration, a nil config may be returned. +// copied from with minor modifications: modules/caddyhttp/reverseproxy/httptransport.go +func (t *TLSConfig) makeTLSClientConfig(ctx caddy.Context) (*tls.Config, error) { + repl := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + if repl == nil { + repl = caddy.NewReplacer() + } + cfg := new(tls.Config) + + if t.CARaw != nil { + caRaw, err := ctx.LoadModule(t, "CARaw") + if err != nil { + return nil, err + } + ca := caRaw.(CA) + cfg.RootCAs = ca.CertPool() + } + + // Renegotiation + switch t.Renegotiation { + case "never", "": + cfg.Renegotiation = tls.RenegotiateNever + case "once": + cfg.Renegotiation = tls.RenegotiateOnceAsClient + case "freely": + cfg.Renegotiation = tls.RenegotiateFreelyAsClient + default: + return nil, fmt.Errorf("invalid TLS renegotiation level: %v", t.Renegotiation) + } + + // override for the server name used verify the TLS handshake + cfg.ServerName = repl.ReplaceKnown(cfg.ServerName, "") + + // throw all security out the window + cfg.InsecureSkipVerify = t.InsecureSkipVerify + + // only return a config if it's not empty + if reflect.DeepEqual(cfg, new(tls.Config)) { + return nil, nil + } + + return cfg, nil +} + +// The HTTPCertPool fetches the trusted root certificates from HTTP(S) +// endpoints. The TLS connection properties can be customized, including custom +// trusted root certificate. One example usage of this module is to get the trusted +// certificates from another Caddy instance that is running the PKI app and ACME server. +type HTTPCertPool struct { + // the list of URLs that respond with PEM-encoded certificates to trust. + Endpoints []string `json:"endpoints,omitempty"` + + // Customize the TLS connection knobs to used during the HTTP call + TLS *TLSConfig `json:"tls,omitempty"` + + pool *x509.CertPool +} + +// CaddyModule implements caddy.Module. +func (HTTPCertPool) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.ca_pool.source.http", + New: func() caddy.Module { + return new(HTTPCertPool) + }, + } +} + +// Provision implements caddy.Provisioner. +func (hcp *HTTPCertPool) Provision(ctx caddy.Context) error { + caPool := x509.NewCertPool() + + customTransport := http.DefaultTransport.(*http.Transport).Clone() + if hcp.TLS != nil { + tlsConfig, err := hcp.TLS.makeTLSClientConfig(ctx) + if err != nil { + return err + } + customTransport.TLSClientConfig = tlsConfig + } + + httpClient := *http.DefaultClient + httpClient.Transport = customTransport + + for _, uri := range hcp.Endpoints { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return err + } + res, err := httpClient.Do(req) + if err != nil { + return err + } + pembs, err := io.ReadAll(res.Body) + res.Body.Close() + if err != nil { + return err + } + if !caPool.AppendCertsFromPEM(pembs) { + return fmt.Errorf("failed to add certs from URL: %s", uri) + } + } + hcp.pool = caPool + return nil +} + +// Syntax: +// +// trust_pool http [] { +// endpoints +// tls +// } +// +// tls_config: +// +// ca +// insecure_skip_verify +// handshake_timeout +// server_name +// renegotiation +// +// is the name of the CA module to source the trust +// +// certificate pool and follows the syntax of the named CA module. +func (hcp *HTTPCertPool) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume module name + hcp.Endpoints = append(hcp.Endpoints, d.RemainingArgs()...) + for nesting := d.Nesting(); d.NextBlock(nesting); { + switch d.Val() { + case "endpoints": + if d.CountRemainingArgs() == 0 { + return d.ArgErr() + } + hcp.Endpoints = append(hcp.Endpoints, d.RemainingArgs()...) + case "tls": + if hcp.TLS != nil { + return d.Err("tls block already defined") + } + hcp.TLS = new(TLSConfig) + if err := hcp.TLS.unmarshalCaddyfile(d); err != nil { + return err + } + default: + return d.Errf("unrecognized directive: %s", d.Val()) + } + } + + return nil +} + +// report error if the endpoints are not valid URLs +func (hcp HTTPCertPool) Validate() (err error) { + for _, u := range hcp.Endpoints { + _, e := url.Parse(u) + if e != nil { + err = errors.Join(err, e) + } + } + return err +} + +// CertPool return the certificate pool generated from the HTTP responses +func (hcp HTTPCertPool) CertPool() *x509.CertPool { + return hcp.pool +} + +var ( + _ caddy.Module = (*InlineCAPool)(nil) + _ caddy.Provisioner = (*InlineCAPool)(nil) + _ CA = (*InlineCAPool)(nil) + _ caddyfile.Unmarshaler = (*InlineCAPool)(nil) + + _ caddy.Module = (*FileCAPool)(nil) + _ caddy.Provisioner = (*FileCAPool)(nil) + _ CA = (*FileCAPool)(nil) + _ caddyfile.Unmarshaler = (*FileCAPool)(nil) + + _ caddy.Module = (*PKIRootCAPool)(nil) + _ caddy.Provisioner = (*PKIRootCAPool)(nil) + _ CA = (*PKIRootCAPool)(nil) + _ caddyfile.Unmarshaler = (*PKIRootCAPool)(nil) + + _ caddy.Module = (*PKIIntermediateCAPool)(nil) + _ caddy.Provisioner = (*PKIIntermediateCAPool)(nil) + _ CA = (*PKIIntermediateCAPool)(nil) + _ caddyfile.Unmarshaler = (*PKIIntermediateCAPool)(nil) + + _ caddy.Module = (*StoragePool)(nil) + _ caddy.Provisioner = (*StoragePool)(nil) + _ CA = (*StoragePool)(nil) + _ caddyfile.Unmarshaler = (*StoragePool)(nil) + + _ caddy.Module = (*HTTPCertPool)(nil) + _ caddy.Provisioner = (*HTTPCertPool)(nil) + _ caddy.Validator = (*HTTPCertPool)(nil) + _ CA = (*HTTPCertPool)(nil) + _ caddyfile.Unmarshaler = (*HTTPCertPool)(nil) +) diff --git a/modules/caddytls/capools_test.go b/modules/caddytls/capools_test.go new file mode 100644 index 00000000000..b355792d16a --- /dev/null +++ b/modules/caddytls/capools_test.go @@ -0,0 +1,778 @@ +package caddytls + +import ( + "encoding/json" + "fmt" + "reflect" + "testing" + "time" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + _ "github.com/caddyserver/caddy/v2/modules/filestorage" +) + +const ( + test_der_1 = `MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==` + test_cert_file_1 = "../../caddytest/caddy.ca.cer" +) + +func TestInlineCAPoolUnmarshalCaddyfile(t *testing.T) { + type args struct { + d *caddyfile.Dispenser + } + tests := []struct { + name string + args args + expected InlineCAPool + wantErr bool + }{ + { + name: "configuring no certificatest produces an error", + args: args{ + d: caddyfile.NewTestDispenser(` + inline { + } + `), + }, + wantErr: true, + }, + { + name: "configuring certificates as arguments in-line produces an error", + args: args{ + d: caddyfile.NewTestDispenser(fmt.Sprintf(` + inline %s + `, test_der_1)), + }, + wantErr: true, + }, + { + name: "single cert", + args: args{ + d: caddyfile.NewTestDispenser(fmt.Sprintf(` + inline { + trust_der %s + } + `, test_der_1)), + }, + expected: InlineCAPool{ + TrustedCACerts: []string{test_der_1}, + }, + wantErr: false, + }, + { + name: "multiple certs in one line", + args: args{ + d: caddyfile.NewTestDispenser(fmt.Sprintf(` + inline { + trust_der %s %s + } + `, test_der_1, test_der_1), + ), + }, + expected: InlineCAPool{ + TrustedCACerts: []string{test_der_1, test_der_1}, + }, + }, + { + name: "multiple certs in multiple lines", + args: args{ + d: caddyfile.NewTestDispenser(fmt.Sprintf(` + inline { + trust_der %s + trust_der %s + } + `, test_der_1, test_der_1)), + }, + expected: InlineCAPool{ + TrustedCACerts: []string{test_der_1, test_der_1}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + icp := &InlineCAPool{} + if err := icp.UnmarshalCaddyfile(tt.args.d); (err != nil) != tt.wantErr { + t.Errorf("InlineCAPool.UnmarshalCaddyfile() error = %v, wantErr %v", err, tt.wantErr) + } + if !tt.wantErr && !reflect.DeepEqual(&tt.expected, icp) { + t.Errorf("InlineCAPool.UnmarshalCaddyfile() = %v, want %v", icp, tt.expected) + } + }) + } +} + +func TestFileCAPoolUnmarshalCaddyfile(t *testing.T) { + type args struct { + d *caddyfile.Dispenser + } + tests := []struct { + name string + expected FileCAPool + args args + wantErr bool + }{ + { + name: "configuring no certificatest produces an error", + args: args{ + d: caddyfile.NewTestDispenser(` + file { + } + `), + }, + wantErr: true, + }, + { + name: "configuring certificates as arguments in-line produces an error", + args: args{ + d: caddyfile.NewTestDispenser(fmt.Sprintf(` + file %s + `, test_cert_file_1)), + }, + expected: FileCAPool{ + TrustedCACertPEMFiles: []string{test_cert_file_1}, + }, + }, + { + name: "single cert", + args: args{ + d: caddyfile.NewTestDispenser(fmt.Sprintf(` + file { + pem_file %s + } + `, test_cert_file_1)), + }, + expected: FileCAPool{ + TrustedCACertPEMFiles: []string{test_cert_file_1}, + }, + wantErr: false, + }, + { + name: "multiple certs inline and in-block are merged", + args: args{ + d: caddyfile.NewTestDispenser(fmt.Sprintf(` + file %s { + pem_file %s + } + `, test_cert_file_1, test_cert_file_1)), + }, + expected: FileCAPool{ + TrustedCACertPEMFiles: []string{test_cert_file_1, test_cert_file_1}, + }, + wantErr: false, + }, + { + name: "multiple certs in one line", + args: args{ + d: caddyfile.NewTestDispenser(fmt.Sprintf(` + file { + pem_file %s %s + } + `, test_der_1, test_der_1), + ), + }, + expected: FileCAPool{ + TrustedCACertPEMFiles: []string{test_der_1, test_der_1}, + }, + }, + { + name: "multiple certs in multiple lines", + args: args{ + d: caddyfile.NewTestDispenser(fmt.Sprintf(` + file { + pem_file %s + pem_file %s + } + `, test_cert_file_1, test_cert_file_1)), + }, + expected: FileCAPool{ + TrustedCACertPEMFiles: []string{test_cert_file_1, test_cert_file_1}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fcap := &FileCAPool{} + if err := fcap.UnmarshalCaddyfile(tt.args.d); (err != nil) != tt.wantErr { + t.Errorf("FileCAPool.UnmarshalCaddyfile() error = %v, wantErr %v", err, tt.wantErr) + } + if !tt.wantErr && !reflect.DeepEqual(&tt.expected, fcap) { + t.Errorf("FileCAPool.UnmarshalCaddyfile() = %v, want %v", fcap, tt.expected) + } + }) + } +} + +func TestPKIRootCAPoolUnmarshalCaddyfile(t *testing.T) { + type args struct { + d *caddyfile.Dispenser + } + tests := []struct { + name string + expected PKIRootCAPool + args args + wantErr bool + }{ + { + name: "configuring no certificatest produces an error", + args: args{ + d: caddyfile.NewTestDispenser(` + pki_root { + } + `), + }, + wantErr: true, + }, + { + name: "single authority as arguments in-line", + args: args{ + d: caddyfile.NewTestDispenser(` + pki_root ca_1 + `), + }, + expected: PKIRootCAPool{ + Authority: []string{"ca_1"}, + }, + }, + { + name: "multiple authorities as arguments in-line", + args: args{ + d: caddyfile.NewTestDispenser(` + pki_root ca_1 ca_2 + `), + }, + expected: PKIRootCAPool{ + Authority: []string{"ca_1", "ca_2"}, + }, + }, + { + name: "single authority in block", + args: args{ + d: caddyfile.NewTestDispenser(` + pki_root { + authority ca_1 + }`), + }, + expected: PKIRootCAPool{ + Authority: []string{"ca_1"}, + }, + wantErr: false, + }, + { + name: "multiple authorities in one line", + args: args{ + d: caddyfile.NewTestDispenser(` + pki_root { + authority ca_1 ca_2 + }`), + }, + expected: PKIRootCAPool{ + Authority: []string{"ca_1", "ca_2"}, + }, + }, + { + name: "multiple authorities in multiple lines", + args: args{ + d: caddyfile.NewTestDispenser(` + pki_root { + authority ca_1 + authority ca_2 + }`), + }, + expected: PKIRootCAPool{ + Authority: []string{"ca_1", "ca_2"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pkir := &PKIRootCAPool{} + if err := pkir.UnmarshalCaddyfile(tt.args.d); (err != nil) != tt.wantErr { + t.Errorf("PKIRootCAPool.UnmarshalCaddyfile() error = %v, wantErr %v", err, tt.wantErr) + } + if !tt.wantErr && !reflect.DeepEqual(&tt.expected, pkir) { + t.Errorf("PKIRootCAPool.UnmarshalCaddyfile() = %v, want %v", pkir, tt.expected) + } + }) + } +} + +func TestPKIIntermediateCAPoolUnmarshalCaddyfile(t *testing.T) { + type args struct { + d *caddyfile.Dispenser + } + tests := []struct { + name string + expected PKIIntermediateCAPool + args args + wantErr bool + }{ + { + name: "configuring no certificatest produces an error", + args: args{ + d: caddyfile.NewTestDispenser(` + pki_intermediate { + }`), + }, + wantErr: true, + }, + { + name: "single authority as arguments in-line", + args: args{ + d: caddyfile.NewTestDispenser(`pki_intermediate ca_1`), + }, + expected: PKIIntermediateCAPool{ + Authority: []string{"ca_1"}, + }, + }, + { + name: "multiple authorities as arguments in-line", + args: args{ + d: caddyfile.NewTestDispenser(`pki_intermediate ca_1 ca_2`), + }, + expected: PKIIntermediateCAPool{ + Authority: []string{"ca_1", "ca_2"}, + }, + }, + { + name: "single authority in block", + args: args{ + d: caddyfile.NewTestDispenser(` + pki_intermediate { + authority ca_1 + }`), + }, + expected: PKIIntermediateCAPool{ + Authority: []string{"ca_1"}, + }, + wantErr: false, + }, + { + name: "multiple authorities in one line", + args: args{ + d: caddyfile.NewTestDispenser(` + pki_intermediate { + authority ca_1 ca_2 + }`), + }, + expected: PKIIntermediateCAPool{ + Authority: []string{"ca_1", "ca_2"}, + }, + }, + { + name: "multiple authorities in multiple lines", + args: args{ + d: caddyfile.NewTestDispenser(` + pki_intermediate { + authority ca_1 + authority ca_2 + }`), + }, + expected: PKIIntermediateCAPool{ + Authority: []string{"ca_1", "ca_2"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pic := &PKIIntermediateCAPool{} + if err := pic.UnmarshalCaddyfile(tt.args.d); (err != nil) != tt.wantErr { + t.Errorf("PKIIntermediateCAPool.UnmarshalCaddyfile() error = %v, wantErr %v", err, tt.wantErr) + } + if !tt.wantErr && !reflect.DeepEqual(&tt.expected, pic) { + t.Errorf("PKIIntermediateCAPool.UnmarshalCaddyfile() = %v, want %v", pic, tt.expected) + } + }) + } +} + +func TestStoragePoolUnmarshalCaddyfile(t *testing.T) { + type args struct { + d *caddyfile.Dispenser + } + tests := []struct { + name string + args args + expected StoragePool + wantErr bool + }{ + { + name: "empty block", + args: args{ + d: caddyfile.NewTestDispenser(`storage { + }`), + }, + expected: StoragePool{}, + wantErr: false, + }, + { + name: "providing single storage key inline", + args: args{ + d: caddyfile.NewTestDispenser(`storage key-1`), + }, + expected: StoragePool{ + PEMKeys: []string{"key-1"}, + }, + wantErr: false, + }, + { + name: "providing multiple storage keys inline", + args: args{ + d: caddyfile.NewTestDispenser(`storage key-1 key-2`), + }, + expected: StoragePool{ + PEMKeys: []string{"key-1", "key-2"}, + }, + wantErr: false, + }, + { + name: "providing keys inside block without specifying storage type", + args: args{ + d: caddyfile.NewTestDispenser(` + storage { + keys key-1 key-2 + } + `), + }, + expected: StoragePool{ + PEMKeys: []string{"key-1", "key-2"}, + }, + wantErr: false, + }, + { + name: "providing keys in-line and inside block merges them", + args: args{ + d: caddyfile.NewTestDispenser(`storage key-1 key-2 key-3 { + keys key-4 key-5 + }`), + }, + expected: StoragePool{ + PEMKeys: []string{"key-1", "key-2", "key-3", "key-4", "key-5"}, + }, + wantErr: false, + }, + { + name: "specifying storage type in block", + args: args{ + d: caddyfile.NewTestDispenser(`storage { + storage file_system /var/caddy/storage + }`), + }, + expected: StoragePool{ + StorageRaw: json.RawMessage(`{"module":"file_system","root":"/var/caddy/storage"}`), + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sp := &StoragePool{} + if err := sp.UnmarshalCaddyfile(tt.args.d); (err != nil) != tt.wantErr { + t.Errorf("StoragePool.UnmarshalCaddyfile() error = %v, wantErr %v", err, tt.wantErr) + } + if !tt.wantErr && !reflect.DeepEqual(&tt.expected, sp) { + t.Errorf("StoragePool.UnmarshalCaddyfile() = %s, want %s", sp.StorageRaw, tt.expected.StorageRaw) + } + }) + } +} + +func TestTLSConfig_unmarshalCaddyfile(t *testing.T) { + type args struct { + d *caddyfile.Dispenser + } + tests := []struct { + name string + args args + expected TLSConfig + wantErr bool + }{ + { + name: "no arguments is valid", + args: args{ + d: caddyfile.NewTestDispenser(` { + }`), + }, + expected: TLSConfig{}, + }, + { + name: "setting 'renegotiation' to 'never' is valid", + args: args{ + d: caddyfile.NewTestDispenser(` { + renegotiation never + }`), + }, + expected: TLSConfig{ + Renegotiation: "never", + }, + }, + { + name: "setting 'renegotiation' to 'once' is valid", + args: args{ + d: caddyfile.NewTestDispenser(` { + renegotiation once + }`), + }, + expected: TLSConfig{ + Renegotiation: "once", + }, + }, + { + name: "setting 'renegotiation' to 'freely' is valid", + args: args{ + d: caddyfile.NewTestDispenser(` { + renegotiation freely + }`), + }, + expected: TLSConfig{ + Renegotiation: "freely", + }, + }, + { + name: "setting 'renegotiation' to other than 'none', 'once, or 'freely' is invalid", + args: args{ + d: caddyfile.NewTestDispenser(` { + renegotiation foo + }`), + }, + wantErr: true, + }, + { + name: "setting 'renegotiation' without argument is invalid", + args: args{ + d: caddyfile.NewTestDispenser(` { + renegotiation + }`), + }, + wantErr: true, + }, + { + name: "setting 'ca' without argument is an error", + args: args{ + d: caddyfile.NewTestDispenser(`{ + ca + }`), + }, + wantErr: true, + }, + { + name: "setting 'ca' to 'file' with in-line cert is valid", + args: args{ + d: caddyfile.NewTestDispenser(`{ + ca file /var/caddy/ca.pem + }`), + }, + expected: TLSConfig{ + CARaw: []byte(`{"pem_files":["/var/caddy/ca.pem"],"provider":"file"}`), + }, + }, + { + name: "setting 'ca' to 'file' with appropriate block is valid", + args: args{ + d: caddyfile.NewTestDispenser(`{ + ca file /var/caddy/ca.pem { + pem_file /var/caddy/ca.pem + } + }`), + }, + expected: TLSConfig{ + CARaw: []byte(`{"pem_files":["/var/caddy/ca.pem","/var/caddy/ca.pem"],"provider":"file"}`), + }, + }, + { + name: "setting 'ca' multiple times is an error", + args: args{ + d: caddyfile.NewTestDispenser(fmt.Sprintf(`{ + ca file /var/caddy/ca.pem { + pem_file /var/caddy/ca.pem + } + ca inline %s + }`, test_der_1)), + }, + wantErr: true, + }, + { + name: "setting 'handshake_timeout' without value is an error", + args: args{ + d: caddyfile.NewTestDispenser(`{ + handshake_timeout + }`), + }, + wantErr: true, + }, + { + name: "setting 'handshake_timeout' properly is successful", + args: args{ + d: caddyfile.NewTestDispenser(`{ + handshake_timeout 42m + }`), + }, + expected: TLSConfig{ + HandshakeTimeout: caddy.Duration(42 * time.Minute), + }, + }, + { + name: "setting 'server_name' without value is an error", + args: args{ + d: caddyfile.NewTestDispenser(`{ + server_name + }`), + }, + wantErr: true, + }, + { + name: "setting 'server_name' properly is successful", + args: args{ + d: caddyfile.NewTestDispenser(`{ + server_name example.com + }`), + }, + expected: TLSConfig{ + ServerName: "example.com", + }, + }, + { + name: "unsupported directives are errors", + args: args{ + d: caddyfile.NewTestDispenser(`{ + foo + }`), + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tr := &TLSConfig{} + if err := tr.unmarshalCaddyfile(tt.args.d); (err != nil) != tt.wantErr { + t.Errorf("TLSConfig.unmarshalCaddyfile() error = %v, wantErr %v", err, tt.wantErr) + } + if !tt.wantErr && !reflect.DeepEqual(&tt.expected, tr) { + t.Errorf("TLSConfig.UnmarshalCaddyfile() = %v, want %v", tr, tt.expected) + } + }) + } +} + +func TestHTTPCertPoolUnmarshalCaddyfile(t *testing.T) { + type args struct { + d *caddyfile.Dispenser + } + tests := []struct { + name string + args args + expected HTTPCertPool + wantErr bool + }{ + { + name: "no block, inline http endpoint", + args: args{ + d: caddyfile.NewTestDispenser(`http http://localhost/ca-certs`), + }, + expected: HTTPCertPool{ + Endpoints: []string{"http://localhost/ca-certs"}, + }, + wantErr: false, + }, + { + name: "no block, inline https endpoint", + args: args{ + d: caddyfile.NewTestDispenser(`http https://localhost/ca-certs`), + }, + expected: HTTPCertPool{ + Endpoints: []string{"https://localhost/ca-certs"}, + }, + wantErr: false, + }, + { + name: "no block, mixed http and https endpoints inline", + args: args{ + d: caddyfile.NewTestDispenser(`http http://localhost/ca-certs https://localhost/ca-certs`), + }, + expected: HTTPCertPool{ + Endpoints: []string{"http://localhost/ca-certs", "https://localhost/ca-certs"}, + }, + wantErr: false, + }, + { + name: "multiple endpoints in separate lines in block", + args: args{ + d: caddyfile.NewTestDispenser(` + http { + endpoints http://localhost/ca-certs + endpoints http://remotehost/ca-certs + } + `), + }, + expected: HTTPCertPool{ + Endpoints: []string{"http://localhost/ca-certs", "http://remotehost/ca-certs"}, + }, + wantErr: false, + }, + { + name: "endpoints defined inline and in block are merged", + args: args{ + d: caddyfile.NewTestDispenser(`http http://localhost/ca-certs { + endpoints http://remotehost/ca-certs + }`), + }, + expected: HTTPCertPool{ + Endpoints: []string{"http://localhost/ca-certs", "http://remotehost/ca-certs"}, + }, + wantErr: false, + }, + { + name: "multiple endpoints defined in block on the same line", + args: args{ + d: caddyfile.NewTestDispenser(`http { + endpoints http://remotehost/ca-certs http://localhost/ca-certs + }`), + }, + expected: HTTPCertPool{ + Endpoints: []string{"http://remotehost/ca-certs", "http://localhost/ca-certs"}, + }, + wantErr: false, + }, + { + name: "declaring 'endpoints' in block without argument is an error", + args: args{ + d: caddyfile.NewTestDispenser(`http { + endpoints + }`), + }, + wantErr: true, + }, + { + name: "multiple endpoints in separate lines in block", + args: args{ + d: caddyfile.NewTestDispenser(` + http { + endpoints http://localhost/ca-certs + endpoints http://remotehost/ca-certs + tls { + renegotiation freely + } + } + `), + }, + expected: HTTPCertPool{ + Endpoints: []string{"http://localhost/ca-certs", "http://remotehost/ca-certs"}, + TLS: &TLSConfig{ + Renegotiation: "freely", + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + hcp := &HTTPCertPool{} + if err := hcp.UnmarshalCaddyfile(tt.args.d); (err != nil) != tt.wantErr { + t.Errorf("HTTPCertPool.UnmarshalCaddyfile() error = %v, wantErr %v", err, tt.wantErr) + } + if !tt.wantErr && !reflect.DeepEqual(&tt.expected, hcp) { + t.Errorf("HTTPCertPool.UnmarshalCaddyfile() = %v, want %v", hcp, tt.expected) + } + }) + } +} diff --git a/modules/caddytls/certmanagers.go b/modules/caddytls/certmanagers.go new file mode 100644 index 00000000000..56950bc84df --- /dev/null +++ b/modules/caddytls/certmanagers.go @@ -0,0 +1,208 @@ +package caddytls + +import ( + "context" + "crypto/tls" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "github.com/caddyserver/certmagic" + "github.com/tailscale/tscert" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func init() { + caddy.RegisterModule(Tailscale{}) + caddy.RegisterModule(HTTPCertGetter{}) +} + +// Tailscale is a module that can get certificates from the local Tailscale process. +type Tailscale struct { + logger *zap.Logger +} + +// CaddyModule returns the Caddy module information. +func (Tailscale) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.get_certificate.tailscale", + New: func() caddy.Module { return new(Tailscale) }, + } +} + +func (ts *Tailscale) Provision(ctx caddy.Context) error { + ts.logger = ctx.Logger() + return nil +} + +func (ts Tailscale) GetCertificate(ctx context.Context, hello *tls.ClientHelloInfo) (*tls.Certificate, error) { + canGetCert, err := ts.canHazCertificate(ctx, hello) + if err == nil && !canGetCert { + return nil, nil // pass-thru: Tailscale can't offer a cert for this name + } + if err != nil { + if c := ts.logger.Check(zapcore.WarnLevel, "could not get status; will try to get certificate anyway"); c != nil { + c.Write(zap.Error(err)) + } + } + return tscert.GetCertificateWithContext(ctx, hello) +} + +// canHazCertificate returns true if Tailscale reports it can get a certificate for the given ClientHello. +func (ts Tailscale) canHazCertificate(ctx context.Context, hello *tls.ClientHelloInfo) (bool, error) { + if !strings.HasSuffix(strings.ToLower(hello.ServerName), tailscaleDomainAliasEnding) { + return false, nil + } + status, err := tscert.GetStatus(ctx) + if err != nil { + return false, err + } + for _, domain := range status.CertDomains { + if certmagic.MatchWildcard(hello.ServerName, domain) { + return true, nil + } + } + return false, nil +} + +// UnmarshalCaddyfile deserializes Caddyfile tokens into ts. +// +// ... tailscale +func (Tailscale) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume cert manager name + if d.NextArg() { + return d.ArgErr() + } + return nil +} + +// tailscaleDomainAliasEnding is the ending for all Tailscale custom domains. +const tailscaleDomainAliasEnding = ".ts.net" + +// HTTPCertGetter can get a certificate via HTTP(S) request. +type HTTPCertGetter struct { + // The URL from which to download the certificate. Required. + // + // The URL will be augmented with query string parameters taken + // from the TLS handshake: + // + // - server_name: The SNI value + // - signature_schemes: Comma-separated list of hex IDs of signatures + // - cipher_suites: Comma-separated list of hex IDs of cipher suites + // + // To be valid, the response must be HTTP 200 with a PEM body + // consisting of blocks for the certificate chain and the private + // key. + // + // To indicate that this manager is not managing a certificate for + // the described handshake, the endpoint should return HTTP 204 + // (No Content). Error statuses will indicate that the manager is + // capable of providing a certificate but was unable to. + URL string `json:"url,omitempty"` + + ctx context.Context +} + +// CaddyModule returns the Caddy module information. +func (hcg HTTPCertGetter) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.get_certificate.http", + New: func() caddy.Module { return new(HTTPCertGetter) }, + } +} + +func (hcg *HTTPCertGetter) Provision(ctx caddy.Context) error { + hcg.ctx = ctx + if hcg.URL == "" { + return fmt.Errorf("URL is required") + } + return nil +} + +func (hcg HTTPCertGetter) GetCertificate(ctx context.Context, hello *tls.ClientHelloInfo) (*tls.Certificate, error) { + sigs := make([]string, len(hello.SignatureSchemes)) + for i, sig := range hello.SignatureSchemes { + sigs[i] = fmt.Sprintf("%x", uint16(sig)) // you won't believe what %x uses if the val is a Stringer + } + suites := make([]string, len(hello.CipherSuites)) + for i, cs := range hello.CipherSuites { + suites[i] = fmt.Sprintf("%x", cs) + } + + parsed, err := url.Parse(hcg.URL) + if err != nil { + return nil, err + } + qs := parsed.Query() + qs.Set("server_name", hello.ServerName) + qs.Set("signature_schemes", strings.Join(sigs, ",")) + qs.Set("cipher_suites", strings.Join(suites, ",")) + parsed.RawQuery = qs.Encode() + + req, err := http.NewRequestWithContext(hcg.ctx, http.MethodGet, parsed.String(), nil) + if err != nil { + return nil, err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode == http.StatusNoContent { + // endpoint is not managing certs for this handshake + return nil, nil + } + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("got HTTP %d", resp.StatusCode) + } + + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %v", err) + } + + cert, err := tlsCertFromCertAndKeyPEMBundle(bodyBytes) + if err != nil { + return nil, err + } + + return &cert, nil +} + +// UnmarshalCaddyfile deserializes Caddyfile tokens into ts. +// +// ... http +func (hcg *HTTPCertGetter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume cert manager name + + if !d.NextArg() { + return d.ArgErr() + } + hcg.URL = d.Val() + + if d.NextArg() { + return d.ArgErr() + } + if d.NextBlock(0) { + return d.Err("block not allowed here") + } + return nil +} + +// Interface guards +var ( + _ certmagic.Manager = (*Tailscale)(nil) + _ caddy.Provisioner = (*Tailscale)(nil) + _ caddyfile.Unmarshaler = (*Tailscale)(nil) + + _ certmagic.Manager = (*HTTPCertGetter)(nil) + _ caddy.Provisioner = (*HTTPCertGetter)(nil) + _ caddyfile.Unmarshaler = (*HTTPCertGetter)(nil) +) diff --git a/modules/caddytls/certselection.go b/modules/caddytls/certselection.go new file mode 100644 index 00000000000..a561e3a1d00 --- /dev/null +++ b/modules/caddytls/certselection.go @@ -0,0 +1,219 @@ +// 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 caddytls + +import ( + "crypto/tls" + "crypto/x509" + "encoding/json" + "fmt" + "math/big" + "slices" + + "github.com/caddyserver/certmagic" + + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +// CustomCertSelectionPolicy represents a policy for selecting the certificate +// used to complete a handshake when there may be multiple options. All fields +// specified must match the candidate certificate for it to be chosen. +// This was needed to solve https://github.com/caddyserver/caddy/issues/2588. +type CustomCertSelectionPolicy struct { + // The certificate must have one of these serial numbers. + SerialNumber []bigInt `json:"serial_number,omitempty"` + + // The certificate must have one of these organization names. + SubjectOrganization []string `json:"subject_organization,omitempty"` + + // The certificate must use this public key algorithm. + PublicKeyAlgorithm PublicKeyAlgorithm `json:"public_key_algorithm,omitempty"` + + // The certificate must have at least one of the tags in the list. + AnyTag []string `json:"any_tag,omitempty"` + + // The certificate must have all of the tags in the list. + AllTags []string `json:"all_tags,omitempty"` +} + +// SelectCertificate implements certmagic.CertificateSelector. It +// only chooses a certificate that at least meets the criteria in +// p. It then chooses the first non-expired certificate that is +// compatible with the client. If none are valid, it chooses the +// first viable candidate anyway. +func (p CustomCertSelectionPolicy) SelectCertificate(hello *tls.ClientHelloInfo, choices []certmagic.Certificate) (certmagic.Certificate, error) { + viable := make([]certmagic.Certificate, 0, len(choices)) + +nextChoice: + for _, cert := range choices { + if len(p.SerialNumber) > 0 { + var found bool + for _, sn := range p.SerialNumber { + snInt := sn.Int // avoid taking address of iteration variable (gosec warning) + if cert.Leaf.SerialNumber.Cmp(&snInt) == 0 { + found = true + break + } + } + if !found { + continue + } + } + + if len(p.SubjectOrganization) > 0 { + found := slices.ContainsFunc(p.SubjectOrganization, func(s string) bool { + return slices.Contains(cert.Leaf.Subject.Organization, s) + }) + if !found { + continue + } + } + + if p.PublicKeyAlgorithm != PublicKeyAlgorithm(x509.UnknownPublicKeyAlgorithm) && + PublicKeyAlgorithm(cert.Leaf.PublicKeyAlgorithm) != p.PublicKeyAlgorithm { + continue + } + + if len(p.AnyTag) > 0 { + var found bool + for _, tag := range p.AnyTag { + if cert.HasTag(tag) { + found = true + break + } + } + if !found { + continue + } + } + + if len(p.AllTags) > 0 { + for _, tag := range p.AllTags { + if !cert.HasTag(tag) { + continue nextChoice + } + } + } + + // this certificate at least meets the policy's requirements, + // but we still have to check expiration and compatibility + viable = append(viable, cert) + } + + if len(viable) == 0 { + return certmagic.Certificate{}, fmt.Errorf("no certificates matched custom selection policy") + } + + return certmagic.DefaultCertificateSelector(hello, viable) +} + +// UnmarshalCaddyfile sets up the CustomCertSelectionPolicy from Caddyfile tokens. Syntax: +// +// cert_selection { +// all_tags +// any_tag +// public_key_algorithm +// serial_number +// subject_organization +// } +func (p *CustomCertSelectionPolicy) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + _, wrapper := d.Next(), d.Val() // consume wrapper name + + // No same-line options are supported + if d.CountRemainingArgs() > 0 { + return d.ArgErr() + } + + var hasPublicKeyAlgorithm bool + for nesting := d.Nesting(); d.NextBlock(nesting); { + optionName := d.Val() + switch optionName { + case "all_tags": + if d.CountRemainingArgs() == 0 { + return d.ArgErr() + } + p.AllTags = append(p.AllTags, d.RemainingArgs()...) + case "any_tag": + if d.CountRemainingArgs() == 0 { + return d.ArgErr() + } + p.AnyTag = append(p.AnyTag, d.RemainingArgs()...) + case "public_key_algorithm": + if hasPublicKeyAlgorithm { + return d.Errf("duplicate %s option '%s'", wrapper, optionName) + } + if d.CountRemainingArgs() != 1 { + return d.ArgErr() + } + d.NextArg() + if err := p.PublicKeyAlgorithm.UnmarshalJSON([]byte(d.Val())); err != nil { + return d.Errf("parsing %s option '%s': %v", wrapper, optionName, err) + } + hasPublicKeyAlgorithm = true + case "serial_number": + if d.CountRemainingArgs() == 0 { + return d.ArgErr() + } + for d.NextArg() { + val, bi := d.Val(), bigInt{} + _, ok := bi.SetString(val, 10) + if !ok { + return d.Errf("parsing %s option '%s': invalid big.int value %s", wrapper, optionName, val) + } + p.SerialNumber = append(p.SerialNumber, bi) + } + case "subject_organization": + if d.CountRemainingArgs() == 0 { + return d.ArgErr() + } + p.SubjectOrganization = append(p.SubjectOrganization, d.RemainingArgs()...) + default: + return d.ArgErr() + } + + // No nested blocks are supported + if d.NextBlock(nesting + 1) { + return d.Errf("malformed %s option '%s': blocks are not supported", wrapper, optionName) + } + } + + return nil +} + +// bigInt is a big.Int type that interops with JSON encodings as a string. +type bigInt struct{ big.Int } + +func (bi bigInt) MarshalJSON() ([]byte, error) { + return json.Marshal(bi.String()) +} + +func (bi *bigInt) UnmarshalJSON(p []byte) error { + if string(p) == "null" { + return nil + } + var stringRep string + err := json.Unmarshal(p, &stringRep) + if err != nil { + return err + } + _, ok := bi.SetString(stringRep, 10) + if !ok { + return fmt.Errorf("not a valid big integer: %s", p) + } + return nil +} + +// Interface guard +var _ caddyfile.Unmarshaler = (*CustomCertSelectionPolicy)(nil) diff --git a/modules/caddytls/connpolicy.go b/modules/caddytls/connpolicy.go new file mode 100644 index 00000000000..727afaa0862 --- /dev/null +++ b/modules/caddytls/connpolicy.go @@ -0,0 +1,1043 @@ +// 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 caddytls + +import ( + "context" + "crypto/tls" + "crypto/x509" + "encoding/base64" + "encoding/json" + "encoding/pem" + "fmt" + "io" + "os" + "strings" + + "github.com/mholt/acmez/v3" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func init() { + caddy.RegisterModule(LeafCertClientAuth{}) +} + +// ConnectionPolicies govern the establishment of TLS connections. It is +// an ordered group of connection policies; the first matching policy will +// be used to configure TLS connections at handshake-time. +type ConnectionPolicies []*ConnectionPolicy + +// Provision sets up each connection policy. It should be called +// during the Validate() phase, after the TLS app (if any) is +// already set up. +func (cp ConnectionPolicies) Provision(ctx caddy.Context) error { + for i, pol := range cp { + // matchers + mods, err := ctx.LoadModule(pol, "MatchersRaw") + if err != nil { + return fmt.Errorf("loading handshake matchers: %v", err) + } + for _, modIface := range mods.(map[string]any) { + cp[i].matchers = append(cp[i].matchers, modIface.(ConnectionMatcher)) + } + + // enable HTTP/2 by default + if pol.ALPN == nil { + pol.ALPN = append(pol.ALPN, defaultALPN...) + } + + // pre-build standard TLS config so we don't have to at handshake-time + err = pol.buildStandardTLSConfig(ctx) + if err != nil { + return fmt.Errorf("connection policy %d: building standard TLS config: %s", i, err) + } + + if pol.ClientAuthentication != nil && len(pol.ClientAuthentication.VerifiersRaw) > 0 { + clientCertValidations, err := ctx.LoadModule(pol.ClientAuthentication, "VerifiersRaw") + if err != nil { + return fmt.Errorf("loading client cert verifiers: %v", err) + } + for _, validator := range clientCertValidations.([]any) { + cp[i].ClientAuthentication.verifiers = append(cp[i].ClientAuthentication.verifiers, validator.(ClientCertificateVerifier)) + } + } + + if len(pol.HandshakeContextRaw) > 0 { + modIface, err := ctx.LoadModule(pol, "HandshakeContextRaw") + if err != nil { + return fmt.Errorf("loading handshake context module: %v", err) + } + cp[i].handshakeContext = modIface.(HandshakeContext) + } + } + + return nil +} + +// TLSConfig returns a standard-lib-compatible TLS configuration which +// selects the first matching policy based on the ClientHello. +func (cp ConnectionPolicies) TLSConfig(_ caddy.Context) *tls.Config { + // using ServerName to match policies is extremely common, especially in configs + // with lots and lots of different policies; we can fast-track those by indexing + // them by SNI, so we don't have to iterate potentially thousands of policies + // (TODO: this map does not account for wildcards, see if this is a problem in practice? look for reports of high connection latency with wildcard certs but low latency for non-wildcards in multi-thousand-cert deployments) + indexedBySNI := make(map[string]ConnectionPolicies) + if len(cp) > 30 { + for _, p := range cp { + for _, m := range p.matchers { + if sni, ok := m.(MatchServerName); ok { + for _, sniName := range sni { + indexedBySNI[sniName] = append(indexedBySNI[sniName], p) + } + } + } + } + } + + return &tls.Config{ + MinVersion: tls.VersionTLS12, + GetConfigForClient: func(hello *tls.ClientHelloInfo) (*tls.Config, error) { + // filter policies by SNI first, if possible, to speed things up + // when there may be lots of policies + possiblePolicies := cp + if indexedPolicies, ok := indexedBySNI[hello.ServerName]; ok { + possiblePolicies = indexedPolicies + } + + policyLoop: + for _, pol := range possiblePolicies { + for _, matcher := range pol.matchers { + if !matcher.Match(hello) { + continue policyLoop + } + } + if pol.Drop { + return nil, fmt.Errorf("dropping connection") + } + return pol.TLSConfig, nil + } + + return nil, fmt.Errorf("no server TLS configuration available for ClientHello: %+v", hello) + }, + } +} + +// ConnectionPolicy specifies the logic for handling a TLS handshake. +// An empty policy is valid; safe and sensible defaults will be used. +type ConnectionPolicy struct { + // How to match this policy with a TLS ClientHello. If + // this policy is the first to match, it will be used. + MatchersRaw caddy.ModuleMap `json:"match,omitempty" caddy:"namespace=tls.handshake_match"` + matchers []ConnectionMatcher + + // How to choose a certificate if more than one matched + // the given ServerName (SNI) value. + CertSelection *CustomCertSelectionPolicy `json:"certificate_selection,omitempty"` + + // The list of cipher suites to support. Caddy's + // defaults are modern and secure. + CipherSuites []string `json:"cipher_suites,omitempty"` + + // The list of elliptic curves to support. Caddy's + // defaults are modern and secure. + Curves []string `json:"curves,omitempty"` + + // Protocols to use for Application-Layer Protocol + // Negotiation (ALPN) during the handshake. + ALPN []string `json:"alpn,omitempty"` + + // Minimum TLS protocol version to allow. Default: `tls1.2` + ProtocolMin string `json:"protocol_min,omitempty"` + + // Maximum TLS protocol version to allow. Default: `tls1.3` + ProtocolMax string `json:"protocol_max,omitempty"` + + // Reject TLS connections. EXPERIMENTAL: May change. + Drop bool `json:"drop,omitempty"` + + // Enables and configures TLS client authentication. + ClientAuthentication *ClientAuthentication `json:"client_authentication,omitempty"` + + // DefaultSNI becomes the ServerName in a ClientHello if there + // is no policy configured for the empty SNI value. + DefaultSNI string `json:"default_sni,omitempty"` + + // FallbackSNI becomes the ServerName in a ClientHello if + // the original ServerName doesn't match any certificates + // in the cache. The use cases for this are very niche; + // typically if a client is a CDN and passes through the + // ServerName of the downstream handshake but can accept + // a certificate with the origin's hostname instead, then + // you would set this to your origin's hostname. Note that + // Caddy must be managing a certificate for this name. + // + // This feature is EXPERIMENTAL and subject to change or removal. + FallbackSNI string `json:"fallback_sni,omitempty"` + + // Also known as "SSLKEYLOGFILE", TLS secrets will be written to + // this file in NSS key log format which can then be parsed by + // Wireshark and other tools. This is INSECURE as it allows other + // programs or tools to decrypt TLS connections. However, this + // capability can be useful for debugging and troubleshooting. + // **ENABLING THIS LOG COMPROMISES SECURITY!** + // + // This feature is EXPERIMENTAL and subject to change or removal. + InsecureSecretsLog string `json:"insecure_secrets_log,omitempty"` + + // A module that can manipulate the context passed into CertMagic's + // certificate management functions during TLS handshakes. + // EXPERIMENTAL - subject to change or removal. + HandshakeContextRaw json.RawMessage `json:"handshake_context,omitempty" caddy:"namespace=tls.context inline_key=module"` + handshakeContext HandshakeContext + + // TLSConfig is the fully-formed, standard lib TLS config + // used to serve TLS connections. Provision all + // ConnectionPolicies to populate this. It is exported only + // so it can be minimally adjusted after provisioning + // if necessary (like to adjust NextProtos to disable HTTP/2), + // and may be unexported in the future. + TLSConfig *tls.Config `json:"-"` +} + +type HandshakeContext interface { + // HandshakeContext returns a context to pass into CertMagic's + // GetCertificate function used to serve, load, and manage certs + // during TLS handshakes. Generally you'll start with the context + // from the ClientHelloInfo, but you may use other information + // from it as well. Return an error to abort the handshake. + HandshakeContext(*tls.ClientHelloInfo) (context.Context, error) +} + +func (p *ConnectionPolicy) buildStandardTLSConfig(ctx caddy.Context) error { + tlsAppIface, err := ctx.App("tls") + if err != nil { + return fmt.Errorf("getting tls app: %v", err) + } + tlsApp := tlsAppIface.(*TLS) + + // fill in some "easy" default values, but for other values + // (such as slices), we should ensure that they start empty + // so the user-provided config can fill them in; then we will + // fill in a default config at the end if they are still unset + cfg := &tls.Config{ + NextProtos: p.ALPN, + GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { + // TODO: I don't love how this works: we pre-build certmagic configs + // so that handshakes are faster. Unfortunately, certmagic configs are + // comprised of settings from both a TLS connection policy and a TLS + // automation policy. The only two fields (as of March 2020; v2 beta 17) + // of a certmagic config that come from the TLS connection policy are + // CertSelection and DefaultServerName, so an automation policy is what + // builds the base certmagic config. Since the pre-built config is + // shared, I don't think we can change any of its fields per-handshake, + // hence the awkward shallow copy (dereference) here and the subsequent + // changing of some of its fields. I'm worried this dereference allocates + // more at handshake-time, but I don't know how to practically pre-build + // a certmagic config for each combination of conn policy + automation policy... + cfg := *tlsApp.getConfigForName(hello.ServerName) + if p.CertSelection != nil { + // you would think we could just set this whether or not + // p.CertSelection is nil, but that leads to panics if + // it is, because cfg.CertSelection is an interface, + // so it will have a non-nil value even if the actual + // value underlying it is nil (sigh) + cfg.CertSelection = p.CertSelection + } + cfg.DefaultServerName = p.DefaultSNI + cfg.FallbackServerName = p.FallbackSNI + + // TODO: experimental: if a handshake context module is configured, allow it + // to modify the context before passing it into CertMagic's GetCertificate + ctx := hello.Context() + if p.handshakeContext != nil { + ctx, err = p.handshakeContext.HandshakeContext(hello) + if err != nil { + return nil, fmt.Errorf("handshake context: %v", err) + } + } + + return cfg.GetCertificateWithContext(ctx, hello) + }, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS13, + } + + // session tickets support + if tlsApp.SessionTickets != nil { + cfg.SessionTicketsDisabled = tlsApp.SessionTickets.Disabled + + // session ticket key rotation + tlsApp.SessionTickets.register(cfg) + ctx.OnCancel(func() { + // do cleanup when the context is canceled because, + // though unlikely, it is possible that a context + // needing a TLS server config could exist for less + // than the lifetime of the whole app + tlsApp.SessionTickets.unregister(cfg) + }) + } + + // TODO: Clean up session ticket active locks in storage if app (or process) is being closed! + + // add all the cipher suites in order, without duplicates + cipherSuitesAdded := make(map[uint16]struct{}) + for _, csName := range p.CipherSuites { + csID := CipherSuiteID(csName) + if csID == 0 { + return fmt.Errorf("unsupported cipher suite: %s", csName) + } + if _, ok := cipherSuitesAdded[csID]; !ok { + cipherSuitesAdded[csID] = struct{}{} + cfg.CipherSuites = append(cfg.CipherSuites, csID) + } + } + + // add all the curve preferences in order, without duplicates + curvesAdded := make(map[tls.CurveID]struct{}) + for _, curveName := range p.Curves { + curveID := SupportedCurves[curveName] + if _, ok := curvesAdded[curveID]; !ok { + curvesAdded[curveID] = struct{}{} + cfg.CurvePreferences = append(cfg.CurvePreferences, curveID) + } + } + + // ensure ALPN includes the ACME TLS-ALPN protocol + var alpnFound bool + for _, a := range p.ALPN { + if a == acmez.ACMETLS1Protocol { + alpnFound = true + break + } + } + if !alpnFound && (cfg.NextProtos == nil || len(cfg.NextProtos) > 0) { + cfg.NextProtos = append(cfg.NextProtos, acmez.ACMETLS1Protocol) + } + + // min and max protocol versions + if (p.ProtocolMin != "" && p.ProtocolMax != "") && p.ProtocolMin > p.ProtocolMax { + return fmt.Errorf("protocol min (%x) cannot be greater than protocol max (%x)", p.ProtocolMin, p.ProtocolMax) + } + if p.ProtocolMin != "" { + cfg.MinVersion = SupportedProtocols[p.ProtocolMin] + } + if p.ProtocolMax != "" { + cfg.MaxVersion = SupportedProtocols[p.ProtocolMax] + } + + // client authentication + if p.ClientAuthentication != nil { + if err := p.ClientAuthentication.provision(ctx); err != nil { + return fmt.Errorf("provisioning client CA: %v", err) + } + if err := p.ClientAuthentication.ConfigureTLSConfig(cfg); err != nil { + return fmt.Errorf("configuring TLS client authentication: %v", err) + } + + // Prevent privilege escalation in case multiple vhosts are configured for + // this TLS server; we could potentially figure out if that's the case, but + // that might be complex to get right every time. Actually, two proper + // solutions could leave tickets enabled, but I am not sure how to do them + // properly without significant time investment; there may be new Go + // APIs that alloaw this (Wrap/UnwrapSession?) but I do not know how to use + // them at this time. TODO: one of these is a possible future enhancement: + // A) Prevent resumptions across server identities (certificates): binding the ticket to the + // certificate we would serve in a full handshake, or even bind a ticket to the exact SNI + // it was issued under (though there are proposals for session resumption across hostnames). + // B) Prevent resumptions falsely authenticating a client: include the realm in the ticket, + // so that it can be validated upon resumption. + cfg.SessionTicketsDisabled = true + } + + if p.InsecureSecretsLog != "" { + filename, err := caddy.NewReplacer().ReplaceOrErr(p.InsecureSecretsLog, true, true) + if err != nil { + return err + } + filename, err = caddy.FastAbs(filename) + if err != nil { + return err + } + logFile, _, err := secretsLogPool.LoadOrNew(filename, func() (caddy.Destructor, error) { + w, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o600) + return destructableWriter{w}, err + }) + if err != nil { + return err + } + ctx.OnCancel(func() { _, _ = secretsLogPool.Delete(filename) }) + + cfg.KeyLogWriter = logFile.(io.Writer) + + if c := tlsApp.logger.Check(zapcore.WarnLevel, "TLS SECURITY COMPROMISED: secrets logging is enabled!"); c != nil { + c.Write(zap.String("log_filename", filename)) + } + } + + setDefaultTLSParams(cfg) + + p.TLSConfig = cfg + + return nil +} + +// SettingsEmpty returns true if p's settings (fields +// except the matchers) are all empty/unset. +func (p ConnectionPolicy) SettingsEmpty() bool { + return p.CertSelection == nil && + p.CipherSuites == nil && + p.Curves == nil && + p.ALPN == nil && + p.ProtocolMin == "" && + p.ProtocolMax == "" && + p.ClientAuthentication == nil && + p.DefaultSNI == "" && + p.InsecureSecretsLog == "" +} + +// UnmarshalCaddyfile sets up the ConnectionPolicy from Caddyfile tokens. Syntax: +// +// connection_policy { +// alpn +// cert_selection { +// ... +// } +// ciphers +// client_auth { +// ... +// } +// curves +// default_sni +// match { +// ... +// } +// protocols [] +// # EXPERIMENTAL: +// drop +// fallback_sni +// insecure_secrets_log +// } +func (cp *ConnectionPolicy) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + _, wrapper := d.Next(), d.Val() + + // No same-line options are supported + if d.CountRemainingArgs() > 0 { + return d.ArgErr() + } + + var hasCertSelection, hasClientAuth, hasDefaultSNI, hasDrop, + hasFallbackSNI, hasInsecureSecretsLog, hasMatch, hasProtocols bool + for nesting := d.Nesting(); d.NextBlock(nesting); { + optionName := d.Val() + switch optionName { + case "alpn": + if d.CountRemainingArgs() == 0 { + return d.ArgErr() + } + cp.ALPN = append(cp.ALPN, d.RemainingArgs()...) + case "cert_selection": + if hasCertSelection { + return d.Errf("duplicate %s option '%s'", wrapper, optionName) + } + p := &CustomCertSelectionPolicy{} + if err := p.UnmarshalCaddyfile(d.NewFromNextSegment()); err != nil { + return err + } + cp.CertSelection, hasCertSelection = p, true + case "client_auth": + if hasClientAuth { + return d.Errf("duplicate %s option '%s'", wrapper, optionName) + } + ca := &ClientAuthentication{} + if err := ca.UnmarshalCaddyfile(d.NewFromNextSegment()); err != nil { + return err + } + cp.ClientAuthentication, hasClientAuth = ca, true + case "ciphers": + if d.CountRemainingArgs() == 0 { + return d.ArgErr() + } + cp.CipherSuites = append(cp.CipherSuites, d.RemainingArgs()...) + case "curves": + if d.CountRemainingArgs() == 0 { + return d.ArgErr() + } + cp.Curves = append(cp.Curves, d.RemainingArgs()...) + case "default_sni": + if hasDefaultSNI { + return d.Errf("duplicate %s option '%s'", wrapper, optionName) + } + if d.CountRemainingArgs() != 1 { + return d.ArgErr() + } + _, cp.DefaultSNI, hasDefaultSNI = d.NextArg(), d.Val(), true + case "drop": // EXPERIMENTAL + if hasDrop { + return d.Errf("duplicate %s option '%s'", wrapper, optionName) + } + cp.Drop, hasDrop = true, true + case "fallback_sni": // EXPERIMENTAL + if hasFallbackSNI { + return d.Errf("duplicate %s option '%s'", wrapper, optionName) + } + if d.CountRemainingArgs() != 1 { + return d.ArgErr() + } + _, cp.FallbackSNI, hasFallbackSNI = d.NextArg(), d.Val(), true + case "insecure_secrets_log": // EXPERIMENTAL + if hasInsecureSecretsLog { + return d.Errf("duplicate %s option '%s'", wrapper, optionName) + } + if d.CountRemainingArgs() != 1 { + return d.ArgErr() + } + _, cp.InsecureSecretsLog, hasInsecureSecretsLog = d.NextArg(), d.Val(), true + case "match": + if hasMatch { + return d.Errf("duplicate %s option '%s'", wrapper, optionName) + } + matcherSet, err := ParseCaddyfileNestedMatcherSet(d) + if err != nil { + return err + } + cp.MatchersRaw, hasMatch = matcherSet, true + case "protocols": + if hasProtocols { + return d.Errf("duplicate %s option '%s'", wrapper, optionName) + } + if d.CountRemainingArgs() == 0 || d.CountRemainingArgs() > 2 { + return d.ArgErr() + } + _, cp.ProtocolMin, hasProtocols = d.NextArg(), d.Val(), true + if d.NextArg() { + cp.ProtocolMax = d.Val() + } + default: + return d.ArgErr() + } + + // No nested blocks are supported + if d.NextBlock(nesting + 1) { + return d.Errf("malformed %s option '%s': blocks are not supported", wrapper, optionName) + } + } + + return nil +} + +// ClientAuthentication configures TLS client auth. +type ClientAuthentication struct { + // Certificate authority module which provides the certificate pool of trusted certificates + CARaw json.RawMessage `json:"ca,omitempty" caddy:"namespace=tls.ca_pool.source inline_key=provider"` + ca CA + + // Deprecated: Use the `ca` field with the `tls.ca_pool.source.inline` module instead. + // A list of base64 DER-encoded CA certificates + // against which to validate client certificates. + // Client certs which are not signed by any of + // these CAs will be rejected. + TrustedCACerts []string `json:"trusted_ca_certs,omitempty"` + + // Deprecated: Use the `ca` field with the `tls.ca_pool.source.file` module instead. + // TrustedCACertPEMFiles is a list of PEM file names + // from which to load certificates of trusted CAs. + // Client certificates which are not signed by any of + // these CA certificates will be rejected. + TrustedCACertPEMFiles []string `json:"trusted_ca_certs_pem_files,omitempty"` + + // Deprecated: This field is deprecated and will be removed in + // a future version. Please use the `validators` field instead + // with the tls.client_auth.verifier.leaf module instead. + // + // A list of base64 DER-encoded client leaf certs + // to accept. If this list is not empty, client certs + // which are not in this list will be rejected. + TrustedLeafCerts []string `json:"trusted_leaf_certs,omitempty"` + + // Client certificate verification modules. These can perform + // custom client authentication checks, such as ensuring the + // certificate is not revoked. + VerifiersRaw []json.RawMessage `json:"verifiers,omitempty" caddy:"namespace=tls.client_auth.verifier inline_key=verifier"` + + verifiers []ClientCertificateVerifier + + // The mode for authenticating the client. Allowed values are: + // + // Mode | Description + // -----|--------------- + // `request` | Ask clients for a certificate, but allow even if there isn't one; do not verify it + // `require` | Require clients to present a certificate, but do not verify it + // `verify_if_given` | Ask clients for a certificate; allow even if there isn't one, but verify it if there is + // `require_and_verify` | Require clients to present a valid certificate that is verified + // + // The default mode is `require_and_verify` if any + // TrustedCACerts or TrustedCACertPEMFiles or TrustedLeafCerts + // are provided; otherwise, the default mode is `require`. + Mode string `json:"mode,omitempty"` + + existingVerifyPeerCert func([][]byte, [][]*x509.Certificate) error +} + +// UnmarshalCaddyfile parses the Caddyfile segment to set up the client authentication. Syntax: +// +// client_auth { +// mode [request|require|verify_if_given|require_and_verify] +// trust_pool { +// ... +// } +// verifier +// } +// +// If `mode` is not provided, it defaults to `require_and_verify` if `trust_pool` is provided. +// Otherwise, it defaults to `require`. +func (ca *ClientAuthentication) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + for d.NextArg() { + // consume any tokens on the same line, if any. + } + for nesting := d.Nesting(); d.NextBlock(nesting); { + subdir := d.Val() + switch subdir { + case "mode": + if d.CountRemainingArgs() > 1 { + return d.ArgErr() + } + if !d.Args(&ca.Mode) { + return d.ArgErr() + } + case "trusted_ca_cert": + caddy.Log().Warn("The 'trusted_ca_cert' field is deprecated. Use the 'trust_pool' field instead.") + if len(ca.CARaw) != 0 { + return d.Err("cannot specify both 'trust_pool' and 'trusted_ca_cert' or 'trusted_ca_cert_file'") + } + if !d.NextArg() { + return d.ArgErr() + } + ca.TrustedCACerts = append(ca.TrustedCACerts, d.Val()) + case "trusted_leaf_cert": + if !d.NextArg() { + return d.ArgErr() + } + ca.TrustedLeafCerts = append(ca.TrustedLeafCerts, d.Val()) + case "trusted_ca_cert_file": + caddy.Log().Warn("The 'trusted_ca_cert_file' field is deprecated. Use the 'trust_pool' field instead.") + if len(ca.CARaw) != 0 { + return d.Err("cannot specify both 'trust_pool' and 'trusted_ca_cert' or 'trusted_ca_cert_file'") + } + if !d.NextArg() { + return d.ArgErr() + } + filename := d.Val() + ders, err := convertPEMFilesToDER(filename) + if err != nil { + return d.WrapErr(err) + } + ca.TrustedCACerts = append(ca.TrustedCACerts, ders...) + case "trusted_leaf_cert_file": + if !d.NextArg() { + return d.ArgErr() + } + filename := d.Val() + ders, err := convertPEMFilesToDER(filename) + if err != nil { + return d.WrapErr(err) + } + ca.TrustedLeafCerts = append(ca.TrustedLeafCerts, ders...) + case "trust_pool": + if len(ca.TrustedCACerts) != 0 { + return d.Err("cannot specify both 'trust_pool' and 'trusted_ca_cert' or 'trusted_ca_cert_file'") + } + if !d.NextArg() { + return d.ArgErr() + } + modName := d.Val() + mod, err := caddyfile.UnmarshalModule(d, "tls.ca_pool.source."+modName) + if err != nil { + return d.WrapErr(err) + } + caMod, ok := mod.(CA) + if !ok { + return fmt.Errorf("trust_pool module '%s' is not a certificate pool provider", caMod) + } + ca.CARaw = caddyconfig.JSONModuleObject(caMod, "provider", modName, nil) + case "verifier": + if !d.NextArg() { + return d.ArgErr() + } + + vType := d.Val() + modID := "tls.client_auth.verifier." + vType + unm, err := caddyfile.UnmarshalModule(d, modID) + if err != nil { + return err + } + + _, ok := unm.(ClientCertificateVerifier) + if !ok { + return d.Errf("module '%s' is not a caddytls.ClientCertificateVerifier", modID) + } + ca.VerifiersRaw = append(ca.VerifiersRaw, caddyconfig.JSONModuleObject(unm, "verifier", vType, nil)) + default: + return d.Errf("unknown subdirective for client_auth: %s", subdir) + } + } + + // only trust_ca_cert or trust_ca_cert_file was specified + if len(ca.TrustedCACerts) > 0 { + fileMod := &InlineCAPool{} + fileMod.TrustedCACerts = append(fileMod.TrustedCACerts, ca.TrustedCACerts...) + ca.CARaw = caddyconfig.JSONModuleObject(fileMod, "provider", "inline", nil) + ca.TrustedCACertPEMFiles, ca.TrustedCACerts = nil, nil + } + return nil +} + +func convertPEMFilesToDER(filename string) ([]string, error) { + certDataPEM, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + var ders []string + // while block is not nil, we have more certificates in the file + for block, rest := pem.Decode(certDataPEM); block != nil; block, rest = pem.Decode(rest) { + if block.Type != "CERTIFICATE" { + return nil, fmt.Errorf("no CERTIFICATE pem block found in %s", filename) + } + ders = append( + ders, + base64.StdEncoding.EncodeToString(block.Bytes), + ) + } + // if we decoded nothing, return an error + if len(ders) == 0 { + return nil, fmt.Errorf("no CERTIFICATE pem block found in %s", filename) + } + return ders, nil +} + +func (clientauth *ClientAuthentication) provision(ctx caddy.Context) error { + if len(clientauth.CARaw) > 0 && (len(clientauth.TrustedCACerts) > 0 || len(clientauth.TrustedCACertPEMFiles) > 0) { + return fmt.Errorf("conflicting config for client authentication trust CA") + } + + // convert all named file paths to inline + if len(clientauth.TrustedCACertPEMFiles) > 0 { + for _, fpath := range clientauth.TrustedCACertPEMFiles { + ders, err := convertPEMFilesToDER(fpath) + if err != nil { + return nil + } + clientauth.TrustedCACerts = append(clientauth.TrustedCACerts, ders...) + } + } + + // if we have TrustedCACerts explicitly set, create an 'inline' CA and return + if len(clientauth.TrustedCACerts) > 0 { + clientauth.ca = InlineCAPool{ + TrustedCACerts: clientauth.TrustedCACerts, + } + return nil + } + + // if we don't have any CARaw set, there's not much work to do + if clientauth.CARaw == nil { + return nil + } + caRaw, err := ctx.LoadModule(clientauth, "CARaw") + if err != nil { + return err + } + ca, ok := caRaw.(CA) + if !ok { + return fmt.Errorf("'ca' module '%s' is not a certificate pool provider", ca) + } + clientauth.ca = ca + + return nil +} + +// Active returns true if clientauth has an actionable configuration. +func (clientauth ClientAuthentication) Active() bool { + return len(clientauth.TrustedCACerts) > 0 || + len(clientauth.TrustedCACertPEMFiles) > 0 || + len(clientauth.TrustedLeafCerts) > 0 || // TODO: DEPRECATED + len(clientauth.VerifiersRaw) > 0 || + len(clientauth.Mode) > 0 || + clientauth.CARaw != nil || clientauth.ca != nil +} + +// ConfigureTLSConfig sets up cfg to enforce clientauth's configuration. +func (clientauth *ClientAuthentication) ConfigureTLSConfig(cfg *tls.Config) error { + // if there's no actionable client auth, simply disable it + if !clientauth.Active() { + cfg.ClientAuth = tls.NoClientCert + return nil + } + + // enforce desired mode of client authentication + if len(clientauth.Mode) > 0 { + switch clientauth.Mode { + case "request": + cfg.ClientAuth = tls.RequestClientCert + case "require": + cfg.ClientAuth = tls.RequireAnyClientCert + case "verify_if_given": + cfg.ClientAuth = tls.VerifyClientCertIfGiven + case "require_and_verify": + cfg.ClientAuth = tls.RequireAndVerifyClientCert + default: + return fmt.Errorf("client auth mode not recognized: %s", clientauth.Mode) + } + } else { + // otherwise, set a safe default mode + if len(clientauth.TrustedCACerts) > 0 || + len(clientauth.TrustedCACertPEMFiles) > 0 || + len(clientauth.TrustedLeafCerts) > 0 || + clientauth.CARaw != nil || clientauth.ca != nil { + cfg.ClientAuth = tls.RequireAndVerifyClientCert + } else { + cfg.ClientAuth = tls.RequireAnyClientCert + } + } + + // enforce CA verification by adding CA certs to the ClientCAs pool + if clientauth.ca != nil { + cfg.ClientCAs = clientauth.ca.CertPool() + } + + // TODO: DEPRECATED: Only here for backwards compatibility. + // If leaf cert is specified, enforce by adding a client auth module + if len(clientauth.TrustedLeafCerts) > 0 { + caddy.Log().Named("tls.connection_policy").Warn("trusted_leaf_certs is deprecated; use leaf verifier module instead") + var trustedLeafCerts []*x509.Certificate + for _, clientCertString := range clientauth.TrustedLeafCerts { + clientCert, err := decodeBase64DERCert(clientCertString) + if err != nil { + return fmt.Errorf("parsing certificate: %v", err) + } + trustedLeafCerts = append(trustedLeafCerts, clientCert) + } + clientauth.verifiers = append(clientauth.verifiers, LeafCertClientAuth{trustedLeafCerts: trustedLeafCerts}) + } + + // if a custom verification function already exists, wrap it + clientauth.existingVerifyPeerCert = cfg.VerifyPeerCertificate + cfg.VerifyPeerCertificate = clientauth.verifyPeerCertificate + return nil +} + +// verifyPeerCertificate is for use as a tls.Config.VerifyPeerCertificate +// callback to do custom client certificate verification. It is intended +// for installation only by clientauth.ConfigureTLSConfig(). +func (clientauth *ClientAuthentication) verifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + // first use any pre-existing custom verification function + if clientauth.existingVerifyPeerCert != nil { + err := clientauth.existingVerifyPeerCert(rawCerts, verifiedChains) + if err != nil { + return err + } + } + for _, verifier := range clientauth.verifiers { + err := verifier.VerifyClientCertificate(rawCerts, verifiedChains) + if err != nil { + return err + } + } + return nil +} + +// decodeBase64DERCert base64-decodes, then DER-decodes, certStr. +func decodeBase64DERCert(certStr string) (*x509.Certificate, error) { + derBytes, err := base64.StdEncoding.DecodeString(certStr) + if err != nil { + return nil, err + } + return x509.ParseCertificate(derBytes) +} + +// setDefaultTLSParams sets the default TLS cipher suites, protocol versions, +// and server preferences of cfg if they are not already set; it does not +// overwrite values, only fills in missing values. +func setDefaultTLSParams(cfg *tls.Config) { + if len(cfg.CipherSuites) == 0 { + cfg.CipherSuites = getOptimalDefaultCipherSuites() + } + + // Not a cipher suite, but still important for mitigating protocol downgrade attacks + // (prepend since having it at end breaks http2 due to non-h2-approved suites before it) + cfg.CipherSuites = append([]uint16{tls.TLS_FALLBACK_SCSV}, cfg.CipherSuites...) + + if len(cfg.CurvePreferences) == 0 { + cfg.CurvePreferences = defaultCurves + } + + if cfg.MinVersion == 0 { + // crypto/tls docs: + // "If EncryptedClientHelloKeys is set, MinVersion, if set, must be VersionTLS13." + if cfg.EncryptedClientHelloKeys == nil { + cfg.MinVersion = tls.VersionTLS12 + } else { + cfg.MinVersion = tls.VersionTLS13 + } + } + if cfg.MaxVersion == 0 { + cfg.MaxVersion = tls.VersionTLS13 + } +} + +// LeafCertClientAuth verifies the client's leaf certificate. +type LeafCertClientAuth struct { + LeafCertificateLoadersRaw []json.RawMessage `json:"leaf_certs_loaders,omitempty" caddy:"namespace=tls.leaf_cert_loader inline_key=loader"` + trustedLeafCerts []*x509.Certificate +} + +// CaddyModule returns the Caddy module information. +func (LeafCertClientAuth) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.client_auth.verifier.leaf", + New: func() caddy.Module { return new(LeafCertClientAuth) }, + } +} + +func (l *LeafCertClientAuth) Provision(ctx caddy.Context) error { + if l.LeafCertificateLoadersRaw == nil { + return nil + } + val, err := ctx.LoadModule(l, "LeafCertificateLoadersRaw") + if err != nil { + return fmt.Errorf("could not parse leaf certificates loaders: %s", err.Error()) + } + trustedLeafCertloaders := []LeafCertificateLoader{} + for _, loader := range val.([]any) { + trustedLeafCertloaders = append(trustedLeafCertloaders, loader.(LeafCertificateLoader)) + } + trustedLeafCertificates := []*x509.Certificate{} + for _, loader := range trustedLeafCertloaders { + certs, err := loader.LoadLeafCertificates() + if err != nil { + return fmt.Errorf("could not load leaf certificates: %s", err.Error()) + } + trustedLeafCertificates = append(trustedLeafCertificates, certs...) + } + l.trustedLeafCerts = trustedLeafCertificates + return nil +} + +func (l LeafCertClientAuth) VerifyClientCertificate(rawCerts [][]byte, _ [][]*x509.Certificate) error { + if len(rawCerts) == 0 { + return fmt.Errorf("no client certificate provided") + } + + remoteLeafCert, err := x509.ParseCertificate(rawCerts[0]) + if err != nil { + return fmt.Errorf("can't parse the given certificate: %s", err.Error()) + } + + for _, trustedLeafCert := range l.trustedLeafCerts { + if remoteLeafCert.Equal(trustedLeafCert) { + return nil + } + } + + return fmt.Errorf("client leaf certificate failed validation") +} + +// PublicKeyAlgorithm is a JSON-unmarshalable wrapper type. +type PublicKeyAlgorithm x509.PublicKeyAlgorithm + +// UnmarshalJSON satisfies json.Unmarshaler. +func (a *PublicKeyAlgorithm) UnmarshalJSON(b []byte) error { + algoStr := strings.ToLower(strings.Trim(string(b), `"`)) + algo, ok := publicKeyAlgorithms[algoStr] + if !ok { + return fmt.Errorf("unrecognized public key algorithm: %s (expected one of %v)", + algoStr, publicKeyAlgorithms) + } + *a = PublicKeyAlgorithm(algo) + return nil +} + +// ConnectionMatcher is a type which matches TLS handshakes. +type ConnectionMatcher interface { + Match(*tls.ClientHelloInfo) bool +} + +// LeafCertificateLoader is a type that loads the trusted leaf certificates +// for the tls.leaf_cert_loader modules +type LeafCertificateLoader interface { + LoadLeafCertificates() ([]*x509.Certificate, error) +} + +// ClientCertificateVerifier is a type which verifies client certificates. +// It is called during verifyPeerCertificate in the TLS handshake. +type ClientCertificateVerifier interface { + VerifyClientCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error +} + +var defaultALPN = []string{"h2", "http/1.1"} + +type destructableWriter struct{ *os.File } + +func (d destructableWriter) Destruct() error { return d.Close() } + +var secretsLogPool = caddy.NewUsagePool() + +// Interface guards +var ( + _ caddyfile.Unmarshaler = (*ClientAuthentication)(nil) + _ caddyfile.Unmarshaler = (*ConnectionPolicy)(nil) +) + +// ParseCaddyfileNestedMatcherSet parses the Caddyfile tokens for a nested +// matcher set, and returns its raw module map value. +func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, error) { + matcherMap := make(map[string]ConnectionMatcher) + + tokensByMatcherName := make(map[string][]caddyfile.Token) + for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); { + matcherName := d.Val() + tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...) + } + + for matcherName, tokens := range tokensByMatcherName { + dd := caddyfile.NewDispenser(tokens) + dd.Next() // consume wrapper name + + unm, err := caddyfile.UnmarshalModule(dd, "tls.handshake_match."+matcherName) + if err != nil { + return nil, err + } + cm, ok := unm.(ConnectionMatcher) + if !ok { + return nil, fmt.Errorf("matcher module '%s' is not a connection matcher", matcherName) + } + matcherMap[matcherName] = cm + } + + matcherSet := make(caddy.ModuleMap) + for name, matcher := range matcherMap { + jsonBytes, err := json.Marshal(matcher) + if err != nil { + return nil, fmt.Errorf("marshaling %T matcher: %v", matcher, err) + } + matcherSet[name] = jsonBytes + } + + return matcherSet, nil +} diff --git a/modules/caddytls/connpolicy_test.go b/modules/caddytls/connpolicy_test.go new file mode 100644 index 00000000000..0caed289920 --- /dev/null +++ b/modules/caddytls/connpolicy_test.go @@ -0,0 +1,280 @@ +// 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 caddytls + +import ( + "encoding/json" + "fmt" + "reflect" + "testing" + + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func TestClientAuthenticationUnmarshalCaddyfileWithDirectiveName(t *testing.T) { + const test_der_1 = `MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==` + const test_cert_file_1 = "../../caddytest/caddy.ca.cer" + type args struct { + d *caddyfile.Dispenser + } + tests := []struct { + name string + args args + expected ClientAuthentication + wantErr bool + }{ + { + name: "empty client_auth block does not error", + args: args{ + d: caddyfile.NewTestDispenser( + `client_auth { + }`, + ), + }, + wantErr: false, + }, + { + name: "providing both 'trust_pool' and 'trusted_ca_cert' returns an error", + args: args{ + d: caddyfile.NewTestDispenser( + `client_auth { + trust_pool inline MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ== + trusted_ca_cert MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ== + }`), + }, + wantErr: true, + }, + { + name: "trust_pool without a module argument returns an error", + args: args{ + d: caddyfile.NewTestDispenser( + `client_auth { + trust_pool + }`), + }, + wantErr: true, + }, + { + name: "providing more than 1 mode produces an error", + args: args{ + d: caddyfile.NewTestDispenser(` + client_auth { + mode require request + } + `), + }, + wantErr: true, + }, + { + name: "not providing 'mode' argument produces an error", + args: args{d: caddyfile.NewTestDispenser(` + client_auth { + mode + } + `)}, + wantErr: true, + }, + { + name: "providing a single 'mode' argument sets the mode", + args: args{ + d: caddyfile.NewTestDispenser(` + client_auth { + mode require + } + `), + }, + expected: ClientAuthentication{ + Mode: "require", + }, + wantErr: false, + }, + { + name: "not providing an argument to 'trusted_ca_cert' produces an error", + args: args{ + d: caddyfile.NewTestDispenser(` + client_auth { + trusted_ca_cert + } + `), + }, + wantErr: true, + }, + { + name: "not providing an argument to 'trusted_leaf_cert' produces an error", + args: args{ + d: caddyfile.NewTestDispenser(` + client_auth { + trusted_leaf_cert + } + `), + }, + wantErr: true, + }, + { + name: "not providing an argument to 'trusted_ca_cert_file' produces an error", + args: args{ + d: caddyfile.NewTestDispenser(` + client_auth { + trusted_ca_cert_file + } + `), + }, + wantErr: true, + }, + { + name: "not providing an argument to 'trusted_leaf_cert_file' produces an error", + args: args{ + d: caddyfile.NewTestDispenser(` + client_auth { + trusted_leaf_cert_file + } + `), + }, + wantErr: true, + }, + { + name: "using 'trusted_ca_cert' adapts successfully", + args: args{ + d: caddyfile.NewTestDispenser(fmt.Sprintf(` + client_auth { + trusted_ca_cert %s + }`, test_der_1)), + }, + expected: ClientAuthentication{ + CARaw: json.RawMessage(fmt.Sprintf(`{"provider":"inline","trusted_ca_certs":["%s"]}`, test_der_1)), + }, + }, + { + name: "using 'inline' trust_pool loads the module successfully", + args: args{ + d: caddyfile.NewTestDispenser(fmt.Sprintf(` + client_auth { + trust_pool inline { + trust_der %s + } + } + `, test_der_1)), + }, + expected: ClientAuthentication{ + CARaw: json.RawMessage(fmt.Sprintf(`{"provider":"inline","trusted_ca_certs":["%s"]}`, test_der_1)), + }, + }, + { + name: "setting 'trusted_ca_cert' and 'trust_pool' produces an error", + args: args{ + d: caddyfile.NewTestDispenser(fmt.Sprintf(` + client_auth { + trusted_ca_cert %s + trust_pool inline { + trust_der %s + } + }`, test_der_1, test_der_1)), + }, + wantErr: true, + }, + { + name: "setting 'trust_pool' and 'trusted_ca_cert' produces an error", + args: args{ + d: caddyfile.NewTestDispenser(fmt.Sprintf(` + client_auth { + trust_pool inline { + trust_der %s + } + trusted_ca_cert %s + }`, test_der_1, test_der_1)), + }, + wantErr: true, + }, + { + name: "setting 'trust_pool' and 'trusted_ca_cert' produces an error", + args: args{ + d: caddyfile.NewTestDispenser(fmt.Sprintf(` + client_auth { + trust_pool inline { + trust_der %s + } + trusted_ca_cert_file %s + }`, test_der_1, test_cert_file_1)), + }, + wantErr: true, + }, + { + name: "configuring 'trusted_ca_cert_file' without an argument is an error", + args: args{ + d: caddyfile.NewTestDispenser(` + client_auth { + trusted_ca_cert_file + } + `), + }, + wantErr: true, + }, + { + name: "configuring 'trusted_ca_cert_file' produces config with 'inline' provider", + args: args{ + d: caddyfile.NewTestDispenser(fmt.Sprintf(` + client_auth { + trusted_ca_cert_file %s + }`, test_cert_file_1), + ), + }, + expected: ClientAuthentication{ + CARaw: json.RawMessage(fmt.Sprintf(`{"provider":"inline","trusted_ca_certs":["%s"]}`, test_der_1)), + }, + wantErr: false, + }, + { + name: "configuring leaf certs does not conflict with 'trust_pool'", + args: args{ + d: caddyfile.NewTestDispenser(fmt.Sprintf(` + client_auth { + trust_pool inline { + trust_der %s + } + trusted_leaf_cert %s + }`, test_der_1, test_der_1)), + }, + expected: ClientAuthentication{ + CARaw: json.RawMessage(fmt.Sprintf(`{"provider":"inline","trusted_ca_certs":["%s"]}`, test_der_1)), + TrustedLeafCerts: []string{test_der_1}, + }, + }, + { + name: "providing trusted leaf certificate file loads the cert successfully", + args: args{ + d: caddyfile.NewTestDispenser(fmt.Sprintf(` + client_auth { + trusted_leaf_cert_file %s + }`, test_cert_file_1)), + }, + expected: ClientAuthentication{ + TrustedLeafCerts: []string{test_der_1}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ca := &ClientAuthentication{} + if err := ca.UnmarshalCaddyfile(tt.args.d); (err != nil) != tt.wantErr { + t.Errorf("ClientAuthentication.UnmarshalCaddyfile() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && !reflect.DeepEqual(&tt.expected, ca) { + t.Errorf("ClientAuthentication.UnmarshalCaddyfile() = %v, want %v", ca, tt.expected) + } + }) + } +} diff --git a/modules/caddytls/distributedstek/distributedstek.go b/modules/caddytls/distributedstek/distributedstek.go new file mode 100644 index 00000000000..f6d0de064c0 --- /dev/null +++ b/modules/caddytls/distributedstek/distributedstek.go @@ -0,0 +1,250 @@ +// 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 distributedstek provides TLS session ticket ephemeral +// keys (STEKs) in a distributed fashion by utilizing configured +// storage for locking and key sharing. This allows a cluster of +// machines to optimally resume TLS sessions in a load-balanced +// environment without any hassle. This is similar to what +// Twitter does, but without needing to rely on SSH, as it is +// built into the web server this way: +// https://blog.twitter.com/engineering/en_us/a/2013/forward-secrecy-at-twitter.html +package distributedstek + +import ( + "bytes" + "encoding/gob" + "encoding/json" + "errors" + "fmt" + "io/fs" + "log" + "runtime/debug" + "time" + + "github.com/caddyserver/certmagic" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddytls" +) + +func init() { + caddy.RegisterModule(Provider{}) +} + +// Provider implements a distributed STEK provider. This +// module will obtain STEKs from a storage module instead +// of generating STEKs internally. This allows STEKs to be +// coordinated, improving TLS session resumption in a cluster. +type Provider struct { + // The storage module wherein to store and obtain session + // ticket keys. If unset, Caddy's default/global-configured + // storage module will be used. + Storage json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"` + + storage certmagic.Storage + stekConfig *caddytls.SessionTicketService + timer *time.Timer + ctx caddy.Context +} + +// CaddyModule returns the Caddy module information. +func (Provider) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.stek.distributed", + New: func() caddy.Module { return new(Provider) }, + } +} + +// Provision provisions s. +func (s *Provider) Provision(ctx caddy.Context) error { + s.ctx = ctx + + // unpack the storage module to use, if different from the default + if s.Storage != nil { + val, err := ctx.LoadModule(s, "Storage") + if err != nil { + return fmt.Errorf("loading TLS storage module: %s", err) + } + cmStorage, err := val.(caddy.StorageConverter).CertMagicStorage() + if err != nil { + return fmt.Errorf("creating TLS storage configuration: %v", err) + } + s.storage = cmStorage + } + + // otherwise, use default storage + if s.storage == nil { + s.storage = ctx.Storage() + } + + return nil +} + +// Initialize sets the configuration for s and returns the starting keys. +func (s *Provider) Initialize(config *caddytls.SessionTicketService) ([][32]byte, error) { + // keep a reference to the config; we'll need it when rotating keys + s.stekConfig = config + + dstek, err := s.getSTEK() + if err != nil { + return nil, err + } + + // create timer for the remaining time on the interval; + // this timer is cleaned up only when rotate() returns + s.timer = time.NewTimer(time.Until(dstek.NextRotation)) + + return dstek.Keys, nil +} + +// Next returns a channel which transmits the latest session ticket keys. +func (s *Provider) Next(doneChan <-chan struct{}) <-chan [][32]byte { + keysChan := make(chan [][32]byte) + go s.rotate(doneChan, keysChan) + return keysChan +} + +func (s *Provider) loadSTEK() (distributedSTEK, error) { + var sg distributedSTEK + gobBytes, err := s.storage.Load(s.ctx, stekFileName) + if err != nil { + return sg, err // don't wrap, in case error is certmagic.ErrNotExist + } + dec := gob.NewDecoder(bytes.NewReader(gobBytes)) + err = dec.Decode(&sg) + if err != nil { + return sg, fmt.Errorf("STEK gob corrupted: %v", err) + } + return sg, nil +} + +func (s *Provider) storeSTEK(dstek distributedSTEK) error { + var buf bytes.Buffer + err := gob.NewEncoder(&buf).Encode(dstek) + if err != nil { + return fmt.Errorf("encoding STEK gob: %v", err) + } + err = s.storage.Store(s.ctx, stekFileName, buf.Bytes()) + if err != nil { + return fmt.Errorf("storing STEK gob: %v", err) + } + return nil +} + +// getSTEK locks and loads the current STEK from storage. If none +// currently exists, a new STEK is created and persisted. If the +// current STEK is outdated (NextRotation time is in the past), +// then it is rotated and persisted. The resulting STEK is returned. +func (s *Provider) getSTEK() (distributedSTEK, error) { + err := s.storage.Lock(s.ctx, stekLockName) + if err != nil { + return distributedSTEK{}, fmt.Errorf("failed to acquire storage lock: %v", err) + } + + //nolint:errcheck + defer s.storage.Unlock(s.ctx, stekLockName) + + // load the current STEKs from storage + dstek, err := s.loadSTEK() + if errors.Is(err, fs.ErrNotExist) { + // if there is none, then make some right away + dstek, err = s.rotateKeys(dstek) + if err != nil { + return dstek, fmt.Errorf("creating new STEK: %v", err) + } + } else if err != nil { + // some other error, that's a problem + return dstek, fmt.Errorf("loading STEK: %v", err) + } else if time.Now().After(dstek.NextRotation) { + // if current STEKs are outdated, rotate them + dstek, err = s.rotateKeys(dstek) + if err != nil { + return dstek, fmt.Errorf("rotating keys: %v", err) + } + } + + return dstek, nil +} + +// rotateKeys rotates the keys of oldSTEK and returns the new distributedSTEK +// with updated keys and timestamps. It stores the returned STEK in storage, +// so this function must only be called in a storage-provided lock. +func (s *Provider) rotateKeys(oldSTEK distributedSTEK) (distributedSTEK, error) { + var newSTEK distributedSTEK + var err error + + newSTEK.Keys, err = s.stekConfig.RotateSTEKs(oldSTEK.Keys) + if err != nil { + return newSTEK, err + } + + now := time.Now() + newSTEK.LastRotation = now + newSTEK.NextRotation = now.Add(time.Duration(s.stekConfig.RotationInterval)) + + err = s.storeSTEK(newSTEK) + if err != nil { + return newSTEK, err + } + + return newSTEK, nil +} + +// rotate rotates keys on a regular basis, sending each updated set of +// keys down keysChan, until doneChan is closed. +func (s *Provider) rotate(doneChan <-chan struct{}, keysChan chan<- [][32]byte) { + defer func() { + if err := recover(); err != nil { + log.Printf("[PANIC] distributed STEK rotation: %v\n%s", err, debug.Stack()) + } + }() + for { + select { + case <-s.timer.C: + dstek, err := s.getSTEK() + if err != nil { + // TODO: improve this handling + log.Printf("[ERROR] Loading STEK: %v", err) + continue + } + + // send the updated keys to the service + keysChan <- dstek.Keys + + // timer channel is already drained, so reset directly (see godoc) + s.timer.Reset(time.Until(dstek.NextRotation)) + + case <-doneChan: + // again, see godocs for why timer is stopped this way + if !s.timer.Stop() { + <-s.timer.C + } + return + } + } +} + +type distributedSTEK struct { + Keys [][32]byte + LastRotation, NextRotation time.Time +} + +const ( + stekLockName = "stek_check" + stekFileName = "stek/stek.bin" +) + +// Interface guard +var _ caddytls.STEKProvider = (*Provider)(nil) diff --git a/modules/caddytls/fileloader.go b/modules/caddytls/fileloader.go new file mode 100644 index 00000000000..7d2927e2ae9 --- /dev/null +++ b/modules/caddytls/fileloader.go @@ -0,0 +1,122 @@ +// 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 caddytls + +import ( + "crypto/tls" + "fmt" + "os" + "strings" + + "github.com/caddyserver/caddy/v2" +) + +func init() { + caddy.RegisterModule(FileLoader{}) +} + +// FileLoader loads certificates and their associated keys from disk. +type FileLoader []CertKeyFilePair + +// Provision implements caddy.Provisioner. +func (fl FileLoader) Provision(ctx caddy.Context) error { + repl, ok := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + if !ok { + repl = caddy.NewReplacer() + } + for k, pair := range fl { + for i, tag := range pair.Tags { + pair.Tags[i] = repl.ReplaceKnown(tag, "") + } + fl[k] = CertKeyFilePair{ + Certificate: repl.ReplaceKnown(pair.Certificate, ""), + Key: repl.ReplaceKnown(pair.Key, ""), + Format: repl.ReplaceKnown(pair.Format, ""), + Tags: pair.Tags, + } + } + return nil +} + +// CaddyModule returns the Caddy module information. +func (FileLoader) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.certificates.load_files", + New: func() caddy.Module { return new(FileLoader) }, + } +} + +// CertKeyFilePair pairs certificate and key file names along with their +// encoding format so that they can be loaded from disk. +type CertKeyFilePair struct { + // Path to the certificate (public key) file. + Certificate string `json:"certificate"` + + // Path to the private key file. + Key string `json:"key"` + + // The format of the cert and key. Can be "pem". Default: "pem" + Format string `json:"format,omitempty"` + + // Arbitrary values to associate with this certificate. + // Can be useful when you want to select a particular + // certificate when there may be multiple valid candidates. + Tags []string `json:"tags,omitempty"` +} + +// LoadCertificates returns the certificates to be loaded by fl. +func (fl FileLoader) LoadCertificates() ([]Certificate, error) { + certs := make([]Certificate, 0, len(fl)) + for _, pair := range fl { + certData, err := os.ReadFile(pair.Certificate) + if err != nil { + return nil, err + } + keyData, err := os.ReadFile(pair.Key) + if err != nil { + return nil, err + } + + var cert tls.Certificate + switch pair.Format { + case "": + fallthrough + + case "pem": + // if the start of the key file looks like an encrypted private key, + // reject it with a helpful error message + if strings.Contains(string(keyData[:40]), "ENCRYPTED") { + return nil, fmt.Errorf("encrypted private keys are not supported; please decrypt the key first") + } + + cert, err = tls.X509KeyPair(certData, keyData) + + default: + return nil, fmt.Errorf("unrecognized certificate/key encoding format: %s", pair.Format) + } + if err != nil { + return nil, err + } + + certs = append(certs, Certificate{Certificate: cert, Tags: pair.Tags}) + } + return certs, nil +} + +// Interface guard +var ( + _ CertificateLoader = (FileLoader)(nil) + _ caddy.Provisioner = (FileLoader)(nil) +) diff --git a/modules/caddytls/folderloader.go b/modules/caddytls/folderloader.go new file mode 100644 index 00000000000..2df6f4cee41 --- /dev/null +++ b/modules/caddytls/folderloader.go @@ -0,0 +1,170 @@ +// 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 caddytls + +import ( + "bytes" + "crypto/tls" + "encoding/pem" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/caddyserver/caddy/v2" +) + +func init() { + caddy.RegisterModule(FolderLoader{}) +} + +// FolderLoader loads certificates and their associated keys from disk +// by recursively walking the specified directories, looking for PEM +// files which contain both a certificate and a key. +type FolderLoader []string + +// CaddyModule returns the Caddy module information. +func (FolderLoader) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.certificates.load_folders", + New: func() caddy.Module { return new(FolderLoader) }, + } +} + +// Provision implements caddy.Provisioner. +func (fl FolderLoader) Provision(ctx caddy.Context) error { + repl, ok := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + if !ok { + repl = caddy.NewReplacer() + } + for k, path := range fl { + fl[k] = repl.ReplaceKnown(path, "") + } + return nil +} + +// LoadCertificates loads all the certificates+keys in the directories +// listed in fl from all files ending with .pem. This method of loading +// certificates expects the certificate and key to be bundled into the +// same file. +func (fl FolderLoader) LoadCertificates() ([]Certificate, error) { + var certs []Certificate + for _, dir := range fl { + err := filepath.Walk(dir, func(fpath string, info os.FileInfo, err error) error { + if err != nil { + return fmt.Errorf("unable to traverse into path: %s", fpath) + } + if info.IsDir() { + return nil + } + if !strings.HasSuffix(strings.ToLower(info.Name()), ".pem") { + return nil + } + + bundle, err := os.ReadFile(fpath) + if err != nil { + return err + } + cert, err := tlsCertFromCertAndKeyPEMBundle(bundle) + if err != nil { + return fmt.Errorf("%s: %w", fpath, err) + } + + certs = append(certs, Certificate{Certificate: cert}) + + return nil + }) + if err != nil { + return nil, err + } + } + return certs, nil +} + +func tlsCertFromCertAndKeyPEMBundle(bundle []byte) (tls.Certificate, error) { + certBuilder, keyBuilder := new(bytes.Buffer), new(bytes.Buffer) + var foundKey bool // use only the first key in the file + + for { + // Decode next block so we can see what type it is + var derBlock *pem.Block + derBlock, bundle = pem.Decode(bundle) + if derBlock == nil { + break + } + + if derBlock.Type == "CERTIFICATE" { + // Re-encode certificate as PEM, appending to certificate chain + if err := pem.Encode(certBuilder, derBlock); err != nil { + return tls.Certificate{}, err + } + } else if derBlock.Type == "EC PARAMETERS" { + // EC keys generated from openssl can be composed of two blocks: + // parameters and key (parameter block should come first) + if !foundKey { + // Encode parameters + if err := pem.Encode(keyBuilder, derBlock); err != nil { + return tls.Certificate{}, err + } + + // Key must immediately follow + derBlock, bundle = pem.Decode(bundle) + if derBlock == nil || derBlock.Type != "EC PRIVATE KEY" { + return tls.Certificate{}, fmt.Errorf("expected elliptic private key to immediately follow EC parameters") + } + if err := pem.Encode(keyBuilder, derBlock); err != nil { + return tls.Certificate{}, err + } + foundKey = true + } + } else if derBlock.Type == "PRIVATE KEY" || strings.HasSuffix(derBlock.Type, " PRIVATE KEY") { + // RSA key + if !foundKey { + if err := pem.Encode(keyBuilder, derBlock); err != nil { + return tls.Certificate{}, err + } + foundKey = true + } + } else { + return tls.Certificate{}, fmt.Errorf("unrecognized PEM block type: %s", derBlock.Type) + } + } + + certPEMBytes, keyPEMBytes := certBuilder.Bytes(), keyBuilder.Bytes() + if len(certPEMBytes) == 0 { + return tls.Certificate{}, fmt.Errorf("failed to parse PEM data") + } + if len(keyPEMBytes) == 0 { + return tls.Certificate{}, fmt.Errorf("no private key block found") + } + + // if the start of the key file looks like an encrypted private key, + // reject it with a helpful error message + if strings.HasPrefix(string(keyPEMBytes[:40]), "ENCRYPTED") { + return tls.Certificate{}, fmt.Errorf("encrypted private keys are not supported; please decrypt the key first") + } + + cert, err := tls.X509KeyPair(certPEMBytes, keyPEMBytes) + if err != nil { + return tls.Certificate{}, fmt.Errorf("making X509 key pair: %v", err) + } + + return cert, nil +} + +var ( + _ CertificateLoader = (FolderLoader)(nil) + _ caddy.Provisioner = (FolderLoader)(nil) +) diff --git a/modules/caddytls/internalissuer.go b/modules/caddytls/internalissuer.go new file mode 100644 index 00000000000..b55757af585 --- /dev/null +++ b/modules/caddytls/internalissuer.go @@ -0,0 +1,203 @@ +// 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 caddytls + +import ( + "bytes" + "context" + "crypto/x509" + "encoding/pem" + "time" + + "github.com/caddyserver/certmagic" + "github.com/smallstep/certificates/authority/provisioner" + "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/modules/caddypki" +) + +func init() { + caddy.RegisterModule(InternalIssuer{}) +} + +// InternalIssuer is a certificate issuer that generates +// certificates internally using a locally-configured +// CA which can be customized using the `pki` app. +type InternalIssuer struct { + // The ID of the CA to use for signing. The default + // CA ID is "local". The CA can be configured with the + // `pki` app. + CA string `json:"ca,omitempty"` + + // The validity period of certificates. + Lifetime caddy.Duration `json:"lifetime,omitempty"` + + // If true, the root will be the issuer instead of + // the intermediate. This is NOT recommended and should + // only be used when devices/clients do not properly + // validate certificate chains. + SignWithRoot bool `json:"sign_with_root,omitempty"` + + ca *caddypki.CA + logger *zap.Logger +} + +// CaddyModule returns the Caddy module information. +func (InternalIssuer) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.issuance.internal", + New: func() caddy.Module { return new(InternalIssuer) }, + } +} + +// Provision sets up the issuer. +func (iss *InternalIssuer) Provision(ctx caddy.Context) error { + iss.logger = ctx.Logger() + + // set some defaults + if iss.CA == "" { + iss.CA = caddypki.DefaultCAID + } + + // get a reference to the configured CA + appModule, err := ctx.App("pki") + if err != nil { + return err + } + pkiApp := appModule.(*caddypki.PKI) + ca, err := pkiApp.GetCA(ctx, iss.CA) + if err != nil { + return err + } + iss.ca = ca + + // set any other default values + if iss.Lifetime == 0 { + iss.Lifetime = caddy.Duration(defaultInternalCertLifetime) + } + + return nil +} + +// IssuerKey returns the unique issuer key for the +// configured CA endpoint. +func (iss InternalIssuer) IssuerKey() string { + return iss.ca.ID +} + +// Issue issues a certificate to satisfy the CSR. +func (iss InternalIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest) (*certmagic.IssuedCertificate, error) { + // prepare the signing authority + authCfg := caddypki.AuthorityConfig{ + SignWithRoot: iss.SignWithRoot, + } + auth, err := iss.ca.NewAuthority(authCfg) + if err != nil { + return nil, err + } + + // get the cert (public key) that will be used for signing + var issuerCert *x509.Certificate + if iss.SignWithRoot { + issuerCert = iss.ca.RootCertificate() + } else { + issuerCert = iss.ca.IntermediateCertificate() + } + + // ensure issued certificate does not expire later than its issuer + lifetime := time.Duration(iss.Lifetime) + if time.Now().Add(lifetime).After(issuerCert.NotAfter) { + lifetime = time.Until(issuerCert.NotAfter) + iss.logger.Warn("cert lifetime would exceed issuer NotAfter, clamping lifetime", + zap.Duration("orig_lifetime", time.Duration(iss.Lifetime)), + zap.Duration("lifetime", lifetime), + zap.Time("not_after", issuerCert.NotAfter), + ) + } + + certChain, err := auth.SignWithContext(ctx, csr, provisioner.SignOptions{}, customCertLifetime(caddy.Duration(lifetime))) + if err != nil { + return nil, err + } + + var buf bytes.Buffer + for _, cert := range certChain { + err := pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) + if err != nil { + return nil, err + } + } + + return &certmagic.IssuedCertificate{ + Certificate: buf.Bytes(), + }, nil +} + +// UnmarshalCaddyfile deserializes Caddyfile tokens into iss. +// +// ... internal { +// ca +// lifetime +// sign_with_root +// } +func (iss *InternalIssuer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume issuer name + for d.NextBlock(0) { + switch d.Val() { + case "ca": + if !d.AllArgs(&iss.CA) { + return d.ArgErr() + } + + case "lifetime": + if !d.NextArg() { + return d.ArgErr() + } + dur, err := caddy.ParseDuration(d.Val()) + if err != nil { + return err + } + iss.Lifetime = caddy.Duration(dur) + + case "sign_with_root": + if d.NextArg() { + return d.ArgErr() + } + iss.SignWithRoot = true + } + } + return nil +} + +// customCertLifetime allows us to customize certificates that are issued +// by Smallstep libs, particularly the NotBefore & NotAfter dates. +type customCertLifetime time.Duration + +func (d customCertLifetime) Modify(cert *x509.Certificate, _ provisioner.SignOptions) error { + cert.NotBefore = time.Now() + cert.NotAfter = cert.NotBefore.Add(time.Duration(d)) + return nil +} + +const defaultInternalCertLifetime = 12 * time.Hour + +// Interface guards +var ( + _ caddy.Provisioner = (*InternalIssuer)(nil) + _ certmagic.Issuer = (*InternalIssuer)(nil) + _ provisioner.CertificateModifier = (*customCertLifetime)(nil) +) diff --git a/modules/caddytls/leaffileloader.go b/modules/caddytls/leaffileloader.go new file mode 100644 index 00000000000..1d3f3a3e59f --- /dev/null +++ b/modules/caddytls/leaffileloader.go @@ -0,0 +1,99 @@ +// 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 caddytls + +import ( + "crypto/x509" + "encoding/pem" + "fmt" + "os" + + "github.com/caddyserver/caddy/v2" +) + +func init() { + caddy.RegisterModule(LeafFileLoader{}) +} + +// LeafFileLoader loads leaf certificates from disk. +type LeafFileLoader struct { + Files []string `json:"files,omitempty"` +} + +// Provision implements caddy.Provisioner. +func (fl *LeafFileLoader) Provision(ctx caddy.Context) error { + repl, ok := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + if !ok { + repl = caddy.NewReplacer() + } + for k, path := range fl.Files { + fl.Files[k] = repl.ReplaceKnown(path, "") + } + return nil +} + +// CaddyModule returns the Caddy module information. +func (LeafFileLoader) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.leaf_cert_loader.file", + New: func() caddy.Module { return new(LeafFileLoader) }, + } +} + +// LoadLeafCertificates returns the certificates to be loaded by fl. +func (fl LeafFileLoader) LoadLeafCertificates() ([]*x509.Certificate, error) { + certificates := make([]*x509.Certificate, 0, len(fl.Files)) + for _, path := range fl.Files { + ders, err := convertPEMFilesToDERBytes(path) + if err != nil { + return nil, err + } + certs, err := x509.ParseCertificates(ders) + if err != nil { + return nil, err + } + certificates = append(certificates, certs...) + } + return certificates, nil +} + +func convertPEMFilesToDERBytes(filename string) ([]byte, error) { + certDataPEM, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + var ders []byte + // while block is not nil, we have more certificates in the file + for block, rest := pem.Decode(certDataPEM); block != nil; block, rest = pem.Decode(rest) { + if block.Type != "CERTIFICATE" { + return nil, fmt.Errorf("no CERTIFICATE pem block found in %s", filename) + } + ders = append( + ders, + block.Bytes..., + ) + } + // if we decoded nothing, return an error + if len(ders) == 0 { + return nil, fmt.Errorf("no CERTIFICATE pem block found in %s", filename) + } + return ders, nil +} + +// Interface guard +var ( + _ LeafCertificateLoader = (*LeafFileLoader)(nil) + _ caddy.Provisioner = (*LeafFileLoader)(nil) +) diff --git a/modules/caddytls/leaffileloader_test.go b/modules/caddytls/leaffileloader_test.go new file mode 100644 index 00000000000..940ed78bd70 --- /dev/null +++ b/modules/caddytls/leaffileloader_test.go @@ -0,0 +1,38 @@ +package caddytls + +import ( + "context" + "encoding/pem" + "os" + "strings" + "testing" + + "github.com/caddyserver/caddy/v2" +) + +func TestLeafFileLoader(t *testing.T) { + fl := LeafFileLoader{Files: []string{"../../caddytest/leafcert.pem"}} + fl.Provision(caddy.Context{Context: context.Background()}) + + out, err := fl.LoadLeafCertificates() + if err != nil { + t.Errorf("Leaf certs file loading test failed: %v", err) + } + if len(out) != 1 { + t.Errorf("Error loading leaf cert in memory struct") + return + } + pemBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: out[0].Raw}) + + pemFileBytes, err := os.ReadFile("../../caddytest/leafcert.pem") + if err != nil { + t.Errorf("Unable to read the example certificate from the file") + } + + // Remove /r because windows. + pemFileString := strings.ReplaceAll(string(pemFileBytes), "\r\n", "\n") + + if string(pemBytes) != pemFileString { + t.Errorf("Leaf Certificate File Loader: Failed to load the correct certificate") + } +} diff --git a/modules/caddytls/leaffolderloader.go b/modules/caddytls/leaffolderloader.go new file mode 100644 index 00000000000..5c7b06e7681 --- /dev/null +++ b/modules/caddytls/leaffolderloader.go @@ -0,0 +1,97 @@ +// 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 caddytls + +import ( + "crypto/x509" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/caddyserver/caddy/v2" +) + +func init() { + caddy.RegisterModule(LeafFolderLoader{}) +} + +// LeafFolderLoader loads certificates and their associated keys from disk +// by recursively walking the specified directories, looking for PEM +// files which contain both a certificate and a key. +type LeafFolderLoader struct { + Folders []string `json:"folders,omitempty"` +} + +// CaddyModule returns the Caddy module information. +func (LeafFolderLoader) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.leaf_cert_loader.folder", + New: func() caddy.Module { return new(LeafFolderLoader) }, + } +} + +// Provision implements caddy.Provisioner. +func (fl *LeafFolderLoader) Provision(ctx caddy.Context) error { + repl, ok := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + if !ok { + repl = caddy.NewReplacer() + } + for k, path := range fl.Folders { + fl.Folders[k] = repl.ReplaceKnown(path, "") + } + return nil +} + +// LoadLeafCertificates loads all the leaf certificates in the directories +// listed in fl from all files ending with .pem. +func (fl LeafFolderLoader) LoadLeafCertificates() ([]*x509.Certificate, error) { + var certs []*x509.Certificate + for _, dir := range fl.Folders { + err := filepath.Walk(dir, func(fpath string, info os.FileInfo, err error) error { + if err != nil { + return fmt.Errorf("unable to traverse into path: %s", fpath) + } + if info.IsDir() { + return nil + } + if !strings.HasSuffix(strings.ToLower(info.Name()), ".pem") { + return nil + } + + certData, err := convertPEMFilesToDERBytes(fpath) + if err != nil { + return err + } + cert, err := x509.ParseCertificate(certData) + if err != nil { + return fmt.Errorf("%s: %w", fpath, err) + } + + certs = append(certs, cert) + + return nil + }) + if err != nil { + return nil, err + } + } + return certs, nil +} + +var ( + _ LeafCertificateLoader = (*LeafFolderLoader)(nil) + _ caddy.Provisioner = (*LeafFolderLoader)(nil) +) diff --git a/modules/caddytls/leaffolderloader_test.go b/modules/caddytls/leaffolderloader_test.go new file mode 100644 index 00000000000..35fecba89b3 --- /dev/null +++ b/modules/caddytls/leaffolderloader_test.go @@ -0,0 +1,37 @@ +package caddytls + +import ( + "context" + "encoding/pem" + "os" + "strings" + "testing" + + "github.com/caddyserver/caddy/v2" +) + +func TestLeafFolderLoader(t *testing.T) { + fl := LeafFolderLoader{Folders: []string{"../../caddytest"}} + fl.Provision(caddy.Context{Context: context.Background()}) + + out, err := fl.LoadLeafCertificates() + if err != nil { + t.Errorf("Leaf certs folder loading test failed: %v", err) + } + if len(out) != 1 { + t.Errorf("Error loading leaf cert in memory struct") + return + } + pemBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: out[0].Raw}) + pemFileBytes, err := os.ReadFile("../../caddytest/leafcert.pem") + if err != nil { + t.Errorf("Unable to read the example certificate from the file") + } + + // Remove /r because windows. + pemFileString := strings.ReplaceAll(string(pemFileBytes), "\r\n", "\n") + + if string(pemBytes) != pemFileString { + t.Errorf("Leaf Certificate Folder Loader: Failed to load the correct certificate") + } +} diff --git a/modules/caddytls/leafpemloader.go b/modules/caddytls/leafpemloader.go new file mode 100644 index 00000000000..28467ccf2c7 --- /dev/null +++ b/modules/caddytls/leafpemloader.go @@ -0,0 +1,76 @@ +// 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 caddytls + +import ( + "crypto/x509" + "fmt" + + "github.com/caddyserver/caddy/v2" +) + +func init() { + caddy.RegisterModule(LeafPEMLoader{}) +} + +// LeafPEMLoader loads leaf certificates by +// decoding their PEM blocks directly. This has the advantage +// of not needing to store them on disk at all. +type LeafPEMLoader struct { + Certificates []string `json:"certificates,omitempty"` +} + +// Provision implements caddy.Provisioner. +func (pl *LeafPEMLoader) Provision(ctx caddy.Context) error { + repl, ok := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + if !ok { + repl = caddy.NewReplacer() + } + for i, cert := range pl.Certificates { + pl.Certificates[i] = repl.ReplaceKnown(cert, "") + } + return nil +} + +// CaddyModule returns the Caddy module information. +func (LeafPEMLoader) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.leaf_cert_loader.pem", + New: func() caddy.Module { return new(LeafPEMLoader) }, + } +} + +// LoadLeafCertificates returns the certificates contained in pl. +func (pl LeafPEMLoader) LoadLeafCertificates() ([]*x509.Certificate, error) { + certs := make([]*x509.Certificate, 0, len(pl.Certificates)) + for i, cert := range pl.Certificates { + derBytes, err := convertPEMToDER([]byte(cert)) + if err != nil { + return nil, fmt.Errorf("PEM leaf certificate loader, cert %d: %v", i, err) + } + cert, err := x509.ParseCertificate(derBytes) + if err != nil { + return nil, fmt.Errorf("PEM cert %d: %v", i, err) + } + certs = append(certs, cert) + } + return certs, nil +} + +// Interface guard +var ( + _ LeafCertificateLoader = (*LeafPEMLoader)(nil) + _ caddy.Provisioner = (*LeafPEMLoader)(nil) +) diff --git a/modules/caddytls/leafpemloader_test.go b/modules/caddytls/leafpemloader_test.go new file mode 100644 index 00000000000..04a9efd2531 --- /dev/null +++ b/modules/caddytls/leafpemloader_test.go @@ -0,0 +1,54 @@ +package caddytls + +import ( + "context" + "encoding/pem" + "os" + "strings" + "testing" + + "github.com/caddyserver/caddy/v2" +) + +func TestLeafPEMLoader(t *testing.T) { + pl := LeafPEMLoader{Certificates: []string{` +-----BEGIN CERTIFICATE----- +MIICUTCCAfugAwIBAgIBADANBgkqhkiG9w0BAQQFADBXMQswCQYDVQQGEwJDTjEL +MAkGA1UECBMCUE4xCzAJBgNVBAcTAkNOMQswCQYDVQQKEwJPTjELMAkGA1UECxMC +VU4xFDASBgNVBAMTC0hlcm9uZyBZYW5nMB4XDTA1MDcxNTIxMTk0N1oXDTA1MDgx +NDIxMTk0N1owVzELMAkGA1UEBhMCQ04xCzAJBgNVBAgTAlBOMQswCQYDVQQHEwJD +TjELMAkGA1UEChMCT04xCzAJBgNVBAsTAlVOMRQwEgYDVQQDEwtIZXJvbmcgWWFu +ZzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCp5hnG7ogBhtlynpOS21cBewKE/B7j +V14qeyslnr26xZUsSVko36ZnhiaO/zbMOoRcKK9vEcgMtcLFuQTWDl3RAgMBAAGj +gbEwga4wHQYDVR0OBBYEFFXI70krXeQDxZgbaCQoR4jUDncEMH8GA1UdIwR4MHaA +FFXI70krXeQDxZgbaCQoR4jUDncEoVukWTBXMQswCQYDVQQGEwJDTjELMAkGA1UE +CBMCUE4xCzAJBgNVBAcTAkNOMQswCQYDVQQKEwJPTjELMAkGA1UECxMCVU4xFDAS +BgNVBAMTC0hlcm9uZyBZYW5nggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEE +BQADQQA/ugzBrjjK9jcWnDVfGHlk3icNRq0oV7Ri32z/+HQX67aRfgZu7KWdI+Ju +Wm7DCfrPNGVwFWUQOmsPue9rZBgO +-----END CERTIFICATE----- +`}} + pl.Provision(caddy.Context{Context: context.Background()}) + + out, err := pl.LoadLeafCertificates() + if err != nil { + t.Errorf("Leaf certs pem loading test failed: %v", err) + } + if len(out) != 1 { + t.Errorf("Error loading leaf cert in memory struct") + return + } + pemBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: out[0].Raw}) + + pemFileBytes, err := os.ReadFile("../../caddytest/leafcert.pem") + if err != nil { + t.Errorf("Unable to read the example certificate from the file") + } + + // Remove /r because windows. + pemFileString := strings.ReplaceAll(string(pemFileBytes), "\r\n", "\n") + + if string(pemBytes) != pemFileString { + t.Errorf("Leaf Certificate Folder Loader: Failed to load the correct certificate") + } +} diff --git a/modules/caddytls/leafstorageloader.go b/modules/caddytls/leafstorageloader.go new file mode 100644 index 00000000000..0215c8af2a6 --- /dev/null +++ b/modules/caddytls/leafstorageloader.go @@ -0,0 +1,129 @@ +// 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 caddytls + +import ( + "crypto/x509" + "encoding/json" + "encoding/pem" + "fmt" + + "github.com/caddyserver/certmagic" + + "github.com/caddyserver/caddy/v2" +) + +func init() { + caddy.RegisterModule(LeafStorageLoader{}) +} + +// LeafStorageLoader loads leaf certificates from the +// globally configured storage module. +type LeafStorageLoader struct { + // A list of certificate file names to be loaded from storage. + Certificates []string `json:"certificates,omitempty"` + + // The storage module where the trusted leaf certificates are stored. Absent + // explicit storage implies the use of Caddy default storage. + StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"` + + // Reference to the globally configured storage module. + storage certmagic.Storage + + ctx caddy.Context +} + +// CaddyModule returns the Caddy module information. +func (LeafStorageLoader) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.leaf_cert_loader.storage", + New: func() caddy.Module { return new(LeafStorageLoader) }, + } +} + +// Provision loads the storage module for sl. +func (sl *LeafStorageLoader) Provision(ctx caddy.Context) error { + if sl.StorageRaw != nil { + val, err := ctx.LoadModule(sl, "StorageRaw") + if err != nil { + return fmt.Errorf("loading storage module: %v", err) + } + cmStorage, err := val.(caddy.StorageConverter).CertMagicStorage() + if err != nil { + return fmt.Errorf("creating storage configuration: %v", err) + } + sl.storage = cmStorage + } + if sl.storage == nil { + sl.storage = ctx.Storage() + } + sl.ctx = ctx + + repl, ok := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + if !ok { + repl = caddy.NewReplacer() + } + for k, path := range sl.Certificates { + sl.Certificates[k] = repl.ReplaceKnown(path, "") + } + return nil +} + +// LoadLeafCertificates returns the certificates to be loaded by sl. +func (sl LeafStorageLoader) LoadLeafCertificates() ([]*x509.Certificate, error) { + certificates := make([]*x509.Certificate, 0, len(sl.Certificates)) + for _, path := range sl.Certificates { + certData, err := sl.storage.Load(sl.ctx, path) + if err != nil { + return nil, err + } + + ders, err := convertPEMToDER(certData) + if err != nil { + return nil, err + } + certs, err := x509.ParseCertificates(ders) + if err != nil { + return nil, err + } + certificates = append(certificates, certs...) + } + return certificates, nil +} + +func convertPEMToDER(pemData []byte) ([]byte, error) { + var ders []byte + // while block is not nil, we have more certificates in the file + for block, rest := pem.Decode(pemData); block != nil; block, rest = pem.Decode(rest) { + if block.Type != "CERTIFICATE" { + return nil, fmt.Errorf("no CERTIFICATE pem block found in the given pem data") + } + ders = append( + ders, + block.Bytes..., + ) + } + // if we decoded nothing, return an error + if len(ders) == 0 { + return nil, fmt.Errorf("no CERTIFICATE pem block found in the given pem data") + } + return ders, nil +} + +// Interface guard +var ( + _ LeafCertificateLoader = (*LeafStorageLoader)(nil) + _ caddy.Provisioner = (*LeafStorageLoader)(nil) +) diff --git a/modules/caddytls/matchers.go b/modules/caddytls/matchers.go new file mode 100644 index 00000000000..dfbec94cc7a --- /dev/null +++ b/modules/caddytls/matchers.go @@ -0,0 +1,512 @@ +// 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 caddytls + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "net/netip" + "regexp" + "slices" + "strconv" + "strings" + + "github.com/caddyserver/certmagic" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/internal" +) + +func init() { + caddy.RegisterModule(MatchServerName{}) + caddy.RegisterModule(MatchServerNameRE{}) + caddy.RegisterModule(MatchRemoteIP{}) + caddy.RegisterModule(MatchLocalIP{}) +} + +// MatchServerName matches based on SNI. Names in +// this list may use left-most-label wildcards, +// similar to wildcard certificates. +type MatchServerName []string + +// CaddyModule returns the Caddy module information. +func (MatchServerName) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.handshake_match.sni", + New: func() caddy.Module { return new(MatchServerName) }, + } +} + +// Match matches hello based on SNI. +func (m MatchServerName) Match(hello *tls.ClientHelloInfo) bool { + var repl *caddy.Replacer + // caddytls.TestServerNameMatcher calls this function without any context + if ctx := hello.Context(); ctx != nil { + // In some situations the existing context may have no replacer + if replAny := ctx.Value(caddy.ReplacerCtxKey); replAny != nil { + repl = replAny.(*caddy.Replacer) + } + } + + if repl == nil { + repl = caddy.NewReplacer() + } + + for _, name := range m { + rs := repl.ReplaceAll(name, "") + if certmagic.MatchWildcard(hello.ServerName, rs) { + return true + } + } + return false +} + +// UnmarshalCaddyfile sets up the MatchServerName from Caddyfile tokens. Syntax: +// +// sni +func (m *MatchServerName) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + for d.Next() { + wrapper := d.Val() + + // At least one same-line option must be provided + if d.CountRemainingArgs() == 0 { + return d.ArgErr() + } + + *m = append(*m, d.RemainingArgs()...) + + // No blocks are supported + if d.NextBlock(d.Nesting()) { + return d.Errf("malformed TLS handshake matcher '%s': blocks are not supported", wrapper) + } + } + + return nil +} + +// MatchRegexp is an embeddable type for matching +// using regular expressions. It adds placeholders +// to the request's replacer. In fact, it is a copy of +// caddyhttp.MatchRegexp with a local replacer prefix +// and placeholders support in a regular expression pattern. +type MatchRegexp struct { + // A unique name for this regular expression. Optional, + // but useful to prevent overwriting captures from other + // regexp matchers. + Name string `json:"name,omitempty"` + + // The regular expression to evaluate, in RE2 syntax, + // which is the same general syntax used by Go, Perl, + // and Python. For details, see + // [Go's regexp package](https://golang.org/pkg/regexp/). + // Captures are accessible via placeholders. Unnamed + // capture groups are exposed as their numeric, 1-based + // index, while named capture groups are available by + // the capture group name. + Pattern string `json:"pattern"` + + compiled *regexp.Regexp +} + +// Provision compiles the regular expression which may include placeholders. +func (mre *MatchRegexp) Provision(caddy.Context) error { + repl := caddy.NewReplacer() + re, err := regexp.Compile(repl.ReplaceAll(mre.Pattern, "")) + if err != nil { + return fmt.Errorf("compiling matcher regexp %s: %v", mre.Pattern, err) + } + mre.compiled = re + return nil +} + +// Validate ensures mre is set up correctly. +func (mre *MatchRegexp) Validate() error { + if mre.Name != "" && !wordRE.MatchString(mre.Name) { + return fmt.Errorf("invalid regexp name (must contain only word characters): %s", mre.Name) + } + return nil +} + +// Match returns true if input matches the compiled regular +// expression in m. It sets values on the replacer repl +// associated with capture groups, using the given scope +// (namespace). +func (mre *MatchRegexp) Match(input string, repl *caddy.Replacer) bool { + matches := mre.compiled.FindStringSubmatch(input) + if matches == nil { + return false + } + + // save all capture groups, first by index + for i, match := range matches { + keySuffix := "." + strconv.Itoa(i) + if mre.Name != "" { + repl.Set(regexpPlaceholderPrefix+"."+mre.Name+keySuffix, match) + } + repl.Set(regexpPlaceholderPrefix+keySuffix, match) + } + + // then by name + for i, name := range mre.compiled.SubexpNames() { + // skip the first element (the full match), and empty names + if i == 0 || name == "" { + continue + } + + keySuffix := "." + name + if mre.Name != "" { + repl.Set(regexpPlaceholderPrefix+"."+mre.Name+keySuffix, matches[i]) + } + repl.Set(regexpPlaceholderPrefix+keySuffix, matches[i]) + } + + return true +} + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. +func (mre *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + // iterate to merge multiple matchers into one + for d.Next() { + // If this is the second iteration of the loop + // then there's more than one *_regexp matcher, + // and we would end up overwriting the old one + if mre.Pattern != "" { + return d.Err("regular expression can only be used once per named matcher") + } + + args := d.RemainingArgs() + switch len(args) { + case 1: + mre.Pattern = args[0] + case 2: + mre.Name = args[0] + mre.Pattern = args[1] + default: + return d.ArgErr() + } + + // Default to the named matcher's name, if no regexp name is provided. + // Note: it requires d.SetContext(caddyfile.MatcherNameCtxKey, value) + // called before this unmarshalling, otherwise it wouldn't work. + if mre.Name == "" { + mre.Name = d.GetContextString(caddyfile.MatcherNameCtxKey) + } + + if d.NextBlock(0) { + return d.Err("malformed regexp matcher: blocks are not supported") + } + } + return nil +} + +// MatchServerNameRE matches based on SNI using a regular expression. +type MatchServerNameRE struct{ MatchRegexp } + +// CaddyModule returns the Caddy module information. +func (MatchServerNameRE) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.handshake_match.sni_regexp", + New: func() caddy.Module { return new(MatchServerNameRE) }, + } +} + +// Match matches hello based on SNI using a regular expression. +func (m MatchServerNameRE) Match(hello *tls.ClientHelloInfo) bool { + // Note: caddytls.TestServerNameMatcher calls this function without any context + ctx := hello.Context() + if ctx == nil { + // layer4.Connection implements GetContext() to pass its context here, + // since hello.Context() returns nil + if mayHaveContext, ok := hello.Conn.(interface{ GetContext() context.Context }); ok { + ctx = mayHaveContext.GetContext() + } + } + + var repl *caddy.Replacer + if ctx != nil { + // In some situations the existing context may have no replacer + if replAny := ctx.Value(caddy.ReplacerCtxKey); replAny != nil { + repl = replAny.(*caddy.Replacer) + } + } + + if repl == nil { + repl = caddy.NewReplacer() + } + + return m.MatchRegexp.Match(hello.ServerName, repl) +} + +// MatchRemoteIP matches based on the remote IP of the +// connection. Specific IPs or CIDR ranges can be specified. +// +// Note that IPs can sometimes be spoofed, so do not rely +// on this as a replacement for actual authentication. +type MatchRemoteIP struct { + // The IPs or CIDR ranges to match. + Ranges []string `json:"ranges,omitempty"` + + // The IPs or CIDR ranges to *NOT* match. + NotRanges []string `json:"not_ranges,omitempty"` + + cidrs []netip.Prefix + notCidrs []netip.Prefix + logger *zap.Logger +} + +// CaddyModule returns the Caddy module information. +func (MatchRemoteIP) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.handshake_match.remote_ip", + New: func() caddy.Module { return new(MatchRemoteIP) }, + } +} + +// Provision parses m's IP ranges, either from IP or CIDR expressions. +func (m *MatchRemoteIP) Provision(ctx caddy.Context) error { + repl := caddy.NewReplacer() + m.logger = ctx.Logger() + for _, str := range m.Ranges { + rs := repl.ReplaceAll(str, "") + cidrs, err := m.parseIPRange(rs) + if err != nil { + return err + } + m.cidrs = append(m.cidrs, cidrs...) + } + for _, str := range m.NotRanges { + rs := repl.ReplaceAll(str, "") + cidrs, err := m.parseIPRange(rs) + if err != nil { + return err + } + m.notCidrs = append(m.notCidrs, cidrs...) + } + return nil +} + +// Match matches hello based on the connection's remote IP. +func (m MatchRemoteIP) Match(hello *tls.ClientHelloInfo) bool { + remoteAddr := hello.Conn.RemoteAddr().String() + ipStr, _, err := net.SplitHostPort(remoteAddr) + if err != nil { + ipStr = remoteAddr // weird; maybe no port? + } + ipAddr, err := netip.ParseAddr(ipStr) + if err != nil { + if c := m.logger.Check(zapcore.ErrorLevel, "invalid client IP address"); c != nil { + c.Write(zap.String("ip", ipStr)) + } + return false + } + return (len(m.cidrs) == 0 || m.matches(ipAddr, m.cidrs)) && + (len(m.notCidrs) == 0 || !m.matches(ipAddr, m.notCidrs)) +} + +func (MatchRemoteIP) parseIPRange(str string) ([]netip.Prefix, error) { + var cidrs []netip.Prefix + if strings.Contains(str, "/") { + ipNet, err := netip.ParsePrefix(str) + if err != nil { + return nil, fmt.Errorf("parsing CIDR expression: %v", err) + } + cidrs = append(cidrs, ipNet) + } else { + ipAddr, err := netip.ParseAddr(str) + if err != nil { + return nil, fmt.Errorf("invalid IP address: '%s': %v", str, err) + } + ip := netip.PrefixFrom(ipAddr, ipAddr.BitLen()) + cidrs = append(cidrs, ip) + } + return cidrs, nil +} + +func (MatchRemoteIP) matches(ip netip.Addr, ranges []netip.Prefix) bool { + return slices.ContainsFunc(ranges, func(prefix netip.Prefix) bool { + return prefix.Contains(ip) + }) +} + +// UnmarshalCaddyfile sets up the MatchRemoteIP from Caddyfile tokens. Syntax: +// +// remote_ip +// +// Note: IPs and CIDRs prefixed with ! symbol are treated as not_ranges +func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + for d.Next() { + wrapper := d.Val() + + // At least one same-line option must be provided + if d.CountRemainingArgs() == 0 { + return d.ArgErr() + } + + for d.NextArg() { + val := d.Val() + var exclamation bool + if len(val) > 1 && val[0] == '!' { + exclamation, val = true, val[1:] + } + ranges := []string{val} + if val == "private_ranges" { + ranges = internal.PrivateRangesCIDR() + } + if exclamation { + m.NotRanges = append(m.NotRanges, ranges...) + } else { + m.Ranges = append(m.Ranges, ranges...) + } + } + + // No blocks are supported + if d.NextBlock(d.Nesting()) { + return d.Errf("malformed TLS handshake matcher '%s': blocks are not supported", wrapper) + } + } + + return nil +} + +// MatchLocalIP matches based on the IP address of the interface +// receiving the connection. Specific IPs or CIDR ranges can be specified. +type MatchLocalIP struct { + // The IPs or CIDR ranges to match. + Ranges []string `json:"ranges,omitempty"` + + cidrs []netip.Prefix + logger *zap.Logger +} + +// CaddyModule returns the Caddy module information. +func (MatchLocalIP) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.handshake_match.local_ip", + New: func() caddy.Module { return new(MatchLocalIP) }, + } +} + +// Provision parses m's IP ranges, either from IP or CIDR expressions. +func (m *MatchLocalIP) Provision(ctx caddy.Context) error { + repl := caddy.NewReplacer() + m.logger = ctx.Logger() + for _, str := range m.Ranges { + rs := repl.ReplaceAll(str, "") + cidrs, err := m.parseIPRange(rs) + if err != nil { + return err + } + m.cidrs = append(m.cidrs, cidrs...) + } + return nil +} + +// Match matches hello based on the connection's remote IP. +func (m MatchLocalIP) Match(hello *tls.ClientHelloInfo) bool { + localAddr := hello.Conn.LocalAddr().String() + ipStr, _, err := net.SplitHostPort(localAddr) + if err != nil { + ipStr = localAddr // weird; maybe no port? + } + ipAddr, err := netip.ParseAddr(ipStr) + if err != nil { + if c := m.logger.Check(zapcore.ErrorLevel, "invalid local IP address"); c != nil { + c.Write(zap.String("ip", ipStr)) + } + return false + } + return (len(m.cidrs) == 0 || m.matches(ipAddr, m.cidrs)) +} + +func (MatchLocalIP) parseIPRange(str string) ([]netip.Prefix, error) { + var cidrs []netip.Prefix + if strings.Contains(str, "/") { + ipNet, err := netip.ParsePrefix(str) + if err != nil { + return nil, fmt.Errorf("parsing CIDR expression: %v", err) + } + cidrs = append(cidrs, ipNet) + } else { + ipAddr, err := netip.ParseAddr(str) + if err != nil { + return nil, fmt.Errorf("invalid IP address: '%s': %v", str, err) + } + ip := netip.PrefixFrom(ipAddr, ipAddr.BitLen()) + cidrs = append(cidrs, ip) + } + return cidrs, nil +} + +func (MatchLocalIP) matches(ip netip.Addr, ranges []netip.Prefix) bool { + return slices.ContainsFunc(ranges, func(prefix netip.Prefix) bool { + return prefix.Contains(ip) + }) +} + +// UnmarshalCaddyfile sets up the MatchLocalIP from Caddyfile tokens. Syntax: +// +// local_ip +func (m *MatchLocalIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + for d.Next() { + wrapper := d.Val() + + // At least one same-line option must be provided + if d.CountRemainingArgs() == 0 { + return d.ArgErr() + } + + for d.NextArg() { + val := d.Val() + if val == "private_ranges" { + m.Ranges = append(m.Ranges, internal.PrivateRangesCIDR()...) + continue + } + m.Ranges = append(m.Ranges, val) + } + + // No blocks are supported + if d.NextBlock(d.Nesting()) { + return d.Errf("malformed TLS handshake matcher '%s': blocks are not supported", wrapper) + } + } + + return nil +} + +// Interface guards +var ( + _ ConnectionMatcher = (*MatchLocalIP)(nil) + _ ConnectionMatcher = (*MatchRemoteIP)(nil) + _ ConnectionMatcher = (*MatchServerName)(nil) + _ ConnectionMatcher = (*MatchServerNameRE)(nil) + + _ caddy.Provisioner = (*MatchLocalIP)(nil) + _ caddy.Provisioner = (*MatchRemoteIP)(nil) + _ caddy.Provisioner = (*MatchServerNameRE)(nil) + + _ caddyfile.Unmarshaler = (*MatchLocalIP)(nil) + _ caddyfile.Unmarshaler = (*MatchRemoteIP)(nil) + _ caddyfile.Unmarshaler = (*MatchServerName)(nil) + _ caddyfile.Unmarshaler = (*MatchServerNameRE)(nil) +) + +var wordRE = regexp.MustCompile(`\w+`) + +const regexpPlaceholderPrefix = "tls.regexp" diff --git a/modules/caddytls/matchers_test.go b/modules/caddytls/matchers_test.go new file mode 100644 index 00000000000..824f7207098 --- /dev/null +++ b/modules/caddytls/matchers_test.go @@ -0,0 +1,296 @@ +// 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 caddytls + +import ( + "context" + "crypto/tls" + "net" + "testing" + + "github.com/caddyserver/caddy/v2" +) + +func TestServerNameMatcher(t *testing.T) { + for i, tc := range []struct { + names []string + input string + expect bool + }{ + { + names: []string{"example.com"}, + input: "example.com", + expect: true, + }, + { + names: []string{"example.com"}, + input: "foo.com", + expect: false, + }, + { + names: []string{"example.com"}, + input: "", + expect: false, + }, + { + names: []string{}, + input: "", + expect: false, + }, + { + names: []string{"foo", "example.com"}, + input: "example.com", + expect: true, + }, + { + names: []string{"foo", "example.com"}, + input: "sub.example.com", + expect: false, + }, + { + names: []string{"foo", "example.com"}, + input: "foo.com", + expect: false, + }, + { + names: []string{"*.example.com"}, + input: "example.com", + expect: false, + }, + { + names: []string{"*.example.com"}, + input: "sub.example.com", + expect: true, + }, + { + names: []string{"*.example.com", "*.sub.example.com"}, + input: "sub2.sub.example.com", + expect: true, + }, + } { + chi := &tls.ClientHelloInfo{ServerName: tc.input} + actual := MatchServerName(tc.names).Match(chi) + if actual != tc.expect { + t.Errorf("Test %d: Expected %t but got %t (input=%s match=%v)", + i, tc.expect, actual, tc.input, tc.names) + } + } +} + +func TestServerNameREMatcher(t *testing.T) { + for i, tc := range []struct { + pattern string + input string + expect bool + }{ + { + pattern: "^example\\.(com|net)$", + input: "example.com", + expect: true, + }, + { + pattern: "^example\\.(com|net)$", + input: "foo.com", + expect: false, + }, + { + pattern: "^example\\.(com|net)$", + input: "", + expect: false, + }, + { + pattern: "", + input: "", + expect: true, + }, + { + pattern: "^example\\.(com|net)$", + input: "foo.example.com", + expect: false, + }, + } { + chi := &tls.ClientHelloInfo{ServerName: tc.input} + mre := MatchServerNameRE{MatchRegexp{Pattern: tc.pattern}} + ctx, _ := caddy.NewContext(caddy.Context{Context: context.Background()}) + if mre.Provision(ctx) != nil { + t.Errorf("Test %d: Failed to provision a regexp matcher (pattern=%v)", i, tc.pattern) + } + actual := mre.Match(chi) + if actual != tc.expect { + t.Errorf("Test %d: Expected %t but got %t (input=%s match=%v)", + i, tc.expect, actual, tc.input, tc.pattern) + } + } +} + +func TestRemoteIPMatcher(t *testing.T) { + ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + + for i, tc := range []struct { + ranges []string + notRanges []string + input string + expect bool + }{ + { + ranges: []string{"127.0.0.1"}, + input: "127.0.0.1:12345", + expect: true, + }, + { + ranges: []string{"127.0.0.1"}, + input: "127.0.0.2:12345", + expect: false, + }, + { + ranges: []string{"127.0.0.1/16"}, + input: "127.0.1.23:12345", + expect: true, + }, + { + ranges: []string{"127.0.0.1", "192.168.1.105"}, + input: "192.168.1.105:12345", + expect: true, + }, + { + notRanges: []string{"127.0.0.1"}, + input: "127.0.0.1:12345", + expect: false, + }, + { + notRanges: []string{"127.0.0.2"}, + input: "127.0.0.1:12345", + expect: true, + }, + { + ranges: []string{"127.0.0.1"}, + notRanges: []string{"127.0.0.2"}, + input: "127.0.0.1:12345", + expect: true, + }, + { + ranges: []string{"127.0.0.2"}, + notRanges: []string{"127.0.0.2"}, + input: "127.0.0.2:12345", + expect: false, + }, + { + ranges: []string{"127.0.0.2"}, + notRanges: []string{"127.0.0.2"}, + input: "127.0.0.3:12345", + expect: false, + }, + } { + matcher := MatchRemoteIP{Ranges: tc.ranges, NotRanges: tc.notRanges} + err := matcher.Provision(ctx) + if err != nil { + t.Fatalf("Test %d: Provision failed: %v", i, err) + } + + addr := testAddr(tc.input) + chi := &tls.ClientHelloInfo{Conn: testConn{addr: addr}} + + actual := matcher.Match(chi) + if actual != tc.expect { + t.Errorf("Test %d: Expected %t but got %t (input=%s ranges=%v notRanges=%v)", + i, tc.expect, actual, tc.input, tc.ranges, tc.notRanges) + } + } +} + +func TestLocalIPMatcher(t *testing.T) { + ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + + for i, tc := range []struct { + ranges []string + input string + expect bool + }{ + { + ranges: []string{"127.0.0.1"}, + input: "127.0.0.1:12345", + expect: true, + }, + { + ranges: []string{"127.0.0.1"}, + input: "127.0.0.2:12345", + expect: false, + }, + { + ranges: []string{"127.0.0.1/16"}, + input: "127.0.1.23:12345", + expect: true, + }, + { + ranges: []string{"127.0.0.1", "192.168.1.105"}, + input: "192.168.1.105:12345", + expect: true, + }, + { + input: "127.0.0.1:12345", + expect: true, + }, + { + ranges: []string{"127.0.0.1"}, + input: "127.0.0.1:12345", + expect: true, + }, + { + ranges: []string{"127.0.0.2"}, + input: "127.0.0.3:12345", + expect: false, + }, + { + ranges: []string{"127.0.0.2"}, + input: "127.0.0.2", + expect: true, + }, + { + ranges: []string{"127.0.0.2"}, + input: "127.0.0.300", + expect: false, + }, + } { + matcher := MatchLocalIP{Ranges: tc.ranges} + err := matcher.Provision(ctx) + if err != nil { + t.Fatalf("Test %d: Provision failed: %v", i, err) + } + + addr := testAddr(tc.input) + chi := &tls.ClientHelloInfo{Conn: testConn{addr: addr}} + + actual := matcher.Match(chi) + if actual != tc.expect { + t.Errorf("Test %d: Expected %t but got %t (input=%s ranges=%v)", + i, tc.expect, actual, tc.input, tc.ranges) + } + } +} + +type testConn struct { + *net.TCPConn + addr testAddr +} + +func (tc testConn) RemoteAddr() net.Addr { return tc.addr } +func (tc testConn) LocalAddr() net.Addr { return tc.addr } + +type testAddr string + +func (testAddr) Network() string { return "tcp" } +func (ta testAddr) String() string { return string(ta) } diff --git a/modules/caddytls/ondemand.go b/modules/caddytls/ondemand.go new file mode 100644 index 00000000000..0970234cecd --- /dev/null +++ b/modules/caddytls/ondemand.go @@ -0,0 +1,190 @@ +// 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 caddytls + +import ( + "context" + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "time" + + "github.com/caddyserver/certmagic" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func init() { + caddy.RegisterModule(PermissionByHTTP{}) +} + +// OnDemandConfig configures on-demand TLS, for obtaining +// needed certificates at handshake-time. Because this +// feature can easily be abused, Caddy must ask permission +// to your application whether a particular domain is allowed +// to have a certificate issued for it. +type OnDemandConfig struct { + // Deprecated. WILL BE REMOVED SOON. Use 'permission' instead with the `http` module. + Ask string `json:"ask,omitempty"` + + // REQUIRED. A module that will determine whether a + // certificate is allowed to be loaded from storage + // or obtained from an issuer on demand. + PermissionRaw json.RawMessage `json:"permission,omitempty" caddy:"namespace=tls.permission inline_key=module"` + permission OnDemandPermission +} + +// OnDemandPermission is a type that can give permission for +// whether a certificate should be allowed to be obtained or +// loaded from storage on-demand. +// EXPERIMENTAL: This API is experimental and subject to change. +type OnDemandPermission interface { + // CertificateAllowed returns nil if a certificate for the given + // name is allowed to be either obtained from an issuer or loaded + // from storage on-demand. + // + // The context passed in has the associated *tls.ClientHelloInfo + // value available at the certmagic.ClientHelloInfoCtxKey key. + // + // In the worst case, this function may be called as frequently + // as every TLS handshake, so it should return as quick as possible + // to reduce latency. In the normal case, this function is only + // called when a certificate is needed that is not already loaded + // into memory ready to serve. + CertificateAllowed(ctx context.Context, name string) error +} + +// PermissionByHTTP determines permission for a TLS certificate by +// making a request to an HTTP endpoint. +type PermissionByHTTP struct { + // The endpoint to access. It should be a full URL. + // A query string parameter "domain" will be added to it, + // containing the domain (or IP) for the desired certificate, + // like so: `?domain=example.com`. Generally, this endpoint + // is not exposed publicly to avoid a minor information leak + // (which domains are serviced by your application). + // + // The endpoint must return a 200 OK status if a certificate + // is allowed; anything else will cause it to be denied. + // Redirects are not followed. + Endpoint string `json:"endpoint"` + + logger *zap.Logger + replacer *caddy.Replacer +} + +// CaddyModule returns the Caddy module information. +func (PermissionByHTTP) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.permission.http", + New: func() caddy.Module { return new(PermissionByHTTP) }, + } +} + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. +func (p *PermissionByHTTP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + if !d.Next() { + return nil + } + if !d.AllArgs(&p.Endpoint) { + return d.ArgErr() + } + return nil +} + +func (p *PermissionByHTTP) Provision(ctx caddy.Context) error { + p.logger = ctx.Logger() + p.replacer = caddy.NewReplacer() + return nil +} + +func (p PermissionByHTTP) CertificateAllowed(ctx context.Context, name string) error { + // run replacer on endpoint URL (for environment variables) -- return errors to prevent surprises (#5036) + askEndpoint, err := p.replacer.ReplaceOrErr(p.Endpoint, true, true) + if err != nil { + return fmt.Errorf("preparing 'ask' endpoint: %v", err) + } + + askURL, err := url.Parse(askEndpoint) + if err != nil { + return fmt.Errorf("parsing ask URL: %v", err) + } + qs := askURL.Query() + qs.Set("domain", name) + askURL.RawQuery = qs.Encode() + askURLString := askURL.String() + + var remote string + if chi, ok := ctx.Value(certmagic.ClientHelloInfoCtxKey).(*tls.ClientHelloInfo); ok && chi != nil { + remote = chi.Conn.RemoteAddr().String() + } + + if c := p.logger.Check(zapcore.DebugLevel, "asking permission endpoint"); c != nil { + c.Write( + zap.String("remote", remote), + zap.String("domain", name), + zap.String("url", askURLString), + ) + } + + resp, err := onDemandAskClient.Get(askURLString) + if err != nil { + return fmt.Errorf("checking %v to determine if certificate for hostname '%s' should be allowed: %v", + askEndpoint, name, err) + } + resp.Body.Close() + + if c := p.logger.Check(zapcore.DebugLevel, "response from permission endpoint"); c != nil { + c.Write( + zap.String("remote", remote), + zap.String("domain", name), + zap.String("url", askURLString), + zap.Int("status", resp.StatusCode), + ) + } + + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return fmt.Errorf("%s: %w %s - non-2xx status code %d", name, ErrPermissionDenied, askEndpoint, resp.StatusCode) + } + + return nil +} + +// ErrPermissionDenied is an error that should be wrapped or returned when the +// configured permission module does not allow a certificate to be issued, +// to distinguish that from other errors such as connection failure. +var ErrPermissionDenied = errors.New("certificate not allowed by permission module") + +// These perpetual values are used for on-demand TLS. +var ( + onDemandAskClient = &http.Client{ + Timeout: 10 * time.Second, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return fmt.Errorf("following http redirects is not allowed") + }, + } +) + +// Interface guards +var ( + _ OnDemandPermission = (*PermissionByHTTP)(nil) + _ caddy.Provisioner = (*PermissionByHTTP)(nil) +) diff --git a/modules/caddytls/pemloader.go b/modules/caddytls/pemloader.go new file mode 100644 index 00000000000..9c5ec17c936 --- /dev/null +++ b/modules/caddytls/pemloader.go @@ -0,0 +1,94 @@ +// 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 caddytls + +import ( + "crypto/tls" + "fmt" + + "github.com/caddyserver/caddy/v2" +) + +func init() { + caddy.RegisterModule(PEMLoader{}) +} + +// PEMLoader loads certificates and their associated keys by +// decoding their PEM blocks directly. This has the advantage +// of not needing to store them on disk at all. +type PEMLoader []CertKeyPEMPair + +// Provision implements caddy.Provisioner. +func (pl PEMLoader) Provision(ctx caddy.Context) error { + repl, ok := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + if !ok { + repl = caddy.NewReplacer() + } + for k, pair := range pl { + for i, tag := range pair.Tags { + pair.Tags[i] = repl.ReplaceKnown(tag, "") + } + pl[k] = CertKeyPEMPair{ + CertificatePEM: repl.ReplaceKnown(pair.CertificatePEM, ""), + KeyPEM: repl.ReplaceKnown(pair.KeyPEM, ""), + Tags: pair.Tags, + } + } + return nil +} + +// CaddyModule returns the Caddy module information. +func (PEMLoader) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.certificates.load_pem", + New: func() caddy.Module { return new(PEMLoader) }, + } +} + +// CertKeyPEMPair pairs certificate and key PEM blocks. +type CertKeyPEMPair struct { + // The certificate (public key) in PEM format. + CertificatePEM string `json:"certificate"` + + // The private key in PEM format. + KeyPEM string `json:"key"` + + // Arbitrary values to associate with this certificate. + // Can be useful when you want to select a particular + // certificate when there may be multiple valid candidates. + Tags []string `json:"tags,omitempty"` +} + +// LoadCertificates returns the certificates contained in pl. +func (pl PEMLoader) LoadCertificates() ([]Certificate, error) { + certs := make([]Certificate, 0, len(pl)) + for i, pair := range pl { + cert, err := tls.X509KeyPair([]byte(pair.CertificatePEM), []byte(pair.KeyPEM)) + if err != nil { + return nil, fmt.Errorf("PEM pair %d: %v", i, err) + } + certs = append(certs, Certificate{ + Certificate: cert, + Tags: pair.Tags, + }) + } + return certs, nil +} + +// Interface guard +var ( + _ CertificateLoader = (PEMLoader)(nil) + _ caddy.Provisioner = (PEMLoader)(nil) +) diff --git a/modules/caddytls/sessiontickets.go b/modules/caddytls/sessiontickets.go new file mode 100644 index 00000000000..bfc5628ac2d --- /dev/null +++ b/modules/caddytls/sessiontickets.go @@ -0,0 +1,246 @@ +// 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 caddytls + +import ( + "crypto/rand" + "crypto/tls" + "encoding/json" + "fmt" + "io" + "log" + "runtime/debug" + "sync" + "time" + + "github.com/caddyserver/caddy/v2" +) + +// SessionTicketService configures and manages TLS session tickets. +type SessionTicketService struct { + // KeySource is the method by which Caddy produces or obtains + // TLS session ticket keys (STEKs). By default, Caddy generates + // them internally using a secure pseudorandom source. + KeySource json.RawMessage `json:"key_source,omitempty" caddy:"namespace=tls.stek inline_key=provider"` + + // How often Caddy rotates STEKs. Default: 12h. + RotationInterval caddy.Duration `json:"rotation_interval,omitempty"` + + // The maximum number of keys to keep in rotation. Default: 4. + MaxKeys int `json:"max_keys,omitempty"` + + // Disables STEK rotation. + DisableRotation bool `json:"disable_rotation,omitempty"` + + // Disables TLS session resumption by tickets. + Disabled bool `json:"disabled,omitempty"` + + keySource STEKProvider + configs map[*tls.Config]struct{} + stopChan chan struct{} + currentKeys [][32]byte + mu *sync.Mutex +} + +func (s *SessionTicketService) provision(ctx caddy.Context) error { + s.configs = make(map[*tls.Config]struct{}) + s.mu = new(sync.Mutex) + + // establish sane defaults + if s.RotationInterval == 0 { + s.RotationInterval = caddy.Duration(defaultSTEKRotationInterval) + } + if s.MaxKeys <= 0 { + s.MaxKeys = defaultMaxSTEKs + } + if s.KeySource == nil { + s.KeySource = json.RawMessage(`{"provider":"standard"}`) + } + + // load the STEK module, which will provide keys + val, err := ctx.LoadModule(s, "KeySource") + if err != nil { + return fmt.Errorf("loading TLS session ticket ephemeral keys provider module: %s", err) + } + s.keySource = val.(STEKProvider) + + // if session tickets or just rotation are + // disabled, no need to start service + if s.Disabled || s.DisableRotation { + return nil + } + + // start the STEK module; this ensures we have + // a starting key before any config needs one + return s.start() +} + +// start loads the starting STEKs and spawns a goroutine +// which loops to rotate the STEKs, which continues until +// stop() is called. If start() was already called, this +// is a no-op. +func (s *SessionTicketService) start() error { + if s.stopChan != nil { + return nil + } + s.stopChan = make(chan struct{}) + + // initializing the key source gives us our + // initial key(s) to start with; if successful, + // we need to be sure to call Next() so that + // the key source can know when it is done + initialKeys, err := s.keySource.Initialize(s) + if err != nil { + return fmt.Errorf("setting STEK module configuration: %v", err) + } + + s.mu.Lock() + s.currentKeys = initialKeys + s.mu.Unlock() + + // keep the keys rotated + go s.stayUpdated() + + return nil +} + +// stayUpdated is a blocking function which rotates +// the keys whenever new ones are sent. It reads +// from keysChan until s.stop() is called. +func (s *SessionTicketService) stayUpdated() { + defer func() { + if err := recover(); err != nil { + log.Printf("[PANIC] session ticket service: %v\n%s", err, debug.Stack()) + } + }() + + // this call is essential when Initialize() + // returns without error, because the stop + // channel is the only way the key source + // will know when to clean up + keysChan := s.keySource.Next(s.stopChan) + + for { + select { + case newKeys := <-keysChan: + s.mu.Lock() + s.currentKeys = newKeys + configs := s.configs + s.mu.Unlock() + for cfg := range configs { + cfg.SetSessionTicketKeys(newKeys) + } + case <-s.stopChan: + return + } + } +} + +// stop terminates the key rotation goroutine. +func (s *SessionTicketService) stop() { + if s.stopChan != nil { + close(s.stopChan) + } +} + +// register sets the session ticket keys on cfg +// and keeps them updated. Any values registered +// must be unregistered, or they will not be +// garbage-collected. s.start() must have been +// called first. If session tickets are disabled +// or if ticket key rotation is disabled, this +// function is a no-op. +func (s *SessionTicketService) register(cfg *tls.Config) { + if s.Disabled || s.DisableRotation { + return + } + s.mu.Lock() + cfg.SetSessionTicketKeys(s.currentKeys) + s.configs[cfg] = struct{}{} + s.mu.Unlock() +} + +// unregister stops session key management on cfg and +// removes the internal stored reference to cfg. If +// session tickets are disabled or if ticket key rotation +// is disabled, this function is a no-op. +func (s *SessionTicketService) unregister(cfg *tls.Config) { + if s.Disabled || s.DisableRotation { + return + } + s.mu.Lock() + delete(s.configs, cfg) + s.mu.Unlock() +} + +// RotateSTEKs rotates the keys in keys by producing a new key and eliding +// the oldest one. The new slice of keys is returned. +func (s SessionTicketService) RotateSTEKs(keys [][32]byte) ([][32]byte, error) { + // produce a new key + newKey, err := s.generateSTEK() + if err != nil { + return nil, fmt.Errorf("generating STEK: %v", err) + } + + // we need to prepend this new key to the list of + // keys so that it is preferred, but we need to be + // careful that we do not grow the slice larger + // than MaxKeys, otherwise we'll be storing one + // more key in memory than we expect; so be sure + // that the slice does not grow beyond the limit + // even for a brief period of time, since there's + // no guarantee when that extra allocation will + // be overwritten; this is why we first trim the + // length to one less the max, THEN prepend the + // new key + if len(keys) >= s.MaxKeys { + keys[len(keys)-1] = [32]byte{} // zero-out memory of oldest key + keys = keys[:s.MaxKeys-1] // trim length of slice + } + keys = append([][32]byte{newKey}, keys...) // prepend new key + + return keys, nil +} + +// generateSTEK generates key material suitable for use as a +// session ticket ephemeral key. +func (s *SessionTicketService) generateSTEK() ([32]byte, error) { + var newTicketKey [32]byte + _, err := io.ReadFull(rand.Reader, newTicketKey[:]) + return newTicketKey, err +} + +// STEKProvider is a type that can provide session ticket ephemeral +// keys (STEKs). +type STEKProvider interface { + // Initialize provides the STEK configuration to the STEK + // module so that it can obtain and manage keys accordingly. + // It returns the initial key(s) to use. Implementations can + // rely on Next() being called if Initialize() returns + // without error, so that it may know when it is done. + Initialize(config *SessionTicketService) ([][32]byte, error) + + // Next returns the channel through which the next session + // ticket keys will be transmitted until doneChan is closed. + // Keys should be sent on keysChan as they are updated. + // When doneChan is closed, any resources allocated in + // Initialize() must be cleaned up. + Next(doneChan <-chan struct{}) (keysChan <-chan [][32]byte) +} + +const ( + defaultSTEKRotationInterval = 12 * time.Hour + defaultMaxSTEKs = 4 +) diff --git a/modules/caddytls/standardstek/stek.go b/modules/caddytls/standardstek/stek.go new file mode 100644 index 00000000000..61cab0eeefd --- /dev/null +++ b/modules/caddytls/standardstek/stek.go @@ -0,0 +1,137 @@ +// 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 standardstek + +import ( + "log" + "runtime/debug" + "sync" + "time" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddytls" +) + +func init() { + caddy.RegisterModule(standardSTEKProvider{}) +} + +type standardSTEKProvider struct { + stekConfig *caddytls.SessionTicketService + timer *time.Timer +} + +// CaddyModule returns the Caddy module information. +func (standardSTEKProvider) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.stek.standard", + New: func() caddy.Module { return new(standardSTEKProvider) }, + } +} + +// Initialize sets the configuration for s and returns the starting keys. +func (s *standardSTEKProvider) Initialize(config *caddytls.SessionTicketService) ([][32]byte, error) { + // keep a reference to the config; we'll need it when rotating keys + s.stekConfig = config + + itvl := time.Duration(s.stekConfig.RotationInterval) + + mutex.Lock() + defer mutex.Unlock() + + // if this is our first rotation or we are overdue + // for one, perform a rotation immediately; otherwise, + // we assume that the keys are non-empty and fresh + since := time.Since(lastRotation) + if lastRotation.IsZero() || since > itvl { + var err error + keys, err = s.stekConfig.RotateSTEKs(keys) + if err != nil { + return nil, err + } + since = 0 // since this is overdue or is the first rotation, use full interval + lastRotation = time.Now() + } + + // create timer for the remaining time on the interval; + // this timer is cleaned up only when Next() returns + s.timer = time.NewTimer(itvl - since) + + return keys, nil +} + +// Next returns a channel which transmits the latest session ticket keys. +func (s *standardSTEKProvider) Next(doneChan <-chan struct{}) <-chan [][32]byte { + keysChan := make(chan [][32]byte) + go s.rotate(doneChan, keysChan) + return keysChan +} + +// rotate rotates keys on a regular basis, sending each updated set of +// keys down keysChan, until doneChan is closed. +func (s *standardSTEKProvider) rotate(doneChan <-chan struct{}, keysChan chan<- [][32]byte) { + defer func() { + if err := recover(); err != nil { + log.Printf("[PANIC] standard STEK rotation: %v\n%s", err, debug.Stack()) + } + }() + for { + select { + case now := <-s.timer.C: + // copy the slice header to avoid races + mutex.RLock() + keysCopy := keys + mutex.RUnlock() + + // generate a new key, rotating old ones + var err error + keysCopy, err = s.stekConfig.RotateSTEKs(keysCopy) + if err != nil { + // TODO: improve this handling + log.Printf("[ERROR] Generating STEK: %v", err) + continue + } + + // replace keys slice with updated value and + // record the timestamp of rotation + mutex.Lock() + keys = keysCopy + lastRotation = now + mutex.Unlock() + + // send the updated keys to the service + keysChan <- keysCopy + + // timer channel is already drained, so reset directly (see godoc) + s.timer.Reset(time.Duration(s.stekConfig.RotationInterval)) + + case <-doneChan: + // again, see godocs for why timer is stopped this way + if !s.timer.Stop() { + <-s.timer.C + } + return + } + } +} + +var ( + lastRotation time.Time + keys [][32]byte + mutex sync.RWMutex // protects keys and lastRotation +) + +// Interface guard +var _ caddytls.STEKProvider = (*standardSTEKProvider)(nil) diff --git a/modules/caddytls/storageloader.go b/modules/caddytls/storageloader.go new file mode 100644 index 00000000000..c9487e89252 --- /dev/null +++ b/modules/caddytls/storageloader.go @@ -0,0 +1,118 @@ +// 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 caddytls + +import ( + "crypto/tls" + "fmt" + "strings" + + "github.com/caddyserver/certmagic" + + "github.com/caddyserver/caddy/v2" +) + +func init() { + caddy.RegisterModule(StorageLoader{}) +} + +// StorageLoader loads certificates and their associated keys +// from the globally configured storage module. +type StorageLoader struct { + // A list of pairs of certificate and key file names along with their + // encoding format so that they can be loaded from storage. + Pairs []CertKeyFilePair `json:"pairs,omitempty"` + + // Reference to the globally configured storage module. + storage certmagic.Storage + + ctx caddy.Context +} + +// CaddyModule returns the Caddy module information. +func (StorageLoader) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.certificates.load_storage", + New: func() caddy.Module { return new(StorageLoader) }, + } +} + +// Provision loads the storage module for sl. +func (sl *StorageLoader) Provision(ctx caddy.Context) error { + sl.storage = ctx.Storage() + sl.ctx = ctx + + repl, ok := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + if !ok { + repl = caddy.NewReplacer() + } + for k, pair := range sl.Pairs { + for i, tag := range pair.Tags { + pair.Tags[i] = repl.ReplaceKnown(tag, "") + } + sl.Pairs[k] = CertKeyFilePair{ + Certificate: repl.ReplaceKnown(pair.Certificate, ""), + Key: repl.ReplaceKnown(pair.Key, ""), + Format: repl.ReplaceKnown(pair.Format, ""), + Tags: pair.Tags, + } + } + return nil +} + +// LoadCertificates returns the certificates to be loaded by sl. +func (sl StorageLoader) LoadCertificates() ([]Certificate, error) { + certs := make([]Certificate, 0, len(sl.Pairs)) + for _, pair := range sl.Pairs { + certData, err := sl.storage.Load(sl.ctx, pair.Certificate) + if err != nil { + return nil, err + } + keyData, err := sl.storage.Load(sl.ctx, pair.Key) + if err != nil { + return nil, err + } + + var cert tls.Certificate + switch pair.Format { + case "": + fallthrough + + case "pem": + // if the start of the key file looks like an encrypted private key, + // reject it with a helpful error message + if strings.Contains(string(keyData[:40]), "ENCRYPTED") { + return nil, fmt.Errorf("encrypted private keys are not supported; please decrypt the key first") + } + + cert, err = tls.X509KeyPair(certData, keyData) + + default: + return nil, fmt.Errorf("unrecognized certificate/key encoding format: %s", pair.Format) + } + if err != nil { + return nil, err + } + + certs = append(certs, Certificate{Certificate: cert, Tags: pair.Tags}) + } + return certs, nil +} + +// Interface guard +var ( + _ CertificateLoader = (*StorageLoader)(nil) + _ caddy.Provisioner = (*StorageLoader)(nil) +) diff --git a/modules/caddytls/tls.go b/modules/caddytls/tls.go new file mode 100644 index 00000000000..abb519eb74e --- /dev/null +++ b/modules/caddytls/tls.go @@ -0,0 +1,780 @@ +// 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 caddytls + +import ( + "context" + "crypto/tls" + "encoding/json" + "fmt" + "log" + "net/http" + "runtime/debug" + "sync" + "time" + + "github.com/caddyserver/certmagic" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyevents" +) + +func init() { + caddy.RegisterModule(TLS{}) + caddy.RegisterModule(AutomateLoader{}) +} + +var ( + certCache *certmagic.Cache + certCacheMu sync.RWMutex +) + +// TLS provides TLS facilities including certificate +// loading and management, client auth, and more. +type TLS struct { + // Certificates to load into memory for quick recall during + // TLS handshakes. Each key is the name of a certificate + // loader module. + // + // The "automate" certificate loader module can be used to + // specify a list of subjects that need certificates to be + // managed automatically. The first matching automation + // policy will be applied to manage the certificate(s). + // + // All loaded certificates get pooled + // into the same cache and may be used to complete TLS + // handshakes for the relevant server names (SNI). + // Certificates loaded manually (anything other than + // "automate") are not automatically managed and will + // have to be refreshed manually before they expire. + CertificatesRaw caddy.ModuleMap `json:"certificates,omitempty" caddy:"namespace=tls.certificates"` + + // Configures certificate automation. + Automation *AutomationConfig `json:"automation,omitempty"` + + // Configures session ticket ephemeral keys (STEKs). + SessionTickets *SessionTicketService `json:"session_tickets,omitempty"` + + // Configures the in-memory certificate cache. + Cache *CertCacheOptions `json:"cache,omitempty"` + + // Disables OCSP stapling for manually-managed certificates only. + // To configure OCSP stapling for automated certificates, use an + // automation policy instead. + // + // Disabling OCSP stapling puts clients at greater risk, reduces their + // privacy, and usually lowers client performance. It is NOT recommended + // to disable this unless you are able to justify the costs. + // EXPERIMENTAL. Subject to change. + DisableOCSPStapling bool `json:"disable_ocsp_stapling,omitempty"` + + // Disables checks in certmagic that the configured storage is ready + // and able to handle writing new content to it. These checks are + // intended to prevent information loss (newly issued certificates), but + // can be expensive on the storage. + // + // Disabling these checks should only be done when the storage + // can be trusted to have enough capacity and no other problems. + // EXPERIMENTAL. Subject to change. + DisableStorageCheck bool `json:"disable_storage_check,omitempty"` + + // Disables the automatic cleanup of the storage backend. + // This is useful when TLS is not being used to store certificates + // and the user wants run their server in a read-only mode. + // + // Storage cleaning creates two files: instance.uuid and last_clean.json. + // The instance.uuid file is used to identify the instance of Caddy + // in a cluster. The last_clean.json file is used to store the last + // time the storage was cleaned. + // EXPERIMENTAL. Subject to change. + DisableStorageClean bool `json:"disable_storage_clean,omitempty"` + + certificateLoaders []CertificateLoader + automateNames []string + ctx caddy.Context + storageCleanTicker *time.Ticker + storageCleanStop chan struct{} + logger *zap.Logger + events *caddyevents.App + + // set of subjects with managed certificates, + // and hashes of manually-loaded certificates + // (managing's value is an optional issuer key, for distinction) + managing, loaded map[string]string +} + +// CaddyModule returns the Caddy module information. +func (TLS) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls", + New: func() caddy.Module { return new(TLS) }, + } +} + +// Provision sets up the configuration for the TLS app. +func (t *TLS) Provision(ctx caddy.Context) error { + eventsAppIface, err := ctx.App("events") + if err != nil { + return fmt.Errorf("getting events app: %v", err) + } + t.events = eventsAppIface.(*caddyevents.App) + t.ctx = ctx + t.logger = ctx.Logger() + repl := caddy.NewReplacer() + t.managing, t.loaded = make(map[string]string), make(map[string]string) + + // set up a new certificate cache; this (re)loads all certificates + cacheOpts := certmagic.CacheOptions{ + GetConfigForCert: func(cert certmagic.Certificate) (*certmagic.Config, error) { + return t.getConfigForName(cert.Names[0]), nil + }, + Logger: t.logger.Named("cache"), + } + if t.Automation != nil { + cacheOpts.OCSPCheckInterval = time.Duration(t.Automation.OCSPCheckInterval) + cacheOpts.RenewCheckInterval = time.Duration(t.Automation.RenewCheckInterval) + } + if t.Cache != nil { + cacheOpts.Capacity = t.Cache.Capacity + } + if cacheOpts.Capacity <= 0 { + cacheOpts.Capacity = 10000 + } + + certCacheMu.Lock() + if certCache == nil { + certCache = certmagic.NewCache(cacheOpts) + } else { + certCache.SetOptions(cacheOpts) + } + certCacheMu.Unlock() + + // certificate loaders + val, err := ctx.LoadModule(t, "CertificatesRaw") + if err != nil { + return fmt.Errorf("loading certificate loader modules: %s", err) + } + for modName, modIface := range val.(map[string]any) { + if modName == "automate" { + // special case; these will be loaded in later using our automation facilities, + // which we want to avoid doing during provisioning + if automateNames, ok := modIface.(*AutomateLoader); ok && automateNames != nil { + repl := caddy.NewReplacer() + subjects := make([]string, len(*automateNames)) + for i, sub := range *automateNames { + subjects[i] = repl.ReplaceAll(sub, "") + } + t.automateNames = subjects + } else { + return fmt.Errorf("loading certificates with 'automate' requires array of strings, got: %T", modIface) + } + continue + } + t.certificateLoaders = append(t.certificateLoaders, modIface.(CertificateLoader)) + } + + // on-demand permission module + if t.Automation != nil && t.Automation.OnDemand != nil && t.Automation.OnDemand.PermissionRaw != nil { + if t.Automation.OnDemand.Ask != "" { + return fmt.Errorf("on-demand TLS config conflict: both 'ask' endpoint and a 'permission' module are specified; 'ask' is deprecated, so use only the permission module") + } + val, err := ctx.LoadModule(t.Automation.OnDemand, "PermissionRaw") + if err != nil { + return fmt.Errorf("loading on-demand TLS permission module: %v", err) + } + t.Automation.OnDemand.permission = val.(OnDemandPermission) + } + + // run replacer on ask URL (for environment variables) -- return errors to prevent surprises (#5036) + if t.Automation != nil && t.Automation.OnDemand != nil && t.Automation.OnDemand.Ask != "" { + t.Automation.OnDemand.Ask, err = repl.ReplaceOrErr(t.Automation.OnDemand.Ask, true, true) + if err != nil { + return fmt.Errorf("preparing 'ask' endpoint: %v", err) + } + perm := PermissionByHTTP{ + Endpoint: t.Automation.OnDemand.Ask, + } + if err := perm.Provision(ctx); err != nil { + return fmt.Errorf("provisioning 'ask' module: %v", err) + } + t.Automation.OnDemand.permission = perm + } + + // automation/management policies + if t.Automation == nil { + t.Automation = new(AutomationConfig) + } + t.Automation.defaultPublicAutomationPolicy = new(AutomationPolicy) + err = t.Automation.defaultPublicAutomationPolicy.Provision(t) + if err != nil { + return fmt.Errorf("provisioning default public automation policy: %v", err) + } + for _, n := range t.automateNames { + // if any names specified by the "automate" loader do not qualify for a public + // certificate, we should initialize a default internal automation policy + // (but we don't want to do this unnecessarily, since it may prompt for password!) + if certmagic.SubjectQualifiesForPublicCert(n) { + continue + } + t.Automation.defaultInternalAutomationPolicy = &AutomationPolicy{ + IssuersRaw: []json.RawMessage{json.RawMessage(`{"module":"internal"}`)}, + } + err = t.Automation.defaultInternalAutomationPolicy.Provision(t) + if err != nil { + return fmt.Errorf("provisioning default internal automation policy: %v", err) + } + break + } + for i, ap := range t.Automation.Policies { + err := ap.Provision(t) + if err != nil { + return fmt.Errorf("provisioning automation policy %d: %v", i, err) + } + } + + // session ticket ephemeral keys (STEK) service and provider + if t.SessionTickets != nil { + err := t.SessionTickets.provision(ctx) + if err != nil { + return fmt.Errorf("provisioning session tickets configuration: %v", err) + } + } + + // load manual/static (unmanaged) certificates - we do this in + // provision so that other apps (such as http) can know which + // certificates have been manually loaded, and also so that + // commands like validate can be a better test + certCacheMu.RLock() + magic := certmagic.New(certCache, certmagic.Config{ + Storage: ctx.Storage(), + Logger: t.logger, + OnEvent: t.onEvent, + OCSP: certmagic.OCSPConfig{ + DisableStapling: t.DisableOCSPStapling, + }, + DisableStorageCheck: t.DisableStorageCheck, + }) + certCacheMu.RUnlock() + for _, loader := range t.certificateLoaders { + certs, err := loader.LoadCertificates() + if err != nil { + return fmt.Errorf("loading certificates: %v", err) + } + for _, cert := range certs { + hash, err := magic.CacheUnmanagedTLSCertificate(ctx, cert.Certificate, cert.Tags) + if err != nil { + return fmt.Errorf("caching unmanaged certificate: %v", err) + } + t.loaded[hash] = "" + } + } + + return nil +} + +// Validate validates t's configuration. +func (t *TLS) Validate() error { + if t.Automation != nil { + // ensure that host aren't repeated; since only the first + // automation policy is used, repeating a host in the lists + // isn't useful and is probably a mistake; same for two + // catch-all/default policies + var hasDefault bool + hostSet := make(map[string]int) + for i, ap := range t.Automation.Policies { + if len(ap.subjects) == 0 { + if hasDefault { + return fmt.Errorf("automation policy %d is the second policy that acts as default/catch-all, but will never be used", i) + } + hasDefault = true + } + for _, h := range ap.subjects { + if first, ok := hostSet[h]; ok { + return fmt.Errorf("automation policy %d: cannot apply more than one automation policy to host: %s (first match in policy %d)", i, h, first) + } + hostSet[h] = i + } + } + } + if t.Cache != nil { + if t.Cache.Capacity < 0 { + return fmt.Errorf("cache capacity must be >= 0") + } + } + return nil +} + +// Start activates the TLS module. +func (t *TLS) Start() error { + // warn if on-demand TLS is enabled but no restrictions are in place + if t.Automation.OnDemand == nil || (t.Automation.OnDemand.Ask == "" && t.Automation.OnDemand.permission == nil) { + for _, ap := range t.Automation.Policies { + if ap.OnDemand && ap.isWildcardOrDefault() { + if c := t.logger.Check(zapcore.WarnLevel, "YOUR SERVER MAY BE VULNERABLE TO ABUSE: on-demand TLS is enabled, but no protections are in place"); c != nil { + c.Write(zap.String("docs", "https://caddyserver.com/docs/automatic-https#on-demand-tls")) + } + break + } + } + } + + // now that we are running, and all manual certificates have + // been loaded, time to load the automated/managed certificates + err := t.Manage(t.automateNames) + if err != nil { + return fmt.Errorf("automate: managing %v: %v", t.automateNames, err) + } + + if !t.DisableStorageClean { + // start the storage cleaner goroutine and ticker, + // which cleans out expired certificates and more + t.keepStorageClean() + } + + return nil +} + +// Stop stops the TLS module and cleans up any allocations. +func (t *TLS) Stop() error { + // stop the storage cleaner goroutine and ticker + if t.storageCleanStop != nil { + close(t.storageCleanStop) + } + if t.storageCleanTicker != nil { + t.storageCleanTicker.Stop() + } + return nil +} + +// Cleanup frees up resources allocated during Provision. +func (t *TLS) Cleanup() error { + // stop the session ticket rotation goroutine + if t.SessionTickets != nil { + t.SessionTickets.stop() + } + + // if a new TLS app was loaded, remove certificates from the cache that are no longer + // being managed or loaded by the new config; if there is no more TLS app running, + // then stop cert maintenance and let the cert cache be GC'ed + if nextTLS, err := caddy.ActiveContext().AppIfConfigured("tls"); err == nil && nextTLS != nil { + nextTLSApp := nextTLS.(*TLS) + + // compute which certificates were managed or loaded into the cert cache by this + // app instance (which is being stopped) that are not managed or loaded by the + // new app instance (which just started), and remove them from the cache + var noLongerManaged []certmagic.SubjectIssuer + var reManage, noLongerLoaded []string + for subj, currentIssuerKey := range t.managing { + // It's a bit nuanced: managed certs can sometimes be different enough that we have to + // swap them out for a different one, even if they are for the same subject/domain. + // We consider "private" certs (internal CA/locally-trusted/etc) to be significantly + // distinct from "public" certs (production CAs/globally-trusted/etc) because of the + // implications when it comes to actual deployments: switching between an internal CA + // and a production CA, for example, is quite significant. Switching from one public CA + // to another, however, is not, and for our purposes we consider those to be the same. + // Anyway, if the next TLS app does not manage a cert for this name at all, definitely + // remove it from the cache. But if it does, and it's not the same kind of issuer/CA + // as we have, also remove it, so that it can swap it out for the right one. + if nextIssuerKey, ok := nextTLSApp.managing[subj]; !ok || nextIssuerKey != currentIssuerKey { + // next app is not managing a cert for this domain at all or is using a different issuer, so remove it + noLongerManaged = append(noLongerManaged, certmagic.SubjectIssuer{Subject: subj, IssuerKey: currentIssuerKey}) + + // then, if the next app is managing a cert for this name, but with a different issuer, re-manage it + if ok && nextIssuerKey != currentIssuerKey { + reManage = append(reManage, subj) + } + } + } + for hash := range t.loaded { + if _, ok := nextTLSApp.loaded[hash]; !ok { + noLongerLoaded = append(noLongerLoaded, hash) + } + } + + // remove the certs + certCacheMu.RLock() + certCache.RemoveManaged(noLongerManaged) + certCache.Remove(noLongerLoaded) + certCacheMu.RUnlock() + + // give the new TLS app a "kick" to manage certs that it is configured for + // with its own configuration instead of the one we just evicted + if err := nextTLSApp.Manage(reManage); err != nil { + if c := t.logger.Check(zapcore.ErrorLevel, "re-managing unloaded certificates with new config"); c != nil { + c.Write( + zap.Strings("subjects", reManage), + zap.Error(err), + ) + } + } + } else { + // no more TLS app running, so delete in-memory cert cache + certCache.Stop() + certCacheMu.Lock() + certCache = nil + certCacheMu.Unlock() + } + + return nil +} + +// Manage immediately begins managing names according to the +// matching automation policy. +func (t *TLS) Manage(names []string) error { + // for a large number of names, we can be more memory-efficient + // by making only one certmagic.Config for all the names that + // use that config, rather than calling ManageAsync once for + // every name; so first, bin names by AutomationPolicy + policyToNames := make(map[*AutomationPolicy][]string) + for _, name := range names { + ap := t.getAutomationPolicyForName(name) + policyToNames[ap] = append(policyToNames[ap], name) + } + + // now that names are grouped by policy, we can simply make one + // certmagic.Config for each (potentially large) group of names + // and call ManageAsync just once for the whole batch + for ap, names := range policyToNames { + err := ap.magic.ManageAsync(t.ctx.Context, names) + if err != nil { + const maxNamesToDisplay = 100 + if len(names) > maxNamesToDisplay { + names = append(names[:maxNamesToDisplay], fmt.Sprintf("(%d more...)", len(names)-maxNamesToDisplay)) + } + return fmt.Errorf("automate: manage %v: %v", names, err) + } + for _, name := range names { + // certs that are issued solely by our internal issuer are a little bit of + // a special case: if you have an initial config that manages example.com + // using internal CA, then after testing it you switch to a production CA, + // you wouldn't want to keep using the same self-signed cert, obviously; + // so we differentiate these by associating the subject with its issuer key; + // we do this because CertMagic has no notion of "InternalIssuer" like we + // do, so we have to do this logic ourselves + var issuerKey string + if len(ap.Issuers) == 1 { + if intIss, ok := ap.Issuers[0].(*InternalIssuer); ok && intIss != nil { + issuerKey = intIss.IssuerKey() + } + } + t.managing[name] = issuerKey + } + } + + return nil +} + +// HandleHTTPChallenge ensures that the ACME HTTP challenge or ZeroSSL HTTP +// validation request is handled for the certificate named by r.Host, if it +// is an HTTP challenge request. It requires that the automation policy for +// r.Host has an issuer that implements GetACMEIssuer() or is a *ZeroSSLIssuer. +func (t *TLS) HandleHTTPChallenge(w http.ResponseWriter, r *http.Request) bool { + acmeChallenge := certmagic.LooksLikeHTTPChallenge(r) + zerosslValidation := certmagic.LooksLikeZeroSSLHTTPValidation(r) + + // no-op if it's not an ACME challenge request + if !acmeChallenge && !zerosslValidation { + return false + } + + // try all the issuers until we find the one that initiated the challenge + ap := t.getAutomationPolicyForName(r.Host) + + if acmeChallenge { + type acmeCapable interface{ GetACMEIssuer() *ACMEIssuer } + + for _, iss := range ap.magic.Issuers { + if acmeIssuer, ok := iss.(acmeCapable); ok { + if acmeIssuer.GetACMEIssuer().issuer.HandleHTTPChallenge(w, r) { + return true + } + } + } + + // it's possible another server in this process initiated the challenge; + // users have requested that Caddy only handle HTTP challenges it initiated, + // so that users can proxy the others through to their backends; but we + // might not have an automation policy for all identifiers that are trying + // to get certificates (e.g. the admin endpoint), so we do this manual check + if challenge, ok := certmagic.GetACMEChallenge(r.Host); ok { + return certmagic.SolveHTTPChallenge(t.logger, w, r, challenge.Challenge) + } + } else if zerosslValidation { + for _, iss := range ap.magic.Issuers { + if ziss, ok := iss.(*ZeroSSLIssuer); ok { + if ziss.issuer.HandleZeroSSLHTTPValidation(w, r) { + return true + } + } + } + } + + return false +} + +// AddAutomationPolicy provisions and adds ap to the list of the app's +// automation policies. If an existing automation policy exists that has +// fewer hosts in its list than ap does, ap will be inserted before that +// other policy (this helps ensure that ap will be prioritized/chosen +// over, say, a catch-all policy). +func (t *TLS) AddAutomationPolicy(ap *AutomationPolicy) error { + if t.Automation == nil { + t.Automation = new(AutomationConfig) + } + err := ap.Provision(t) + if err != nil { + return err + } + // sort new automation policies just before any other which is a superset + // of this one; if we find an existing policy that covers every subject in + // ap but less specifically (e.g. a catch-all policy, or one with wildcards + // or with fewer subjects), insert ap just before it, otherwise ap would + // never be used because the first matching policy is more general + for i, existing := range t.Automation.Policies { + // first see if existing is superset of ap for all names + var otherIsSuperset bool + outer: + for _, thisSubj := range ap.subjects { + for _, otherSubj := range existing.subjects { + if certmagic.MatchWildcard(thisSubj, otherSubj) { + otherIsSuperset = true + break outer + } + } + } + // if existing AP is a superset or if it contains fewer names (i.e. is + // more general), then new AP is more specific, so insert before it + if otherIsSuperset || len(existing.SubjectsRaw) < len(ap.SubjectsRaw) { + t.Automation.Policies = append(t.Automation.Policies[:i], + append([]*AutomationPolicy{ap}, t.Automation.Policies[i:]...)...) + return nil + } + } + // otherwise just append the new one + t.Automation.Policies = append(t.Automation.Policies, ap) + return nil +} + +func (t *TLS) getConfigForName(name string) *certmagic.Config { + ap := t.getAutomationPolicyForName(name) + return ap.magic +} + +// getAutomationPolicyForName returns the first matching automation policy +// for the given subject name. If no matching policy can be found, the +// default policy is used, depending on whether the name qualifies for a +// public certificate or not. +func (t *TLS) getAutomationPolicyForName(name string) *AutomationPolicy { + for _, ap := range t.Automation.Policies { + if len(ap.subjects) == 0 { + return ap // no host filter is an automatic match + } + for _, h := range ap.subjects { + if certmagic.MatchWildcard(name, h) { + return ap + } + } + } + if certmagic.SubjectQualifiesForPublicCert(name) || t.Automation.defaultInternalAutomationPolicy == nil { + return t.Automation.defaultPublicAutomationPolicy + } + return t.Automation.defaultInternalAutomationPolicy +} + +// AllMatchingCertificates returns the list of all certificates in +// the cache which could be used to satisfy the given SAN. +func AllMatchingCertificates(san string) []certmagic.Certificate { + return certCache.AllMatchingCertificates(san) +} + +func (t *TLS) HasCertificateForSubject(subject string) bool { + certCacheMu.RLock() + allMatchingCerts := certCache.AllMatchingCertificates(subject) + certCacheMu.RUnlock() + for _, cert := range allMatchingCerts { + // check if the cert is manually loaded by this config + if _, ok := t.loaded[cert.Hash()]; ok { + return true + } + // check if the cert is automatically managed by this config + for _, name := range cert.Names { + if _, ok := t.managing[name]; ok { + return true + } + } + } + return false +} + +// keepStorageClean starts a goroutine that immediately cleans up all +// known storage units if it was not recently done, and then runs the +// operation at every tick from t.storageCleanTicker. +func (t *TLS) keepStorageClean() { + t.storageCleanTicker = time.NewTicker(t.storageCleanInterval()) + t.storageCleanStop = make(chan struct{}) + go func() { + defer func() { + if err := recover(); err != nil { + log.Printf("[PANIC] storage cleaner: %v\n%s", err, debug.Stack()) + } + }() + t.cleanStorageUnits() + for { + select { + case <-t.storageCleanStop: + return + case <-t.storageCleanTicker.C: + t.cleanStorageUnits() + } + } + }() +} + +func (t *TLS) cleanStorageUnits() { + storageCleanMu.Lock() + defer storageCleanMu.Unlock() + + // TODO: This check might not be needed anymore now that CertMagic syncs + // and throttles storage cleaning globally across the cluster. + // The original comment below might be outdated: + // + // If storage was cleaned recently, don't do it again for now. Although the ticker + // calling this function drops missed ticks for us, config reloads discard the old + // ticker and replace it with a new one, possibly invoking a cleaning to happen again + // too soon. (We divide the interval by 2 because the actual cleaning takes non-zero + // time, and we don't want to skip cleanings if we don't have to; whereas if a cleaning + // took most of the interval, we'd probably want to skip the next one so we aren't + // constantly cleaning. This allows cleanings to take up to half the interval's + // duration before we decide to skip the next one.) + if !storageClean.IsZero() && time.Since(storageClean) < t.storageCleanInterval()/2 { + return + } + + id, err := caddy.InstanceID() + if err != nil { + if c := t.logger.Check(zapcore.WarnLevel, "unable to get instance ID; storage clean stamps will be incomplete"); c != nil { + c.Write(zap.Error(err)) + } + } + options := certmagic.CleanStorageOptions{ + Logger: t.logger, + InstanceID: id.String(), + Interval: t.storageCleanInterval(), + OCSPStaples: true, + ExpiredCerts: true, + ExpiredCertGracePeriod: 24 * time.Hour * 14, + } + + // start with the default/global storage + err = certmagic.CleanStorage(t.ctx, t.ctx.Storage(), options) + if err != nil { + // probably don't want to return early, since we should still + // see if any other storages can get cleaned up + if c := t.logger.Check(zapcore.ErrorLevel, "could not clean default/global storage"); c != nil { + c.Write(zap.Error(err)) + } + } + + // then clean each storage defined in ACME automation policies + if t.Automation != nil { + for _, ap := range t.Automation.Policies { + if ap.storage == nil { + continue + } + if err := certmagic.CleanStorage(t.ctx, ap.storage, options); err != nil { + if c := t.logger.Check(zapcore.ErrorLevel, "could not clean storage configured in automation policy"); c != nil { + c.Write(zap.Error(err)) + } + } + } + } + + // remember last time storage was finished cleaning + storageClean = time.Now() + + t.logger.Info("finished cleaning storage units") +} + +func (t *TLS) storageCleanInterval() time.Duration { + if t.Automation != nil && t.Automation.StorageCleanInterval > 0 { + return time.Duration(t.Automation.StorageCleanInterval) + } + return defaultStorageCleanInterval +} + +// onEvent translates CertMagic events into Caddy events then dispatches them. +func (t *TLS) onEvent(ctx context.Context, eventName string, data map[string]any) error { + evt := t.events.Emit(t.ctx, eventName, data) + return evt.Aborted +} + +// CertificateLoader is a type that can load certificates. +// Certificates can optionally be associated with tags. +type CertificateLoader interface { + LoadCertificates() ([]Certificate, error) +} + +// Certificate is a TLS certificate, optionally +// associated with arbitrary tags. +type Certificate struct { + tls.Certificate + Tags []string +} + +// AutomateLoader will automatically manage certificates for the names in the +// list, including obtaining and renewing certificates. Automated certificates +// are managed according to their matching automation policy, configured +// elsewhere in this app. +// +// Technically, this is a no-op certificate loader module that is treated as +// a special case: it uses this app's automation features to load certificates +// for the list of hostnames, rather than loading certificates manually. But +// the end result is the same: certificates for these subject names will be +// loaded into the in-memory cache and may then be used. +type AutomateLoader []string + +// CaddyModule returns the Caddy module information. +func (AutomateLoader) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.certificates.automate", + New: func() caddy.Module { return new(AutomateLoader) }, + } +} + +// CertCacheOptions configures the certificate cache. +type CertCacheOptions struct { + // Maximum number of certificates to allow in the + // cache. If reached, certificates will be randomly + // evicted to make room for new ones. Default: 10,000 + Capacity int `json:"capacity,omitempty"` +} + +// Variables related to storage cleaning. +var ( + defaultStorageCleanInterval = 24 * time.Hour + + storageClean time.Time + storageCleanMu sync.Mutex +) + +// Interface guards +var ( + _ caddy.App = (*TLS)(nil) + _ caddy.Provisioner = (*TLS)(nil) + _ caddy.Validator = (*TLS)(nil) + _ caddy.CleanerUpper = (*TLS)(nil) +) diff --git a/modules/caddytls/values.go b/modules/caddytls/values.go new file mode 100644 index 00000000000..2f03d254e4f --- /dev/null +++ b/modules/caddytls/values.go @@ -0,0 +1,158 @@ +// 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 caddytls + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + + "github.com/caddyserver/certmagic" + "github.com/klauspost/cpuid/v2" +) + +// CipherSuiteNameSupported returns true if name is +// a supported cipher suite. +func CipherSuiteNameSupported(name string) bool { + return CipherSuiteID(name) != 0 +} + +// CipherSuiteID returns the ID of the cipher suite associated with +// the given name, or 0 if the name is not recognized/supported. +func CipherSuiteID(name string) uint16 { + for _, cs := range SupportedCipherSuites() { + if cs.Name == name { + return cs.ID + } + } + return 0 +} + +// SupportedCipherSuites returns a list of all the cipher suites +// Caddy supports. The list is NOT ordered by security preference. +func SupportedCipherSuites() []*tls.CipherSuite { + return tls.CipherSuites() +} + +// defaultCipherSuites is the ordered list of all the cipher +// suites we want to support by default, assuming AES-NI +// (hardware acceleration for AES). +var defaultCipherSuitesWithAESNI = []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, +} + +// defaultCipherSuites is the ordered list of all the cipher +// suites we want to support by default, assuming lack of +// AES-NI (NO hardware acceleration for AES). +var defaultCipherSuitesWithoutAESNI = []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, +} + +// getOptimalDefaultCipherSuites returns an appropriate cipher +// suite to use depending on the hardware support for AES. +// +// See https://github.com/caddyserver/caddy/issues/1674 +func getOptimalDefaultCipherSuites() []uint16 { + if cpuid.CPU.Supports(cpuid.AESNI) { + return defaultCipherSuitesWithAESNI + } + return defaultCipherSuitesWithoutAESNI +} + +// SupportedCurves is the unordered map of supported curves +// or key exchange mechanisms ("curves" traditionally). +// https://golang.org/pkg/crypto/tls/#CurveID +var SupportedCurves = map[string]tls.CurveID{ + "X25519mlkem768": tls.X25519MLKEM768, + "x25519": tls.X25519, + "secp256r1": tls.CurveP256, + "secp384r1": tls.CurveP384, + "secp521r1": tls.CurveP521, +} + +// supportedCertKeyTypes is all the key types that are supported +// for certificates that are obtained through ACME. +var supportedCertKeyTypes = map[string]certmagic.KeyType{ + "rsa2048": certmagic.RSA2048, + "rsa4096": certmagic.RSA4096, + "p256": certmagic.P256, + "p384": certmagic.P384, + "ed25519": certmagic.ED25519, +} + +// defaultCurves is the list of only the curves or key exchange +// mechanisms we want to use by default. The order is irrelevant. +// +// This list should only include mechanisms which are fast by +// design (e.g. X25519) and those for which an optimized assembly +// implementation exists (e.g. P256). The latter ones can be +// found here: +// https://github.com/golang/go/tree/master/src/crypto/elliptic +var defaultCurves = []tls.CurveID{ + tls.X25519MLKEM768, + tls.X25519, + tls.CurveP256, +} + +// SupportedProtocols is a map of supported protocols. +var SupportedProtocols = map[string]uint16{ + "tls1.2": tls.VersionTLS12, + "tls1.3": tls.VersionTLS13, +} + +// unsupportedProtocols is a map of unsupported protocols. +// Used for logging only, not enforcement. +var unsupportedProtocols = map[string]uint16{ + //nolint:staticcheck + "ssl3.0": tls.VersionSSL30, + "tls1.0": tls.VersionTLS10, + "tls1.1": tls.VersionTLS11, +} + +// publicKeyAlgorithms is the map of supported public key algorithms. +var publicKeyAlgorithms = map[string]x509.PublicKeyAlgorithm{ + "rsa": x509.RSA, + "dsa": x509.DSA, + "ecdsa": x509.ECDSA, +} + +// ProtocolName returns the standard name for the passed protocol version ID +// (e.g. "TLS1.3") or a fallback representation of the ID value if the version +// is not supported. +func ProtocolName(id uint16) string { + for k, v := range SupportedProtocols { + if v == id { + return k + } + } + + for k, v := range unsupportedProtocols { + if v == id { + return k + } + } + + return fmt.Sprintf("0x%04x", id) +} diff --git a/modules/caddytls/zerosslissuer.go b/modules/caddytls/zerosslissuer.go new file mode 100644 index 00000000000..b8727ab669d --- /dev/null +++ b/modules/caddytls/zerosslissuer.go @@ -0,0 +1,256 @@ +// 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 caddytls + +import ( + "context" + "crypto/x509" + "fmt" + "strconv" + "time" + + "github.com/caddyserver/certmagic" + "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func init() { + caddy.RegisterModule(new(ZeroSSLIssuer)) +} + +// ZeroSSLIssuer uses the ZeroSSL API to get certificates. +// Note that this is distinct from ZeroSSL's ACME endpoint. +// To use ZeroSSL's ACME endpoint, use the ACMEIssuer +// configured with ZeroSSL's ACME directory endpoint. +type ZeroSSLIssuer struct { + // The API key (or "access key") for using the ZeroSSL API. + // REQUIRED. + APIKey string `json:"api_key,omitempty"` + + // How many days the certificate should be valid for. + // Only certain values are accepted; see ZeroSSL docs. + ValidityDays int `json:"validity_days,omitempty"` + + // The host to bind to when opening a listener for + // verifying domain names (or IPs). + ListenHost string `json:"listen_host,omitempty"` + + // If HTTP is forwarded from port 80, specify the + // forwarded port here. + AlternateHTTPPort int `json:"alternate_http_port,omitempty"` + + // Use CNAME validation instead of HTTP. ZeroSSL's + // API uses CNAME records for DNS validation, similar + // to how Let's Encrypt uses TXT records for the + // DNS challenge. + CNAMEValidation *DNSChallengeConfig `json:"cname_validation,omitempty"` + + logger *zap.Logger + storage certmagic.Storage + issuer *certmagic.ZeroSSLIssuer +} + +// CaddyModule returns the Caddy module information. +func (*ZeroSSLIssuer) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.issuance.zerossl", + New: func() caddy.Module { return new(ZeroSSLIssuer) }, + } +} + +// Provision sets up the issuer. +func (iss *ZeroSSLIssuer) Provision(ctx caddy.Context) error { + iss.logger = ctx.Logger() + iss.storage = ctx.Storage() + repl := caddy.NewReplacer() + + var dnsManager *certmagic.DNSManager + if iss.CNAMEValidation != nil && len(iss.CNAMEValidation.ProviderRaw) > 0 { + val, err := ctx.LoadModule(iss.CNAMEValidation, "ProviderRaw") + if err != nil { + return fmt.Errorf("loading DNS provider module: %v", err) + } + dnsManager = &certmagic.DNSManager{ + DNSProvider: val.(certmagic.DNSProvider), + TTL: time.Duration(iss.CNAMEValidation.TTL), + PropagationDelay: time.Duration(iss.CNAMEValidation.PropagationDelay), + PropagationTimeout: time.Duration(iss.CNAMEValidation.PropagationTimeout), + Resolvers: iss.CNAMEValidation.Resolvers, + OverrideDomain: iss.CNAMEValidation.OverrideDomain, + Logger: iss.logger.Named("cname"), + } + } + + iss.issuer = &certmagic.ZeroSSLIssuer{ + APIKey: repl.ReplaceAll(iss.APIKey, ""), + ValidityDays: iss.ValidityDays, + ListenHost: iss.ListenHost, + AltHTTPPort: iss.AlternateHTTPPort, + Storage: iss.storage, + CNAMEValidation: dnsManager, + Logger: iss.logger, + } + + return nil +} + +// Issue obtains a certificate for the given csr. +func (iss *ZeroSSLIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest) (*certmagic.IssuedCertificate, error) { + return iss.issuer.Issue(ctx, csr) +} + +// IssuerKey returns the unique issuer key for the configured CA endpoint. +func (iss *ZeroSSLIssuer) IssuerKey() string { + return iss.issuer.IssuerKey() +} + +// Revoke revokes the given certificate. +func (iss *ZeroSSLIssuer) Revoke(ctx context.Context, cert certmagic.CertificateResource, reason int) error { + return iss.issuer.Revoke(ctx, cert, reason) +} + +// UnmarshalCaddyfile deserializes Caddyfile tokens into iss. +// +// ... zerossl { +// validity_days +// alt_http_port +// dns ... +// propagation_delay +// propagation_timeout +// resolvers +// dns_ttl +// } +func (iss *ZeroSSLIssuer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume issuer name + + // API key is required + if !d.NextArg() { + return d.ArgErr() + } + iss.APIKey = d.Val() + if d.NextArg() { + return d.ArgErr() + } + + for nesting := d.Nesting(); d.NextBlock(nesting); { + switch d.Val() { + case "validity_days": + if iss.ValidityDays != 0 { + return d.Errf("validity days is already specified: %d", iss.ValidityDays) + } + days, err := strconv.Atoi(d.Val()) + if err != nil { + return d.Errf("invalid number of days %s: %v", d.Val(), err) + } + iss.ValidityDays = days + + case "alt_http_port": + if !d.NextArg() { + return d.ArgErr() + } + port, err := strconv.Atoi(d.Val()) + if err != nil { + return d.Errf("invalid port %s: %v", d.Val(), err) + } + iss.AlternateHTTPPort = port + + case "dns": + if !d.NextArg() { + return d.ArgErr() + } + provName := d.Val() + if iss.CNAMEValidation == nil { + iss.CNAMEValidation = new(DNSChallengeConfig) + } + unm, err := caddyfile.UnmarshalModule(d, "dns.providers."+provName) + if err != nil { + return err + } + iss.CNAMEValidation.ProviderRaw = caddyconfig.JSONModuleObject(unm, "name", provName, nil) + + case "propagation_delay": + if !d.NextArg() { + return d.ArgErr() + } + delayStr := d.Val() + delay, err := caddy.ParseDuration(delayStr) + if err != nil { + return d.Errf("invalid propagation_delay duration %s: %v", delayStr, err) + } + if iss.CNAMEValidation == nil { + iss.CNAMEValidation = new(DNSChallengeConfig) + } + iss.CNAMEValidation.PropagationDelay = caddy.Duration(delay) + + case "propagation_timeout": + if !d.NextArg() { + return d.ArgErr() + } + timeoutStr := d.Val() + var timeout time.Duration + if timeoutStr == "-1" { + timeout = time.Duration(-1) + } else { + var err error + timeout, err = caddy.ParseDuration(timeoutStr) + if err != nil { + return d.Errf("invalid propagation_timeout duration %s: %v", timeoutStr, err) + } + } + if iss.CNAMEValidation == nil { + iss.CNAMEValidation = new(DNSChallengeConfig) + } + iss.CNAMEValidation.PropagationTimeout = caddy.Duration(timeout) + + case "resolvers": + if iss.CNAMEValidation == nil { + iss.CNAMEValidation = new(DNSChallengeConfig) + } + iss.CNAMEValidation.Resolvers = d.RemainingArgs() + if len(iss.CNAMEValidation.Resolvers) == 0 { + return d.ArgErr() + } + + case "dns_ttl": + if !d.NextArg() { + return d.ArgErr() + } + ttlStr := d.Val() + ttl, err := caddy.ParseDuration(ttlStr) + if err != nil { + return d.Errf("invalid dns_ttl duration %s: %v", ttlStr, err) + } + if iss.CNAMEValidation == nil { + iss.CNAMEValidation = new(DNSChallengeConfig) + } + iss.CNAMEValidation.TTL = caddy.Duration(ttl) + + default: + return d.Errf("unrecognized zerossl issuer property: %s", d.Val()) + } + } + + return nil +} + +// Interface guards +var ( + _ certmagic.Issuer = (*ZeroSSLIssuer)(nil) + _ certmagic.Revoker = (*ZeroSSLIssuer)(nil) + _ caddy.Provisioner = (*ZeroSSLIssuer)(nil) +) diff --git a/modules/filestorage/filestorage.go b/modules/filestorage/filestorage.go new file mode 100644 index 00000000000..76aff4e8b7a --- /dev/null +++ b/modules/filestorage/filestorage.go @@ -0,0 +1,85 @@ +// 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 filestorage + +import ( + "github.com/caddyserver/certmagic" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func init() { + caddy.RegisterModule(FileStorage{}) +} + +// FileStorage is a certmagic.Storage wrapper for certmagic.FileStorage. +type FileStorage struct { + // The base path to the folder used for storage. + Root string `json:"root,omitempty"` +} + +// CaddyModule returns the Caddy module information. +func (FileStorage) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.storage.file_system", + New: func() caddy.Module { return new(FileStorage) }, + } +} + +// CertMagicStorage converts s to a certmagic.Storage instance. +func (s FileStorage) CertMagicStorage() (certmagic.Storage, error) { + return &certmagic.FileStorage{Path: s.Root}, nil +} + +// UnmarshalCaddyfile sets up the storage module from Caddyfile tokens. +func (s *FileStorage) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + if !d.Next() { + return d.Err("expected tokens") + } + if d.NextArg() { + s.Root = d.Val() + } + if d.NextArg() { + return d.ArgErr() + } + for d.NextBlock(0) { + switch d.Val() { + case "root": + if !d.NextArg() { + return d.ArgErr() + } + if s.Root != "" { + return d.Err("root already set") + } + s.Root = d.Val() + if d.NextArg() { + return d.ArgErr() + } + default: + return d.Errf("unrecognized parameter '%s'", d.Val()) + } + } + if s.Root == "" { + return d.Err("missing root path (to use default, omit storage config entirely)") + } + return nil +} + +// Interface guards +var ( + _ caddy.StorageConverter = (*FileStorage)(nil) + _ caddyfile.Unmarshaler = (*FileStorage)(nil) +) diff --git a/modules/logging/appendencoder.go b/modules/logging/appendencoder.go new file mode 100644 index 00000000000..63bd532d02b --- /dev/null +++ b/modules/logging/appendencoder.go @@ -0,0 +1,357 @@ +// 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 logging + +import ( + "encoding/json" + "fmt" + "os" + "strings" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/buffer" + "go.uber.org/zap/zapcore" + "golang.org/x/term" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func init() { + caddy.RegisterModule(AppendEncoder{}) +} + +// AppendEncoder can be used to add fields to all log entries +// that pass through it. It is a wrapper around another +// encoder, which it uses to actually encode the log entries. +// It is most useful for adding information about the Caddy +// instance that is producing the log entries, possibly via +// an environment variable. +type AppendEncoder struct { + // The underlying encoder that actually encodes the + // log entries. If not specified, defaults to "json", + // unless the output is a terminal, in which case + // it defaults to "console". + WrappedRaw json.RawMessage `json:"wrap,omitempty" caddy:"namespace=caddy.logging.encoders inline_key=format"` + + // A map of field names to their values. The values + // can be global placeholders (e.g. env vars), or constants. + // Note that the encoder does not run as part of an HTTP + // request context, so request placeholders are not available. + Fields map[string]any `json:"fields,omitempty"` + + wrapped zapcore.Encoder + repl *caddy.Replacer + + wrappedIsDefault bool + ctx caddy.Context +} + +// CaddyModule returns the Caddy module information. +func (AppendEncoder) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.logging.encoders.append", + New: func() caddy.Module { return new(AppendEncoder) }, + } +} + +// Provision sets up the encoder. +func (fe *AppendEncoder) Provision(ctx caddy.Context) error { + fe.ctx = ctx + fe.repl = caddy.NewReplacer() + + if fe.WrappedRaw == nil { + // if wrap is not specified, default to JSON + fe.wrapped = &JSONEncoder{} + if p, ok := fe.wrapped.(caddy.Provisioner); ok { + if err := p.Provision(ctx); err != nil { + return fmt.Errorf("provisioning fallback encoder module: %v", err) + } + } + fe.wrappedIsDefault = true + } else { + // set up wrapped encoder + val, err := ctx.LoadModule(fe, "WrappedRaw") + if err != nil { + return fmt.Errorf("loading fallback encoder module: %v", err) + } + fe.wrapped = val.(zapcore.Encoder) + } + + return nil +} + +// ConfigureDefaultFormat will set the default format to "console" +// if the writer is a terminal. If already configured, it passes +// through the writer so a deeply nested encoder can configure +// its own default format. +func (fe *AppendEncoder) ConfigureDefaultFormat(wo caddy.WriterOpener) error { + if !fe.wrappedIsDefault { + if cfd, ok := fe.wrapped.(caddy.ConfiguresFormatterDefault); ok { + return cfd.ConfigureDefaultFormat(wo) + } + return nil + } + + if caddy.IsWriterStandardStream(wo) && term.IsTerminal(int(os.Stderr.Fd())) { + fe.wrapped = &ConsoleEncoder{} + if p, ok := fe.wrapped.(caddy.Provisioner); ok { + if err := p.Provision(fe.ctx); err != nil { + return fmt.Errorf("provisioning fallback encoder module: %v", err) + } + } + } + return nil +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax: +// +// append { +// wrap +// fields { +// +// } +// +// } +func (fe *AppendEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume encoder name + + // parse a field + parseField := func() error { + if fe.Fields == nil { + fe.Fields = make(map[string]any) + } + field := d.Val() + if !d.NextArg() { + return d.ArgErr() + } + fe.Fields[field] = d.ScalarVal() + if d.NextArg() { + return d.ArgErr() + } + return nil + } + + for d.NextBlock(0) { + switch d.Val() { + case "wrap": + if !d.NextArg() { + return d.ArgErr() + } + moduleName := d.Val() + moduleID := "caddy.logging.encoders." + moduleName + unm, err := caddyfile.UnmarshalModule(d, moduleID) + if err != nil { + return err + } + enc, ok := unm.(zapcore.Encoder) + if !ok { + return d.Errf("module %s (%T) is not a zapcore.Encoder", moduleID, unm) + } + fe.WrappedRaw = caddyconfig.JSONModuleObject(enc, "format", moduleName, nil) + + case "fields": + for nesting := d.Nesting(); d.NextBlock(nesting); { + err := parseField() + if err != nil { + return err + } + } + + default: + // if unknown, assume it's a field so that + // the config can be flat + err := parseField() + if err != nil { + return err + } + } + } + return nil +} + +// AddArray is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error { + return fe.wrapped.AddArray(key, marshaler) +} + +// AddObject is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) AddObject(key string, marshaler zapcore.ObjectMarshaler) error { + return fe.wrapped.AddObject(key, marshaler) +} + +// AddBinary is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) AddBinary(key string, value []byte) { + fe.wrapped.AddBinary(key, value) +} + +// AddByteString is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) AddByteString(key string, value []byte) { + fe.wrapped.AddByteString(key, value) +} + +// AddBool is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) AddBool(key string, value bool) { + fe.wrapped.AddBool(key, value) +} + +// AddComplex128 is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) AddComplex128(key string, value complex128) { + fe.wrapped.AddComplex128(key, value) +} + +// AddComplex64 is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) AddComplex64(key string, value complex64) { + fe.wrapped.AddComplex64(key, value) +} + +// AddDuration is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) AddDuration(key string, value time.Duration) { + fe.wrapped.AddDuration(key, value) +} + +// AddFloat64 is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) AddFloat64(key string, value float64) { + fe.wrapped.AddFloat64(key, value) +} + +// AddFloat32 is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) AddFloat32(key string, value float32) { + fe.wrapped.AddFloat32(key, value) +} + +// AddInt is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) AddInt(key string, value int) { + fe.wrapped.AddInt(key, value) +} + +// AddInt64 is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) AddInt64(key string, value int64) { + fe.wrapped.AddInt64(key, value) +} + +// AddInt32 is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) AddInt32(key string, value int32) { + fe.wrapped.AddInt32(key, value) +} + +// AddInt16 is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) AddInt16(key string, value int16) { + fe.wrapped.AddInt16(key, value) +} + +// AddInt8 is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) AddInt8(key string, value int8) { + fe.wrapped.AddInt8(key, value) +} + +// AddString is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) AddString(key, value string) { + fe.wrapped.AddString(key, value) +} + +// AddTime is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) AddTime(key string, value time.Time) { + fe.wrapped.AddTime(key, value) +} + +// AddUint is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) AddUint(key string, value uint) { + fe.wrapped.AddUint(key, value) +} + +// AddUint64 is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) AddUint64(key string, value uint64) { + fe.wrapped.AddUint64(key, value) +} + +// AddUint32 is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) AddUint32(key string, value uint32) { + fe.wrapped.AddUint32(key, value) +} + +// AddUint16 is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) AddUint16(key string, value uint16) { + fe.wrapped.AddUint16(key, value) +} + +// AddUint8 is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) AddUint8(key string, value uint8) { + fe.wrapped.AddUint8(key, value) +} + +// AddUintptr is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) AddUintptr(key string, value uintptr) { + fe.wrapped.AddUintptr(key, value) +} + +// AddReflected is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) AddReflected(key string, value any) error { + return fe.wrapped.AddReflected(key, value) +} + +// OpenNamespace is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) OpenNamespace(key string) { + fe.wrapped.OpenNamespace(key) +} + +// Clone is part of the zapcore.ObjectEncoder interface. +func (fe AppendEncoder) Clone() zapcore.Encoder { + return AppendEncoder{ + Fields: fe.Fields, + wrapped: fe.wrapped.Clone(), + repl: fe.repl, + } +} + +// EncodeEntry partially implements the zapcore.Encoder interface. +func (fe AppendEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) { + fe.wrapped = fe.wrapped.Clone() + for _, field := range fields { + field.AddTo(fe) + } + + // append fields from config + for key, value := range fe.Fields { + // if the value is a string + if str, ok := value.(string); ok { + isPlaceholder := strings.HasPrefix(str, "{") && + strings.HasSuffix(str, "}") && + strings.Count(str, "{") == 1 + if isPlaceholder { + // and it looks like a placeholder, evaluate it + replaced, _ := fe.repl.Get(strings.Trim(str, "{}")) + zap.Any(key, replaced).AddTo(fe) + } else { + // just use the string as-is + zap.String(key, str).AddTo(fe) + } + } else { + // not a string, so use the value as any + zap.Any(key, value).AddTo(fe) + } + } + + return fe.wrapped.EncodeEntry(ent, nil) +} + +// Interface guards +var ( + _ zapcore.Encoder = (*AppendEncoder)(nil) + _ caddyfile.Unmarshaler = (*AppendEncoder)(nil) + _ caddy.ConfiguresFormatterDefault = (*AppendEncoder)(nil) +) diff --git a/modules/logging/cores.go b/modules/logging/cores.go new file mode 100644 index 00000000000..49aa7640ce3 --- /dev/null +++ b/modules/logging/cores.go @@ -0,0 +1,36 @@ +package logging + +import ( + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func init() { + caddy.RegisterModule(MockCore{}) +} + +// MockCore is a no-op module, purely for testing +type MockCore struct { + zapcore.Core `json:"-"` +} + +// CaddyModule returns the Caddy module information. +func (MockCore) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.logging.cores.mock", + New: func() caddy.Module { return new(MockCore) }, + } +} + +func (lec *MockCore) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + return nil +} + +// Interface guards +var ( + _ zapcore.Core = (*MockCore)(nil) + _ caddy.Module = (*MockCore)(nil) + _ caddyfile.Unmarshaler = (*MockCore)(nil) +) diff --git a/modules/logging/encoders.go b/modules/logging/encoders.go new file mode 100644 index 00000000000..5c563114b35 --- /dev/null +++ b/modules/logging/encoders.go @@ -0,0 +1,309 @@ +// 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 logging + +import ( + "time" + + "go.uber.org/zap" + "go.uber.org/zap/buffer" + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func init() { + caddy.RegisterModule(ConsoleEncoder{}) + caddy.RegisterModule(JSONEncoder{}) +} + +// ConsoleEncoder encodes log entries that are mostly human-readable. +type ConsoleEncoder struct { + zapcore.Encoder `json:"-"` + LogEncoderConfig +} + +// CaddyModule returns the Caddy module information. +func (ConsoleEncoder) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.logging.encoders.console", + New: func() caddy.Module { return new(ConsoleEncoder) }, + } +} + +// Provision sets up the encoder. +func (ce *ConsoleEncoder) Provision(_ caddy.Context) error { + if ce.LevelFormat == "" { + ce.LevelFormat = "color" + } + if ce.TimeFormat == "" { + ce.TimeFormat = "wall_milli" + } + ce.Encoder = zapcore.NewConsoleEncoder(ce.ZapcoreEncoderConfig()) + return nil +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax: +// +// console { +// +// } +// +// See the godoc on the LogEncoderConfig type for the syntax of +// subdirectives that are common to most/all encoders. +func (ce *ConsoleEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume encoder name + if d.NextArg() { + return d.ArgErr() + } + err := ce.LogEncoderConfig.UnmarshalCaddyfile(d) + if err != nil { + return err + } + return nil +} + +// JSONEncoder encodes entries as JSON. +type JSONEncoder struct { + zapcore.Encoder `json:"-"` + LogEncoderConfig +} + +// CaddyModule returns the Caddy module information. +func (JSONEncoder) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.logging.encoders.json", + New: func() caddy.Module { return new(JSONEncoder) }, + } +} + +// Provision sets up the encoder. +func (je *JSONEncoder) Provision(_ caddy.Context) error { + je.Encoder = zapcore.NewJSONEncoder(je.ZapcoreEncoderConfig()) + return nil +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax: +// +// json { +// +// } +// +// See the godoc on the LogEncoderConfig type for the syntax of +// subdirectives that are common to most/all encoders. +func (je *JSONEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume encoder name + if d.NextArg() { + return d.ArgErr() + } + err := je.LogEncoderConfig.UnmarshalCaddyfile(d) + if err != nil { + return err + } + return nil +} + +// LogEncoderConfig holds configuration common to most encoders. +type LogEncoderConfig struct { + MessageKey *string `json:"message_key,omitempty"` + LevelKey *string `json:"level_key,omitempty"` + TimeKey *string `json:"time_key,omitempty"` + NameKey *string `json:"name_key,omitempty"` + CallerKey *string `json:"caller_key,omitempty"` + StacktraceKey *string `json:"stacktrace_key,omitempty"` + LineEnding *string `json:"line_ending,omitempty"` + + // Recognized values are: unix_seconds_float, unix_milli_float, unix_nano, iso8601, rfc3339, rfc3339_nano, wall, wall_milli, wall_nano, common_log. + // The value may also be custom format per the Go `time` package layout specification, as described [here](https://pkg.go.dev/time#pkg-constants). + TimeFormat string `json:"time_format,omitempty"` + TimeLocal bool `json:"time_local,omitempty"` + + // Recognized values are: s/second/seconds, ns/nano/nanos, ms/milli/millis, string. + // Empty and unrecognized value default to seconds. + DurationFormat string `json:"duration_format,omitempty"` + + // Recognized values are: lower, upper, color. + // Empty and unrecognized value default to lower. + LevelFormat string `json:"level_format,omitempty"` +} + +// UnmarshalCaddyfile populates the struct from Caddyfile tokens. Syntax: +// +// { +// message_key +// level_key +// time_key +// name_key +// caller_key +// stacktrace_key +// line_ending +// time_format +// time_local +// duration_format +// level_format +// } +func (lec *LogEncoderConfig) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + for d.NextBlock(0) { + subdir := d.Val() + switch subdir { + case "time_local": + lec.TimeLocal = true + if d.NextArg() { + return d.ArgErr() + } + continue + } + var arg string + if !d.AllArgs(&arg) { + return d.ArgErr() + } + switch subdir { + case "message_key": + lec.MessageKey = &arg + case "level_key": + lec.LevelKey = &arg + case "time_key": + lec.TimeKey = &arg + case "name_key": + lec.NameKey = &arg + case "caller_key": + lec.CallerKey = &arg + case "stacktrace_key": + lec.StacktraceKey = &arg + case "line_ending": + lec.LineEnding = &arg + case "time_format": + lec.TimeFormat = arg + case "duration_format": + lec.DurationFormat = arg + case "level_format": + lec.LevelFormat = arg + default: + return d.Errf("unrecognized subdirective %s", subdir) + } + } + return nil +} + +// ZapcoreEncoderConfig returns the equivalent zapcore.EncoderConfig. +// If lec is nil, zap.NewProductionEncoderConfig() is returned. +func (lec *LogEncoderConfig) ZapcoreEncoderConfig() zapcore.EncoderConfig { + cfg := zap.NewProductionEncoderConfig() + if lec == nil { + lec = new(LogEncoderConfig) + } + if lec.MessageKey != nil { + cfg.MessageKey = *lec.MessageKey + } + if lec.LevelKey != nil { + cfg.LevelKey = *lec.LevelKey + } + if lec.TimeKey != nil { + cfg.TimeKey = *lec.TimeKey + } + if lec.NameKey != nil { + cfg.NameKey = *lec.NameKey + } + if lec.CallerKey != nil { + cfg.CallerKey = *lec.CallerKey + } + if lec.StacktraceKey != nil { + cfg.StacktraceKey = *lec.StacktraceKey + } + if lec.LineEnding != nil { + cfg.LineEnding = *lec.LineEnding + } + + // time format + var timeFormatter zapcore.TimeEncoder + switch lec.TimeFormat { + case "", "unix_seconds_float": + timeFormatter = zapcore.EpochTimeEncoder + case "unix_milli_float": + timeFormatter = zapcore.EpochMillisTimeEncoder + case "unix_nano": + timeFormatter = zapcore.EpochNanosTimeEncoder + case "iso8601": + timeFormatter = zapcore.ISO8601TimeEncoder + default: + timeFormat := lec.TimeFormat + switch lec.TimeFormat { + case "rfc3339": + timeFormat = time.RFC3339 + case "rfc3339_nano": + timeFormat = time.RFC3339Nano + case "wall": + timeFormat = "2006/01/02 15:04:05" + case "wall_milli": + timeFormat = "2006/01/02 15:04:05.000" + case "wall_nano": + timeFormat = "2006/01/02 15:04:05.000000000" + case "common_log": + timeFormat = "02/Jan/2006:15:04:05 -0700" + } + timeFormatter = func(ts time.Time, encoder zapcore.PrimitiveArrayEncoder) { + var time time.Time + if lec.TimeLocal { + time = ts.Local() + } else { + time = ts.UTC() + } + encoder.AppendString(time.Format(timeFormat)) + } + } + cfg.EncodeTime = timeFormatter + + // duration format + var durFormatter zapcore.DurationEncoder + switch lec.DurationFormat { + case "s", "second", "seconds": + durFormatter = zapcore.SecondsDurationEncoder + case "ns", "nano", "nanos": + durFormatter = zapcore.NanosDurationEncoder + case "ms", "milli", "millis": + durFormatter = zapcore.MillisDurationEncoder + case "string": + durFormatter = zapcore.StringDurationEncoder + default: + durFormatter = zapcore.SecondsDurationEncoder + } + cfg.EncodeDuration = durFormatter + + // level format + var levelFormatter zapcore.LevelEncoder + switch lec.LevelFormat { + case "", "lower": + levelFormatter = zapcore.LowercaseLevelEncoder + case "upper": + levelFormatter = zapcore.CapitalLevelEncoder + case "color": + levelFormatter = zapcore.CapitalColorLevelEncoder + } + cfg.EncodeLevel = levelFormatter + + return cfg +} + +var bufferpool = buffer.NewPool() + +// Interface guards +var ( + _ zapcore.Encoder = (*ConsoleEncoder)(nil) + _ zapcore.Encoder = (*JSONEncoder)(nil) + + _ caddyfile.Unmarshaler = (*ConsoleEncoder)(nil) + _ caddyfile.Unmarshaler = (*JSONEncoder)(nil) +) diff --git a/modules/logging/filewriter.go b/modules/logging/filewriter.go new file mode 100644 index 00000000000..ef3211cbbb5 --- /dev/null +++ b/modules/logging/filewriter.go @@ -0,0 +1,326 @@ +// 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 logging + +import ( + "encoding/json" + "fmt" + "io" + "math" + "os" + "path/filepath" + "strconv" + + "github.com/dustin/go-humanize" + "gopkg.in/natefinch/lumberjack.v2" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func init() { + caddy.RegisterModule(FileWriter{}) +} + +// fileMode is a string made of 1 to 4 octal digits representing +// a numeric mode as specified with the `chmod` unix command. +// `"0777"` and `"777"` are thus equivalent values. +type fileMode os.FileMode + +// UnmarshalJSON satisfies json.Unmarshaler. +func (m *fileMode) UnmarshalJSON(b []byte) error { + if len(b) == 0 { + return io.EOF + } + + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + mode, err := parseFileMode(s) + if err != nil { + return err + } + + *m = fileMode(mode) + return err +} + +// MarshalJSON satisfies json.Marshaler. +func (m *fileMode) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf("\"%04o\"", *m)), nil +} + +// parseFileMode parses a file mode string, +// adding support for `chmod` unix command like +// 1 to 4 digital octal values. +func parseFileMode(s string) (os.FileMode, error) { + modeStr := fmt.Sprintf("%04s", s) + mode, err := strconv.ParseUint(modeStr, 8, 32) + if err != nil { + return 0, err + } + return os.FileMode(mode), nil +} + +// FileWriter can write logs to files. By default, log files +// are rotated ("rolled") when they get large, and old log +// files get deleted, to ensure that the process does not +// exhaust disk space. +type FileWriter struct { + // Filename is the name of the file to write. + Filename string `json:"filename,omitempty"` + + // The file permissions mode. + // 0600 by default. + Mode fileMode `json:"mode,omitempty"` + + // Roll toggles log rolling or rotation, which is + // enabled by default. + Roll *bool `json:"roll,omitempty"` + + // When a log file reaches approximately this size, + // it will be rotated. + RollSizeMB int `json:"roll_size_mb,omitempty"` + + // Whether to compress rolled files. Default: true + RollCompress *bool `json:"roll_gzip,omitempty"` + + // Whether to use local timestamps in rolled filenames. + // Default: false + RollLocalTime bool `json:"roll_local_time,omitempty"` + + // The maximum number of rolled log files to keep. + // Default: 10 + RollKeep int `json:"roll_keep,omitempty"` + + // How many days to keep rolled log files. Default: 90 + RollKeepDays int `json:"roll_keep_days,omitempty"` +} + +// CaddyModule returns the Caddy module information. +func (FileWriter) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.logging.writers.file", + New: func() caddy.Module { return new(FileWriter) }, + } +} + +// Provision sets up the module +func (fw *FileWriter) Provision(ctx caddy.Context) error { + // Replace placeholder in filename + repl := caddy.NewReplacer() + filename, err := repl.ReplaceOrErr(fw.Filename, true, true) + if err != nil { + return fmt.Errorf("invalid filename for log file: %v", err) + } + + fw.Filename = filename + return nil +} + +func (fw FileWriter) String() string { + fpath, err := caddy.FastAbs(fw.Filename) + if err == nil { + return fpath + } + return fw.Filename +} + +// WriterKey returns a unique key representing this fw. +func (fw FileWriter) WriterKey() string { + return "file:" + fw.Filename +} + +// OpenWriter opens a new file writer. +func (fw FileWriter) OpenWriter() (io.WriteCloser, error) { + modeIfCreating := os.FileMode(fw.Mode) + if modeIfCreating == 0 { + modeIfCreating = 0o600 + } + + // roll log files as a sensible default to avoid disk space exhaustion + roll := fw.Roll == nil || *fw.Roll + + // create the file if it does not exist; create with the configured mode, or default + // to restrictive if not set. (lumberjack will reuse the file mode across log rotation) + if err := os.MkdirAll(filepath.Dir(fw.Filename), 0o700); err != nil { + return nil, err + } + file, err := os.OpenFile(fw.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, modeIfCreating) + if err != nil { + return nil, err + } + info, err := file.Stat() + if roll { + file.Close() // lumberjack will reopen it on its own + } + + // Ensure already existing files have the right mode, since OpenFile will not set the mode in such case. + if configuredMode := os.FileMode(fw.Mode); configuredMode != 0 { + if err != nil { + return nil, fmt.Errorf("unable to stat log file to see if we need to set permissions: %v", err) + } + // only chmod if the configured mode is different + if info.Mode()&os.ModePerm != configuredMode&os.ModePerm { + if err = os.Chmod(fw.Filename, configuredMode); err != nil { + return nil, err + } + } + } + + // if not rolling, then the plain file handle is all we need + if !roll { + return file, nil + } + + // otherwise, return a rolling log + if fw.RollSizeMB == 0 { + fw.RollSizeMB = 100 + } + if fw.RollCompress == nil { + compress := true + fw.RollCompress = &compress + } + if fw.RollKeep == 0 { + fw.RollKeep = 10 + } + if fw.RollKeepDays == 0 { + fw.RollKeepDays = 90 + } + return &lumberjack.Logger{ + Filename: fw.Filename, + MaxSize: fw.RollSizeMB, + MaxAge: fw.RollKeepDays, + MaxBackups: fw.RollKeep, + LocalTime: fw.RollLocalTime, + Compress: *fw.RollCompress, + }, nil +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax: +// +// file { +// mode +// roll_disabled +// roll_size +// roll_uncompressed +// roll_local_time +// roll_keep +// roll_keep_for +// } +// +// The roll_size value has megabyte resolution. +// Fractional values are rounded up to the next whole megabyte (MiB). +// +// By default, compression is enabled, but can be turned off by setting +// the roll_uncompressed option. +// +// The roll_keep_for duration has day resolution. +// Fractional values are rounded up to the next whole number of days. +// +// If any of the mode, roll_size, roll_keep, or roll_keep_for subdirectives are +// omitted or set to a zero value, then Caddy's default value for that +// subdirective is used. +func (fw *FileWriter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume writer name + if !d.NextArg() { + return d.ArgErr() + } + fw.Filename = d.Val() + if d.NextArg() { + return d.ArgErr() + } + + for d.NextBlock(0) { + switch d.Val() { + case "mode": + var modeStr string + if !d.AllArgs(&modeStr) { + return d.ArgErr() + } + mode, err := parseFileMode(modeStr) + if err != nil { + return d.Errf("parsing mode: %v", err) + } + fw.Mode = fileMode(mode) + + case "roll_disabled": + var f bool + fw.Roll = &f + if d.NextArg() { + return d.ArgErr() + } + + case "roll_size": + var sizeStr string + if !d.AllArgs(&sizeStr) { + return d.ArgErr() + } + size, err := humanize.ParseBytes(sizeStr) + if err != nil { + return d.Errf("parsing size: %v", err) + } + fw.RollSizeMB = int(math.Ceil(float64(size) / humanize.MiByte)) + + case "roll_uncompressed": + var f bool + fw.RollCompress = &f + if d.NextArg() { + return d.ArgErr() + } + + case "roll_local_time": + fw.RollLocalTime = true + if d.NextArg() { + return d.ArgErr() + } + + case "roll_keep": + var keepStr string + if !d.AllArgs(&keepStr) { + return d.ArgErr() + } + keep, err := strconv.Atoi(keepStr) + if err != nil { + return d.Errf("parsing roll_keep number: %v", err) + } + fw.RollKeep = keep + + case "roll_keep_for": + var keepForStr string + if !d.AllArgs(&keepForStr) { + return d.ArgErr() + } + keepFor, err := caddy.ParseDuration(keepForStr) + if err != nil { + return d.Errf("parsing roll_keep_for duration: %v", err) + } + if keepFor < 0 { + return d.Errf("negative roll_keep_for duration: %v", keepFor) + } + fw.RollKeepDays = int(math.Ceil(keepFor.Hours() / 24)) + } + } + return nil +} + +// Interface guards +var ( + _ caddy.Provisioner = (*FileWriter)(nil) + _ caddy.WriterOpener = (*FileWriter)(nil) + _ caddyfile.Unmarshaler = (*FileWriter)(nil) +) diff --git a/modules/logging/filewriter_test.go b/modules/logging/filewriter_test.go new file mode 100644 index 00000000000..f9072f98a1c --- /dev/null +++ b/modules/logging/filewriter_test.go @@ -0,0 +1,387 @@ +// 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 !windows + +package logging + +import ( + "encoding/json" + "os" + "path" + "path/filepath" + "syscall" + "testing" + + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func TestFileCreationMode(t *testing.T) { + on := true + off := false + + tests := []struct { + name string + fw FileWriter + wantMode os.FileMode + }{ + { + name: "default mode no roll", + fw: FileWriter{ + Roll: &off, + }, + wantMode: 0o600, + }, + { + name: "default mode roll", + fw: FileWriter{ + Roll: &on, + }, + wantMode: 0o600, + }, + { + name: "custom mode no roll", + fw: FileWriter{ + Roll: &off, + Mode: 0o666, + }, + wantMode: 0o666, + }, + { + name: "custom mode roll", + fw: FileWriter{ + Roll: &on, + Mode: 0o666, + }, + wantMode: 0o666, + }, + } + + m := syscall.Umask(0o000) + defer syscall.Umask(m) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir, err := os.MkdirTemp("", "caddytest") + if err != nil { + t.Fatalf("failed to create tempdir: %v", err) + } + defer os.RemoveAll(dir) + fpath := filepath.Join(dir, "test.log") + tt.fw.Filename = fpath + + logger, err := tt.fw.OpenWriter() + if err != nil { + t.Fatalf("failed to create file: %v", err) + } + defer logger.Close() + + st, err := os.Stat(fpath) + if err != nil { + t.Fatalf("failed to check file permissions: %v", err) + } + + if st.Mode() != tt.wantMode { + t.Errorf("%s: file mode is %v, want %v", tt.name, st.Mode(), tt.wantMode) + } + }) + } +} + +func TestFileRotationPreserveMode(t *testing.T) { + m := syscall.Umask(0o000) + defer syscall.Umask(m) + + dir, err := os.MkdirTemp("", "caddytest") + if err != nil { + t.Fatalf("failed to create tempdir: %v", err) + } + defer os.RemoveAll(dir) + + fpath := path.Join(dir, "test.log") + + roll := true + mode := fileMode(0o640) + fw := FileWriter{ + Filename: fpath, + Mode: mode, + Roll: &roll, + RollSizeMB: 1, + } + + logger, err := fw.OpenWriter() + if err != nil { + t.Fatalf("failed to create file: %v", err) + } + defer logger.Close() + + b := make([]byte, 1024*1024-1000) + logger.Write(b) + logger.Write(b[0:2000]) + + files, err := os.ReadDir(dir) + if err != nil { + t.Fatalf("failed to read temporary log dir: %v", err) + } + + // We might get 2 or 3 files depending + // on the race between compressed log file generation, + // removal of the non compressed file and reading the directory. + // Ordering of the files are [ test-*.log test-*.log.gz test.log ] + if len(files) < 2 || len(files) > 3 { + t.Log("got files: ", files) + t.Fatalf("got %v files want 2", len(files)) + } + + wantPattern := "test-*-*-*-*-*.*.log" + test_date_log := files[0] + if m, _ := path.Match(wantPattern, test_date_log.Name()); m != true { + t.Fatalf("got %v filename want %v", test_date_log.Name(), wantPattern) + } + + st, err := os.Stat(path.Join(dir, test_date_log.Name())) + if err != nil { + t.Fatalf("failed to check file permissions: %v", err) + } + + if st.Mode() != os.FileMode(mode) { + t.Errorf("file mode is %v, want %v", st.Mode(), mode) + } + + test_dot_log := files[len(files)-1] + if test_dot_log.Name() != "test.log" { + t.Fatalf("got %v filename want test.log", test_dot_log.Name()) + } + + st, err = os.Stat(path.Join(dir, test_dot_log.Name())) + if err != nil { + t.Fatalf("failed to check file permissions: %v", err) + } + + if st.Mode() != os.FileMode(mode) { + t.Errorf("file mode is %v, want %v", st.Mode(), mode) + } +} + +func TestFileModeConfig(t *testing.T) { + tests := []struct { + name string + d *caddyfile.Dispenser + fw FileWriter + wantErr bool + }{ + { + name: "set mode", + d: caddyfile.NewTestDispenser(` +file test.log { + mode 0666 +} +`), + fw: FileWriter{ + Mode: 0o666, + }, + wantErr: false, + }, + { + name: "set mode 3 digits", + d: caddyfile.NewTestDispenser(` +file test.log { + mode 666 +} +`), + fw: FileWriter{ + Mode: 0o666, + }, + wantErr: false, + }, + { + name: "set mode 2 digits", + d: caddyfile.NewTestDispenser(` +file test.log { + mode 66 +} +`), + fw: FileWriter{ + Mode: 0o066, + }, + wantErr: false, + }, + { + name: "set mode 1 digits", + d: caddyfile.NewTestDispenser(` +file test.log { + mode 6 +} +`), + fw: FileWriter{ + Mode: 0o006, + }, + wantErr: false, + }, + { + name: "invalid mode", + d: caddyfile.NewTestDispenser(` +file test.log { + mode foobar +} +`), + fw: FileWriter{}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fw := &FileWriter{} + if err := fw.UnmarshalCaddyfile(tt.d); (err != nil) != tt.wantErr { + t.Fatalf("UnmarshalCaddyfile() error = %v, want %v", err, tt.wantErr) + } + if fw.Mode != tt.fw.Mode { + t.Errorf("got mode %v, want %v", fw.Mode, tt.fw.Mode) + } + }) + } +} + +func TestFileModeJSON(t *testing.T) { + tests := []struct { + name string + config string + fw FileWriter + wantErr bool + }{ + { + name: "set mode", + config: ` +{ + "mode": "0666" +} +`, + fw: FileWriter{ + Mode: 0o666, + }, + wantErr: false, + }, + { + name: "set mode invalid value", + config: ` +{ + "mode": "0x666" +} +`, + fw: FileWriter{}, + wantErr: true, + }, + { + name: "set mode invalid string", + config: ` +{ + "mode": 777 +} +`, + fw: FileWriter{}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fw := &FileWriter{} + if err := json.Unmarshal([]byte(tt.config), fw); (err != nil) != tt.wantErr { + t.Fatalf("UnmarshalJSON() error = %v, want %v", err, tt.wantErr) + } + if fw.Mode != tt.fw.Mode { + t.Errorf("got mode %v, want %v", fw.Mode, tt.fw.Mode) + } + }) + } +} + +func TestFileModeToJSON(t *testing.T) { + tests := []struct { + name string + mode fileMode + want string + wantErr bool + }{ + { + name: "none zero", + mode: 0644, + want: `"0644"`, + wantErr: false, + }, + { + name: "zero mode", + mode: 0, + want: `"0000"`, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var b []byte + var err error + + if b, err = json.Marshal(&tt.mode); (err != nil) != tt.wantErr { + t.Fatalf("MarshalJSON() error = %v, want %v", err, tt.wantErr) + } + + got := string(b[:]) + + if got != tt.want { + t.Errorf("got mode %v, want %v", got, tt.want) + } + }) + } +} + +func TestFileModeModification(t *testing.T) { + m := syscall.Umask(0o000) + defer syscall.Umask(m) + + dir, err := os.MkdirTemp("", "caddytest") + if err != nil { + t.Fatalf("failed to create tempdir: %v", err) + } + defer os.RemoveAll(dir) + + fpath := path.Join(dir, "test.log") + f_tmp, err := os.OpenFile(fpath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(0600)) + if err != nil { + t.Fatalf("failed to create test file: %v", err) + } + f_tmp.Close() + + fw := FileWriter{ + Mode: 0o666, + Filename: fpath, + } + + logger, err := fw.OpenWriter() + if err != nil { + t.Fatalf("failed to create file: %v", err) + } + defer logger.Close() + + st, err := os.Stat(fpath) + if err != nil { + t.Fatalf("failed to check file permissions: %v", err) + } + + want := os.FileMode(fw.Mode) + if st.Mode() != want { + t.Errorf("file mode is %v, want %v", st.Mode(), want) + } +} diff --git a/modules/logging/filewriter_test_windows.go b/modules/logging/filewriter_test_windows.go new file mode 100644 index 00000000000..d32a8d2c081 --- /dev/null +++ b/modules/logging/filewriter_test_windows.go @@ -0,0 +1,55 @@ +// 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 windows + +package logging + +import ( + "os" + "path" + "testing" +) + +// Windows relies on ACLs instead of unix permissions model. +// Go allows to open files with a particular mode put it is limited to read or write. +// See https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/syscall/syscall_windows.go;l=708. +// This is pretty restrictive and has few interest for log files and thus we just test that log files are +// opened with R/W permissions by default on Windows too. +func TestFileCreationMode(t *testing.T) { + dir, err := os.MkdirTemp("", "caddytest") + if err != nil { + t.Fatalf("failed to create tempdir: %v", err) + } + defer os.RemoveAll(dir) + + fw := &FileWriter{ + Filename: path.Join(dir, "test.log"), + } + + logger, err := fw.OpenWriter() + if err != nil { + t.Fatalf("failed to create file: %v", err) + } + defer logger.Close() + + st, err := os.Stat(fw.Filename) + if err != nil { + t.Fatalf("failed to check file permissions: %v", err) + } + + if st.Mode().Perm()&0o600 != 0o600 { + t.Fatalf("file mode is %v, want rw for user", st.Mode().Perm()) + } +} diff --git a/modules/logging/filterencoder.go b/modules/logging/filterencoder.go new file mode 100644 index 00000000000..c46df0788bf --- /dev/null +++ b/modules/logging/filterencoder.go @@ -0,0 +1,454 @@ +// 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 logging + +import ( + "encoding/json" + "fmt" + "os" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/buffer" + "go.uber.org/zap/zapcore" + "golang.org/x/term" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func init() { + caddy.RegisterModule(FilterEncoder{}) +} + +// FilterEncoder can filter (manipulate) fields on +// log entries before they are actually encoded by +// an underlying encoder. +type FilterEncoder struct { + // The underlying encoder that actually encodes the + // log entries. If not specified, defaults to "json", + // unless the output is a terminal, in which case + // it defaults to "console". + WrappedRaw json.RawMessage `json:"wrap,omitempty" caddy:"namespace=caddy.logging.encoders inline_key=format"` + + // A map of field names to their filters. Note that this + // is not a module map; the keys are field names. + // + // Nested fields can be referenced by representing a + // layer of nesting with `>`. In other words, for an + // object like `{"a":{"b":0}}`, the inner field can + // be referenced as `a>b`. + // + // The following fields are fundamental to the log and + // cannot be filtered because they are added by the + // underlying logging library as special cases: ts, + // level, logger, and msg. + FieldsRaw map[string]json.RawMessage `json:"fields,omitempty" caddy:"namespace=caddy.logging.encoders.filter inline_key=filter"` + + wrapped zapcore.Encoder + Fields map[string]LogFieldFilter `json:"-"` + + // used to keep keys unique across nested objects + keyPrefix string + + wrappedIsDefault bool + ctx caddy.Context +} + +// CaddyModule returns the Caddy module information. +func (FilterEncoder) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.logging.encoders.filter", + New: func() caddy.Module { return new(FilterEncoder) }, + } +} + +// Provision sets up the encoder. +func (fe *FilterEncoder) Provision(ctx caddy.Context) error { + fe.ctx = ctx + + if fe.WrappedRaw == nil { + // if wrap is not specified, default to JSON + fe.wrapped = &JSONEncoder{} + if p, ok := fe.wrapped.(caddy.Provisioner); ok { + if err := p.Provision(ctx); err != nil { + return fmt.Errorf("provisioning fallback encoder module: %v", err) + } + } + fe.wrappedIsDefault = true + } else { + // set up wrapped encoder + val, err := ctx.LoadModule(fe, "WrappedRaw") + if err != nil { + return fmt.Errorf("loading fallback encoder module: %v", err) + } + fe.wrapped = val.(zapcore.Encoder) + } + + // set up each field filter + if fe.Fields == nil { + fe.Fields = make(map[string]LogFieldFilter) + } + vals, err := ctx.LoadModule(fe, "FieldsRaw") + if err != nil { + return fmt.Errorf("loading log filter modules: %v", err) + } + for fieldName, modIface := range vals.(map[string]any) { + fe.Fields[fieldName] = modIface.(LogFieldFilter) + } + + return nil +} + +// ConfigureDefaultFormat will set the default format to "console" +// if the writer is a terminal. If already configured as a filter +// encoder, it passes through the writer so a deeply nested filter +// encoder can configure its own default format. +func (fe *FilterEncoder) ConfigureDefaultFormat(wo caddy.WriterOpener) error { + if !fe.wrappedIsDefault { + if cfd, ok := fe.wrapped.(caddy.ConfiguresFormatterDefault); ok { + return cfd.ConfigureDefaultFormat(wo) + } + return nil + } + + if caddy.IsWriterStandardStream(wo) && term.IsTerminal(int(os.Stderr.Fd())) { + fe.wrapped = &ConsoleEncoder{} + if p, ok := fe.wrapped.(caddy.Provisioner); ok { + if err := p.Provision(fe.ctx); err != nil { + return fmt.Errorf("provisioning fallback encoder module: %v", err) + } + } + } + return nil +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax: +// +// filter { +// wrap +// fields { +// { +// +// } +// } +// { +// +// } +// } +func (fe *FilterEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume encoder name + + // parse a field + parseField := func() error { + if fe.FieldsRaw == nil { + fe.FieldsRaw = make(map[string]json.RawMessage) + } + field := d.Val() + if !d.NextArg() { + return d.ArgErr() + } + filterName := d.Val() + moduleID := "caddy.logging.encoders.filter." + filterName + unm, err := caddyfile.UnmarshalModule(d, moduleID) + if err != nil { + return err + } + filter, ok := unm.(LogFieldFilter) + if !ok { + return d.Errf("module %s (%T) is not a logging.LogFieldFilter", moduleID, unm) + } + fe.FieldsRaw[field] = caddyconfig.JSONModuleObject(filter, "filter", filterName, nil) + return nil + } + + for d.NextBlock(0) { + switch d.Val() { + case "wrap": + if !d.NextArg() { + return d.ArgErr() + } + moduleName := d.Val() + moduleID := "caddy.logging.encoders." + moduleName + unm, err := caddyfile.UnmarshalModule(d, moduleID) + if err != nil { + return err + } + enc, ok := unm.(zapcore.Encoder) + if !ok { + return d.Errf("module %s (%T) is not a zapcore.Encoder", moduleID, unm) + } + fe.WrappedRaw = caddyconfig.JSONModuleObject(enc, "format", moduleName, nil) + + case "fields": + for nesting := d.Nesting(); d.NextBlock(nesting); { + err := parseField() + if err != nil { + return err + } + } + + default: + // if unknown, assume it's a field so that + // the config can be flat + err := parseField() + if err != nil { + return err + } + } + } + return nil +} + +// AddArray is part of the zapcore.ObjectEncoder interface. +// Array elements do not get filtered. +func (fe FilterEncoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error { + if filter, ok := fe.Fields[fe.keyPrefix+key]; ok { + filter.Filter(zap.Array(key, marshaler)).AddTo(fe.wrapped) + return nil + } + return fe.wrapped.AddArray(key, marshaler) +} + +// AddObject is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddObject(key string, marshaler zapcore.ObjectMarshaler) error { + if fe.filtered(key, marshaler) { + return nil + } + fe.keyPrefix += key + ">" + return fe.wrapped.AddObject(key, logObjectMarshalerWrapper{ + enc: fe, + marsh: marshaler, + }) +} + +// AddBinary is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddBinary(key string, value []byte) { + if !fe.filtered(key, value) { + fe.wrapped.AddBinary(key, value) + } +} + +// AddByteString is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddByteString(key string, value []byte) { + if !fe.filtered(key, value) { + fe.wrapped.AddByteString(key, value) + } +} + +// AddBool is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddBool(key string, value bool) { + if !fe.filtered(key, value) { + fe.wrapped.AddBool(key, value) + } +} + +// AddComplex128 is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddComplex128(key string, value complex128) { + if !fe.filtered(key, value) { + fe.wrapped.AddComplex128(key, value) + } +} + +// AddComplex64 is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddComplex64(key string, value complex64) { + if !fe.filtered(key, value) { + fe.wrapped.AddComplex64(key, value) + } +} + +// AddDuration is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddDuration(key string, value time.Duration) { + if !fe.filtered(key, value) { + fe.wrapped.AddDuration(key, value) + } +} + +// AddFloat64 is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddFloat64(key string, value float64) { + if !fe.filtered(key, value) { + fe.wrapped.AddFloat64(key, value) + } +} + +// AddFloat32 is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddFloat32(key string, value float32) { + if !fe.filtered(key, value) { + fe.wrapped.AddFloat32(key, value) + } +} + +// AddInt is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddInt(key string, value int) { + if !fe.filtered(key, value) { + fe.wrapped.AddInt(key, value) + } +} + +// AddInt64 is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddInt64(key string, value int64) { + if !fe.filtered(key, value) { + fe.wrapped.AddInt64(key, value) + } +} + +// AddInt32 is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddInt32(key string, value int32) { + if !fe.filtered(key, value) { + fe.wrapped.AddInt32(key, value) + } +} + +// AddInt16 is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddInt16(key string, value int16) { + if !fe.filtered(key, value) { + fe.wrapped.AddInt16(key, value) + } +} + +// AddInt8 is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddInt8(key string, value int8) { + if !fe.filtered(key, value) { + fe.wrapped.AddInt8(key, value) + } +} + +// AddString is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddString(key, value string) { + if !fe.filtered(key, value) { + fe.wrapped.AddString(key, value) + } +} + +// AddTime is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddTime(key string, value time.Time) { + if !fe.filtered(key, value) { + fe.wrapped.AddTime(key, value) + } +} + +// AddUint is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddUint(key string, value uint) { + if !fe.filtered(key, value) { + fe.wrapped.AddUint(key, value) + } +} + +// AddUint64 is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddUint64(key string, value uint64) { + if !fe.filtered(key, value) { + fe.wrapped.AddUint64(key, value) + } +} + +// AddUint32 is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddUint32(key string, value uint32) { + if !fe.filtered(key, value) { + fe.wrapped.AddUint32(key, value) + } +} + +// AddUint16 is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddUint16(key string, value uint16) { + if !fe.filtered(key, value) { + fe.wrapped.AddUint16(key, value) + } +} + +// AddUint8 is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddUint8(key string, value uint8) { + if !fe.filtered(key, value) { + fe.wrapped.AddUint8(key, value) + } +} + +// AddUintptr is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddUintptr(key string, value uintptr) { + if !fe.filtered(key, value) { + fe.wrapped.AddUintptr(key, value) + } +} + +// AddReflected is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddReflected(key string, value any) error { + if !fe.filtered(key, value) { + return fe.wrapped.AddReflected(key, value) + } + return nil +} + +// OpenNamespace is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) OpenNamespace(key string) { + fe.wrapped.OpenNamespace(key) +} + +// Clone is part of the zapcore.ObjectEncoder interface. +// We don't use it as of Oct 2019 (v2 beta 7), I'm not +// really sure what it'd be useful for in our case. +func (fe FilterEncoder) Clone() zapcore.Encoder { + return FilterEncoder{ + Fields: fe.Fields, + wrapped: fe.wrapped.Clone(), + keyPrefix: fe.keyPrefix, + } +} + +// EncodeEntry partially implements the zapcore.Encoder interface. +func (fe FilterEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) { + // without this clone and storing it to fe.wrapped, fields + // from subsequent log entries get appended to previous + // ones, and I'm not 100% sure why; see end of + // https://github.com/uber-go/zap/issues/750 + fe.wrapped = fe.wrapped.Clone() + for _, field := range fields { + field.AddTo(fe) + } + return fe.wrapped.EncodeEntry(ent, nil) +} + +// filtered returns true if the field was filtered. +// If true is returned, the field was filtered and +// added to the underlying encoder (so do not do +// that again). If false was returned, the field has +// not yet been added to the underlying encoder. +func (fe FilterEncoder) filtered(key string, value any) bool { + filter, ok := fe.Fields[fe.keyPrefix+key] + if !ok { + return false + } + filter.Filter(zap.Any(key, value)).AddTo(fe.wrapped) + return true +} + +// logObjectMarshalerWrapper allows us to recursively +// filter fields of objects as they get encoded. +type logObjectMarshalerWrapper struct { + enc FilterEncoder + marsh zapcore.ObjectMarshaler +} + +// MarshalLogObject implements the zapcore.ObjectMarshaler interface. +func (mom logObjectMarshalerWrapper) MarshalLogObject(_ zapcore.ObjectEncoder) error { + return mom.marsh.MarshalLogObject(mom.enc) +} + +// Interface guards +var ( + _ zapcore.Encoder = (*FilterEncoder)(nil) + _ zapcore.ObjectMarshaler = (*logObjectMarshalerWrapper)(nil) + _ caddyfile.Unmarshaler = (*FilterEncoder)(nil) + _ caddy.ConfiguresFormatterDefault = (*FilterEncoder)(nil) +) diff --git a/modules/logging/filters.go b/modules/logging/filters.go new file mode 100644 index 00000000000..79d908fca63 --- /dev/null +++ b/modules/logging/filters.go @@ -0,0 +1,681 @@ +// 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 logging + +import ( + "crypto/sha256" + "errors" + "fmt" + "net" + "net/http" + "net/url" + "regexp" + "strconv" + "strings" + + "go.uber.org/zap/zapcore" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func init() { + caddy.RegisterModule(DeleteFilter{}) + caddy.RegisterModule(HashFilter{}) + caddy.RegisterModule(ReplaceFilter{}) + caddy.RegisterModule(IPMaskFilter{}) + caddy.RegisterModule(QueryFilter{}) + caddy.RegisterModule(CookieFilter{}) + caddy.RegisterModule(RegexpFilter{}) + caddy.RegisterModule(RenameFilter{}) +} + +// LogFieldFilter can filter (or manipulate) +// a field in a log entry. +type LogFieldFilter interface { + Filter(zapcore.Field) zapcore.Field +} + +// DeleteFilter is a Caddy log field filter that +// deletes the field. +type DeleteFilter struct{} + +// CaddyModule returns the Caddy module information. +func (DeleteFilter) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.logging.encoders.filter.delete", + New: func() caddy.Module { return new(DeleteFilter) }, + } +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. +func (DeleteFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + return nil +} + +// Filter filters the input field. +func (DeleteFilter) Filter(in zapcore.Field) zapcore.Field { + in.Type = zapcore.SkipType + return in +} + +// hash returns the first 4 bytes of the SHA-256 hash of the given data as hexadecimal +func hash(s string) string { + return fmt.Sprintf("%.4x", sha256.Sum256([]byte(s))) +} + +// HashFilter is a Caddy log field filter that +// replaces the field with the initial 4 bytes +// of the SHA-256 hash of the content. Operates +// on string fields, or on arrays of strings +// where each string is hashed. +type HashFilter struct{} + +// CaddyModule returns the Caddy module information. +func (HashFilter) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.logging.encoders.filter.hash", + New: func() caddy.Module { return new(HashFilter) }, + } +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. +func (f *HashFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + return nil +} + +// Filter filters the input field with the replacement value. +func (f *HashFilter) Filter(in zapcore.Field) zapcore.Field { + if array, ok := in.Interface.(caddyhttp.LoggableStringArray); ok { + newArray := make(caddyhttp.LoggableStringArray, len(array)) + for i, s := range array { + newArray[i] = hash(s) + } + in.Interface = newArray + } else { + in.String = hash(in.String) + } + + return in +} + +// ReplaceFilter is a Caddy log field filter that +// replaces the field with the indicated string. +type ReplaceFilter struct { + Value string `json:"value,omitempty"` +} + +// CaddyModule returns the Caddy module information. +func (ReplaceFilter) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.logging.encoders.filter.replace", + New: func() caddy.Module { return new(ReplaceFilter) }, + } +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. +func (f *ReplaceFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume filter name + if d.NextArg() { + f.Value = d.Val() + } + return nil +} + +// Filter filters the input field with the replacement value. +func (f *ReplaceFilter) Filter(in zapcore.Field) zapcore.Field { + in.Type = zapcore.StringType + in.String = f.Value + return in +} + +// IPMaskFilter is a Caddy log field filter that +// masks IP addresses in a string, or in an array +// of strings. The string may be a comma separated +// list of IP addresses, where all of the values +// will be masked. +type IPMaskFilter struct { + // The IPv4 mask, as an subnet size CIDR. + IPv4MaskRaw int `json:"ipv4_cidr,omitempty"` + + // The IPv6 mask, as an subnet size CIDR. + IPv6MaskRaw int `json:"ipv6_cidr,omitempty"` + + v4Mask net.IPMask + v6Mask net.IPMask +} + +// CaddyModule returns the Caddy module information. +func (IPMaskFilter) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.logging.encoders.filter.ip_mask", + New: func() caddy.Module { return new(IPMaskFilter) }, + } +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. +func (m *IPMaskFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume filter name + + args := d.RemainingArgs() + if len(args) > 2 { + return d.Errf("too many arguments") + } + if len(args) > 0 { + val, err := strconv.Atoi(args[0]) + if err != nil { + return d.Errf("error parsing %s: %v", args[0], err) + } + m.IPv4MaskRaw = val + + if len(args) > 1 { + val, err := strconv.Atoi(args[1]) + if err != nil { + return d.Errf("error parsing %s: %v", args[1], err) + } + m.IPv6MaskRaw = val + } + } + + for d.NextBlock(0) { + switch d.Val() { + case "ipv4": + if !d.NextArg() { + return d.ArgErr() + } + val, err := strconv.Atoi(d.Val()) + if err != nil { + return d.Errf("error parsing %s: %v", d.Val(), err) + } + m.IPv4MaskRaw = val + + case "ipv6": + if !d.NextArg() { + return d.ArgErr() + } + val, err := strconv.Atoi(d.Val()) + if err != nil { + return d.Errf("error parsing %s: %v", d.Val(), err) + } + m.IPv6MaskRaw = val + + default: + return d.Errf("unrecognized subdirective %s", d.Val()) + } + } + return nil +} + +// Provision parses m's IP masks, from integers. +func (m *IPMaskFilter) Provision(ctx caddy.Context) error { + parseRawToMask := func(rawField int, bitLen int) net.IPMask { + if rawField == 0 { + return nil + } + + // we assume the int is a subnet size CIDR + // e.g. "16" being equivalent to masking the last + // two bytes of an ipv4 address, like "255.255.0.0" + return net.CIDRMask(rawField, bitLen) + } + + m.v4Mask = parseRawToMask(m.IPv4MaskRaw, 32) + m.v6Mask = parseRawToMask(m.IPv6MaskRaw, 128) + + return nil +} + +// Filter filters the input field. +func (m IPMaskFilter) Filter(in zapcore.Field) zapcore.Field { + if array, ok := in.Interface.(caddyhttp.LoggableStringArray); ok { + newArray := make(caddyhttp.LoggableStringArray, len(array)) + for i, s := range array { + newArray[i] = m.mask(s) + } + in.Interface = newArray + } else { + in.String = m.mask(in.String) + } + + return in +} + +func (m IPMaskFilter) mask(s string) string { + output := "" + for _, value := range strings.Split(s, ",") { + value = strings.TrimSpace(value) + host, port, err := net.SplitHostPort(value) + if err != nil { + host = value // assume whole thing was IP address + } + ipAddr := net.ParseIP(host) + if ipAddr == nil { + output += value + ", " + continue + } + mask := m.v4Mask + if ipAddr.To4() == nil { + mask = m.v6Mask + } + masked := ipAddr.Mask(mask) + if port == "" { + output += masked.String() + ", " + continue + } + + output += net.JoinHostPort(masked.String(), port) + ", " + } + return strings.TrimSuffix(output, ", ") +} + +type filterAction string + +const ( + // Replace value(s). + replaceAction filterAction = "replace" + + // Hash value(s). + hashAction filterAction = "hash" + + // Delete. + deleteAction filterAction = "delete" +) + +func (a filterAction) IsValid() error { + switch a { + case replaceAction, deleteAction, hashAction: + return nil + } + + return errors.New("invalid action type") +} + +type queryFilterAction struct { + // `replace` to replace the value(s) associated with the parameter(s), `hash` to replace them with the 4 initial bytes of the SHA-256 of their content or `delete` to remove them entirely. + Type filterAction `json:"type"` + + // The name of the query parameter. + Parameter string `json:"parameter"` + + // The value to use as replacement if the action is `replace`. + Value string `json:"value,omitempty"` +} + +// QueryFilter is a Caddy log field filter that filters +// query parameters from a URL. +// +// This filter updates the logged URL string to remove, replace or hash +// query parameters containing sensitive data. For instance, it can be +// used to redact any kind of secrets which were passed as query parameters, +// such as OAuth access tokens, session IDs, magic link tokens, etc. +type QueryFilter struct { + // A list of actions to apply to the query parameters of the URL. + Actions []queryFilterAction `json:"actions"` +} + +// Validate checks that action types are correct. +func (f *QueryFilter) Validate() error { + for _, a := range f.Actions { + if err := a.Type.IsValid(); err != nil { + return err + } + } + + return nil +} + +// CaddyModule returns the Caddy module information. +func (QueryFilter) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.logging.encoders.filter.query", + New: func() caddy.Module { return new(QueryFilter) }, + } +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. +func (m *QueryFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume filter name + for d.NextBlock(0) { + qfa := queryFilterAction{} + switch d.Val() { + case "replace": + if !d.NextArg() { + return d.ArgErr() + } + + qfa.Type = replaceAction + qfa.Parameter = d.Val() + + if !d.NextArg() { + return d.ArgErr() + } + qfa.Value = d.Val() + + case "hash": + if !d.NextArg() { + return d.ArgErr() + } + + qfa.Type = hashAction + qfa.Parameter = d.Val() + + case "delete": + if !d.NextArg() { + return d.ArgErr() + } + + qfa.Type = deleteAction + qfa.Parameter = d.Val() + + default: + return d.Errf("unrecognized subdirective %s", d.Val()) + } + + m.Actions = append(m.Actions, qfa) + } + return nil +} + +// Filter filters the input field. +func (m QueryFilter) Filter(in zapcore.Field) zapcore.Field { + if array, ok := in.Interface.(caddyhttp.LoggableStringArray); ok { + newArray := make(caddyhttp.LoggableStringArray, len(array)) + for i, s := range array { + newArray[i] = m.processQueryString(s) + } + in.Interface = newArray + } else { + in.String = m.processQueryString(in.String) + } + + return in +} + +func (m QueryFilter) processQueryString(s string) string { + u, err := url.Parse(s) + if err != nil { + return s + } + + q := u.Query() + for _, a := range m.Actions { + switch a.Type { + case replaceAction: + for i := range q[a.Parameter] { + q[a.Parameter][i] = a.Value + } + + case hashAction: + for i := range q[a.Parameter] { + q[a.Parameter][i] = hash(a.Value) + } + + case deleteAction: + q.Del(a.Parameter) + } + } + + u.RawQuery = q.Encode() + return u.String() +} + +type cookieFilterAction struct { + // `replace` to replace the value of the cookie, `hash` to replace it with the 4 initial bytes of the SHA-256 of its content or `delete` to remove it entirely. + Type filterAction `json:"type"` + + // The name of the cookie. + Name string `json:"name"` + + // The value to use as replacement if the action is `replace`. + Value string `json:"value,omitempty"` +} + +// CookieFilter is a Caddy log field filter that filters +// cookies. +// +// This filter updates the logged HTTP header string +// to remove, replace or hash cookies containing sensitive data. For instance, +// it can be used to redact any kind of secrets, such as session IDs. +// +// If several actions are configured for the same cookie name, only the first +// will be applied. +type CookieFilter struct { + // A list of actions to apply to the cookies. + Actions []cookieFilterAction `json:"actions"` +} + +// Validate checks that action types are correct. +func (f *CookieFilter) Validate() error { + for _, a := range f.Actions { + if err := a.Type.IsValid(); err != nil { + return err + } + } + + return nil +} + +// CaddyModule returns the Caddy module information. +func (CookieFilter) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.logging.encoders.filter.cookie", + New: func() caddy.Module { return new(CookieFilter) }, + } +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. +func (m *CookieFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume filter name + for d.NextBlock(0) { + cfa := cookieFilterAction{} + switch d.Val() { + case "replace": + if !d.NextArg() { + return d.ArgErr() + } + + cfa.Type = replaceAction + cfa.Name = d.Val() + + if !d.NextArg() { + return d.ArgErr() + } + cfa.Value = d.Val() + + case "hash": + if !d.NextArg() { + return d.ArgErr() + } + + cfa.Type = hashAction + cfa.Name = d.Val() + + case "delete": + if !d.NextArg() { + return d.ArgErr() + } + + cfa.Type = deleteAction + cfa.Name = d.Val() + + default: + return d.Errf("unrecognized subdirective %s", d.Val()) + } + + m.Actions = append(m.Actions, cfa) + } + return nil +} + +// Filter filters the input field. +func (m CookieFilter) Filter(in zapcore.Field) zapcore.Field { + cookiesSlice, ok := in.Interface.(caddyhttp.LoggableStringArray) + if !ok { + return in + } + + // using a dummy Request to make use of the Cookies() function to parse it + originRequest := http.Request{Header: http.Header{"Cookie": cookiesSlice}} + cookies := originRequest.Cookies() + transformedRequest := http.Request{Header: make(http.Header)} + +OUTER: + for _, c := range cookies { + for _, a := range m.Actions { + if c.Name != a.Name { + continue + } + + switch a.Type { + case replaceAction: + c.Value = a.Value + transformedRequest.AddCookie(c) + continue OUTER + + case hashAction: + c.Value = hash(c.Value) + transformedRequest.AddCookie(c) + continue OUTER + + case deleteAction: + continue OUTER + } + } + + transformedRequest.AddCookie(c) + } + + in.Interface = caddyhttp.LoggableStringArray(transformedRequest.Header["Cookie"]) + + return in +} + +// RegexpFilter is a Caddy log field filter that +// replaces the field matching the provided regexp +// with the indicated string. If the field is an +// array of strings, each of them will have the +// regexp replacement applied. +type RegexpFilter struct { + // The regular expression pattern defining what to replace. + RawRegexp string `json:"regexp,omitempty"` + + // The value to use as replacement + Value string `json:"value,omitempty"` + + regexp *regexp.Regexp +} + +// CaddyModule returns the Caddy module information. +func (RegexpFilter) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.logging.encoders.filter.regexp", + New: func() caddy.Module { return new(RegexpFilter) }, + } +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. +func (f *RegexpFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume filter name + if d.NextArg() { + f.RawRegexp = d.Val() + } + if d.NextArg() { + f.Value = d.Val() + } + return nil +} + +// Provision compiles m's regexp. +func (m *RegexpFilter) Provision(ctx caddy.Context) error { + r, err := regexp.Compile(m.RawRegexp) + if err != nil { + return err + } + + m.regexp = r + + return nil +} + +// Filter filters the input field with the replacement value if it matches the regexp. +func (f *RegexpFilter) Filter(in zapcore.Field) zapcore.Field { + if array, ok := in.Interface.(caddyhttp.LoggableStringArray); ok { + newArray := make(caddyhttp.LoggableStringArray, len(array)) + for i, s := range array { + newArray[i] = f.regexp.ReplaceAllString(s, f.Value) + } + in.Interface = newArray + } else { + in.String = f.regexp.ReplaceAllString(in.String, f.Value) + } + + return in +} + +// RenameFilter is a Caddy log field filter that +// renames the field's key with the indicated name. +type RenameFilter struct { + Name string `json:"name,omitempty"` +} + +// CaddyModule returns the Caddy module information. +func (RenameFilter) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.logging.encoders.filter.rename", + New: func() caddy.Module { return new(RenameFilter) }, + } +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. +func (f *RenameFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume filter name + if d.NextArg() { + f.Name = d.Val() + } + return nil +} + +// Filter renames the input field with the replacement name. +func (f *RenameFilter) Filter(in zapcore.Field) zapcore.Field { + in.Key = f.Name + return in +} + +// Interface guards +var ( + _ LogFieldFilter = (*DeleteFilter)(nil) + _ LogFieldFilter = (*HashFilter)(nil) + _ LogFieldFilter = (*ReplaceFilter)(nil) + _ LogFieldFilter = (*IPMaskFilter)(nil) + _ LogFieldFilter = (*QueryFilter)(nil) + _ LogFieldFilter = (*CookieFilter)(nil) + _ LogFieldFilter = (*RegexpFilter)(nil) + _ LogFieldFilter = (*RenameFilter)(nil) + + _ caddyfile.Unmarshaler = (*DeleteFilter)(nil) + _ caddyfile.Unmarshaler = (*HashFilter)(nil) + _ caddyfile.Unmarshaler = (*ReplaceFilter)(nil) + _ caddyfile.Unmarshaler = (*IPMaskFilter)(nil) + _ caddyfile.Unmarshaler = (*QueryFilter)(nil) + _ caddyfile.Unmarshaler = (*CookieFilter)(nil) + _ caddyfile.Unmarshaler = (*RegexpFilter)(nil) + _ caddyfile.Unmarshaler = (*RenameFilter)(nil) + + _ caddy.Provisioner = (*IPMaskFilter)(nil) + _ caddy.Provisioner = (*RegexpFilter)(nil) + + _ caddy.Validator = (*QueryFilter)(nil) +) diff --git a/modules/logging/filters_test.go b/modules/logging/filters_test.go new file mode 100644 index 00000000000..8f7ba0d7071 --- /dev/null +++ b/modules/logging/filters_test.go @@ -0,0 +1,240 @@ +package logging + +import ( + "testing" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "go.uber.org/zap/zapcore" +) + +func TestIPMaskSingleValue(t *testing.T) { + f := IPMaskFilter{IPv4MaskRaw: 16, IPv6MaskRaw: 32} + f.Provision(caddy.Context{}) + + out := f.Filter(zapcore.Field{String: "255.255.255.255"}) + if out.String != "255.255.0.0" { + t.Fatalf("field has not been filtered: %s", out.String) + } + + out = f.Filter(zapcore.Field{String: "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"}) + if out.String != "ffff:ffff::" { + t.Fatalf("field has not been filtered: %s", out.String) + } + + out = f.Filter(zapcore.Field{String: "not-an-ip"}) + if out.String != "not-an-ip" { + t.Fatalf("field has been filtered: %s", out.String) + } +} + +func TestIPMaskCommaValue(t *testing.T) { + f := IPMaskFilter{IPv4MaskRaw: 16, IPv6MaskRaw: 32} + f.Provision(caddy.Context{}) + + out := f.Filter(zapcore.Field{String: "255.255.255.255, 244.244.244.244"}) + if out.String != "255.255.0.0, 244.244.0.0" { + t.Fatalf("field has not been filtered: %s", out.String) + } + + out = f.Filter(zapcore.Field{String: "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff, ff00:ffff:ffff:ffff:ffff:ffff:ffff:ffff"}) + if out.String != "ffff:ffff::, ff00:ffff::" { + t.Fatalf("field has not been filtered: %s", out.String) + } + + out = f.Filter(zapcore.Field{String: "not-an-ip, 255.255.255.255"}) + if out.String != "not-an-ip, 255.255.0.0" { + t.Fatalf("field has not been filtered: %s", out.String) + } +} + +func TestIPMaskMultiValue(t *testing.T) { + f := IPMaskFilter{IPv4MaskRaw: 16, IPv6MaskRaw: 32} + f.Provision(caddy.Context{}) + + out := f.Filter(zapcore.Field{Interface: caddyhttp.LoggableStringArray{ + "255.255.255.255", + "244.244.244.244", + }}) + arr, ok := out.Interface.(caddyhttp.LoggableStringArray) + if !ok { + t.Fatalf("field is wrong type: %T", out.Integer) + } + if arr[0] != "255.255.0.0" { + t.Fatalf("field entry 0 has not been filtered: %s", arr[0]) + } + if arr[1] != "244.244.0.0" { + t.Fatalf("field entry 1 has not been filtered: %s", arr[1]) + } + + out = f.Filter(zapcore.Field{Interface: caddyhttp.LoggableStringArray{ + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + "ff00:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + }}) + arr, ok = out.Interface.(caddyhttp.LoggableStringArray) + if !ok { + t.Fatalf("field is wrong type: %T", out.Integer) + } + if arr[0] != "ffff:ffff::" { + t.Fatalf("field entry 0 has not been filtered: %s", arr[0]) + } + if arr[1] != "ff00:ffff::" { + t.Fatalf("field entry 1 has not been filtered: %s", arr[1]) + } +} + +func TestQueryFilterSingleValue(t *testing.T) { + f := QueryFilter{[]queryFilterAction{ + {replaceAction, "foo", "REDACTED"}, + {replaceAction, "notexist", "REDACTED"}, + {deleteAction, "bar", ""}, + {deleteAction, "notexist", ""}, + {hashAction, "hash", ""}, + }} + + if f.Validate() != nil { + t.Fatalf("the filter must be valid") + } + + out := f.Filter(zapcore.Field{String: "/path?foo=a&foo=b&bar=c&bar=d&baz=e&hash=hashed"}) + if out.String != "/path?baz=e&foo=REDACTED&foo=REDACTED&hash=e3b0c442" { + t.Fatalf("query parameters have not been filtered: %s", out.String) + } +} + +func TestQueryFilterMultiValue(t *testing.T) { + f := QueryFilter{ + Actions: []queryFilterAction{ + {Type: replaceAction, Parameter: "foo", Value: "REDACTED"}, + {Type: replaceAction, Parameter: "notexist", Value: "REDACTED"}, + {Type: deleteAction, Parameter: "bar"}, + {Type: deleteAction, Parameter: "notexist"}, + {Type: hashAction, Parameter: "hash"}, + }, + } + + if f.Validate() != nil { + t.Fatalf("the filter must be valid") + } + + out := f.Filter(zapcore.Field{Interface: caddyhttp.LoggableStringArray{ + "/path1?foo=a&foo=b&bar=c&bar=d&baz=e&hash=hashed", + "/path2?foo=c&foo=d&bar=e&bar=f&baz=g&hash=hashed", + }}) + arr, ok := out.Interface.(caddyhttp.LoggableStringArray) + if !ok { + t.Fatalf("field is wrong type: %T", out.Interface) + } + + expected1 := "/path1?baz=e&foo=REDACTED&foo=REDACTED&hash=e3b0c442" + expected2 := "/path2?baz=g&foo=REDACTED&foo=REDACTED&hash=e3b0c442" + if arr[0] != expected1 { + t.Fatalf("query parameters in entry 0 have not been filtered correctly: got %s, expected %s", arr[0], expected1) + } + if arr[1] != expected2 { + t.Fatalf("query parameters in entry 1 have not been filtered correctly: got %s, expected %s", arr[1], expected2) + } +} + +func TestValidateQueryFilter(t *testing.T) { + f := QueryFilter{[]queryFilterAction{ + {}, + }} + if f.Validate() == nil { + t.Fatalf("empty action type must be invalid") + } + + f = QueryFilter{[]queryFilterAction{ + {Type: "foo"}, + }} + if f.Validate() == nil { + t.Fatalf("unknown action type must be invalid") + } +} + +func TestCookieFilter(t *testing.T) { + f := CookieFilter{[]cookieFilterAction{ + {replaceAction, "foo", "REDACTED"}, + {deleteAction, "bar", ""}, + {hashAction, "hash", ""}, + }} + + out := f.Filter(zapcore.Field{Interface: caddyhttp.LoggableStringArray{ + "foo=a; foo=b; bar=c; bar=d; baz=e; hash=hashed", + }}) + outval := out.Interface.(caddyhttp.LoggableStringArray) + expected := caddyhttp.LoggableStringArray{ + "foo=REDACTED; foo=REDACTED; baz=e; hash=1a06df82", + } + if outval[0] != expected[0] { + t.Fatalf("cookies have not been filtered: %s", out.String) + } +} + +func TestValidateCookieFilter(t *testing.T) { + f := CookieFilter{[]cookieFilterAction{ + {}, + }} + if f.Validate() == nil { + t.Fatalf("empty action type must be invalid") + } + + f = CookieFilter{[]cookieFilterAction{ + {Type: "foo"}, + }} + if f.Validate() == nil { + t.Fatalf("unknown action type must be invalid") + } +} + +func TestRegexpFilterSingleValue(t *testing.T) { + f := RegexpFilter{RawRegexp: `secret`, Value: "REDACTED"} + f.Provision(caddy.Context{}) + + out := f.Filter(zapcore.Field{String: "foo-secret-bar"}) + if out.String != "foo-REDACTED-bar" { + t.Fatalf("field has not been filtered: %s", out.String) + } +} + +func TestRegexpFilterMultiValue(t *testing.T) { + f := RegexpFilter{RawRegexp: `secret`, Value: "REDACTED"} + f.Provision(caddy.Context{}) + + out := f.Filter(zapcore.Field{Interface: caddyhttp.LoggableStringArray{"foo-secret-bar", "bar-secret-foo"}}) + arr, ok := out.Interface.(caddyhttp.LoggableStringArray) + if !ok { + t.Fatalf("field is wrong type: %T", out.Integer) + } + if arr[0] != "foo-REDACTED-bar" { + t.Fatalf("field entry 0 has not been filtered: %s", arr[0]) + } + if arr[1] != "bar-REDACTED-foo" { + t.Fatalf("field entry 1 has not been filtered: %s", arr[1]) + } +} + +func TestHashFilterSingleValue(t *testing.T) { + f := HashFilter{} + + out := f.Filter(zapcore.Field{String: "foo"}) + if out.String != "2c26b46b" { + t.Fatalf("field has not been filtered: %s", out.String) + } +} + +func TestHashFilterMultiValue(t *testing.T) { + f := HashFilter{} + + out := f.Filter(zapcore.Field{Interface: caddyhttp.LoggableStringArray{"foo", "bar"}}) + arr, ok := out.Interface.(caddyhttp.LoggableStringArray) + if !ok { + t.Fatalf("field is wrong type: %T", out.Integer) + } + if arr[0] != "2c26b46b" { + t.Fatalf("field entry 0 has not been filtered: %s", arr[0]) + } + if arr[1] != "fcde2b2e" { + t.Fatalf("field entry 1 has not been filtered: %s", arr[1]) + } +} diff --git a/modules/logging/netwriter.go b/modules/logging/netwriter.go new file mode 100644 index 00000000000..dc2b0922cba --- /dev/null +++ b/modules/logging/netwriter.go @@ -0,0 +1,223 @@ +// 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 logging + +import ( + "fmt" + "io" + "net" + "os" + "sync" + "time" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func init() { + caddy.RegisterModule(NetWriter{}) +} + +// NetWriter implements a log writer that outputs to a network socket. If +// the socket goes down, it will dump logs to stderr while it attempts to +// reconnect. +type NetWriter struct { + // The address of the network socket to which to connect. + Address string `json:"address,omitempty"` + + // The timeout to wait while connecting to the socket. + DialTimeout caddy.Duration `json:"dial_timeout,omitempty"` + + // If enabled, allow connections errors when first opening the + // writer. The error and subsequent log entries will be reported + // to stderr instead until a connection can be re-established. + SoftStart bool `json:"soft_start,omitempty"` + + addr caddy.NetworkAddress +} + +// CaddyModule returns the Caddy module information. +func (NetWriter) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.logging.writers.net", + New: func() caddy.Module { return new(NetWriter) }, + } +} + +// Provision sets up the module. +func (nw *NetWriter) Provision(ctx caddy.Context) error { + repl := caddy.NewReplacer() + address, err := repl.ReplaceOrErr(nw.Address, true, true) + if err != nil { + return fmt.Errorf("invalid host in address: %v", err) + } + + nw.addr, err = caddy.ParseNetworkAddress(address) + if err != nil { + return fmt.Errorf("parsing network address '%s': %v", address, err) + } + + if nw.addr.PortRangeSize() != 1 { + return fmt.Errorf("multiple ports not supported") + } + + if nw.DialTimeout < 0 { + return fmt.Errorf("timeout cannot be less than 0") + } + + return nil +} + +func (nw NetWriter) String() string { + return nw.addr.String() +} + +// WriterKey returns a unique key representing this nw. +func (nw NetWriter) WriterKey() string { + return nw.addr.String() +} + +// OpenWriter opens a new network connection. +func (nw NetWriter) OpenWriter() (io.WriteCloser, error) { + reconn := &redialerConn{ + nw: nw, + timeout: time.Duration(nw.DialTimeout), + } + conn, err := reconn.dial() + if err != nil { + if !nw.SoftStart { + return nil, err + } + // don't block config load if remote is down or some other external problem; + // we can dump logs to stderr for now (see issue #5520) + fmt.Fprintf(os.Stderr, "[ERROR] net log writer failed to connect: %v (will retry connection and print errors here in the meantime)\n", err) + } + reconn.connMu.Lock() + reconn.Conn = conn + reconn.connMu.Unlock() + return reconn, nil +} + +// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax: +// +// net
    { +// dial_timeout +// soft_start +// } +func (nw *NetWriter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume writer name + if !d.NextArg() { + return d.ArgErr() + } + nw.Address = d.Val() + if d.NextArg() { + return d.ArgErr() + } + for d.NextBlock(0) { + switch d.Val() { + case "dial_timeout": + if !d.NextArg() { + return d.ArgErr() + } + timeout, err := caddy.ParseDuration(d.Val()) + if err != nil { + return d.Errf("invalid duration: %s", d.Val()) + } + if d.NextArg() { + return d.ArgErr() + } + nw.DialTimeout = caddy.Duration(timeout) + + case "soft_start": + if d.NextArg() { + return d.ArgErr() + } + nw.SoftStart = true + } + } + return nil +} + +// redialerConn wraps an underlying Conn so that if any +// writes fail, the connection is redialed and the write +// is retried. +type redialerConn struct { + net.Conn + connMu sync.RWMutex + nw NetWriter + timeout time.Duration + lastRedial time.Time +} + +// Write wraps the underlying Conn.Write method, but if that fails, +// it will re-dial the connection anew and try writing again. +func (reconn *redialerConn) Write(b []byte) (n int, err error) { + reconn.connMu.RLock() + conn := reconn.Conn + reconn.connMu.RUnlock() + if conn != nil { + if n, err = conn.Write(b); err == nil { + return + } + } + + // problem with the connection - lock it and try to fix it + reconn.connMu.Lock() + defer reconn.connMu.Unlock() + + // if multiple concurrent writes failed on the same broken conn, then + // one of them might have already re-dialed by now; try writing again + if reconn.Conn != nil { + if n, err = reconn.Conn.Write(b); err == nil { + return + } + } + + // there's still a problem, so try to re-attempt dialing the socket + // if some time has passed in which the issue could have potentially + // been resolved - we don't want to block at every single log + // emission (!) - see discussion in #4111 + if time.Since(reconn.lastRedial) > 10*time.Second { + reconn.lastRedial = time.Now() + conn2, err2 := reconn.dial() + if err2 != nil { + // logger socket still offline; instead of discarding the log, dump it to stderr + os.Stderr.Write(b) + return + } + if n, err = conn2.Write(b); err == nil { + if reconn.Conn != nil { + reconn.Conn.Close() + } + reconn.Conn = conn2 + } + } else { + // last redial attempt was too recent; just dump to stderr for now + os.Stderr.Write(b) + } + + return +} + +func (reconn *redialerConn) dial() (net.Conn, error) { + return net.DialTimeout(reconn.nw.addr.Network, reconn.nw.addr.JoinHostPort(0), reconn.timeout) +} + +// Interface guards +var ( + _ caddy.Provisioner = (*NetWriter)(nil) + _ caddy.WriterOpener = (*NetWriter)(nil) + _ caddyfile.Unmarshaler = (*NetWriter)(nil) +) diff --git a/modules/logging/nopencoder.go b/modules/logging/nopencoder.go new file mode 100644 index 00000000000..62c1f787fa9 --- /dev/null +++ b/modules/logging/nopencoder.go @@ -0,0 +1,114 @@ +// 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 logging + +import ( + "time" + + "go.uber.org/zap/buffer" + "go.uber.org/zap/zapcore" +) + +// nopEncoder is a zapcore.Encoder that does nothing. +type nopEncoder struct{} + +// AddArray is part of the zapcore.ObjectEncoder interface. +// Array elements do not get filtered. +func (nopEncoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error { return nil } + +// AddObject is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddObject(key string, marshaler zapcore.ObjectMarshaler) error { return nil } + +// AddBinary is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddBinary(key string, value []byte) {} + +// AddByteString is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddByteString(key string, value []byte) {} + +// AddBool is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddBool(key string, value bool) {} + +// AddComplex128 is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddComplex128(key string, value complex128) {} + +// AddComplex64 is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddComplex64(key string, value complex64) {} + +// AddDuration is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddDuration(key string, value time.Duration) {} + +// AddFloat64 is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddFloat64(key string, value float64) {} + +// AddFloat32 is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddFloat32(key string, value float32) {} + +// AddInt is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddInt(key string, value int) {} + +// AddInt64 is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddInt64(key string, value int64) {} + +// AddInt32 is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddInt32(key string, value int32) {} + +// AddInt16 is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddInt16(key string, value int16) {} + +// AddInt8 is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddInt8(key string, value int8) {} + +// AddString is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddString(key, value string) {} + +// AddTime is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddTime(key string, value time.Time) {} + +// AddUint is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddUint(key string, value uint) {} + +// AddUint64 is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddUint64(key string, value uint64) {} + +// AddUint32 is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddUint32(key string, value uint32) {} + +// AddUint16 is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddUint16(key string, value uint16) {} + +// AddUint8 is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddUint8(key string, value uint8) {} + +// AddUintptr is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddUintptr(key string, value uintptr) {} + +// AddReflected is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddReflected(key string, value any) error { return nil } + +// OpenNamespace is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) OpenNamespace(key string) {} + +// Clone is part of the zapcore.ObjectEncoder interface. +// We don't use it as of Oct 2019 (v2 beta 7), I'm not +// really sure what it'd be useful for in our case. +func (ne nopEncoder) Clone() zapcore.Encoder { return ne } + +// EncodeEntry partially implements the zapcore.Encoder interface. +func (nopEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) { + return bufferpool.Get(), nil +} + +// Interface guard +var _ zapcore.Encoder = (*nopEncoder)(nil) diff --git a/modules/metrics/adminmetrics.go b/modules/metrics/adminmetrics.go new file mode 100644 index 00000000000..1e3a841ddb2 --- /dev/null +++ b/modules/metrics/adminmetrics.go @@ -0,0 +1,73 @@ +// Copyright 2020 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 metrics + +import ( + "errors" + "net/http" + + "github.com/prometheus/client_golang/prometheus" + + "github.com/caddyserver/caddy/v2" +) + +func init() { + caddy.RegisterModule(AdminMetrics{}) +} + +// AdminMetrics is a module that serves a metrics endpoint so that any gathered +// metrics can be exposed for scraping. This module is not configurable, and +// is permanently mounted to the admin API endpoint at "/metrics". +// See the Metrics module for a configurable endpoint that is usable if the +// Admin API is disabled. +type AdminMetrics struct { + registry *prometheus.Registry + + metricsHandler http.Handler +} + +// CaddyModule returns the Caddy module information. +func (AdminMetrics) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "admin.api.metrics", + New: func() caddy.Module { return new(AdminMetrics) }, + } +} + +// Provision - +func (m *AdminMetrics) Provision(ctx caddy.Context) error { + m.registry = ctx.GetMetricsRegistry() + if m.registry == nil { + return errors.New("no metrics registry found") + } + m.metricsHandler = createMetricsHandler(nil, false, m.registry) + return nil +} + +// Routes returns a route for the /metrics endpoint. +func (m *AdminMetrics) Routes() []caddy.AdminRoute { + return []caddy.AdminRoute{{Pattern: "/metrics", Handler: caddy.AdminHandlerFunc(m.serveHTTP)}} +} + +func (m *AdminMetrics) serveHTTP(w http.ResponseWriter, r *http.Request) error { + m.metricsHandler.ServeHTTP(w, r) + return nil +} + +// Interface guards +var ( + _ caddy.Provisioner = (*AdminMetrics)(nil) + _ caddy.AdminRouter = (*AdminMetrics)(nil) +) diff --git a/modules/metrics/metrics.go b/modules/metrics/metrics.go new file mode 100644 index 00000000000..42b30d88dd7 --- /dev/null +++ b/modules/metrics/metrics.go @@ -0,0 +1,126 @@ +// Copyright 2020 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 metrics + +import ( + "errors" + "net/http" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func init() { + caddy.RegisterModule(Metrics{}) + httpcaddyfile.RegisterHandlerDirective("metrics", parseCaddyfile) +} + +// Metrics is a module that serves a /metrics endpoint so that any gathered +// metrics can be exposed for scraping. This module is configurable by end-users +// unlike AdminMetrics. +type Metrics struct { + metricsHandler http.Handler + + // Disable OpenMetrics negotiation, enabled by default. May be necessary if + // the produced metrics cannot be parsed by the service scraping metrics. + DisableOpenMetrics bool `json:"disable_openmetrics,omitempty"` +} + +// CaddyModule returns the Caddy module information. +func (Metrics) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.metrics", + New: func() caddy.Module { return new(Metrics) }, + } +} + +type zapLogger struct { + zl *zap.Logger +} + +func (l *zapLogger) Println(v ...any) { + l.zl.Sugar().Error(v...) +} + +// Provision sets up m. +func (m *Metrics) Provision(ctx caddy.Context) error { + log := ctx.Logger() + registry := ctx.GetMetricsRegistry() + if registry == nil { + return errors.New("no metrics registry found") + } + m.metricsHandler = createMetricsHandler(&zapLogger{log}, !m.DisableOpenMetrics, registry) + return nil +} + +func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + var m Metrics + err := m.UnmarshalCaddyfile(h.Dispenser) + return m, err +} + +// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax: +// +// metrics [] { +// disable_openmetrics +// } +func (m *Metrics) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume directive name + args := d.RemainingArgs() + if len(args) > 0 { + return d.ArgErr() + } + + for d.NextBlock(0) { + switch d.Val() { + case "disable_openmetrics": + m.DisableOpenMetrics = true + default: + return d.Errf("unrecognized subdirective %q", d.Val()) + } + } + return nil +} + +func (m Metrics) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + m.metricsHandler.ServeHTTP(w, r) + return nil +} + +// Interface guards +var ( + _ caddy.Provisioner = (*Metrics)(nil) + _ caddyhttp.MiddlewareHandler = (*Metrics)(nil) + _ caddyfile.Unmarshaler = (*Metrics)(nil) +) + +func createMetricsHandler(logger promhttp.Logger, enableOpenMetrics bool, registry *prometheus.Registry) http.Handler { + return promhttp.InstrumentMetricHandler(registry, + promhttp.HandlerFor(registry, promhttp.HandlerOpts{ + // will only log errors if logger is non-nil + ErrorLog: logger, + + // Allow OpenMetrics format to be negotiated - largely compatible, + // except quantile/le label values always have a decimal. + EnableOpenMetrics: enableOpenMetrics, + }), + ) +} diff --git a/modules/metrics/metrics_test.go b/modules/metrics/metrics_test.go new file mode 100644 index 00000000000..9eb02fbb3d0 --- /dev/null +++ b/modules/metrics/metrics_test.go @@ -0,0 +1,45 @@ +package metrics + +import ( + "testing" + + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func TestMetricsUnmarshalCaddyfile(t *testing.T) { + m := &Metrics{} + d := caddyfile.NewTestDispenser(`metrics bogus`) + err := m.UnmarshalCaddyfile(d) + if err == nil { + t.Errorf("expected error") + } + + m = &Metrics{} + d = caddyfile.NewTestDispenser(`metrics`) + err = m.UnmarshalCaddyfile(d) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if m.DisableOpenMetrics { + t.Errorf("DisableOpenMetrics should've been false: %v", m.DisableOpenMetrics) + } + + m = &Metrics{} + d = caddyfile.NewTestDispenser(`metrics { disable_openmetrics }`) + err = m.UnmarshalCaddyfile(d) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if !m.DisableOpenMetrics { + t.Errorf("DisableOpenMetrics should've been true: %v", m.DisableOpenMetrics) + } + + m = &Metrics{} + d = caddyfile.NewTestDispenser(`metrics { bogus }`) + err = m.UnmarshalCaddyfile(d) + if err == nil { + t.Errorf("expected error: %v", err) + } +} diff --git a/modules/standard/imports.go b/modules/standard/imports.go new file mode 100644 index 00000000000..813c63d6c44 --- /dev/null +++ b/modules/standard/imports.go @@ -0,0 +1,18 @@ +package standard + +import ( + // standard Caddy modules + _ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + _ "github.com/caddyserver/caddy/v2/modules/caddyevents" + _ "github.com/caddyserver/caddy/v2/modules/caddyevents/eventsconfig" + _ "github.com/caddyserver/caddy/v2/modules/caddyfs" + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/standard" + _ "github.com/caddyserver/caddy/v2/modules/caddypki" + _ "github.com/caddyserver/caddy/v2/modules/caddypki/acmeserver" + _ "github.com/caddyserver/caddy/v2/modules/caddytls" + _ "github.com/caddyserver/caddy/v2/modules/caddytls/distributedstek" + _ "github.com/caddyserver/caddy/v2/modules/caddytls/standardstek" + _ "github.com/caddyserver/caddy/v2/modules/filestorage" + _ "github.com/caddyserver/caddy/v2/modules/logging" + _ "github.com/caddyserver/caddy/v2/modules/metrics" +) diff --git a/modules_test.go b/modules_test.go new file mode 100644 index 00000000000..cadc4773535 --- /dev/null +++ b/modules_test.go @@ -0,0 +1,118 @@ +// 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 ( + "reflect" + "testing" +) + +func TestGetModules(t *testing.T) { + modulesMu.Lock() + modules = map[string]ModuleInfo{ + "a": {ID: "a"}, + "a.b": {ID: "a.b"}, + "a.b.c": {ID: "a.b.c"}, + "a.b.cd": {ID: "a.b.cd"}, + "a.c": {ID: "a.c"}, + "a.d": {ID: "a.d"}, + "b": {ID: "b"}, + "b.a": {ID: "b.a"}, + "b.b": {ID: "b.b"}, + "b.a.c": {ID: "b.a.c"}, + "c": {ID: "c"}, + } + modulesMu.Unlock() + + for i, tc := range []struct { + input string + expect []ModuleInfo + }{ + { + input: "", + expect: []ModuleInfo{ + {ID: "a"}, + {ID: "b"}, + {ID: "c"}, + }, + }, + { + input: "a", + expect: []ModuleInfo{ + {ID: "a.b"}, + {ID: "a.c"}, + {ID: "a.d"}, + }, + }, + { + input: "a.b", + expect: []ModuleInfo{ + {ID: "a.b.c"}, + {ID: "a.b.cd"}, + }, + }, + { + input: "a.b.c", + }, + { + input: "b", + expect: []ModuleInfo{ + {ID: "b.a"}, + {ID: "b.b"}, + }, + }, + { + input: "asdf", + }, + } { + actual := GetModules(tc.input) + if !reflect.DeepEqual(actual, tc.expect) { + t.Errorf("Test %d: Expected %v but got %v", i, tc.expect, actual) + } + } +} + +func TestModuleID(t *testing.T) { + for i, tc := range []struct { + input ModuleID + expectNamespace string + expectName string + }{ + { + input: "foo", + expectNamespace: "", + expectName: "foo", + }, + { + input: "foo.bar", + expectNamespace: "foo", + expectName: "bar", + }, + { + input: "a.b.c", + expectNamespace: "a.b", + expectName: "c", + }, + } { + actualNamespace := tc.input.Namespace() + if actualNamespace != tc.expectNamespace { + t.Errorf("Test %d: Expected namespace '%s' but got '%s'", i, tc.expectNamespace, actualNamespace) + } + actualName := tc.input.Name() + if actualName != tc.expectName { + t.Errorf("Test %d: Expected name '%s' but got '%s'", i, tc.expectName, actualName) + } + } +} diff --git a/notify/notify_linux.go b/notify/notify_linux.go new file mode 100644 index 00000000000..3457a5a6ae8 --- /dev/null +++ b/notify/notify_linux.go @@ -0,0 +1,81 @@ +// 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 notify provides facilities for notifying process managers +// of state changes, mainly for when running as a system service. +package notify + +import ( + "fmt" + "net" + "os" + "strings" +) + +// The documentation about this IPC protocol is available here: +// https://www.freedesktop.org/software/systemd/man/sd_notify.html + +func sdNotify(payload string) error { + if socketPath == "" { + return nil + } + + socketAddr := &net.UnixAddr{ + Name: socketPath, + Net: "unixgram", + } + + conn, err := net.DialUnix(socketAddr.Net, nil, socketAddr) + if err != nil { + return err + } + defer conn.Close() + + _, err = conn.Write([]byte(payload)) + return err +} + +// Ready notifies systemd that caddy has finished its +// initialization routines. +func Ready() error { + return sdNotify("READY=1") +} + +// Reloading notifies systemd that caddy is reloading its config. +func Reloading() error { + return sdNotify("RELOADING=1") +} + +// Stopping notifies systemd that caddy is stopping. +func Stopping() error { + return sdNotify("STOPPING=1") +} + +// Status sends systemd an updated status message. +func Status(msg string) error { + return sdNotify("STATUS=" + msg) +} + +// Error is like Status, but sends systemd an error message +// instead, with an optional errno-style error number. +func Error(err error, errno int) error { + collapsedErr := strings.ReplaceAll(err.Error(), "\n", " ") + msg := fmt.Sprintf("STATUS=%s", collapsedErr) + if errno > 0 { + msg += fmt.Sprintf("\nERRNO=%d", errno) + } + return sdNotify(msg) +} + +var socketPath, _ = os.LookupEnv("NOTIFY_SOCKET") diff --git a/notify/notify_other.go b/notify/notify_other.go new file mode 100644 index 00000000000..dbe9bdb9145 --- /dev/null +++ b/notify/notify_other.go @@ -0,0 +1,23 @@ +// 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 !linux && !windows + +package notify + +func Ready() error { return nil } +func Reloading() error { return nil } +func Stopping() error { return nil } +func Status(_ string) error { return nil } +func Error(_ error, _ int) error { return nil } diff --git a/notify/notify_windows.go b/notify/notify_windows.go new file mode 100644 index 00000000000..5666a4c227b --- /dev/null +++ b/notify/notify_windows.go @@ -0,0 +1,55 @@ +// 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 notify + +import "golang.org/x/sys/windows/svc" + +// globalStatus store windows service status, it can be +// use to notify caddy status. +var globalStatus chan<- svc.Status + +func SetGlobalStatus(status chan<- svc.Status) { + globalStatus = status +} + +func Ready() error { + if globalStatus != nil { + globalStatus <- svc.Status{ + State: svc.Running, + Accepts: svc.AcceptStop | svc.AcceptShutdown, + } + } + return nil +} + +func Reloading() error { + if globalStatus != nil { + globalStatus <- svc.Status{State: svc.StartPending} + } + return nil +} + +func Stopping() error { + if globalStatus != nil { + globalStatus <- svc.Status{State: svc.StopPending} + } + return nil +} + +// TODO: not implemented +func Status(_ string) error { return nil } + +// TODO: not implemented +func Error(_ error, _ int) error { return nil } diff --git a/plugins.go b/plugins.go deleted file mode 100644 index a753ae9485f..00000000000 --- a/plugins.go +++ /dev/null @@ -1,379 +0,0 @@ -package caddy - -import ( - "fmt" - "log" - "net" - "sort" - - "github.com/mholt/caddy/caddyfile" -) - -// These are all the registered plugins. -var ( - // serverTypes is a map of registered server types. - serverTypes = make(map[string]ServerType) - - // plugins is a map of server type to map of plugin name to - // Plugin. These are the "general" plugins that may or may - // not be associated with a specific server type. If it's - // applicable to multiple server types or the server type is - // irrelevant, the key is empty string (""). But all plugins - // must have a name. - plugins = make(map[string]map[string]Plugin) - - // eventHooks is a map of hook name to Hook. All hooks plugins - // must have a name. - eventHooks = make(map[string]EventHook) - - // parsingCallbacks maps server type to map of directive - // to list of callback functions. These aren't really - // plugins on their own, but are often registered from - // plugins. - parsingCallbacks = make(map[string]map[string][]ParsingCallback) - - // caddyfileLoaders is the list of all Caddyfile loaders - // in registration order. - caddyfileLoaders []caddyfileLoader -) - -// DescribePlugins returns a string describing the registered plugins. -func DescribePlugins() string { - str := "Server types:\n" - for name := range serverTypes { - str += " " + name + "\n" - } - - // List the loaders in registration order - str += "\nCaddyfile loaders:\n" - for _, loader := range caddyfileLoaders { - str += " " + loader.name + "\n" - } - if defaultCaddyfileLoader.name != "" { - str += " " + defaultCaddyfileLoader.name + "\n" - } - - if len(eventHooks) > 0 { - // List the event hook plugins - str += "\nEvent hook plugins:\n" - for hookPlugin := range eventHooks { - str += " hook." + hookPlugin + "\n" - } - } - - // Let's alphabetize the rest of these... - var others []string - for stype, stypePlugins := range plugins { - for name := range stypePlugins { - var s string - if stype != "" { - s = stype + "." - } - s += name - others = append(others, s) - } - } - - sort.Strings(others) - str += "\nOther plugins:\n" - for _, name := range others { - str += " " + name + "\n" - } - - return str -} - -// ValidDirectives returns the list of all directives that are -// recognized for the server type serverType. However, not all -// directives may be installed. This makes it possible to give -// more helpful error messages, like "did you mean ..." or -// "maybe you need to plug in ...". -func ValidDirectives(serverType string) []string { - stype, err := getServerType(serverType) - if err != nil { - return nil - } - return stype.Directives() -} - -// ServerListener pairs a server to its listener and/or packetconn. -type ServerListener struct { - server Server - listener net.Listener - packet net.PacketConn -} - -// LocalAddr returns the local network address of the packetconn. It returns -// nil when it is not set. -func (s ServerListener) LocalAddr() net.Addr { - if s.packet == nil { - return nil - } - return s.packet.LocalAddr() -} - -// Addr returns the listener's network address. It returns nil when it is -// not set. -func (s ServerListener) Addr() net.Addr { - if s.listener == nil { - return nil - } - return s.listener.Addr() -} - -// Context is a type which carries a server type through -// the load and setup phase; it maintains the state -// between loading the Caddyfile, then executing its -// directives, then making the servers for Caddy to -// manage. Typically, such state involves configuration -// structs, etc. -type Context interface { - // Called after the Caddyfile is parsed into server - // blocks but before the directives are executed, - // this method gives you an opportunity to inspect - // the server blocks and prepare for the execution - // of directives. Return the server blocks (which - // you may modify, if desired) and an error, if any. - // The first argument is the name or path to the - // configuration file (Caddyfile). - // - // This function can be a no-op and simply return its - // input if there is nothing to do here. - InspectServerBlocks(string, []caddyfile.ServerBlock) ([]caddyfile.ServerBlock, error) - - // This is what Caddy calls to make server instances. - // By this time, all directives have been executed and, - // presumably, the context has enough state to produce - // server instances for Caddy to start. - MakeServers() ([]Server, error) -} - -// RegisterServerType registers a server type srv by its -// name, typeName. -func RegisterServerType(typeName string, srv ServerType) { - if _, ok := serverTypes[typeName]; ok { - panic("server type already registered") - } - serverTypes[typeName] = srv -} - -// ServerType contains information about a server type. -type ServerType struct { - // Function that returns the list of directives, in - // execution order, that are valid for this server - // type. Directives should be one word if possible - // and lower-cased. - Directives func() []string - - // DefaultInput returns a default config input if none - // is otherwise loaded. This is optional, but highly - // recommended, otherwise a blank Caddyfile will be - // used. - DefaultInput func() Input - - // The function that produces a new server type context. - // This will be called when a new Caddyfile is being - // loaded, parsed, and executed independently of any - // startup phases before this one. It's a way to keep - // each set of server instances separate and to reduce - // the amount of global state you need. - NewContext func() Context -} - -// Plugin is a type which holds information about a plugin. -type Plugin struct { - // ServerType is the type of server this plugin is for. - // Can be empty if not applicable, or if the plugin - // can associate with any server type. - ServerType string - - // Action is the plugin's setup function, if associated - // with a directive in the Caddyfile. - Action SetupFunc -} - -// RegisterPlugin plugs in plugin. All plugins should register -// themselves, even if they do not perform an action associated -// with a directive. It is important for the process to know -// which plugins are available. -// -// The plugin MUST have a name: lower case and one word. -// If this plugin has an action, it must be the name of -// the directive that invokes it. A name is always required -// and must be unique for the server type. -func RegisterPlugin(name string, plugin Plugin) { - if name == "" { - panic("plugin must have a name") - } - if _, ok := plugins[plugin.ServerType]; !ok { - plugins[plugin.ServerType] = make(map[string]Plugin) - } - if _, dup := plugins[plugin.ServerType][name]; dup { - panic("plugin named " + name + " already registered for server type " + plugin.ServerType) - } - plugins[plugin.ServerType][name] = plugin -} - -// EventName represents the name of an event used with event hooks. -type EventName string - -// Define the event names for the startup and shutdown events -const ( - StartupEvent EventName = "startup" - ShutdownEvent EventName = "shutdown" -) - -// EventHook is a type which holds information about a startup hook plugin. -type EventHook func(eventType EventName, eventInfo interface{}) error - -// RegisterEventHook plugs in hook. All the hooks should register themselves -// and they must have a name. -func RegisterEventHook(name string, hook EventHook) { - if name == "" { - panic("event hook must have a name") - } - if _, dup := eventHooks[name]; dup { - panic("hook named " + name + " already registered") - } - eventHooks[name] = hook -} - -// EmitEvent executes the different hooks passing the EventType as an -// argument. This is a blocking function. Hook developers should -// use 'go' keyword if they don't want to block Caddy. -func EmitEvent(event EventName, info interface{}) { - for name, hook := range eventHooks { - err := hook(event, info) - - if err != nil { - log.Printf("error on '%s' hook: %v", name, err) - } - } -} - -// ParsingCallback is a function that is called after -// a directive's setup functions have been executed -// for all the server blocks. -type ParsingCallback func(Context) error - -// RegisterParsingCallback registers callback to be called after -// executing the directive afterDir for server type serverType. -func RegisterParsingCallback(serverType, afterDir string, callback ParsingCallback) { - if _, ok := parsingCallbacks[serverType]; !ok { - parsingCallbacks[serverType] = make(map[string][]ParsingCallback) - } - parsingCallbacks[serverType][afterDir] = append(parsingCallbacks[serverType][afterDir], callback) -} - -// SetupFunc is used to set up a plugin, or in other words, -// execute a directive. It will be called once per key for -// each server block it appears in. -type SetupFunc func(c *Controller) error - -// DirectiveAction gets the action for directive dir of -// server type serverType. -func DirectiveAction(serverType, dir string) (SetupFunc, error) { - if stypePlugins, ok := plugins[serverType]; ok { - if plugin, ok := stypePlugins[dir]; ok { - return plugin.Action, nil - } - } - if genericPlugins, ok := plugins[""]; ok { - if plugin, ok := genericPlugins[dir]; ok { - return plugin.Action, nil - } - } - return nil, fmt.Errorf("no action found for directive '%s' with server type '%s' (missing a plugin?)", - dir, serverType) -} - -// Loader is a type that can load a Caddyfile. -// It is passed the name of the server type. -// It returns an error only if something went -// wrong, not simply if there is no Caddyfile -// for this loader to load. -// -// A Loader should only load the Caddyfile if -// a certain condition or requirement is met, -// as returning a non-nil Input value along with -// another Loader will result in an error. -// In other words, loading the Caddyfile must -// be deliberate & deterministic, not haphazard. -// -// The exception is the default Caddyfile loader, -// which will be called only if no other Caddyfile -// loaders return a non-nil Input. The default -// loader may always return an Input value. -type Loader interface { - Load(serverType string) (Input, error) -} - -// LoaderFunc is a convenience type similar to http.HandlerFunc -// that allows you to use a plain function as a Load() method. -type LoaderFunc func(serverType string) (Input, error) - -// Load loads a Caddyfile. -func (lf LoaderFunc) Load(serverType string) (Input, error) { - return lf(serverType) -} - -// RegisterCaddyfileLoader registers loader named name. -func RegisterCaddyfileLoader(name string, loader Loader) { - caddyfileLoaders = append(caddyfileLoaders, caddyfileLoader{name: name, loader: loader}) -} - -// SetDefaultCaddyfileLoader registers loader by name -// as the default Caddyfile loader if no others produce -// a Caddyfile. If another Caddyfile loader has already -// been set as the default, this replaces it. -// -// Do not call RegisterCaddyfileLoader on the same -// loader; that would be redundant. -func SetDefaultCaddyfileLoader(name string, loader Loader) { - defaultCaddyfileLoader = caddyfileLoader{name: name, loader: loader} -} - -// loadCaddyfileInput iterates the registered Caddyfile loaders -// and, if needed, calls the default loader, to load a Caddyfile. -// It is an error if any of the loaders return an error or if -// more than one loader returns a Caddyfile. -func loadCaddyfileInput(serverType string) (Input, error) { - var loadedBy string - var caddyfileToUse Input - for _, l := range caddyfileLoaders { - cdyfile, err := l.loader.Load(serverType) - if err != nil { - return nil, fmt.Errorf("loading Caddyfile via %s: %v", l.name, err) - } - if cdyfile != nil { - if caddyfileToUse != nil { - return nil, fmt.Errorf("Caddyfile loaded multiple times; first by %s, then by %s", loadedBy, l.name) - } - loaderUsed = l - caddyfileToUse = cdyfile - loadedBy = l.name - } - } - if caddyfileToUse == nil && defaultCaddyfileLoader.loader != nil { - cdyfile, err := defaultCaddyfileLoader.loader.Load(serverType) - if err != nil { - return nil, err - } - if cdyfile != nil { - loaderUsed = defaultCaddyfileLoader - caddyfileToUse = cdyfile - } - } - return caddyfileToUse, nil -} - -// caddyfileLoader pairs the name of a loader to the loader. -type caddyfileLoader struct { - name string - loader Loader -} - -var ( - defaultCaddyfileLoader caddyfileLoader // the default loader if all else fail - loaderUsed caddyfileLoader // the loader that was used (relevant for reloads) -) diff --git a/replacer.go b/replacer.go new file mode 100644 index 00000000000..297dd935c69 --- /dev/null +++ b/replacer.go @@ -0,0 +1,445 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddy + +import ( + "bytes" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + "sync" + "time" + + "go.uber.org/zap" +) + +// NewReplacer returns a new Replacer. +func NewReplacer() *Replacer { + rep := &Replacer{ + static: make(map[string]any), + mapMutex: &sync.RWMutex{}, + } + rep.providers = []replacementProvider{ + globalDefaultReplacementProvider{}, + fileReplacementProvider{}, + ReplacerFunc(rep.fromStatic), + } + return rep +} + +// NewEmptyReplacer returns a new Replacer, +// without the global default replacements. +func NewEmptyReplacer() *Replacer { + rep := &Replacer{ + static: make(map[string]any), + mapMutex: &sync.RWMutex{}, + } + rep.providers = []replacementProvider{ + ReplacerFunc(rep.fromStatic), + } + return rep +} + +// Replacer can replace values in strings. +// A default/empty Replacer is not valid; +// use NewReplacer to make one. +type Replacer struct { + providers []replacementProvider + static map[string]any + mapMutex *sync.RWMutex +} + +// WithoutFile returns a copy of the current Replacer +// without support for the {file.*} placeholder, which +// may be unsafe in some contexts. +// +// EXPERIMENTAL: Subject to change or removal. +func (r *Replacer) WithoutFile() *Replacer { + rep := &Replacer{static: r.static} + for _, v := range r.providers { + if _, ok := v.(fileReplacementProvider); ok { + continue + } + rep.providers = append(rep.providers, v) + } + return rep +} + +// Map adds mapFunc to the list of value providers. +// mapFunc will be executed only at replace-time. +func (r *Replacer) Map(mapFunc ReplacerFunc) { + r.providers = append(r.providers, mapFunc) +} + +// Set sets a custom variable to a static value. +func (r *Replacer) Set(variable string, value any) { + r.mapMutex.Lock() + r.static[variable] = value + r.mapMutex.Unlock() +} + +// Get gets a value from the replacer. It returns +// the value and whether the variable was known. +func (r *Replacer) Get(variable string) (any, bool) { + for _, mapFunc := range r.providers { + if val, ok := mapFunc.replace(variable); ok { + return val, true + } + } + return nil, false +} + +// GetString is the same as Get, but coerces the value to a +// string representation as efficiently as possible. +func (r *Replacer) GetString(variable string) (string, bool) { + s, found := r.Get(variable) + return ToString(s), found +} + +// Delete removes a variable with a static value +// that was created using Set. +func (r *Replacer) Delete(variable string) { + r.mapMutex.Lock() + delete(r.static, variable) + r.mapMutex.Unlock() +} + +// fromStatic provides values from r.static. +func (r *Replacer) fromStatic(key string) (any, bool) { + r.mapMutex.RLock() + defer r.mapMutex.RUnlock() + val, ok := r.static[key] + return val, ok +} + +// ReplaceOrErr is like ReplaceAll, but any placeholders +// that are empty or not recognized will cause an error to +// be returned. +func (r *Replacer) ReplaceOrErr(input string, errOnEmpty, errOnUnknown bool) (string, error) { + return r.replace(input, "", false, errOnEmpty, errOnUnknown, nil) +} + +// ReplaceKnown is like ReplaceAll but only replaces +// placeholders that are known (recognized). Unrecognized +// placeholders will remain in the output. +func (r *Replacer) ReplaceKnown(input, empty string) string { + out, _ := r.replace(input, empty, false, false, false, nil) + return out +} + +// ReplaceAll efficiently replaces placeholders in input with +// their values. All placeholders are replaced in the output +// whether they are recognized or not. Values that are empty +// string will be substituted with empty. +func (r *Replacer) ReplaceAll(input, empty string) string { + out, _ := r.replace(input, empty, true, false, false, nil) + return out +} + +// ReplaceFunc is the same as ReplaceAll, but calls f for every +// replacement to be made, in case f wants to change or inspect +// the replacement. +func (r *Replacer) ReplaceFunc(input string, f ReplacementFunc) (string, error) { + return r.replace(input, "", true, false, false, f) +} + +func (r *Replacer) replace(input, empty string, + treatUnknownAsEmpty, errOnEmpty, errOnUnknown bool, + f ReplacementFunc, +) (string, error) { + if !strings.Contains(input, string(phOpen)) && !strings.Contains(input, string(phClose)) { + return input, nil + } + + var sb strings.Builder + + // it is reasonable to assume that the output + // will be approximately as long as the input + sb.Grow(len(input)) + + // iterate the input to find each placeholder + var lastWriteCursor int + + // fail fast if too many placeholders are unclosed + var unclosedCount int + +scan: + for i := 0; i < len(input); i++ { + // check for escaped braces + if i > 0 && input[i-1] == phEscape && (input[i] == phClose || input[i] == phOpen) { + sb.WriteString(input[lastWriteCursor : i-1]) + lastWriteCursor = i + continue + } + + if input[i] != phOpen { + continue + } + + // our iterator is now on an unescaped open brace (start of placeholder) + + // too many unclosed placeholders in absolutely ridiculous input can be extremely slow (issue #4170) + if unclosedCount > 100 { + return "", fmt.Errorf("too many unclosed placeholders") + } + + // find the end of the placeholder + end := strings.Index(input[i:], string(phClose)) + i + if end < i { + unclosedCount++ + continue + } + + // if necessary look for the first closing brace that is not escaped + for end > 0 && end < len(input)-1 && input[end-1] == phEscape { + nextEnd := strings.Index(input[end+1:], string(phClose)) + if nextEnd < 0 { + unclosedCount++ + continue scan + } + end += nextEnd + 1 + } + + // write the substring from the last cursor to this point + sb.WriteString(input[lastWriteCursor:i]) + + // trim opening bracket + key := input[i+1 : end] + + // try to get a value for this key, handle empty values accordingly + val, found := r.Get(key) + if !found { + // placeholder is unknown (unrecognized); handle accordingly + if errOnUnknown { + return "", fmt.Errorf("unrecognized placeholder %s%s%s", + string(phOpen), key, string(phClose)) + } else if !treatUnknownAsEmpty { + // if treatUnknownAsEmpty is true, we'll handle an empty + // val later; so only continue otherwise + lastWriteCursor = i + continue + } + } + + // apply any transformations + if f != nil { + var err error + val, err = f(key, val) + if err != nil { + return "", err + } + } + + // convert val to a string as efficiently as possible + valStr := ToString(val) + + // write the value; if it's empty, either return + // an error or write a default value + if valStr == "" { + if errOnEmpty { + return "", fmt.Errorf("evaluated placeholder %s%s%s is empty", + string(phOpen), key, string(phClose)) + } else if empty != "" { + sb.WriteString(empty) + } + } else { + sb.WriteString(valStr) + } + + // advance cursor to end of placeholder + i = end + lastWriteCursor = i + 1 + } + + // flush any unwritten remainder + sb.WriteString(input[lastWriteCursor:]) + + return sb.String(), nil +} + +// ToString returns val as a string, as efficiently as possible. +// EXPERIMENTAL: may be changed or removed later. +func ToString(val any) string { + switch v := val.(type) { + case nil: + return "" + case string: + return v + case fmt.Stringer: + return v.String() + case error: + return v.Error() + case byte: + return string(v) + case []byte: + return string(v) + case []rune: + return string(v) + case int: + return strconv.Itoa(v) + case int32: + return strconv.Itoa(int(v)) + case int64: + return strconv.Itoa(int(v)) + case uint: + return strconv.FormatUint(uint64(v), 10) + case uint32: + return strconv.FormatUint(uint64(v), 10) + case uint64: + return strconv.FormatUint(v, 10) + case float32: + return strconv.FormatFloat(float64(v), 'f', -1, 32) + case float64: + return strconv.FormatFloat(v, 'f', -1, 64) + case bool: + if v { + return "true" + } + return "false" + default: + return fmt.Sprintf("%+v", v) + } +} + +// ReplacerFunc is a function that returns a replacement for the +// given key along with true if the function is able to service +// that key (even if the value is blank). If the function does +// not recognize the key, false should be returned. +type ReplacerFunc func(key string) (any, bool) + +func (f ReplacerFunc) replace(key string) (any, bool) { + return f(key) +} + +// replacementProvider is a type that can provide replacements +// for placeholders. Allows for type assertion to determine +// which type of provider it is. +type replacementProvider interface { + replace(key string) (any, bool) +} + +// fileReplacementsProvider handles {file.*} replacements, +// reading a file from disk and replacing with its contents. +type fileReplacementProvider struct{} + +func (f fileReplacementProvider) replace(key string) (any, bool) { + if !strings.HasPrefix(key, filePrefix) { + return nil, false + } + + filename := key[len(filePrefix):] + maxSize := 1024 * 1024 + body, err := readFileIntoBuffer(filename, maxSize) + if err != nil { + wd, _ := os.Getwd() + Log().Error("placeholder: failed to read file", + zap.String("file", filename), + zap.String("working_dir", wd), + zap.Error(err)) + return nil, true + } + body = bytes.TrimSuffix(body, []byte("\n")) + body = bytes.TrimSuffix(body, []byte("\r")) + return string(body), true +} + +// globalDefaultReplacementsProvider handles replacements +// that can be used in any context, such as system variables, +// time, or environment variables. +type globalDefaultReplacementProvider struct{} + +func (f globalDefaultReplacementProvider) replace(key string) (any, bool) { + // check environment variable + const envPrefix = "env." + if strings.HasPrefix(key, envPrefix) { + return os.Getenv(key[len(envPrefix):]), true + } + + switch key { + case "system.hostname": + // OK if there is an error; just return empty string + name, _ := os.Hostname() + return name, true + case "system.slash": + return string(filepath.Separator), true + case "system.os": + return runtime.GOOS, true + case "system.wd": + // OK if there is an error; just return empty string + wd, _ := os.Getwd() + return wd, true + case "system.arch": + return runtime.GOARCH, true + case "time.now": + return nowFunc(), true + case "time.now.http": + // According to the comment for http.TimeFormat, the timezone must be in UTC + // to generate the correct format. + // https://github.com/caddyserver/caddy/issues/5773 + return nowFunc().UTC().Format(http.TimeFormat), true + case "time.now.common_log": + return nowFunc().Format("02/Jan/2006:15:04:05 -0700"), true + case "time.now.year": + return strconv.Itoa(nowFunc().Year()), true + case "time.now.unix": + return strconv.FormatInt(nowFunc().Unix(), 10), true + case "time.now.unix_ms": + return strconv.FormatInt(nowFunc().UnixNano()/int64(time.Millisecond), 10), true + } + + return nil, false +} + +// readFileIntoBuffer reads the file at filePath into a size limited buffer. +func readFileIntoBuffer(filename string, size int) ([]byte, error) { + file, err := os.Open(filename) + if err != nil { + return nil, err + } + defer file.Close() + + buffer := make([]byte, size) + n, err := file.Read(buffer) + if err != nil && err != io.EOF { + return nil, err + } + + // slice the buffer to the actual size + return buffer[:n], nil +} + +// ReplacementFunc is a function that is called when a +// replacement is being performed. It receives the +// variable (i.e. placeholder name) and the value that +// will be the replacement, and returns the value that +// will actually be the replacement, or an error. Note +// that errors are sometimes ignored by replacers. +type ReplacementFunc func(variable string, val any) (any, error) + +// nowFunc is a variable so tests can change it +// in order to obtain a deterministic time. +var nowFunc = time.Now + +// ReplacerCtxKey is the context key for a replacer. +const ReplacerCtxKey CtxKey = "replacer" + +const phOpen, phClose, phEscape = '{', '}', '\\' + +const filePrefix = "file." diff --git a/replacer_fuzz.go b/replacer_fuzz.go new file mode 100644 index 00000000000..50fb0b61107 --- /dev/null +++ b/replacer_fuzz.go @@ -0,0 +1,25 @@ +// 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 caddy + +func FuzzReplacer(data []byte) (score int) { + NewReplacer().ReplaceAll(string(data), "") + NewReplacer().ReplaceAll(NewReplacer().ReplaceAll(string(data), ""), "") + NewReplacer().ReplaceAll(NewReplacer().ReplaceAll(string(data), ""), NewReplacer().ReplaceAll(string(data), "")) + NewReplacer().ReplaceAll(string(data[:len(data)/2]), string(data[len(data)/2:])) + return 0 +} diff --git a/replacer_test.go b/replacer_test.go new file mode 100644 index 00000000000..1c1a7048f33 --- /dev/null +++ b/replacer_test.go @@ -0,0 +1,532 @@ +// 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 ( + "fmt" + "os" + "path/filepath" + "runtime" + "sync" + "testing" +) + +func TestReplacer(t *testing.T) { + type testCase struct { + input, expect, empty string + } + + rep := testReplacer() + + // ReplaceAll + for i, tc := range []testCase{ + { + input: "{", + expect: "{", + }, + { + input: `\{`, + expect: `{`, + }, + { + input: "foo{", + expect: "foo{", + }, + { + input: `foo\{`, + expect: `foo{`, + }, + { + input: "foo{bar", + expect: "foo{bar", + }, + { + input: `foo\{bar`, + expect: `foo{bar`, + }, + { + input: "foo{bar}", + expect: "foo", + }, + { + input: `foo\{bar\}`, + expect: `foo{bar}`, + }, + { + input: "}", + expect: "}", + }, + { + input: `\}`, + expect: `}`, + }, + { + input: "{}", + expect: "", + }, + { + input: `\{\}`, + expect: `{}`, + }, + { + input: `{"json": "object"}`, + expect: "", + }, + { + input: `\{"json": "object"}`, + expect: `{"json": "object"}`, + }, + { + input: `\{"json": "object"\}`, + expect: `{"json": "object"}`, + }, + { + input: `\{"json": "object{bar}"\}`, + expect: `{"json": "object"}`, + }, + { + input: `\{"json": \{"nested": "object"\}\}`, + expect: `{"json": {"nested": "object"}}`, + }, + { + input: `\{"json": \{"nested": "{bar}"\}\}`, + expect: `{"json": {"nested": ""}}`, + }, + { + input: `pre \{"json": \{"nested": "{bar}"\}\}`, + expect: `pre {"json": {"nested": ""}}`, + }, + { + input: `\{"json": \{"nested": "{bar}"\}\} post`, + expect: `{"json": {"nested": ""}} post`, + }, + { + input: `pre \{"json": \{"nested": "{bar}"\}\} post`, + expect: `pre {"json": {"nested": ""}} post`, + }, + { + input: `{{`, + expect: "{{", + }, + { + input: `{{}`, + expect: "", + }, + { + input: `{"json": "object"\}`, + expect: "", + }, + { + input: `{unknown}`, + empty: "-", + expect: "-", + }, + { + input: `back\slashes`, + expect: `back\slashes`, + }, + { + input: `double back\\slashes`, + expect: `double back\\slashes`, + }, + { + input: `placeholder {with \{ brace} in name`, + expect: `placeholder in name`, + }, + { + input: `placeholder {with \} brace} in name`, + expect: `placeholder in name`, + }, + { + input: `placeholder {with \} \} braces} in name`, + expect: `placeholder in name`, + }, + { + input: `\{'group':'default','max_age':3600,'endpoints':[\{'url':'https://some.domain.local/a/d/g'\}],'include_subdomains':true\}`, + expect: `{'group':'default','max_age':3600,'endpoints':[{'url':'https://some.domain.local/a/d/g'}],'include_subdomains':true}`, + }, + { + input: `{}{}{}{\\\\}\\\\`, + expect: `{\\\}\\\\`, + }, + { + input: string([]byte{0x26, 0x00, 0x83, 0x7B, 0x84, 0x07, 0x5C, 0x7D, 0x84}), + expect: string([]byte{0x26, 0x00, 0x83, 0x7B, 0x84, 0x07, 0x7D, 0x84}), + }, + { + input: `\\}`, + expect: `\}`, + }, + } { + actual := rep.ReplaceAll(tc.input, tc.empty) + if actual != tc.expect { + t.Errorf("Test %d: '%s': expected '%s' but got '%s'", + i, tc.input, tc.expect, actual) + } + } +} + +func TestReplacerSet(t *testing.T) { + rep := testReplacer() + + for _, tc := range []struct { + variable string + value any + }{ + { + variable: "test1", + value: "val1", + }, + { + variable: "asdf", + value: "123", + }, + { + variable: "numbers", + value: 123.456, + }, + { + variable: "äöü", + value: "öö_äü", + }, + { + variable: "with space", + value: "space value", + }, + { + variable: "1", + value: "test-123", + }, + { + variable: "mySuper_IP", + value: "1.2.3.4", + }, + { + variable: "testEmpty", + value: "", + }, + } { + rep.Set(tc.variable, tc.value) + + // test if key is added + if val, ok := rep.static[tc.variable]; ok { + if val != tc.value { + t.Errorf("Expected value '%s' for key '%s' got '%s'", tc.value, tc.variable, val) + } + } else { + t.Errorf("Expected existing key '%s' found nothing", tc.variable) + } + } + + // test if all keys are still there (by length) + length := len(rep.static) + if len(rep.static) != 8 { + t.Errorf("Expected length '%v' got '%v'", 7, length) + } +} + +func TestReplacerReplaceKnown(t *testing.T) { + rep := Replacer{ + mapMutex: &sync.RWMutex{}, + providers: []replacementProvider{ + // split our possible vars to two functions (to test if both functions are called) + ReplacerFunc(func(key string) (val any, ok bool) { + switch key { + case "test1": + return "val1", true + case "asdf": + return "123", true + case "äöü": + return "öö_äü", true + case "with space": + return "space value", true + default: + return "NOOO", false + } + }), + ReplacerFunc(func(key string) (val any, ok bool) { + switch key { + case "1": + return "test-123", true + case "mySuper_IP": + return "1.2.3.4", true + case "testEmpty": + return "", true + default: + return "NOOO", false + } + }), + }, + } + + for _, tc := range []struct { + testInput string + expected string + }{ + { + // test vars without space + testInput: "{test1}{asdf}{äöü}{1}{with space}{mySuper_IP}", + expected: "val1123öö_äütest-123space value1.2.3.4", + }, + { + // test vars with space + testInput: "{test1} {asdf} {äöü} {1} {with space} {mySuper_IP} ", + expected: "val1 123 öö_äü test-123 space value 1.2.3.4 ", + }, + { + // test with empty val + testInput: "{test1} {testEmpty} {asdf} {1} ", + expected: "val1 EMPTY 123 test-123 ", + }, + { + // test vars with not finished placeholders + testInput: "{te{test1}{as{{df{1}", + expected: "{teval1{as{{dftest-123", + }, + { + // test with non existing vars + testInput: "{test1} {nope} {1} ", + expected: "val1 {nope} test-123 ", + }, + } { + actual := rep.ReplaceKnown(tc.testInput, "EMPTY") + + // test if all are replaced as expected + if actual != tc.expected { + t.Errorf("Expected '%s' got '%s' for '%s'", tc.expected, actual, tc.testInput) + } + } +} + +func TestReplacerDelete(t *testing.T) { + rep := Replacer{ + mapMutex: &sync.RWMutex{}, + static: map[string]any{ + "key1": "val1", + "key2": "val2", + "key3": "val3", + "key4": "val4", + }, + } + + startLen := len(rep.static) + + toDel := []string{ + "key2", "key4", + } + + for _, key := range toDel { + rep.Delete(key) + + // test if key is removed from static map + if _, ok := rep.static[key]; ok { + t.Errorf("Expected '%s' to be removed. It is still in static map.", key) + } + } + + // check if static slice is smaller + expected := startLen - len(toDel) + actual := len(rep.static) + if len(rep.static) != expected { + t.Errorf("Expected length '%v' got length '%v'", expected, actual) + } +} + +func TestReplacerMap(t *testing.T) { + rep := testReplacer() + + for i, tc := range []ReplacerFunc{ + func(key string) (val any, ok bool) { + return "", false + }, + func(key string) (val any, ok bool) { + return "", false + }, + } { + rep.Map(tc) + + // test if function (which listens on specific key) is added by checking length + if len(rep.providers) == i+1 { + // check if the last function is the one we just added + pTc := fmt.Sprintf("%p", tc) + pRep := fmt.Sprintf("%p", rep.providers[i]) + if pRep != pTc { + t.Errorf("Expected func pointer '%s' got '%s'", pTc, pRep) + } + } else { + t.Errorf("Expected providers length '%v' got length '%v'", i+1, len(rep.providers)) + } + } +} + +func TestReplacerNew(t *testing.T) { + repl := NewReplacer() + + if len(repl.providers) != 3 { + t.Errorf("Expected providers length '%v' got length '%v'", 3, len(repl.providers)) + } + + // test if default global replacements are added as the first provider + hostname, _ := os.Hostname() + wd, _ := os.Getwd() + os.Setenv("CADDY_REPLACER_TEST", "envtest") + defer os.Setenv("CADDY_REPLACER_TEST", "") + + for _, tc := range []struct { + variable string + value string + }{ + { + variable: "system.hostname", + value: hostname, + }, + { + variable: "system.slash", + value: string(filepath.Separator), + }, + { + variable: "system.os", + value: runtime.GOOS, + }, + { + variable: "system.arch", + value: runtime.GOARCH, + }, + { + variable: "system.wd", + value: wd, + }, + { + variable: "env.CADDY_REPLACER_TEST", + value: "envtest", + }, + } { + if val, ok := repl.providers[0].replace(tc.variable); ok { + if val != tc.value { + t.Errorf("Expected value '%s' for key '%s' got '%s'", tc.value, tc.variable, val) + } + } else { + t.Errorf("Expected key '%s' to be recognized by first provider", tc.variable) + } + } + + // test if file provider is added as the second provider + for _, tc := range []struct { + variable string + value string + }{ + { + variable: "file.caddytest/integration/testdata/foo.txt", + value: "foo", + }, + { + variable: "file.caddytest/integration/testdata/foo_with_trailing_newline.txt", + value: "foo", + }, + { + variable: "file.caddytest/integration/testdata/foo_with_multiple_trailing_newlines.txt", + value: "foo" + getEOL(), + }, + } { + if val, ok := repl.providers[1].replace(tc.variable); ok { + if val != tc.value { + t.Errorf("Expected value '%s' for key '%s' got '%s'", tc.value, tc.variable, val) + } + } else { + t.Errorf("Expected key '%s' to be recognized by second provider", tc.variable) + } + } +} + +func getEOL() string { + if os.PathSeparator == '\\' { + return "\r\n" // Windows EOL + } + return "\n" // Unix and modern macOS EOL +} + +func TestReplacerNewWithoutFile(t *testing.T) { + repl := NewReplacer().WithoutFile() + + for _, tc := range []struct { + variable string + value string + notFound bool + }{ + { + variable: "file.caddytest/integration/testdata/foo.txt", + notFound: true, + }, + { + variable: "system.os", + value: runtime.GOOS, + }, + } { + if val, ok := repl.Get(tc.variable); ok && !tc.notFound { + if val != tc.value { + t.Errorf("Expected value '%s' for key '%s' got '%s'", tc.value, tc.variable, val) + } + } else if !tc.notFound { + t.Errorf("Expected key '%s' to be recognized", tc.variable) + } + } +} + +func BenchmarkReplacer(b *testing.B) { + type testCase struct { + name, input, empty string + } + + rep := testReplacer() + rep.Set("str", "a string") + rep.Set("int", 123.456) + + for _, bm := range []testCase{ + { + name: "no placeholder", + input: `simple string`, + }, + { + name: "string replacement", + input: `str={str}`, + }, + { + name: "int replacement", + input: `int={int}`, + }, + { + name: "placeholder", + input: `{"json": "object"}`, + }, + { + name: "escaped placeholder", + input: `\{"json": \{"nested": "{bar}"\}\}`, + }, + } { + b.Run(bm.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + rep.ReplaceAll(bm.input, bm.empty) + } + }) + } +} + +func testReplacer() Replacer { + return Replacer{ + providers: make([]replacementProvider, 0), + static: make(map[string]any), + mapMutex: &sync.RWMutex{}, + } +} diff --git a/rlimit_nonposix.go b/rlimit_nonposix.go deleted file mode 100644 index fbd58a7455b..00000000000 --- a/rlimit_nonposix.go +++ /dev/null @@ -1,8 +0,0 @@ -// +build windows plan9 nacl - -package caddy - -// checkFdlimit issues a warning if the OS limit for -// max file descriptors is below a recommended minimum. -func checkFdlimit() { -} diff --git a/rlimit_posix.go b/rlimit_posix.go deleted file mode 100644 index 90e1568b337..00000000000 --- a/rlimit_posix.go +++ /dev/null @@ -1,23 +0,0 @@ -// +build !windows,!plan9,!nacl - -package caddy - -import ( - "fmt" - "syscall" -) - -// checkFdlimit issues a warning if the OS limit for -// max file descriptors is below a recommended minimum. -func checkFdlimit() { - const min = 8192 - - // Warn if ulimit is too low for production sites - rlimit := &syscall.Rlimit{} - err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, rlimit) - if err == nil && rlimit.Cur < min { - fmt.Printf("WARNING: File descriptor limit %d is too low for production servers. "+ - "At least %d is recommended. Fix with \"ulimit -n %d\".\n", rlimit.Cur, min, min) - } - -} diff --git a/service_windows.go b/service_windows.go new file mode 100644 index 00000000000..4720dbaa47b --- /dev/null +++ b/service_windows.go @@ -0,0 +1,61 @@ +// 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 ( + "os" + "path/filepath" + + "golang.org/x/sys/windows/svc" + + "github.com/caddyserver/caddy/v2/notify" +) + +func init() { + isService, err := svc.IsWindowsService() + if err != nil || !isService { + return + } + + // Windows services always start in the system32 directory, try to + // switch into the directory where the caddy executable is. + execPath, err := os.Executable() + if err == nil { + _ = os.Chdir(filepath.Dir(execPath)) + } + + go func() { + _ = svc.Run("", runner{}) + }() +} + +type runner struct{} + +func (runner) Execute(args []string, request <-chan svc.ChangeRequest, status chan<- svc.Status) (bool, uint32) { + notify.SetGlobalStatus(status) + status <- svc.Status{State: svc.StartPending} + + for { + req := <-request + switch req.Cmd { + case svc.Interrogate: + status <- req.CurrentStatus + case svc.Stop, svc.Shutdown: + status <- svc.Status{State: svc.StopPending} + exitProcessFromSignal("SIGINT") + return false, 0 + } + } +} diff --git a/sigtrap.go b/sigtrap.go index 58d199c9e6e..b0be1c73146 100644 --- a/sigtrap.go +++ b/sigtrap.go @@ -1,25 +1,39 @@ +// 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 ( - "log" + "context" "os" "os/signal" - "sync" + + "go.uber.org/zap" ) -// TrapSignals create signal handlers for all applicable signals for this -// system. If your Go program uses signals, this is a rather invasive -// function; best to implement them yourself in that case. Signals are not -// required for the caddy package to function properly, but this is a -// convenient way to allow the user to control this part of your program. +// TrapSignals create signal/interrupt handlers as best it can for the +// current OS. This is a rather invasive function to call in a Go program +// that captures signals already, so in that case it would be better to +// implement these handlers yourself. func TrapSignals() { trapSignalsCrossPlatform() trapSignalsPosix() } -// trapSignalsCrossPlatform captures SIGINT, which triggers forceful -// shutdown that executes shutdown callbacks first. A second interrupt -// signal will exit the process immediately. +// trapSignalsCrossPlatform captures SIGINT or interrupt (depending +// on the OS), which initiates a graceful shutdown. A second SIGINT +// or interrupt will forcefully exit the process immediately. func trapSignalsCrossPlatform() { go func() { shutdown := make(chan os.Signal, 1) @@ -29,60 +43,28 @@ func trapSignalsCrossPlatform() { <-shutdown if i > 0 { - log.Println("[INFO] SIGINT: Force quit") - if PidFile != "" { - os.Remove(PidFile) - } - os.Exit(2) - } - - log.Println("[INFO] SIGINT: Shutting down") - - if PidFile != "" { - os.Remove(PidFile) + Log().Warn("force quit", zap.String("signal", "SIGINT")) + os.Exit(ExitCodeForceQuit) } - go func() { - os.Exit(executeShutdownCallbacks("SIGINT")) - }() + Log().Info("shutting down", zap.String("signal", "SIGINT")) + go exitProcessFromSignal("SIGINT") } }() } -// executeShutdownCallbacks executes the shutdown callbacks as initiated -// by signame. It logs any errors and returns the recommended exit status. -// This function is idempotent; subsequent invocations always return 0. -func executeShutdownCallbacks(signame string) (exitCode int) { - shutdownCallbacksOnce.Do(func() { - // execute third-party shutdown hooks - EmitEvent(ShutdownEvent, signame) - - errs := allShutdownCallbacks() - if len(errs) > 0 { - for _, err := range errs { - log.Printf("[ERROR] %s shutdown: %v", signame, err) - } - exitCode = 4 - } - }) - return +// exitProcessFromSignal exits the process from a system signal. +func exitProcessFromSignal(sigName string) { + logger := Log().With(zap.String("signal", sigName)) + exitProcess(context.TODO(), logger) } -// allShutdownCallbacks executes all the shutdown callbacks -// for all the instances, and returns all the errors generated -// during their execution. An error executing one shutdown -// callback does not stop execution of others. Only one shutdown -// callback is executed at a time. -func allShutdownCallbacks() []error { - var errs []error - instancesMu.Lock() - for _, inst := range instances { - errs = append(errs, inst.ShutdownCallbacks()...) - } - instancesMu.Unlock() - return errs -} - -// shutdownCallbacksOnce ensures that shutdown callbacks -// for all instances are only executed once. -var shutdownCallbacksOnce sync.Once +// Exit codes. Generally, you should NOT +// automatically restart the process if the +// exit code is ExitCodeFailedStartup (1). +const ( + ExitCodeSuccess = iota + ExitCodeFailedStartup + ExitCodeForceQuit + ExitCodeFailedQuit +) diff --git a/sigtrap_nonposix.go b/sigtrap_nonposix.go index 96f68119d43..f80f593b03d 100644 --- a/sigtrap_nonposix.go +++ b/sigtrap_nonposix.go @@ -1,4 +1,18 @@ -// +build windows plan9 nacl +// 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 windows || plan9 || nacl || js package caddy diff --git a/sigtrap_posix.go b/sigtrap_posix.go index 0ba0b33e910..2c630612114 100644 --- a/sigtrap_posix.go +++ b/sigtrap_posix.go @@ -1,85 +1,61 @@ -// +build !windows,!plan9,!nacl +// 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 !windows && !plan9 && !nacl && !js package caddy import ( - "log" + "context" "os" "os/signal" "syscall" + + "github.com/caddyserver/certmagic" + "go.uber.org/zap" ) // trapSignalsPosix captures POSIX-only signals. func trapSignalsPosix() { + // Ignore all SIGPIPE signals to prevent weird issues with systemd: https://github.com/dunglas/frankenphp/issues/1020 + // Docker/Moby has a similar hack: https://github.com/moby/moby/blob/d828b032a87606ae34267e349bf7f7ccb1f6495a/cmd/dockerd/docker.go#L87-L90 + signal.Ignore(syscall.SIGPIPE) + go func() { sigchan := make(chan os.Signal, 1) signal.Notify(sigchan, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2) for sig := range sigchan { switch sig { - case syscall.SIGTERM: - log.Println("[INFO] SIGTERM: Terminating process") - if PidFile != "" { - os.Remove(PidFile) - } - os.Exit(0) - case syscall.SIGQUIT: - log.Println("[INFO] SIGQUIT: Shutting down") - exitCode := executeShutdownCallbacks("SIGQUIT") - err := Stop() - if err != nil { - log.Printf("[ERROR] SIGQUIT stop: %v", err) - exitCode = 3 - } - if PidFile != "" { - os.Remove(PidFile) - } - os.Exit(exitCode) + Log().Info("quitting process immediately", zap.String("signal", "SIGQUIT")) + certmagic.CleanUpOwnLocks(context.TODO(), Log()) // try to clean up locks anyway, it's important + os.Exit(ExitCodeForceQuit) - case syscall.SIGHUP: - log.Println("[INFO] SIGHUP: Hanging up") - err := Stop() - if err != nil { - log.Printf("[ERROR] SIGHUP stop: %v", err) - } + case syscall.SIGTERM: + Log().Info("shutting down apps, then terminating", zap.String("signal", "SIGTERM")) + exitProcessFromSignal("SIGTERM") case syscall.SIGUSR1: - log.Println("[INFO] SIGUSR1: Reloading") - - // Start with the existing Caddyfile - caddyfileToUse, inst, err := getCurrentCaddyfile() - if err != nil { - log.Printf("[ERROR] SIGUSR1: %v", err) - continue - } - if loaderUsed.loader == nil { - // This also should never happen - log.Println("[ERROR] SIGUSR1: no Caddyfile loader with which to reload Caddyfile") - continue - } - - // Load the updated Caddyfile - newCaddyfile, err := loaderUsed.loader.Load(inst.serverType) - if err != nil { - log.Printf("[ERROR] SIGUSR1: loading updated Caddyfile: %v", err) - continue - } - if newCaddyfile != nil { - caddyfileToUse = newCaddyfile - } - - // Kick off the restart; our work is done - inst, err = inst.Restart(caddyfileToUse) - if err != nil { - log.Printf("[ERROR] SIGUSR1: %v", err) - } + Log().Info("not implemented", zap.String("signal", "SIGUSR1")) case syscall.SIGUSR2: - log.Println("[INFO] SIGUSR2: Upgrading") - if err := Upgrade(); err != nil { - log.Printf("[ERROR] SIGUSR2: upgrading: %v", err) - } + Log().Info("not implemented", zap.String("signal", "SIGUSR2")) + + case syscall.SIGHUP: + // ignore; this signal is sometimes sent outside of the user's control + Log().Info("not implemented", zap.String("signal", "SIGHUP")) } } }() diff --git a/startupshutdown/startupshutdown.go b/startupshutdown/startupshutdown.go deleted file mode 100644 index a940f138264..00000000000 --- a/startupshutdown/startupshutdown.go +++ /dev/null @@ -1,73 +0,0 @@ -package startupshutdown - -import ( - "log" - "os" - "os/exec" - "strings" - - "github.com/mholt/caddy" -) - -func init() { - caddy.RegisterPlugin("startup", caddy.Plugin{Action: Startup}) - caddy.RegisterPlugin("shutdown", caddy.Plugin{Action: Shutdown}) -} - -// Startup registers a startup callback to execute during server start. -func Startup(c *caddy.Controller) error { - return registerCallback(c, c.OnFirstStartup) -} - -// Shutdown registers a shutdown callback to execute during server stop. -func Shutdown(c *caddy.Controller) error { - return registerCallback(c, c.OnFinalShutdown) -} - -// registerCallback registers a callback function to execute by -// using c to parse the directive. It registers the callback -// to be executed using registerFunc. -func registerCallback(c *caddy.Controller, registerFunc func(func() error)) error { - var funcs []func() error - - for c.Next() { - args := c.RemainingArgs() - if len(args) == 0 { - return c.ArgErr() - } - - nonblock := false - if len(args) > 1 && args[len(args)-1] == "&" { - // Run command in background; non-blocking - nonblock = true - args = args[:len(args)-1] - } - - command, args, err := caddy.SplitCommandAndArgs(strings.Join(args, " ")) - if err != nil { - return c.Err(err.Error()) - } - - fn := func() error { - cmd := exec.Command(command, args...) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - if nonblock { - log.Printf("[INFO] Nonblocking Command:\"%s %s\"", command, strings.Join(args, " ")) - return cmd.Start() - } - log.Printf("[INFO] Blocking Command:\"%s %s\"", command, strings.Join(args, " ")) - return cmd.Run() - } - - funcs = append(funcs, fn) - } - - return c.OncePerServerBlock(func() error { - for _, fn := range funcs { - registerFunc(fn) - } - return nil - }) -} diff --git a/startupshutdown/startupshutdown_test.go b/startupshutdown/startupshutdown_test.go deleted file mode 100644 index 01af82db74a..00000000000 --- a/startupshutdown/startupshutdown_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package startupshutdown - -import ( - "os" - "path/filepath" - "strconv" - "testing" - "time" - - "github.com/mholt/caddy" -) - -// The Startup function's tests are symmetrical to Shutdown tests, -// because the Startup and Shutdown functions share virtually the -// same functionality -func TestStartup(t *testing.T) { - tempDirPath := os.TempDir() - - testDir := filepath.Join(tempDirPath, "temp_dir_for_testing_startupshutdown") - defer func() { - // clean up after non-blocking startup function quits - time.Sleep(500 * time.Millisecond) - os.RemoveAll(testDir) - }() - osSenitiveTestDir := filepath.FromSlash(testDir) - os.RemoveAll(osSenitiveTestDir) // start with a clean slate - - var registeredFunction func() error - fakeRegister := func(fn func() error) { - registeredFunction = fn - } - - tests := []struct { - input string - shouldExecutionErr bool - shouldRemoveErr bool - }{ - // test case #0 tests proper functionality blocking commands - {"startup mkdir " + osSenitiveTestDir, false, false}, - - // test case #1 tests proper functionality of non-blocking commands - {"startup mkdir " + osSenitiveTestDir + " &", false, true}, - - // test case #2 tests handling of non-existent commands - {"startup " + strconv.Itoa(int(time.Now().UnixNano())), true, true}, - } - - for i, test := range tests { - c := caddy.NewTestController("", test.input) - err := registerCallback(c, fakeRegister) - if err != nil { - t.Errorf("Expected no errors, got: %v", err) - } - if registeredFunction == nil { - t.Fatalf("Expected function to be registered, but it wasn't") - } - err = registeredFunction() - if err != nil && !test.shouldExecutionErr { - t.Errorf("Test %d received an error of:\n%v", i, err) - } - err = os.Remove(osSenitiveTestDir) - if err != nil && !test.shouldRemoveErr { - t.Errorf("Test %d received an error of:\n%v", i, err) - } - } -} diff --git a/storage.go b/storage.go new file mode 100644 index 00000000000..62f9b1c650e --- /dev/null +++ b/storage.go @@ -0,0 +1,160 @@ +// 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 ( + "os" + "path/filepath" + "runtime" + + "github.com/caddyserver/certmagic" + "go.uber.org/zap" +) + +// StorageConverter is a type that can convert itself +// to a valid, usable certmagic.Storage value. (The +// value might be short-lived.) This interface allows +// us to adapt any CertMagic storage implementation +// into a consistent API for Caddy configuration. +type StorageConverter interface { + CertMagicStorage() (certmagic.Storage, error) +} + +// HomeDir returns the best guess of the current user's home +// directory from environment variables. If unknown, "." (the +// current directory) is returned instead, except GOOS=android, +// which returns "/sdcard". +func HomeDir() string { + home := homeDirUnsafe() + if home == "" && runtime.GOOS == "android" { + home = "/sdcard" + } + if home == "" { + home = "." + } + return home +} + +// homeDirUnsafe is a low-level function that returns +// the user's home directory from environment +// variables. Careful: if it cannot be determined, an +// empty string is returned. If not accounting for +// that case, use HomeDir() instead; otherwise you +// may end up using the root of the file system. +func homeDirUnsafe() string { + home := os.Getenv("HOME") + if home == "" && runtime.GOOS == "windows" { + drive := os.Getenv("HOMEDRIVE") + path := os.Getenv("HOMEPATH") + home = drive + path + if drive == "" || path == "" { + home = os.Getenv("USERPROFILE") + } + } + if home == "" && runtime.GOOS == "plan9" { + home = os.Getenv("home") + } + return home +} + +// AppConfigDir returns the directory where to store user's config. +// +// If XDG_CONFIG_HOME is set, it returns: $XDG_CONFIG_HOME/caddy. +// Otherwise, os.UserConfigDir() is used; if successful, it appends +// "Caddy" (Windows & Mac) or "caddy" (every other OS) to the path. +// If it returns an error, the fallback path "./caddy" is returned. +// +// The config directory is not guaranteed to be different from +// AppDataDir(). +// +// Unlike os.UserConfigDir(), this function prefers the +// XDG_CONFIG_HOME env var on all platforms, not just Unix. +// +// Ref: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html +func AppConfigDir() string { + if basedir := os.Getenv("XDG_CONFIG_HOME"); basedir != "" { + return filepath.Join(basedir, "caddy") + } + basedir, err := os.UserConfigDir() + if err != nil { + Log().Warn("unable to determine directory for user configuration; falling back to current directory", zap.Error(err)) + return "./caddy" + } + subdir := "caddy" + switch runtime.GOOS { + case "windows", "darwin": + subdir = "Caddy" + } + return filepath.Join(basedir, subdir) +} + +// AppDataDir returns a directory path that is suitable for storing +// application data on disk. It uses the environment for finding the +// best place to store data, and appends a "caddy" or "Caddy" (depending +// on OS and environment) subdirectory. +// +// For a base directory path: +// If XDG_DATA_HOME is set, it returns: $XDG_DATA_HOME/caddy; otherwise, +// on Windows it returns: %AppData%/Caddy, +// on Mac: $HOME/Library/Application Support/Caddy, +// on Plan9: $home/lib/caddy, +// on Android: $HOME/caddy, +// and on everything else: $HOME/.local/share/caddy. +// +// If a data directory cannot be determined, it returns "./caddy" +// (this is not ideal, and the environment should be fixed). +// +// The data directory is not guaranteed to be different from AppConfigDir(). +// +// Ref: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html +func AppDataDir() string { + if basedir := os.Getenv("XDG_DATA_HOME"); basedir != "" { + return filepath.Join(basedir, "caddy") + } + switch runtime.GOOS { + case "windows": + appData := os.Getenv("AppData") + if appData != "" { + return filepath.Join(appData, "Caddy") + } + case "darwin": + home := homeDirUnsafe() + if home != "" { + return filepath.Join(home, "Library", "Application Support", "Caddy") + } + case "plan9": + home := homeDirUnsafe() + if home != "" { + return filepath.Join(home, "lib", "caddy") + } + case "android": + home := homeDirUnsafe() + if home != "" { + return filepath.Join(home, "caddy") + } + default: + home := homeDirUnsafe() + if home != "" { + return filepath.Join(home, ".local", "share", "caddy") + } + } + return "./caddy" +} + +// ConfigAutosavePath is the default path to which the last config will be persisted. +var ConfigAutosavePath = filepath.Join(AppConfigDir(), "autosave.json") + +// DefaultStorage is Caddy's default storage module. +var DefaultStorage = &certmagic.FileStorage{Path: AppDataDir()} diff --git a/upgrade.go b/upgrade.go deleted file mode 100644 index f79b36dd4c3..00000000000 --- a/upgrade.go +++ /dev/null @@ -1,212 +0,0 @@ -package caddy - -import ( - "encoding/gob" - "fmt" - "io/ioutil" - "log" - "os" - "os/exec" - "sync" -) - -func init() { - // register CaddyfileInput with gob so it knows into - // which concrete type to decode an Input interface - gob.Register(CaddyfileInput{}) -} - -// IsUpgrade returns true if this process is part of an upgrade -// where a parent caddy process spawned this one to upgrade -// the binary. -func IsUpgrade() bool { - mu.Lock() - defer mu.Unlock() - return isUpgrade -} - -// Upgrade re-launches the process, preserving the listeners -// for a graceful upgrade. It does NOT load new configuration; -// it only starts the process anew with the current config. -// This makes it possible to perform zero-downtime binary upgrades. -// -// TODO: For more information when debugging, see: -// https://forum.golangbridge.org/t/bind-address-already-in-use-even-after-listener-closed/1510?u=matt -// https://github.com/mholt/shared-conn -func Upgrade() error { - log.Println("[INFO] Upgrading") - - // use existing Caddyfile; do not change configuration during upgrade - currentCaddyfile, _, err := getCurrentCaddyfile() - if err != nil { - return err - } - - if len(os.Args) == 0 { // this should never happen, but... - os.Args = []string{""} - } - - // tell the child that it's a restart - env := os.Environ() - if !IsUpgrade() { - env = append(env, "CADDY__UPGRADE=1") - } - - // prepare our payload to the child process - cdyfileGob := transferGob{ - ListenerFds: make(map[string]uintptr), - Caddyfile: currentCaddyfile, - } - - // prepare a pipe to the fork's stdin so it can get the Caddyfile - rpipe, wpipe, err := os.Pipe() - if err != nil { - return err - } - - // prepare a pipe that the child process will use to communicate - // its success with us by sending > 0 bytes - sigrpipe, sigwpipe, err := os.Pipe() - if err != nil { - return err - } - - // pass along relevant file descriptors to child process; ordering - // is very important since we rely on these being in certain positions. - extraFiles := []*os.File{sigwpipe} // fd 3 - - // add file descriptors of all the sockets - for i, j := 0, 0; ; i++ { - instancesMu.Lock() - if i >= len(instances) { - instancesMu.Unlock() - break - } - inst := instances[i] - instancesMu.Unlock() - - for _, s := range inst.servers { - gs, gracefulOk := s.server.(GracefulServer) - ln, lnOk := s.listener.(Listener) - pc, pcOk := s.packet.(PacketConn) - if gracefulOk { - if lnOk { - lnFile, _ := ln.File() - extraFiles = append(extraFiles, lnFile) - cdyfileGob.ListenerFds["tcp"+gs.Address()] = uintptr(4 + j) // 4 fds come before any of the listeners - j++ - } - if pcOk { - pcFile, _ := pc.File() - extraFiles = append(extraFiles, pcFile) - cdyfileGob.ListenerFds["udp"+gs.Address()] = uintptr(4 + j) // 4 fds come before any of the listeners - j++ - } - } - } - } - - // set up the command - cmd := exec.Command(os.Args[0], os.Args[1:]...) - cmd.Stdin = rpipe // fd 0 - cmd.Stdout = os.Stdout // fd 1 - cmd.Stderr = os.Stderr // fd 2 - cmd.ExtraFiles = extraFiles - cmd.Env = env - - // spawn the child process - err = cmd.Start() - if err != nil { - return err - } - - // immediately close our dup'ed fds and the write end of our signal pipe - for _, f := range extraFiles { - f.Close() - } - - // feed Caddyfile to the child - err = gob.NewEncoder(wpipe).Encode(cdyfileGob) - if err != nil { - return err - } - wpipe.Close() - - // determine whether child startup succeeded - answer, readErr := ioutil.ReadAll(sigrpipe) - if answer == nil || len(answer) == 0 { - cmdErr := cmd.Wait() // get exit status - errStr := fmt.Sprintf("child failed to initialize: %v", cmdErr) - if readErr != nil { - errStr += fmt.Sprintf(" - additionally, error communicating with child process: %v", readErr) - } - return fmt.Errorf(errStr) - } - - // looks like child is successful; we can exit gracefully. - log.Println("[INFO] Upgrade finished") - return Stop() -} - -// getCurrentCaddyfile gets the Caddyfile used by the -// current (first) Instance and returns both of them. -func getCurrentCaddyfile() (Input, *Instance, error) { - instancesMu.Lock() - if len(instances) == 0 { - instancesMu.Unlock() - return nil, nil, fmt.Errorf("no server instances are fully running") - } - inst := instances[0] - instancesMu.Unlock() - - currentCaddyfile := inst.caddyfileInput - if currentCaddyfile == nil { - // hmm, did spawing process forget to close stdin? Anyhow, this is unusual. - return nil, inst, fmt.Errorf("no Caddyfile to reload (was stdin left open?)") - } - return currentCaddyfile, inst, nil -} - -// signalSuccessToParent tells the parent our status using pipe at index 3. -// If this process is not a restart, this function does nothing. -// Calling this function once this process has successfully initialized -// is vital so that the parent process can unblock and kill itself. -// This function is idempotent; it executes at most once per process. -func signalSuccessToParent() { - signalParentOnce.Do(func() { - if IsUpgrade() { - ppipe := os.NewFile(3, "") // parent is reading from pipe at index 3 - _, err := ppipe.Write([]byte("success")) // we must send some bytes to the parent - if err != nil { - log.Printf("[ERROR] Communicating successful init to parent: %v", err) - } - ppipe.Close() - } - }) -} - -// signalParentOnce is used to make sure that the parent is only -// signaled once; doing so more than once breaks whatever socket is -// at fd 4 (TODO: the reason for this is still unclear - to reproduce, -// call Stop() and Start() in succession at least once after a -// restart, then try loading first host of Caddyfile in the browser -// - this was pre-v0.9; this code and godoc is borrowed from the -// implementation then, but I'm not sure if it's been fixed yet, as -// of v0.10.7). Do not use this directly; call signalSuccessToParent -// instead. -var signalParentOnce sync.Once - -// transferGob is used if this is a child process as part of -// a graceful upgrade; it is used to map listeners to their -// index in the list of inherited file descriptors. This -// variable is not safe for concurrent access. -var loadedGob transferGob - -// transferGob maps bind address to index of the file descriptor -// in the Files array passed to the child process. It also contains -// the Caddyfile contents and any other state needed by the new process. -// Used only during graceful upgrades. -type transferGob struct { - ListenerFds map[string]uintptr - Caddyfile Input -} diff --git a/usagepool.go b/usagepool.go new file mode 100644 index 00000000000..e011be961d0 --- /dev/null +++ b/usagepool.go @@ -0,0 +1,227 @@ +// 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 ( + "fmt" + "sync" + "sync/atomic" +) + +// UsagePool is a thread-safe map that pools values +// based on usage (reference counting). Values are +// only inserted if they do not already exist. There +// are two ways to add values to the pool: +// +// 1. LoadOrStore will increment usage and store the +// value immediately if it does not already exist. +// 2. LoadOrNew will atomically check for existence +// and construct the value immediately if it does +// not already exist, or increment the usage +// otherwise, then store that value in the pool. +// When the constructed value is finally deleted +// from the pool (when its usage reaches 0), it +// will be cleaned up by calling Destruct(). +// +// The use of LoadOrNew allows values to be created +// and reused and finally cleaned up only once, even +// though they may have many references throughout +// their lifespan. This is helpful, for example, when +// sharing thread-safe io.Writers that you only want +// to open and close once. +// +// There is no way to overwrite existing keys in the +// pool without first deleting it as many times as it +// was stored. Deleting too many times will panic. +// +// The implementation does not use a sync.Pool because +// UsagePool needs additional atomicity to run the +// constructor functions when creating a new value when +// LoadOrNew is used. (We could probably use sync.Pool +// but we'd still have to layer our own additional locks +// on top.) +// +// An empty UsagePool is NOT safe to use; always call +// NewUsagePool() to make a new one. +type UsagePool struct { + sync.RWMutex + pool map[any]*usagePoolVal +} + +// NewUsagePool returns a new usage pool that is ready to use. +func NewUsagePool() *UsagePool { + return &UsagePool{ + pool: make(map[any]*usagePoolVal), + } +} + +// LoadOrNew loads the value associated with key from the pool if it +// already exists. If the key doesn't exist, it will call construct +// to create a new value and then stores that in the pool. An error +// is only returned if the constructor returns an error. The loaded +// or constructed value is returned. The loaded return value is true +// if the value already existed and was loaded, or false if it was +// newly constructed. +func (up *UsagePool) LoadOrNew(key any, construct Constructor) (value any, loaded bool, err error) { + var upv *usagePoolVal + up.Lock() + upv, loaded = up.pool[key] + if loaded { + atomic.AddInt32(&upv.refs, 1) + up.Unlock() + upv.RLock() + value = upv.value + err = upv.err + upv.RUnlock() + } else { + upv = &usagePoolVal{refs: 1} + upv.Lock() + up.pool[key] = upv + up.Unlock() + value, err = construct() + if err == nil { + upv.value = value + } else { + upv.err = err + up.Lock() + // this *should* be safe, I think, because we have a + // write lock on upv, but we might also need to ensure + // that upv.err is nil before doing this, since we + // released the write lock on up during construct... + // but then again it's also after midnight... + delete(up.pool, key) + up.Unlock() + } + upv.Unlock() + } + return +} + +// LoadOrStore loads the value associated with key from the pool if it +// already exists, or stores it if it does not exist. It returns the +// value that was either loaded or stored, and true if the value already +// existed and was loaded, false if the value didn't exist and was stored. +func (up *UsagePool) LoadOrStore(key, val any) (value any, loaded bool) { + var upv *usagePoolVal + up.Lock() + upv, loaded = up.pool[key] + if loaded { + atomic.AddInt32(&upv.refs, 1) + up.Unlock() + upv.Lock() + if upv.err == nil { + value = upv.value + } else { + upv.value = val + upv.err = nil + } + upv.Unlock() + } else { + upv = &usagePoolVal{refs: 1, value: val} + up.pool[key] = upv + up.Unlock() + value = val + } + return +} + +// Range iterates the pool similarly to how sync.Map.Range() does: +// it calls f for every key in the pool, and if f returns false, +// iteration is stopped. Ranging does not affect usage counts. +// +// This method is somewhat naive and acquires a read lock on the +// entire pool during iteration, so do your best to make f() really +// fast, m'kay? +func (up *UsagePool) Range(f func(key, value any) bool) { + up.RLock() + defer up.RUnlock() + for key, upv := range up.pool { + upv.RLock() + if upv.err != nil { + upv.RUnlock() + continue + } + val := upv.value + upv.RUnlock() + if !f(key, val) { + break + } + } +} + +// Delete decrements the usage count for key and removes the +// value from the underlying map if the usage is 0. It returns +// true if the usage count reached 0 and the value was deleted. +// It panics if the usage count drops below 0; always call +// Delete precisely as many times as LoadOrStore. +func (up *UsagePool) Delete(key any) (deleted bool, err error) { + up.Lock() + upv, ok := up.pool[key] + if !ok { + up.Unlock() + return false, nil + } + refs := atomic.AddInt32(&upv.refs, -1) + if refs == 0 { + delete(up.pool, key) + up.Unlock() + upv.RLock() + val := upv.value + upv.RUnlock() + if destructor, ok := val.(Destructor); ok { + err = destructor.Destruct() + } + deleted = true + } else { + up.Unlock() + if refs < 0 { + panic(fmt.Sprintf("deleted more than stored: %#v (usage: %d)", + upv.value, upv.refs)) + } + } + return +} + +// References returns the number of references (count of usages) to a +// key in the pool, and true if the key exists, or false otherwise. +func (up *UsagePool) References(key any) (int, bool) { + up.RLock() + upv, loaded := up.pool[key] + up.RUnlock() + if loaded { + // I wonder if it'd be safer to read this value during + // our lock on the UsagePool... guess we'll see... + refs := atomic.LoadInt32(&upv.refs) + return int(refs), true + } + return 0, false +} + +// Constructor is a function that returns a new value +// that can destruct itself when it is no longer needed. +type Constructor func() (Destructor, error) + +// Destructor is a value that can clean itself up when +// it is deallocated. +type Destructor interface { + Destruct() error +} + +type usagePoolVal struct { + refs int32 // accessed atomically; must be 64-bit aligned for 32-bit systems + value any + err error + sync.RWMutex +} diff --git a/vendor/cloud.google.com/go/compute/metadata/LICENSE b/vendor/cloud.google.com/go/compute/metadata/LICENSE deleted file mode 100644 index a4c5efd822f..00000000000 --- a/vendor/cloud.google.com/go/compute/metadata/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - 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 2014 Google Inc. - - 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. diff --git a/vendor/cloud.google.com/go/compute/metadata/metadata.go b/vendor/cloud.google.com/go/compute/metadata/metadata.go deleted file mode 100644 index e708c031b95..00000000000 --- a/vendor/cloud.google.com/go/compute/metadata/metadata.go +++ /dev/null @@ -1,437 +0,0 @@ -// Copyright 2014 Google Inc. All Rights Reserved. -// -// 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 metadata provides access to Google Compute Engine (GCE) -// metadata and API service accounts. -// -// This package is a wrapper around the GCE metadata service, -// as documented at https://developers.google.com/compute/docs/metadata. -package metadata // import "cloud.google.com/go/compute/metadata" - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net" - "net/http" - "net/url" - "os" - "runtime" - "strings" - "sync" - "time" - - "golang.org/x/net/context" - "golang.org/x/net/context/ctxhttp" -) - -const ( - // metadataIP is the documented metadata server IP address. - metadataIP = "169.254.169.254" - - // metadataHostEnv is the environment variable specifying the - // GCE metadata hostname. If empty, the default value of - // metadataIP ("169.254.169.254") is used instead. - // This is variable name is not defined by any spec, as far as - // I know; it was made up for the Go package. - metadataHostEnv = "GCE_METADATA_HOST" - - userAgent = "gcloud-golang/0.1" -) - -type cachedValue struct { - k string - trim bool - mu sync.Mutex - v string -} - -var ( - projID = &cachedValue{k: "project/project-id", trim: true} - projNum = &cachedValue{k: "project/numeric-project-id", trim: true} - instID = &cachedValue{k: "instance/id", trim: true} -) - -var ( - metaClient = &http.Client{ - Transport: &http.Transport{ - Dial: (&net.Dialer{ - Timeout: 2 * time.Second, - KeepAlive: 30 * time.Second, - }).Dial, - ResponseHeaderTimeout: 2 * time.Second, - }, - } - subscribeClient = &http.Client{ - Transport: &http.Transport{ - Dial: (&net.Dialer{ - Timeout: 2 * time.Second, - KeepAlive: 30 * time.Second, - }).Dial, - }, - } -) - -// NotDefinedError is returned when requested metadata is not defined. -// -// The underlying string is the suffix after "/computeMetadata/v1/". -// -// This error is not returned if the value is defined to be the empty -// string. -type NotDefinedError string - -func (suffix NotDefinedError) Error() string { - return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix)) -} - -// Get returns a value from the metadata service. -// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/". -// -// If the GCE_METADATA_HOST environment variable is not defined, a default of -// 169.254.169.254 will be used instead. -// -// If the requested metadata is not defined, the returned error will -// be of type NotDefinedError. -func Get(suffix string) (string, error) { - val, _, err := getETag(metaClient, suffix) - return val, err -} - -// getETag returns a value from the metadata service as well as the associated -// ETag using the provided client. This func is otherwise equivalent to Get. -func getETag(client *http.Client, suffix string) (value, etag string, err error) { - // Using a fixed IP makes it very difficult to spoof the metadata service in - // a container, which is an important use-case for local testing of cloud - // deployments. To enable spoofing of the metadata service, the environment - // variable GCE_METADATA_HOST is first inspected to decide where metadata - // requests shall go. - host := os.Getenv(metadataHostEnv) - if host == "" { - // Using 169.254.169.254 instead of "metadata" here because Go - // binaries built with the "netgo" tag and without cgo won't - // know the search suffix for "metadata" is - // ".google.internal", and this IP address is documented as - // being stable anyway. - host = metadataIP - } - url := "http://" + host + "/computeMetadata/v1/" + suffix - req, _ := http.NewRequest("GET", url, nil) - req.Header.Set("Metadata-Flavor", "Google") - req.Header.Set("User-Agent", userAgent) - res, err := client.Do(req) - if err != nil { - return "", "", err - } - defer res.Body.Close() - if res.StatusCode == http.StatusNotFound { - return "", "", NotDefinedError(suffix) - } - if res.StatusCode != 200 { - return "", "", fmt.Errorf("status code %d trying to fetch %s", res.StatusCode, url) - } - all, err := ioutil.ReadAll(res.Body) - if err != nil { - return "", "", err - } - return string(all), res.Header.Get("Etag"), nil -} - -func getTrimmed(suffix string) (s string, err error) { - s, err = Get(suffix) - s = strings.TrimSpace(s) - return -} - -func (c *cachedValue) get() (v string, err error) { - defer c.mu.Unlock() - c.mu.Lock() - if c.v != "" { - return c.v, nil - } - if c.trim { - v, err = getTrimmed(c.k) - } else { - v, err = Get(c.k) - } - if err == nil { - c.v = v - } - return -} - -var ( - onGCEOnce sync.Once - onGCE bool -) - -// OnGCE reports whether this process is running on Google Compute Engine. -func OnGCE() bool { - onGCEOnce.Do(initOnGCE) - return onGCE -} - -func initOnGCE() { - onGCE = testOnGCE() -} - -func testOnGCE() bool { - // The user explicitly said they're on GCE, so trust them. - if os.Getenv(metadataHostEnv) != "" { - return true - } - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - resc := make(chan bool, 2) - - // Try two strategies in parallel. - // See https://github.com/GoogleCloudPlatform/google-cloud-go/issues/194 - go func() { - req, _ := http.NewRequest("GET", "http://"+metadataIP, nil) - req.Header.Set("User-Agent", userAgent) - res, err := ctxhttp.Do(ctx, metaClient, req) - if err != nil { - resc <- false - return - } - defer res.Body.Close() - resc <- res.Header.Get("Metadata-Flavor") == "Google" - }() - - go func() { - addrs, err := net.LookupHost("metadata.google.internal") - if err != nil || len(addrs) == 0 { - resc <- false - return - } - resc <- strsContains(addrs, metadataIP) - }() - - tryHarder := systemInfoSuggestsGCE() - if tryHarder { - res := <-resc - if res { - // The first strategy succeeded, so let's use it. - return true - } - // Wait for either the DNS or metadata server probe to - // contradict the other one and say we are running on - // GCE. Give it a lot of time to do so, since the system - // info already suggests we're running on a GCE BIOS. - timer := time.NewTimer(5 * time.Second) - defer timer.Stop() - select { - case res = <-resc: - return res - case <-timer.C: - // Too slow. Who knows what this system is. - return false - } - } - - // There's no hint from the system info that we're running on - // GCE, so use the first probe's result as truth, whether it's - // true or false. The goal here is to optimize for speed for - // users who are NOT running on GCE. We can't assume that - // either a DNS lookup or an HTTP request to a blackholed IP - // address is fast. Worst case this should return when the - // metaClient's Transport.ResponseHeaderTimeout or - // Transport.Dial.Timeout fires (in two seconds). - return <-resc -} - -// systemInfoSuggestsGCE reports whether the local system (without -// doing network requests) suggests that we're running on GCE. If this -// returns true, testOnGCE tries a bit harder to reach its metadata -// server. -func systemInfoSuggestsGCE() bool { - if runtime.GOOS != "linux" { - // We don't have any non-Linux clues available, at least yet. - return false - } - slurp, _ := ioutil.ReadFile("/sys/class/dmi/id/product_name") - name := strings.TrimSpace(string(slurp)) - return name == "Google" || name == "Google Compute Engine" -} - -// Subscribe subscribes to a value from the metadata service. -// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/". -// The suffix may contain query parameters. -// -// Subscribe calls fn with the latest metadata value indicated by the provided -// suffix. If the metadata value is deleted, fn is called with the empty string -// and ok false. Subscribe blocks until fn returns a non-nil error or the value -// is deleted. Subscribe returns the error value returned from the last call to -// fn, which may be nil when ok == false. -func Subscribe(suffix string, fn func(v string, ok bool) error) error { - const failedSubscribeSleep = time.Second * 5 - - // First check to see if the metadata value exists at all. - val, lastETag, err := getETag(subscribeClient, suffix) - if err != nil { - return err - } - - if err := fn(val, true); err != nil { - return err - } - - ok := true - if strings.ContainsRune(suffix, '?') { - suffix += "&wait_for_change=true&last_etag=" - } else { - suffix += "?wait_for_change=true&last_etag=" - } - for { - val, etag, err := getETag(subscribeClient, suffix+url.QueryEscape(lastETag)) - if err != nil { - if _, deleted := err.(NotDefinedError); !deleted { - time.Sleep(failedSubscribeSleep) - continue // Retry on other errors. - } - ok = false - } - lastETag = etag - - if err := fn(val, ok); err != nil || !ok { - return err - } - } -} - -// ProjectID returns the current instance's project ID string. -func ProjectID() (string, error) { return projID.get() } - -// NumericProjectID returns the current instance's numeric project ID. -func NumericProjectID() (string, error) { return projNum.get() } - -// InternalIP returns the instance's primary internal IP address. -func InternalIP() (string, error) { - return getTrimmed("instance/network-interfaces/0/ip") -} - -// ExternalIP returns the instance's primary external (public) IP address. -func ExternalIP() (string, error) { - return getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip") -} - -// Hostname returns the instance's hostname. This will be of the form -// ".c..internal". -func Hostname() (string, error) { - return getTrimmed("instance/hostname") -} - -// InstanceTags returns the list of user-defined instance tags, -// assigned when initially creating a GCE instance. -func InstanceTags() ([]string, error) { - var s []string - j, err := Get("instance/tags") - if err != nil { - return nil, err - } - if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil { - return nil, err - } - return s, nil -} - -// InstanceID returns the current VM's numeric instance ID. -func InstanceID() (string, error) { - return instID.get() -} - -// InstanceName returns the current VM's instance ID string. -func InstanceName() (string, error) { - host, err := Hostname() - if err != nil { - return "", err - } - return strings.Split(host, ".")[0], nil -} - -// Zone returns the current VM's zone, such as "us-central1-b". -func Zone() (string, error) { - zone, err := getTrimmed("instance/zone") - // zone is of the form "projects//zones/". - if err != nil { - return "", err - } - return zone[strings.LastIndex(zone, "/")+1:], nil -} - -// InstanceAttributes returns the list of user-defined attributes, -// assigned when initially creating a GCE VM instance. The value of an -// attribute can be obtained with InstanceAttributeValue. -func InstanceAttributes() ([]string, error) { return lines("instance/attributes/") } - -// ProjectAttributes returns the list of user-defined attributes -// applying to the project as a whole, not just this VM. The value of -// an attribute can be obtained with ProjectAttributeValue. -func ProjectAttributes() ([]string, error) { return lines("project/attributes/") } - -func lines(suffix string) ([]string, error) { - j, err := Get(suffix) - if err != nil { - return nil, err - } - s := strings.Split(strings.TrimSpace(j), "\n") - for i := range s { - s[i] = strings.TrimSpace(s[i]) - } - return s, nil -} - -// InstanceAttributeValue returns the value of the provided VM -// instance attribute. -// -// If the requested attribute is not defined, the returned error will -// be of type NotDefinedError. -// -// InstanceAttributeValue may return ("", nil) if the attribute was -// defined to be the empty string. -func InstanceAttributeValue(attr string) (string, error) { - return Get("instance/attributes/" + attr) -} - -// ProjectAttributeValue returns the value of the provided -// project attribute. -// -// If the requested attribute is not defined, the returned error will -// be of type NotDefinedError. -// -// ProjectAttributeValue may return ("", nil) if the attribute was -// defined to be the empty string. -func ProjectAttributeValue(attr string) (string, error) { - return Get("project/attributes/" + attr) -} - -// Scopes returns the service account scopes for the given account. -// The account may be empty or the string "default" to use the instance's -// main account. -func Scopes(serviceAccount string) ([]string, error) { - if serviceAccount == "" { - serviceAccount = "default" - } - return lines("instance/service-accounts/" + serviceAccount + "/scopes") -} - -func strsContains(ss []string, s string) bool { - for _, v := range ss { - if v == s { - return true - } - } - return false -} diff --git a/vendor/github.com/aead/chacha20/LICENSE b/vendor/github.com/aead/chacha20/LICENSE deleted file mode 100644 index b6a9210b9f6..00000000000 --- a/vendor/github.com/aead/chacha20/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Andreas Auernhammer - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/aead/chacha20/chacha/chacha.go b/vendor/github.com/aead/chacha20/chacha/chacha.go deleted file mode 100644 index 8c387a9721e..00000000000 --- a/vendor/github.com/aead/chacha20/chacha/chacha.go +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright (c) 2016 Andreas Auernhammer. All rights reserved. -// Use of this source code is governed by a license that can be -// found in the LICENSE file. - -// Package chacha implements some low-level functions of the -// ChaCha cipher family. -package chacha // import "github.com/aead/chacha20/chacha" - -import ( - "encoding/binary" - "errors" -) - -const ( - // NonceSize is the size of the ChaCha20 nonce in bytes. - NonceSize = 8 - - // INonceSize is the size of the IETF-ChaCha20 nonce in bytes. - INonceSize = 12 - - // XNonceSize is the size of the XChaCha20 nonce in bytes. - XNonceSize = 24 - - // KeySize is the size of the key in bytes. - KeySize = 32 -) - -var ( - useSSE2 bool - useSSSE3 bool - useAVX2 bool -) - -var ( - errKeySize = errors.New("chacha20/chacha: bad key length") - errInvalidNonce = errors.New("chacha20/chacha: bad nonce length") -) - -func setup(state *[64]byte, nonce, key []byte) (err error) { - if len(key) != KeySize { - err = errKeySize - return - } - var Nonce [16]byte - switch len(nonce) { - case NonceSize: - copy(Nonce[8:], nonce) - initialize(state, key, &Nonce) - case INonceSize: - copy(Nonce[4:], nonce) - initialize(state, key, &Nonce) - case XNonceSize: - var tmpKey [32]byte - var hNonce [16]byte - - copy(hNonce[:], nonce[:16]) - copy(tmpKey[:], key) - hChaCha20(&tmpKey, &hNonce, &tmpKey) - copy(Nonce[8:], nonce[16:]) - initialize(state, tmpKey[:], &Nonce) - - // BUG(aead): A "good" compiler will remove this (optimizations) - // But using the provided key instead of tmpKey, - // will change the key (-> probably confuses users) - for i := range tmpKey { - tmpKey[i] = 0 - } - default: - err = errInvalidNonce - } - return -} - -// XORKeyStream crypts bytes from src to dst using the given nonce and key. -// The length of the nonce determinds the version of ChaCha20: -// - NonceSize: ChaCha20/r with a 64 bit nonce and a 2^64 * 64 byte period. -// - INonceSize: ChaCha20/r as defined in RFC 7539 and a 2^32 * 64 byte period. -// - XNonceSize: XChaCha20/r with a 192 bit nonce and a 2^64 * 64 byte period. -// The rounds argument specifies the number of rounds performed for keystream -// generation - valid values are 8, 12 or 20. The src and dst may be the same slice -// but otherwise should not overlap. If len(dst) < len(src) this function panics. -// If the nonce is neither 64, 96 nor 192 bits long, this function panics. -func XORKeyStream(dst, src, nonce, key []byte, rounds int) { - if rounds != 20 && rounds != 12 && rounds != 8 { - panic("chacha20/chacha: bad number of rounds") - } - if len(dst) < len(src) { - panic("chacha20/chacha: dst buffer is to small") - } - if len(nonce) == INonceSize && uint64(len(src)) > (1<<38) { - panic("chacha20/chacha: src is too large") - } - - var block, state [64]byte - if err := setup(&state, nonce, key); err != nil { - panic(err) - } - xorKeyStream(dst, src, &block, &state, rounds) -} - -// Cipher implements ChaCha20/r (XChaCha20/r) for a given number of rounds r. -type Cipher struct { - state, block [64]byte - off int - rounds int // 20 for ChaCha20 - noncesize int -} - -// NewCipher returns a new *chacha.Cipher implementing the ChaCha20/r or XChaCha20/r -// (r = 8, 12 or 20) stream cipher. The nonce must be unique for one key for all time. -// The length of the nonce determinds the version of ChaCha20: -// - NonceSize: ChaCha20/r with a 64 bit nonce and a 2^64 * 64 byte period. -// - INonceSize: ChaCha20/r as defined in RFC 7539 and a 2^32 * 64 byte period. -// - XNonceSize: XChaCha20/r with a 192 bit nonce and a 2^64 * 64 byte period. -// If the nonce is neither 64, 96 nor 192 bits long, a non-nil error is returned. -func NewCipher(nonce, key []byte, rounds int) (*Cipher, error) { - if rounds != 20 && rounds != 12 && rounds != 8 { - panic("chacha20/chacha: bad number of rounds") - } - - c := new(Cipher) - if err := setup(&(c.state), nonce, key); err != nil { - return nil, err - } - c.rounds = rounds - - if len(nonce) == INonceSize { - c.noncesize = INonceSize - } else { - c.noncesize = NonceSize - } - - return c, nil -} - -// XORKeyStream crypts bytes from src to dst. Src and dst may be the same slice -// but otherwise should not overlap. If len(dst) < len(src) the function panics. -func (c *Cipher) XORKeyStream(dst, src []byte) { - if len(dst) < len(src) { - panic("chacha20/chacha: dst buffer is to small") - } - - if c.off > 0 { - n := len(c.block[c.off:]) - if len(src) <= n { - for i, v := range src { - dst[i] = v ^ c.block[c.off] - c.off++ - } - if c.off == 64 { - c.off = 0 - } - return - } - - for i, v := range c.block[c.off:] { - dst[i] = src[i] ^ v - } - src = src[n:] - dst = dst[n:] - c.off = 0 - } - - c.off += xorKeyStream(dst, src, &(c.block), &(c.state), c.rounds) -} - -// SetCounter skips ctr * 64 byte blocks. SetCounter(0) resets the cipher. -// This function always skips the unused keystream of the current 64 byte block. -func (c *Cipher) SetCounter(ctr uint64) { - if c.noncesize == INonceSize { - binary.LittleEndian.PutUint32(c.state[48:], uint32(ctr)) - } else { - binary.LittleEndian.PutUint64(c.state[48:], ctr) - } - c.off = 0 -} diff --git a/vendor/github.com/aead/chacha20/chacha/chachaAVX2_amd64.s b/vendor/github.com/aead/chacha20/chacha/chachaAVX2_amd64.s deleted file mode 100644 index 8d0223329c1..00000000000 --- a/vendor/github.com/aead/chacha20/chacha/chachaAVX2_amd64.s +++ /dev/null @@ -1,542 +0,0 @@ -// Copyright (c) 2016 Andreas Auernhammer. All rights reserved. -// Use of this source code is governed by a license that can be -// found in the LICENSE file. - -// +build go1.7,amd64,!gccgo,!appengine,!nacl - -#include "textflag.h" - -DATA ·sigma_AVX<>+0x00(SB)/4, $0x61707865 -DATA ·sigma_AVX<>+0x04(SB)/4, $0x3320646e -DATA ·sigma_AVX<>+0x08(SB)/4, $0x79622d32 -DATA ·sigma_AVX<>+0x0C(SB)/4, $0x6b206574 -GLOBL ·sigma_AVX<>(SB), (NOPTR+RODATA), $16 - -DATA ·one_AVX<>+0x00(SB)/8, $1 -DATA ·one_AVX<>+0x08(SB)/8, $0 -GLOBL ·one_AVX<>(SB), (NOPTR+RODATA), $16 - -DATA ·one_AVX2<>+0x00(SB)/8, $0 -DATA ·one_AVX2<>+0x08(SB)/8, $0 -DATA ·one_AVX2<>+0x10(SB)/8, $1 -DATA ·one_AVX2<>+0x18(SB)/8, $0 -GLOBL ·one_AVX2<>(SB), (NOPTR+RODATA), $32 - -DATA ·two_AVX2<>+0x00(SB)/8, $2 -DATA ·two_AVX2<>+0x08(SB)/8, $0 -DATA ·two_AVX2<>+0x10(SB)/8, $2 -DATA ·two_AVX2<>+0x18(SB)/8, $0 -GLOBL ·two_AVX2<>(SB), (NOPTR+RODATA), $32 - -DATA ·rol16_AVX2<>+0x00(SB)/8, $0x0504070601000302 -DATA ·rol16_AVX2<>+0x08(SB)/8, $0x0D0C0F0E09080B0A -DATA ·rol16_AVX2<>+0x10(SB)/8, $0x0504070601000302 -DATA ·rol16_AVX2<>+0x18(SB)/8, $0x0D0C0F0E09080B0A -GLOBL ·rol16_AVX2<>(SB), (NOPTR+RODATA), $32 - -DATA ·rol8_AVX2<>+0x00(SB)/8, $0x0605040702010003 -DATA ·rol8_AVX2<>+0x08(SB)/8, $0x0E0D0C0F0A09080B -DATA ·rol8_AVX2<>+0x10(SB)/8, $0x0605040702010003 -DATA ·rol8_AVX2<>+0x18(SB)/8, $0x0E0D0C0F0A09080B -GLOBL ·rol8_AVX2<>(SB), (NOPTR+RODATA), $32 - -#define ROTL(n, t, v) \ - VPSLLD $n, v, t; \ - VPSRLD $(32-n), v, v; \ - VPXOR v, t, v - -#define CHACHA_QROUND(v0, v1, v2, v3, t, c16, c8) \ - VPADDD v0, v1, v0; \ - VPXOR v3, v0, v3; \ - VPSHUFB c16, v3, v3; \ - VPADDD v2, v3, v2; \ - VPXOR v1, v2, v1; \ - ROTL(12, t, v1); \ - VPADDD v0, v1, v0; \ - VPXOR v3, v0, v3; \ - VPSHUFB c8, v3, v3; \ - VPADDD v2, v3, v2; \ - VPXOR v1, v2, v1; \ - ROTL(7, t, v1) - -#define CHACHA_SHUFFLE(v1, v2, v3) \ - VPSHUFD $0x39, v1, v1; \ - VPSHUFD $0x4E, v2, v2; \ - VPSHUFD $-109, v3, v3 - -#define XOR_AVX2(dst, src, off, v0, v1, v2, v3, t0, t1) \ - VMOVDQU (0+off)(src), t0; \ - VPERM2I128 $32, v1, v0, t1; \ - VPXOR t0, t1, t0; \ - VMOVDQU t0, (0+off)(dst); \ - VMOVDQU (32+off)(src), t0; \ - VPERM2I128 $32, v3, v2, t1; \ - VPXOR t0, t1, t0; \ - VMOVDQU t0, (32+off)(dst); \ - VMOVDQU (64+off)(src), t0; \ - VPERM2I128 $49, v1, v0, t1; \ - VPXOR t0, t1, t0; \ - VMOVDQU t0, (64+off)(dst); \ - VMOVDQU (96+off)(src), t0; \ - VPERM2I128 $49, v3, v2, t1; \ - VPXOR t0, t1, t0; \ - VMOVDQU t0, (96+off)(dst) - -#define XOR_UPPER_AVX2(dst, src, off, v0, v1, v2, v3, t0, t1) \ - VMOVDQU (0+off)(src), t0; \ - VPERM2I128 $32, v1, v0, t1; \ - VPXOR t0, t1, t0; \ - VMOVDQU t0, (0+off)(dst); \ - VMOVDQU (32+off)(src), t0; \ - VPERM2I128 $32, v3, v2, t1; \ - VPXOR t0, t1, t0; \ - VMOVDQU t0, (32+off)(dst); \ - -#define EXTRACT_LOWER(dst, v0, v1, v2, v3, t0) \ - VPERM2I128 $49, v1, v0, t0; \ - VMOVDQU t0, 0(dst); \ - VPERM2I128 $49, v3, v2, t0; \ - VMOVDQU t0, 32(dst) - -#define XOR_AVX(dst, src, off, v0, v1, v2, v3, t0) \ - VPXOR 0+off(src), v0, t0; \ - VMOVDQU t0, 0+off(dst); \ - VPXOR 16+off(src), v1, t0; \ - VMOVDQU t0, 16+off(dst); \ - VPXOR 32+off(src), v2, t0; \ - VMOVDQU t0, 32+off(dst); \ - VPXOR 48+off(src), v3, t0; \ - VMOVDQU t0, 48+off(dst) - -#define TWO 0(SP) -#define C16 32(SP) -#define C8 64(SP) -#define STATE_0 96(SP) -#define STATE_1 128(SP) -#define STATE_2 160(SP) -#define STATE_3 192(SP) -#define TMP_0 224(SP) -#define TMP_1 256(SP) - -// func xorKeyStreamAVX(dst, src []byte, block, state *[64]byte, rounds int) int -TEXT ·xorKeyStreamAVX2(SB), 4, $320-80 - MOVQ dst_base+0(FP), DI - MOVQ src_base+24(FP), SI - MOVQ src_len+32(FP), CX - MOVQ block+48(FP), BX - MOVQ state+56(FP), AX - MOVQ rounds+64(FP), DX - - MOVQ SP, R8 - ADDQ $32, SP - ANDQ $-32, SP - - VMOVDQU 0(AX), Y2 - VMOVDQU 32(AX), Y3 - VPERM2I128 $0x22, Y2, Y0, Y0 - VPERM2I128 $0x33, Y2, Y1, Y1 - VPERM2I128 $0x22, Y3, Y2, Y2 - VPERM2I128 $0x33, Y3, Y3, Y3 - - TESTQ CX, CX - JZ done - - VMOVDQU ·one_AVX2<>(SB), Y4 - VPADDD Y4, Y3, Y3 - - VMOVDQA Y0, STATE_0 - VMOVDQA Y1, STATE_1 - VMOVDQA Y2, STATE_2 - VMOVDQA Y3, STATE_3 - - VMOVDQU ·rol16_AVX2<>(SB), Y4 - VMOVDQU ·rol8_AVX2<>(SB), Y5 - VMOVDQU ·two_AVX2<>(SB), Y6 - VMOVDQA Y4, Y14 - VMOVDQA Y5, Y15 - VMOVDQA Y4, C16 - VMOVDQA Y5, C8 - VMOVDQA Y6, TWO - - CMPQ CX, $64 - JBE between_0_and_64 - CMPQ CX, $192 - JBE between_64_and_192 - CMPQ CX, $320 - JBE between_192_and_320 - CMPQ CX, $448 - JBE between_320_and_448 - -at_least_512: - VMOVDQA Y0, Y4 - VMOVDQA Y1, Y5 - VMOVDQA Y2, Y6 - VPADDQ TWO, Y3, Y7 - VMOVDQA Y0, Y8 - VMOVDQA Y1, Y9 - VMOVDQA Y2, Y10 - VPADDQ TWO, Y7, Y11 - VMOVDQA Y0, Y12 - VMOVDQA Y1, Y13 - VMOVDQA Y2, Y14 - VPADDQ TWO, Y11, Y15 - - MOVQ DX, R9 - -chacha_loop_512: - VMOVDQA Y8, TMP_0 - CHACHA_QROUND(Y0, Y1, Y2, Y3, Y8, C16, C8) - CHACHA_QROUND(Y4, Y5, Y6, Y7, Y8, C16, C8) - VMOVDQA TMP_0, Y8 - VMOVDQA Y0, TMP_0 - CHACHA_QROUND(Y8, Y9, Y10, Y11, Y0, C16, C8) - CHACHA_QROUND(Y12, Y13, Y14, Y15, Y0, C16, C8) - CHACHA_SHUFFLE(Y1, Y2, Y3) - CHACHA_SHUFFLE(Y5, Y6, Y7) - CHACHA_SHUFFLE(Y9, Y10, Y11) - CHACHA_SHUFFLE(Y13, Y14, Y15) - - CHACHA_QROUND(Y12, Y13, Y14, Y15, Y0, C16, C8) - CHACHA_QROUND(Y8, Y9, Y10, Y11, Y0, C16, C8) - VMOVDQA TMP_0, Y0 - VMOVDQA Y8, TMP_0 - CHACHA_QROUND(Y4, Y5, Y6, Y7, Y8, C16, C8) - CHACHA_QROUND(Y0, Y1, Y2, Y3, Y8, C16, C8) - VMOVDQA TMP_0, Y8 - CHACHA_SHUFFLE(Y3, Y2, Y1) - CHACHA_SHUFFLE(Y7, Y6, Y5) - CHACHA_SHUFFLE(Y11, Y10, Y9) - CHACHA_SHUFFLE(Y15, Y14, Y13) - SUBQ $2, R9 - JA chacha_loop_512 - - VMOVDQA Y12, TMP_0 - VMOVDQA Y13, TMP_1 - VPADDD STATE_0, Y0, Y0 - VPADDD STATE_1, Y1, Y1 - VPADDD STATE_2, Y2, Y2 - VPADDD STATE_3, Y3, Y3 - XOR_AVX2(DI, SI, 0, Y0, Y1, Y2, Y3, Y12, Y13) - VMOVDQA STATE_0, Y0 - VMOVDQA STATE_1, Y1 - VMOVDQA STATE_2, Y2 - VMOVDQA STATE_3, Y3 - VPADDQ TWO, Y3, Y3 - - VPADDD Y0, Y4, Y4 - VPADDD Y1, Y5, Y5 - VPADDD Y2, Y6, Y6 - VPADDD Y3, Y7, Y7 - XOR_AVX2(DI, SI, 128, Y4, Y5, Y6, Y7, Y12, Y13) - VPADDQ TWO, Y3, Y3 - - VPADDD Y0, Y8, Y8 - VPADDD Y1, Y9, Y9 - VPADDD Y2, Y10, Y10 - VPADDD Y3, Y11, Y11 - XOR_AVX2(DI, SI, 256, Y8, Y9, Y10, Y11, Y12, Y13) - VPADDQ TWO, Y3, Y3 - - VPADDD TMP_0, Y0, Y12 - VPADDD TMP_1, Y1, Y13 - VPADDD Y2, Y14, Y14 - VPADDD Y3, Y15, Y15 - VPADDQ TWO, Y3, Y3 - - CMPQ CX, $512 - JB less_than_512 - - XOR_AVX2(DI, SI, 384, Y12, Y13, Y14, Y15, Y4, Y5) - VMOVDQA Y3, STATE_3 - ADDQ $512, SI - ADDQ $512, DI - SUBQ $512, CX - CMPQ CX, $448 - JA at_least_512 - - TESTQ CX, CX - JZ done - - VMOVDQA C16, Y14 - VMOVDQA C8, Y15 - - CMPQ CX, $64 - JBE between_0_and_64 - CMPQ CX, $192 - JBE between_64_and_192 - CMPQ CX, $320 - JBE between_192_and_320 - JMP between_320_and_448 - -less_than_512: - XOR_UPPER_AVX2(DI, SI, 384, Y12, Y13, Y14, Y15, Y4, Y5) - EXTRACT_LOWER(BX, Y12, Y13, Y14, Y15, Y4) - ADDQ $448, SI - ADDQ $448, DI - SUBQ $448, CX - JMP finalize - -between_320_and_448: - VMOVDQA Y0, Y4 - VMOVDQA Y1, Y5 - VMOVDQA Y2, Y6 - VPADDQ TWO, Y3, Y7 - VMOVDQA Y0, Y8 - VMOVDQA Y1, Y9 - VMOVDQA Y2, Y10 - VPADDQ TWO, Y7, Y11 - - MOVQ DX, R9 - -chacha_loop_384: - CHACHA_QROUND(Y0, Y1, Y2, Y3, Y13, Y14, Y15) - CHACHA_QROUND(Y4, Y5, Y6, Y7, Y13, Y14, Y15) - CHACHA_QROUND(Y8, Y9, Y10, Y11, Y13, Y14, Y15) - CHACHA_SHUFFLE(Y1, Y2, Y3) - CHACHA_SHUFFLE(Y5, Y6, Y7) - CHACHA_SHUFFLE(Y9, Y10, Y11) - CHACHA_QROUND(Y0, Y1, Y2, Y3, Y13, Y14, Y15) - CHACHA_QROUND(Y4, Y5, Y6, Y7, Y13, Y14, Y15) - CHACHA_QROUND(Y8, Y9, Y10, Y11, Y13, Y14, Y15) - CHACHA_SHUFFLE(Y3, Y2, Y1) - CHACHA_SHUFFLE(Y7, Y6, Y5) - CHACHA_SHUFFLE(Y11, Y10, Y9) - SUBQ $2, R9 - JA chacha_loop_384 - - VPADDD STATE_0, Y0, Y0 - VPADDD STATE_1, Y1, Y1 - VPADDD STATE_2, Y2, Y2 - VPADDD STATE_3, Y3, Y3 - XOR_AVX2(DI, SI, 0, Y0, Y1, Y2, Y3, Y12, Y13) - VMOVDQA STATE_0, Y0 - VMOVDQA STATE_1, Y1 - VMOVDQA STATE_2, Y2 - VMOVDQA STATE_3, Y3 - VPADDQ TWO, Y3, Y3 - - VPADDD Y0, Y4, Y4 - VPADDD Y1, Y5, Y5 - VPADDD Y2, Y6, Y6 - VPADDD Y3, Y7, Y7 - XOR_AVX2(DI, SI, 128, Y4, Y5, Y6, Y7, Y12, Y13) - VPADDQ TWO, Y3, Y3 - - VPADDD Y0, Y8, Y8 - VPADDD Y1, Y9, Y9 - VPADDD Y2, Y10, Y10 - VPADDD Y3, Y11, Y11 - VPADDQ TWO, Y3, Y3 - - CMPQ CX, $384 - JB less_than_384 - - XOR_AVX2(DI, SI, 256, Y8, Y9, Y10, Y11, Y12, Y13) - SUBQ $384, CX - TESTQ CX, CX - JE done - - ADDQ $384, SI - ADDQ $384, DI - JMP between_0_and_64 - -less_than_384: - XOR_UPPER_AVX2(DI, SI, 256, Y8, Y9, Y10, Y11, Y12, Y13) - EXTRACT_LOWER(BX, Y8, Y9, Y10, Y11, Y12) - ADDQ $320, SI - ADDQ $320, DI - SUBQ $320, CX - JMP finalize - -between_192_and_320: - VMOVDQA Y0, Y4 - VMOVDQA Y1, Y5 - VMOVDQA Y2, Y6 - VMOVDQA Y3, Y7 - VMOVDQA Y0, Y8 - VMOVDQA Y1, Y9 - VMOVDQA Y2, Y10 - VPADDQ TWO, Y3, Y11 - - MOVQ DX, R9 - -chacha_loop_256: - CHACHA_QROUND(Y4, Y5, Y6, Y7, Y13, Y14, Y15) - CHACHA_QROUND(Y8, Y9, Y10, Y11, Y13, Y14, Y15) - CHACHA_SHUFFLE(Y5, Y6, Y7) - CHACHA_SHUFFLE(Y9, Y10, Y11) - CHACHA_QROUND(Y4, Y5, Y6, Y7, Y13, Y14, Y15) - CHACHA_QROUND(Y8, Y9, Y10, Y11, Y13, Y14, Y15) - CHACHA_SHUFFLE(Y7, Y6, Y5) - CHACHA_SHUFFLE(Y11, Y10, Y9) - SUBQ $2, R9 - JA chacha_loop_256 - - VPADDD Y0, Y4, Y4 - VPADDD Y1, Y5, Y5 - VPADDD Y2, Y6, Y6 - VPADDD Y3, Y7, Y7 - VPADDQ TWO, Y3, Y3 - XOR_AVX2(DI, SI, 0, Y4, Y5, Y6, Y7, Y12, Y13) - VPADDD Y0, Y8, Y8 - VPADDD Y1, Y9, Y9 - VPADDD Y2, Y10, Y10 - VPADDD Y3, Y11, Y11 - VPADDQ TWO, Y3, Y3 - - CMPQ CX, $256 - JB less_than_256 - - XOR_AVX2(DI, SI, 128, Y8, Y9, Y10, Y11, Y12, Y13) - SUBQ $256, CX - TESTQ CX, CX - JE done - - ADDQ $256, SI - ADDQ $256, DI - JMP between_0_and_64 - -less_than_256: - XOR_UPPER_AVX2(DI, SI, 128, Y8, Y9, Y10, Y11, Y12, Y13) - EXTRACT_LOWER(BX, Y8, Y9, Y10, Y11, Y12) - ADDQ $192, SI - ADDQ $192, DI - SUBQ $192, CX - JMP finalize - -between_64_and_192: - VMOVDQA Y0, Y4 - VMOVDQA Y1, Y5 - VMOVDQA Y2, Y6 - VMOVDQA Y3, Y7 - - MOVQ DX, R9 - -chacha_loop_128: - CHACHA_QROUND(Y4, Y5, Y6, Y7, Y13, Y14, Y15) - CHACHA_SHUFFLE(Y5, Y6, Y7) - CHACHA_QROUND(Y4, Y5, Y6, Y7, Y13, Y14, Y15) - CHACHA_SHUFFLE(Y7, Y6, Y5) - SUBQ $2, R9 - JA chacha_loop_128 - - VPADDD Y0, Y4, Y4 - VPADDD Y1, Y5, Y5 - VPADDD Y2, Y6, Y6 - VPADDD Y3, Y7, Y7 - VPADDQ TWO, Y3, Y3 - - CMPQ CX, $128 - JB less_than_128 - - XOR_AVX2(DI, SI, 0, Y4, Y5, Y6, Y7, Y12, Y13) - SUBQ $128, CX - TESTQ CX, CX - JE done - - ADDQ $128, SI - ADDQ $128, DI - JMP between_0_and_64 - -less_than_128: - XOR_UPPER_AVX2(DI, SI, 0, Y4, Y5, Y6, Y7, Y12, Y13) - EXTRACT_LOWER(BX, Y4, Y5, Y6, Y7, Y13) - ADDQ $64, SI - ADDQ $64, DI - SUBQ $64, CX - JMP finalize - -between_0_and_64: - VMOVDQA X0, X4 - VMOVDQA X1, X5 - VMOVDQA X2, X6 - VMOVDQA X3, X7 - - MOVQ DX, R9 - -chacha_loop_64: - CHACHA_QROUND(X4, X5, X6, X7, X13, X14, X15) - CHACHA_SHUFFLE(X5, X6, X7) - CHACHA_QROUND(X4, X5, X6, X7, X13, X14, X15) - CHACHA_SHUFFLE(X7, X6, X5) - SUBQ $2, R9 - JA chacha_loop_64 - - VPADDD X0, X4, X4 - VPADDD X1, X5, X5 - VPADDD X2, X6, X6 - VPADDD X3, X7, X7 - VMOVDQU ·one_AVX<>(SB), X0 - VPADDQ X0, X3, X3 - - CMPQ CX, $64 - JB less_than_64 - - XOR_AVX(DI, SI, 0, X4, X5, X6, X7, X13) - SUBQ $64, CX - JMP done - -less_than_64: - VMOVDQU X4, 0(BX) - VMOVDQU X5, 16(BX) - VMOVDQU X6, 32(BX) - VMOVDQU X7, 48(BX) - -finalize: - XORQ R11, R11 - XORQ R12, R12 - MOVQ CX, BP - -xor_loop: - MOVB 0(SI), R11 - MOVB 0(BX), R12 - XORQ R11, R12 - MOVB R12, 0(DI) - INCQ SI - INCQ BX - INCQ DI - DECQ BP - JA xor_loop - -done: - VMOVDQU X3, 48(AX) - VZEROUPPER - MOVQ R8, SP - MOVQ CX, ret+72(FP) - RET - -// func hChaCha20AVX(out *[32]byte, nonce *[16]byte, key *[32]byte) -TEXT ·hChaCha20AVX(SB), 4, $0-24 - MOVQ out+0(FP), DI - MOVQ nonce+8(FP), AX - MOVQ key+16(FP), BX - - VMOVDQU ·sigma_AVX<>(SB), X0 - VMOVDQU 0(BX), X1 - VMOVDQU 16(BX), X2 - VMOVDQU 0(AX), X3 - VMOVDQU ·rol16_AVX2<>(SB), X5 - VMOVDQU ·rol8_AVX2<>(SB), X6 - - MOVQ $20, CX - -chacha_loop: - CHACHA_QROUND(X0, X1, X2, X3, X4, X5, X6) - CHACHA_SHUFFLE(X1, X2, X3) - CHACHA_QROUND(X0, X1, X2, X3, X4, X5, X6) - CHACHA_SHUFFLE(X3, X2, X1) - SUBQ $2, CX - JNZ chacha_loop - - VMOVDQU X0, 0(DI) - VMOVDQU X3, 16(DI) - VZEROUPPER - RET - -// func supportsAVX2() bool -TEXT ·supportsAVX2(SB), 4, $0-1 - MOVQ runtime·support_avx(SB), AX - MOVQ runtime·support_avx2(SB), BX - ANDQ AX, BX - MOVB BX, ret+0(FP) - RET diff --git a/vendor/github.com/aead/chacha20/chacha/chacha_386.go b/vendor/github.com/aead/chacha20/chacha/chacha_386.go deleted file mode 100644 index e3135efb875..00000000000 --- a/vendor/github.com/aead/chacha20/chacha/chacha_386.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) 2016 Andreas Auernhammer. All rights reserved. -// Use of this source code is governed by a license that can be -// found in the LICENSE file. - -// +build 386,!gccgo,!appengine,!nacl - -package chacha - -import "encoding/binary" - -func init() { - useSSE2 = supportsSSE2() - useSSSE3 = supportsSSSE3() - useAVX2 = false -} - -func initialize(state *[64]byte, key []byte, nonce *[16]byte) { - binary.LittleEndian.PutUint32(state[0:], sigma[0]) - binary.LittleEndian.PutUint32(state[4:], sigma[1]) - binary.LittleEndian.PutUint32(state[8:], sigma[2]) - binary.LittleEndian.PutUint32(state[12:], sigma[3]) - copy(state[16:], key[:]) - copy(state[48:], nonce[:]) -} - -// This function is implemented in chacha_386.s -//go:noescape -func supportsSSE2() bool - -// This function is implemented in chacha_386.s -//go:noescape -func supportsSSSE3() bool - -// This function is implemented in chacha_386.s -//go:noescape -func hChaCha20SSE2(out *[32]byte, nonce *[16]byte, key *[32]byte) - -// This function is implemented in chacha_386.s -//go:noescape -func hChaCha20SSSE3(out *[32]byte, nonce *[16]byte, key *[32]byte) - -// This function is implemented in chacha_386.s -//go:noescape -func xorKeyStreamSSE2(dst, src []byte, block, state *[64]byte, rounds int) int - -// This function is implemented in chacha_386.s -//go:noescape -func xorKeyStreamSSSE3(dst, src []byte, block, state *[64]byte, rounds int) int - -func hChaCha20(out *[32]byte, nonce *[16]byte, key *[32]byte) { - if useSSSE3 { - hChaCha20SSSE3(out, nonce, key) - } else if useSSE2 { - hChaCha20SSE2(out, nonce, key) - } else { - hChaCha20Generic(out, nonce, key) - } -} - -func xorKeyStream(dst, src []byte, block, state *[64]byte, rounds int) int { - if useSSSE3 { - return xorKeyStreamSSSE3(dst, src, block, state, rounds) - } else if useSSE2 { - return xorKeyStreamSSE2(dst, src, block, state, rounds) - } - return xorKeyStreamGeneric(dst, src, block, state, rounds) -} diff --git a/vendor/github.com/aead/chacha20/chacha/chacha_386.s b/vendor/github.com/aead/chacha20/chacha/chacha_386.s deleted file mode 100644 index d7bba759584..00000000000 --- a/vendor/github.com/aead/chacha20/chacha/chacha_386.s +++ /dev/null @@ -1,311 +0,0 @@ -// Copyright (c) 2016 Andreas Auernhammer. All rights reserved. -// Use of this source code is governed by a license that can be -// found in the LICENSE file. - -// +build 386,!gccgo,!appengine,!nacl - -#include "textflag.h" - -DATA ·sigma<>+0x00(SB)/4, $0x61707865 -DATA ·sigma<>+0x04(SB)/4, $0x3320646e -DATA ·sigma<>+0x08(SB)/4, $0x79622d32 -DATA ·sigma<>+0x0C(SB)/4, $0x6b206574 -GLOBL ·sigma<>(SB), (NOPTR+RODATA), $16 - -DATA ·one<>+0x00(SB)/8, $1 -DATA ·one<>+0x08(SB)/8, $0 -GLOBL ·one<>(SB), (NOPTR+RODATA), $16 - -DATA ·rol16<>+0x00(SB)/8, $0x0504070601000302 -DATA ·rol16<>+0x08(SB)/8, $0x0D0C0F0E09080B0A -GLOBL ·rol16<>(SB), (NOPTR+RODATA), $16 - -DATA ·rol8<>+0x00(SB)/8, $0x0605040702010003 -DATA ·rol8<>+0x08(SB)/8, $0x0E0D0C0F0A09080B -GLOBL ·rol8<>(SB), (NOPTR+RODATA), $16 - -#define ROTL_SSE2(n, t, v) \ - MOVO v, t; \ - PSLLL $n, t; \ - PSRLL $(32-n), v; \ - PXOR t, v - -#define CHACHA_QROUND_SSE2(v0, v1, v2, v3, t0) \ - PADDL v1, v0; \ - PXOR v0, v3; \ - ROTL_SSE2(16, t0, v3); \ - PADDL v3, v2; \ - PXOR v2, v1; \ - ROTL_SSE2(12, t0, v1); \ - PADDL v1, v0; \ - PXOR v0, v3; \ - ROTL_SSE2(8, t0, v3); \ - PADDL v3, v2; \ - PXOR v2, v1; \ - ROTL_SSE2(7, t0, v1) - -#define CHACHA_QROUND_SSSE3(v0, v1, v2, v3, t0, r16, r8) \ - PADDL v1, v0; \ - PXOR v0, v3; \ - PSHUFB r16, v3; \ - PADDL v3, v2; \ - PXOR v2, v1; \ - ROTL_SSE2(12, t0, v1); \ - PADDL v1, v0; \ - PXOR v0, v3; \ - PSHUFB r8, v3; \ - PADDL v3, v2; \ - PXOR v2, v1; \ - ROTL_SSE2(7, t0, v1) - -#define CHACHA_SHUFFLE(v1, v2, v3) \ - PSHUFL $0x39, v1, v1; \ - PSHUFL $0x4E, v2, v2; \ - PSHUFL $0x93, v3, v3 - -#define XOR(dst, src, off, v0, v1, v2, v3, t0) \ - MOVOU 0+off(src), t0; \ - PXOR v0, t0; \ - MOVOU t0, 0+off(dst); \ - MOVOU 16+off(src), t0; \ - PXOR v1, t0; \ - MOVOU t0, 16+off(dst); \ - MOVOU 32+off(src), t0; \ - PXOR v2, t0; \ - MOVOU t0, 32+off(dst); \ - MOVOU 48+off(src), t0; \ - PXOR v3, t0; \ - MOVOU t0, 48+off(dst) - -#define FINALIZE(dst, src, block, len, t0, t1) \ - XORL t0, t0; \ - XORL t1, t1; \ - finalize: \ - MOVB 0(src), t0; \ - MOVB 0(block), t1; \ - XORL t0, t1; \ - MOVB t1, 0(dst); \ - INCL src; \ - INCL block; \ - INCL dst; \ - DECL len; \ - JA finalize \ - -// func xorKeyStreamSSE2(dst, src []byte, block, state *[64]byte, rounds int) int -TEXT ·xorKeyStreamSSE2(SB), 4, $0-40 - MOVL dst_base+0(FP), DI - MOVL src_base+12(FP), SI - MOVL src_len+16(FP), CX - MOVL state+28(FP), AX - MOVL rounds+32(FP), DX - - MOVOU 0(AX), X0 - MOVOU 16(AX), X1 - MOVOU 32(AX), X2 - MOVOU 48(AX), X3 - - TESTL CX, CX - JZ done - -at_least_64: - MOVO X0, X4 - MOVO X1, X5 - MOVO X2, X6 - MOVO X3, X7 - - MOVL DX, BX - -chacha_loop: - CHACHA_QROUND_SSE2(X4, X5, X6, X7, X0) - CHACHA_SHUFFLE(X5, X6, X7) - CHACHA_QROUND_SSE2(X4, X5, X6, X7, X0) - CHACHA_SHUFFLE(X7, X6, X5) - SUBL $2, BX - JA chacha_loop - - MOVOU 0(AX), X0 - PADDL X0, X4 - PADDL X1, X5 - PADDL X2, X6 - PADDL X3, X7 - MOVOU ·one<>(SB), X0 - PADDQ X0, X3 - - CMPL CX, $64 - JB less_than_64 - - XOR(DI, SI, 0, X4, X5, X6, X7, X0) - MOVOU 0(AX), X0 - ADDL $64, SI - ADDL $64, DI - SUBL $64, CX - JNZ at_least_64 - -less_than_64: - MOVL CX, BP - TESTL BP, BP - JZ done - - MOVL block+24(FP), BX - MOVOU X4, 0(BX) - MOVOU X5, 16(BX) - MOVOU X6, 32(BX) - MOVOU X7, 48(BX) - FINALIZE(DI, SI, BX, BP, AX, DX) - -done: - MOVL state+28(FP), AX - MOVOU X3, 48(AX) - MOVL CX, ret+36(FP) - RET - -// func xorKeyStreamSSSE3(dst, src []byte, block, state *[64]byte, rounds int) int -TEXT ·xorKeyStreamSSSE3(SB), 4, $64-40 - MOVL dst_base+0(FP), DI - MOVL src_base+12(FP), SI - MOVL src_len+16(FP), CX - MOVL state+28(FP), AX - MOVL rounds+32(FP), DX - - MOVOU 48(AX), X3 - TESTL CX, CX - JZ done - - MOVL SP, BP - ADDL $16, SP - ANDL $-16, SP - - MOVOU ·one<>(SB), X0 - MOVOU 16(AX), X1 - MOVOU 32(AX), X2 - MOVO X0, 0(SP) - MOVO X1, 16(SP) - MOVO X2, 32(SP) - - MOVOU 0(AX), X0 - MOVOU ·rol16<>(SB), X1 - MOVOU ·rol8<>(SB), X2 - -at_least_64: - MOVO X0, X4 - MOVO 16(SP), X5 - MOVO 32(SP), X6 - MOVO X3, X7 - - MOVL DX, BX - -chacha_loop: - CHACHA_QROUND_SSSE3(X4, X5, X6, X7, X0, X1, X2) - CHACHA_SHUFFLE(X5, X6, X7) - CHACHA_QROUND_SSSE3(X4, X5, X6, X7, X0, X1, X2) - CHACHA_SHUFFLE(X7, X6, X5) - SUBL $2, BX - JA chacha_loop - - MOVOU 0(AX), X0 - PADDL X0, X4 - PADDL 16(SP), X5 - PADDL 32(SP), X6 - PADDL X3, X7 - PADDQ 0(SP), X3 - - CMPL CX, $64 - JB less_than_64 - - XOR(DI, SI, 0, X4, X5, X6, X7, X0) - MOVOU 0(AX), X0 - ADDL $64, SI - ADDL $64, DI - SUBL $64, CX - JNZ at_least_64 - -less_than_64: - MOVL BP, SP - MOVL CX, BP - TESTL BP, BP - JE done - - MOVL block+24(FP), BX - MOVOU X4, 0(BX) - MOVOU X5, 16(BX) - MOVOU X6, 32(BX) - MOVOU X7, 48(BX) - FINALIZE(DI, SI, BX, BP, AX, DX) - -done: - MOVL state+28(FP), AX - MOVOU X3, 48(AX) - MOVL CX, ret+36(FP) - RET - -// func supportsSSE2() bool -TEXT ·supportsSSE2(SB), NOSPLIT, $0-1 - XORL AX, AX - INCL AX - CPUID - SHRL $26, DX - ANDL $1, DX - MOVB DX, ret+0(FP) - RET - -// func supportsSSSE3() bool -TEXT ·supportsSSSE3(SB), NOSPLIT, $0-1 - XORL AX, AX - INCL AX - CPUID - SHRL $9, CX - ANDL $1, CX - MOVB CX, ret+0(FP) - RET - -// func hChaCha20SSE2(out *[32]byte, nonce *[16]byte, key *[32]byte) -TEXT ·hChaCha20SSE2(SB), 4, $0-12 - MOVL out+0(FP), DI - MOVL nonce+4(FP), AX - MOVL key+8(FP), BX - - MOVOU ·sigma<>(SB), X0 - MOVOU 0(BX), X1 - MOVOU 16(BX), X2 - MOVOU 0(AX), X3 - - MOVL $20, CX - -chacha_loop: - CHACHA_QROUND_SSE2(X0, X1, X2, X3, X4) - CHACHA_SHUFFLE(X1, X2, X3) - CHACHA_QROUND_SSE2(X0, X1, X2, X3, X4) - CHACHA_SHUFFLE(X3, X2, X1) - SUBL $2, CX - JNZ chacha_loop - - MOVOU X0, 0(DI) - MOVOU X3, 16(DI) - RET - -// func hChaCha20SSSE3(out *[32]byte, nonce *[16]byte, key *[32]byte) -TEXT ·hChaCha20SSSE3(SB), 4, $0-12 - MOVL out+0(FP), DI - MOVL nonce+4(FP), AX - MOVL key+8(FP), BX - - MOVOU ·sigma<>(SB), X0 - MOVOU 0(BX), X1 - MOVOU 16(BX), X2 - MOVOU 0(AX), X3 - MOVOU ·rol16<>(SB), X5 - MOVOU ·rol8<>(SB), X6 - - MOVL $20, CX - -chacha_loop: - CHACHA_QROUND_SSSE3(X0, X1, X2, X3, X4, X5, X6) - CHACHA_SHUFFLE(X1, X2, X3) - CHACHA_QROUND_SSSE3(X0, X1, X2, X3, X4, X5, X6) - CHACHA_SHUFFLE(X3, X2, X1) - SUBL $2, CX - JNZ chacha_loop - - MOVOU X0, 0(DI) - MOVOU X3, 16(DI) - RET diff --git a/vendor/github.com/aead/chacha20/chacha/chacha_amd64.s b/vendor/github.com/aead/chacha20/chacha/chacha_amd64.s deleted file mode 100644 index 5bc41ef7d8c..00000000000 --- a/vendor/github.com/aead/chacha20/chacha/chacha_amd64.s +++ /dev/null @@ -1,788 +0,0 @@ -// Copyright (c) 2016 Andreas Auernhammer. All rights reserved. -// Use of this source code is governed by a license that can be -// found in the LICENSE file. - -// +build amd64,!gccgo,!appengine,!nacl - -#include "textflag.h" - -DATA ·sigma<>+0x00(SB)/4, $0x61707865 -DATA ·sigma<>+0x04(SB)/4, $0x3320646e -DATA ·sigma<>+0x08(SB)/4, $0x79622d32 -DATA ·sigma<>+0x0C(SB)/4, $0x6b206574 -GLOBL ·sigma<>(SB), (NOPTR+RODATA), $16 - -DATA ·one<>+0x00(SB)/8, $1 -DATA ·one<>+0x08(SB)/8, $0 -GLOBL ·one<>(SB), (NOPTR+RODATA), $16 - -DATA ·rol16<>+0x00(SB)/8, $0x0504070601000302 -DATA ·rol16<>+0x08(SB)/8, $0x0D0C0F0E09080B0A -GLOBL ·rol16<>(SB), (NOPTR+RODATA), $16 - -DATA ·rol8<>+0x00(SB)/8, $0x0605040702010003 -DATA ·rol8<>+0x08(SB)/8, $0x0E0D0C0F0A09080B -GLOBL ·rol8<>(SB), (NOPTR+RODATA), $16 - -#define ROTL_SSE2(n, t, v) \ - MOVO v, t; \ - PSLLL $n, t; \ - PSRLL $(32-n), v; \ - PXOR t, v - -#define CHACHA_QROUND_SSE2(v0, v1, v2, v3, t0) \ - PADDL v1, v0; \ - PXOR v0, v3; \ - ROTL_SSE2(16, t0, v3); \ - PADDL v3, v2; \ - PXOR v2, v1; \ - ROTL_SSE2(12, t0, v1); \ - PADDL v1, v0; \ - PXOR v0, v3; \ - ROTL_SSE2(8, t0, v3); \ - PADDL v3, v2; \ - PXOR v2, v1; \ - ROTL_SSE2(7, t0, v1) - -#define CHACHA_QROUND_SSSE3(v0, v1, v2, v3, t0, r16, r8) \ - PADDL v1, v0; \ - PXOR v0, v3; \ - PSHUFB r16, v3; \ - PADDL v3, v2; \ - PXOR v2, v1; \ - ROTL_SSE2(12, t0, v1); \ - PADDL v1, v0; \ - PXOR v0, v3; \ - PSHUFB r8, v3; \ - PADDL v3, v2; \ - PXOR v2, v1; \ - ROTL_SSE2(7, t0, v1) - -#define CHACHA_SHUFFLE(v1, v2, v3) \ - PSHUFL $0x39, v1, v1; \ - PSHUFL $0x4E, v2, v2; \ - PSHUFL $0x93, v3, v3 - -#define XOR(dst, src, off, v0, v1, v2, v3, t0) \ - MOVOU 0+off(src), t0; \ - PXOR v0, t0; \ - MOVOU t0, 0+off(dst); \ - MOVOU 16+off(src), t0; \ - PXOR v1, t0; \ - MOVOU t0, 16+off(dst); \ - MOVOU 32+off(src), t0; \ - PXOR v2, t0; \ - MOVOU t0, 32+off(dst); \ - MOVOU 48+off(src), t0; \ - PXOR v3, t0; \ - MOVOU t0, 48+off(dst) - -// func xorKeyStreamSSE2(dst, src []byte, block, state *[64]byte, rounds int) int -TEXT ·xorKeyStreamSSE2(SB), 4, $112-80 - MOVQ dst_base+0(FP), DI - MOVQ src_base+24(FP), SI - MOVQ src_len+32(FP), CX - MOVQ block+48(FP), BX - MOVQ state+56(FP), AX - MOVQ rounds+64(FP), DX - - MOVQ SP, R9 - ADDQ $16, SP - ANDQ $-16, SP - - MOVOU 0(AX), X0 - MOVOU 16(AX), X1 - MOVOU 32(AX), X2 - MOVOU 48(AX), X3 - MOVOU ·one<>(SB), X15 - - TESTQ CX, CX - JZ done - - CMPQ CX, $64 - JBE between_0_and_64 - - CMPQ CX, $128 - JBE between_64_and_128 - - MOVO X0, 0(SP) - MOVO X1, 16(SP) - MOVO X2, 32(SP) - MOVO X3, 48(SP) - MOVO X15, 64(SP) - - CMPQ CX, $192 - JBE between_128_and_192 - - MOVQ $192, R14 - -at_least_256: - MOVO X0, X4 - MOVO X1, X5 - MOVO X2, X6 - MOVO X3, X7 - PADDQ 64(SP), X7 - MOVO X0, X12 - MOVO X1, X13 - MOVO X2, X14 - MOVO X7, X15 - PADDQ 64(SP), X15 - MOVO X0, X8 - MOVO X1, X9 - MOVO X2, X10 - MOVO X15, X11 - PADDQ 64(SP), X11 - - MOVQ DX, R8 - -chacha_loop_256: - MOVO X8, 80(SP) - CHACHA_QROUND_SSE2(X0, X1, X2, X3, X8) - CHACHA_QROUND_SSE2(X4, X5, X6, X7, X8) - MOVO 80(SP), X8 - - MOVO X0, 80(SP) - CHACHA_QROUND_SSE2(X12, X13, X14, X15, X0) - CHACHA_QROUND_SSE2(X8, X9, X10, X11, X0) - MOVO 80(SP), X0 - - CHACHA_SHUFFLE(X1, X2, X3) - CHACHA_SHUFFLE(X5, X6, X7) - CHACHA_SHUFFLE(X13, X14, X15) - CHACHA_SHUFFLE(X9, X10, X11) - - MOVO X8, 80(SP) - CHACHA_QROUND_SSE2(X0, X1, X2, X3, X8) - CHACHA_QROUND_SSE2(X4, X5, X6, X7, X8) - MOVO 80(SP), X8 - - MOVO X0, 80(SP) - CHACHA_QROUND_SSE2(X12, X13, X14, X15, X0) - CHACHA_QROUND_SSE2(X8, X9, X10, X11, X0) - MOVO 80(SP), X0 - - CHACHA_SHUFFLE(X3, X2, X1) - CHACHA_SHUFFLE(X7, X6, X5) - CHACHA_SHUFFLE(X15, X14, X13) - CHACHA_SHUFFLE(X11, X10, X9) - SUBQ $2, R8 - JA chacha_loop_256 - - MOVO X8, 80(SP) - - PADDL 0(SP), X0 - PADDL 16(SP), X1 - PADDL 32(SP), X2 - PADDL 48(SP), X3 - XOR(DI, SI, 0, X0, X1, X2, X3, X8) - - MOVO 0(SP), X0 - MOVO 16(SP), X1 - MOVO 32(SP), X2 - MOVO 48(SP), X3 - PADDQ 64(SP), X3 - - PADDL X0, X4 - PADDL X1, X5 - PADDL X2, X6 - PADDL X3, X7 - PADDQ 64(SP), X3 - XOR(DI, SI, 64, X4, X5, X6, X7, X8) - - MOVO 64(SP), X5 - MOVO 80(SP), X8 - - PADDL X0, X12 - PADDL X1, X13 - PADDL X2, X14 - PADDL X3, X15 - PADDQ X5, X3 - XOR(DI, SI, 128, X12, X13, X14, X15, X4) - - PADDL X0, X8 - PADDL X1, X9 - PADDL X2, X10 - PADDL X3, X11 - PADDQ X5, X3 - - CMPQ CX, $256 - JB less_than_64 - - XOR(DI, SI, 192, X8, X9, X10, X11, X4) - MOVO X3, 48(SP) - ADDQ $256, SI - ADDQ $256, DI - SUBQ $256, CX - CMPQ CX, $192 - JA at_least_256 - - TESTQ CX, CX - JZ done - MOVO 64(SP), X15 - CMPQ CX, $64 - JBE between_0_and_64 - CMPQ CX, $128 - JBE between_64_and_128 - -between_128_and_192: - MOVQ $128, R14 - MOVO X0, X4 - MOVO X1, X5 - MOVO X2, X6 - MOVO X3, X7 - PADDQ X15, X7 - MOVO X0, X8 - MOVO X1, X9 - MOVO X2, X10 - MOVO X7, X11 - PADDQ X15, X11 - - MOVQ DX, R8 - -chacha_loop_192: - CHACHA_QROUND_SSE2(X0, X1, X2, X3, X12) - CHACHA_QROUND_SSE2(X4, X5, X6, X7, X12) - CHACHA_QROUND_SSE2(X8, X9, X10, X11, X12) - CHACHA_SHUFFLE(X1, X2, X3) - CHACHA_SHUFFLE(X5, X6, X7) - CHACHA_SHUFFLE(X9, X10, X11) - CHACHA_QROUND_SSE2(X0, X1, X2, X3, X12) - CHACHA_QROUND_SSE2(X4, X5, X6, X7, X12) - CHACHA_QROUND_SSE2(X8, X9, X10, X11, X12) - CHACHA_SHUFFLE(X3, X2, X1) - CHACHA_SHUFFLE(X7, X6, X5) - CHACHA_SHUFFLE(X11, X10, X9) - SUBQ $2, R8 - JA chacha_loop_192 - - PADDL 0(SP), X0 - PADDL 16(SP), X1 - PADDL 32(SP), X2 - PADDL 48(SP), X3 - XOR(DI, SI, 0, X0, X1, X2, X3, X12) - - MOVO 0(SP), X0 - MOVO 16(SP), X1 - MOVO 32(SP), X2 - MOVO 48(SP), X3 - PADDQ X15, X3 - - PADDL X0, X4 - PADDL X1, X5 - PADDL X2, X6 - PADDL X3, X7 - PADDQ X15, X3 - XOR(DI, SI, 64, X4, X5, X6, X7, X12) - - PADDL X0, X8 - PADDL X1, X9 - PADDL X2, X10 - PADDL X3, X11 - PADDQ X15, X3 - - CMPQ CX, $192 - JB less_than_64 - - XOR(DI, SI, 128, X8, X9, X10, X11, X12) - SUBQ $192, CX - JMP done - -between_64_and_128: - MOVQ $64, R14 - MOVO X0, X4 - MOVO X1, X5 - MOVO X2, X6 - MOVO X3, X7 - MOVO X0, X8 - MOVO X1, X9 - MOVO X2, X10 - MOVO X3, X11 - PADDQ X15, X11 - - MOVQ DX, R8 - -chacha_loop_128: - CHACHA_QROUND_SSE2(X4, X5, X6, X7, X12) - CHACHA_QROUND_SSE2(X8, X9, X10, X11, X12) - CHACHA_SHUFFLE(X5, X6, X7) - CHACHA_SHUFFLE(X9, X10, X11) - CHACHA_QROUND_SSE2(X4, X5, X6, X7, X12) - CHACHA_QROUND_SSE2(X8, X9, X10, X11, X12) - CHACHA_SHUFFLE(X7, X6, X5) - CHACHA_SHUFFLE(X11, X10, X9) - SUBQ $2, R8 - JA chacha_loop_128 - - PADDL X0, X4 - PADDL X1, X5 - PADDL X2, X6 - PADDL X3, X7 - PADDQ X15, X3 - PADDL X0, X8 - PADDL X1, X9 - PADDL X2, X10 - PADDL X3, X11 - PADDQ X15, X3 - XOR(DI, SI, 0, X4, X5, X6, X7, X12) - - CMPQ CX, $128 - JB less_than_64 - - XOR(DI, SI, 64, X8, X9, X10, X11, X12) - SUBQ $128, CX - JMP done - -between_0_and_64: - MOVQ $0, R14 - MOVO X0, X8 - MOVO X1, X9 - MOVO X2, X10 - MOVO X3, X11 - MOVQ DX, R8 - -chacha_loop_64: - CHACHA_QROUND_SSE2(X8, X9, X10, X11, X12) - CHACHA_SHUFFLE(X9, X10, X11) - CHACHA_QROUND_SSE2(X8, X9, X10, X11, X12) - CHACHA_SHUFFLE(X11, X10, X9) - SUBQ $2, R8 - JA chacha_loop_64 - - PADDL X0, X8 - PADDL X1, X9 - PADDL X2, X10 - PADDL X3, X11 - PADDQ X15, X3 - CMPQ CX, $64 - JB less_than_64 - - XOR(DI, SI, 0, X8, X9, X10, X11, X12) - SUBQ $64, CX - JMP done - -less_than_64: - // R14 contains the num of bytes already xor'd - ADDQ R14, SI - ADDQ R14, DI - SUBQ R14, CX - MOVOU X8, 0(BX) - MOVOU X9, 16(BX) - MOVOU X10, 32(BX) - MOVOU X11, 48(BX) - XORQ R11, R11 - XORQ R12, R12 - MOVQ CX, BP - -xor_loop: - MOVB 0(SI), R11 - MOVB 0(BX), R12 - XORQ R11, R12 - MOVB R12, 0(DI) - INCQ SI - INCQ BX - INCQ DI - DECQ BP - JA xor_loop - -done: - MOVOU X3, 48(AX) - MOVQ R9, SP - MOVQ CX, ret+72(FP) - RET - -// func xorKeyStreamSSSE3(dst, src []byte, block, state *[64]byte, rounds int) int -TEXT ·xorKeyStreamSSSE3(SB), 4, $144-80 - MOVQ dst_base+0(FP), DI - MOVQ src_base+24(FP), SI - MOVQ src_len+32(FP), CX - MOVQ block+48(FP), BX - MOVQ state+56(FP), AX - MOVQ rounds+64(FP), DX - - MOVQ SP, R9 - ADDQ $16, SP - ANDQ $-16, SP - - MOVOU 0(AX), X0 - MOVOU 16(AX), X1 - MOVOU 32(AX), X2 - MOVOU 48(AX), X3 - MOVOU ·rol16<>(SB), X13 - MOVOU ·rol8<>(SB), X14 - MOVOU ·one<>(SB), X15 - - TESTQ CX, CX - JZ done - - CMPQ CX, $64 - JBE between_0_and_64 - - CMPQ CX, $128 - JBE between_64_and_128 - - MOVO X0, 0(SP) - MOVO X1, 16(SP) - MOVO X2, 32(SP) - MOVO X3, 48(SP) - MOVO X15, 64(SP) - - CMPQ CX, $192 - JBE between_128_and_192 - - MOVO X13, 96(SP) - MOVO X14, 112(SP) - MOVQ $192, R14 - -at_least_256: - MOVO X0, X4 - MOVO X1, X5 - MOVO X2, X6 - MOVO X3, X7 - PADDQ 64(SP), X7 - MOVO X0, X12 - MOVO X1, X13 - MOVO X2, X14 - MOVO X7, X15 - PADDQ 64(SP), X15 - MOVO X0, X8 - MOVO X1, X9 - MOVO X2, X10 - MOVO X15, X11 - PADDQ 64(SP), X11 - - MOVQ DX, R8 - -chacha_loop_256: - MOVO X8, 80(SP) - CHACHA_QROUND_SSSE3(X0, X1, X2, X3, X8, 96(SP), 112(SP)) - CHACHA_QROUND_SSSE3(X4, X5, X6, X7, X8, 96(SP), 112(SP)) - MOVO 80(SP), X8 - - MOVO X0, 80(SP) - CHACHA_QROUND_SSSE3(X12, X13, X14, X15, X0, 96(SP), 112(SP)) - CHACHA_QROUND_SSSE3(X8, X9, X10, X11, X0, 96(SP), 112(SP)) - MOVO 80(SP), X0 - - CHACHA_SHUFFLE(X1, X2, X3) - CHACHA_SHUFFLE(X5, X6, X7) - CHACHA_SHUFFLE(X13, X14, X15) - CHACHA_SHUFFLE(X9, X10, X11) - - MOVO X8, 80(SP) - CHACHA_QROUND_SSSE3(X0, X1, X2, X3, X8, 96(SP), 112(SP)) - CHACHA_QROUND_SSSE3(X4, X5, X6, X7, X8, 96(SP), 112(SP)) - MOVO 80(SP), X8 - - MOVO X0, 80(SP) - CHACHA_QROUND_SSSE3(X12, X13, X14, X15, X0, 96(SP), 112(SP)) - CHACHA_QROUND_SSSE3(X8, X9, X10, X11, X0, 96(SP), 112(SP)) - MOVO 80(SP), X0 - - CHACHA_SHUFFLE(X3, X2, X1) - CHACHA_SHUFFLE(X7, X6, X5) - CHACHA_SHUFFLE(X15, X14, X13) - CHACHA_SHUFFLE(X11, X10, X9) - SUBQ $2, R8 - JA chacha_loop_256 - - MOVO X8, 80(SP) - - PADDL 0(SP), X0 - PADDL 16(SP), X1 - PADDL 32(SP), X2 - PADDL 48(SP), X3 - XOR(DI, SI, 0, X0, X1, X2, X3, X8) - MOVO 0(SP), X0 - MOVO 16(SP), X1 - MOVO 32(SP), X2 - MOVO 48(SP), X3 - PADDQ 64(SP), X3 - - PADDL X0, X4 - PADDL X1, X5 - PADDL X2, X6 - PADDL X3, X7 - PADDQ 64(SP), X3 - XOR(DI, SI, 64, X4, X5, X6, X7, X8) - - MOVO 64(SP), X5 - MOVO 80(SP), X8 - - PADDL X0, X12 - PADDL X1, X13 - PADDL X2, X14 - PADDL X3, X15 - PADDQ X5, X3 - XOR(DI, SI, 128, X12, X13, X14, X15, X4) - - PADDL X0, X8 - PADDL X1, X9 - PADDL X2, X10 - PADDL X3, X11 - PADDQ X5, X3 - - CMPQ CX, $256 - JB less_than_64 - - XOR(DI, SI, 192, X8, X9, X10, X11, X4) - MOVO X3, 48(SP) - ADDQ $256, SI - ADDQ $256, DI - SUBQ $256, CX - CMPQ CX, $192 - JA at_least_256 - - TESTQ CX, CX - JZ done - MOVOU ·rol16<>(SB), X13 - MOVOU ·rol8<>(SB), X14 - MOVO 64(SP), X15 - CMPQ CX, $64 - JBE between_0_and_64 - CMPQ CX, $128 - JBE between_64_and_128 - -between_128_and_192: - MOVQ $128, R14 - MOVO X0, X4 - MOVO X1, X5 - MOVO X2, X6 - MOVO X3, X7 - PADDQ X15, X7 - MOVO X0, X8 - MOVO X1, X9 - MOVO X2, X10 - MOVO X7, X11 - PADDQ X15, X11 - - MOVQ DX, R8 - -chacha_loop_192: - CHACHA_QROUND_SSSE3(X0, X1, X2, X3, X12, X13, X14) - CHACHA_QROUND_SSSE3(X4, X5, X6, X7, X12, X13, X14) - CHACHA_QROUND_SSSE3(X8, X9, X10, X11, X12, X13, X14) - CHACHA_SHUFFLE(X1, X2, X3) - CHACHA_SHUFFLE(X5, X6, X7) - CHACHA_SHUFFLE(X9, X10, X11) - CHACHA_QROUND_SSSE3(X0, X1, X2, X3, X12, X13, X14) - CHACHA_QROUND_SSSE3(X4, X5, X6, X7, X12, X13, X14) - CHACHA_QROUND_SSSE3(X8, X9, X10, X11, X12, X13, X14) - CHACHA_SHUFFLE(X3, X2, X1) - CHACHA_SHUFFLE(X7, X6, X5) - CHACHA_SHUFFLE(X11, X10, X9) - SUBQ $2, R8 - JA chacha_loop_192 - - PADDL 0(SP), X0 - PADDL 16(SP), X1 - PADDL 32(SP), X2 - PADDL 48(SP), X3 - XOR(DI, SI, 0, X0, X1, X2, X3, X12) - - MOVO 0(SP), X0 - MOVO 16(SP), X1 - MOVO 32(SP), X2 - MOVO 48(SP), X3 - PADDQ X15, X3 - - PADDL X0, X4 - PADDL X1, X5 - PADDL X2, X6 - PADDL X3, X7 - PADDQ X15, X3 - XOR(DI, SI, 64, X4, X5, X6, X7, X12) - - PADDL X0, X8 - PADDL X1, X9 - PADDL X2, X10 - PADDL X3, X11 - PADDQ X15, X3 - - CMPQ CX, $192 - JB less_than_64 - - XOR(DI, SI, 128, X8, X9, X10, X11, X12) - SUBQ $192, CX - JMP done - -between_64_and_128: - MOVQ $64, R14 - MOVO X0, X4 - MOVO X1, X5 - MOVO X2, X6 - MOVO X3, X7 - MOVO X0, X8 - MOVO X1, X9 - MOVO X2, X10 - MOVO X3, X11 - PADDQ X15, X11 - - MOVQ DX, R8 - -chacha_loop_128: - CHACHA_QROUND_SSSE3(X4, X5, X6, X7, X12, X13, X14) - CHACHA_QROUND_SSSE3(X8, X9, X10, X11, X12, X13, X14) - CHACHA_SHUFFLE(X5, X6, X7) - CHACHA_SHUFFLE(X9, X10, X11) - CHACHA_QROUND_SSSE3(X4, X5, X6, X7, X12, X13, X14) - CHACHA_QROUND_SSSE3(X8, X9, X10, X11, X12, X13, X14) - CHACHA_SHUFFLE(X7, X6, X5) - CHACHA_SHUFFLE(X11, X10, X9) - SUBQ $2, R8 - JA chacha_loop_128 - - PADDL X0, X4 - PADDL X1, X5 - PADDL X2, X6 - PADDL X3, X7 - PADDQ X15, X3 - PADDL X0, X8 - PADDL X1, X9 - PADDL X2, X10 - PADDL X3, X11 - PADDQ X15, X3 - XOR(DI, SI, 0, X4, X5, X6, X7, X12) - - CMPQ CX, $128 - JB less_than_64 - - XOR(DI, SI, 64, X8, X9, X10, X11, X12) - SUBQ $128, CX - JMP done - -between_0_and_64: - MOVQ $0, R14 - MOVO X0, X8 - MOVO X1, X9 - MOVO X2, X10 - MOVO X3, X11 - MOVQ DX, R8 - -chacha_loop_64: - CHACHA_QROUND_SSSE3(X8, X9, X10, X11, X12, X13, X14) - CHACHA_SHUFFLE(X9, X10, X11) - CHACHA_QROUND_SSSE3(X8, X9, X10, X11, X12, X13, X14) - CHACHA_SHUFFLE(X11, X10, X9) - SUBQ $2, R8 - JA chacha_loop_64 - - PADDL X0, X8 - PADDL X1, X9 - PADDL X2, X10 - PADDL X3, X11 - PADDQ X15, X3 - CMPQ CX, $64 - JB less_than_64 - - XOR(DI, SI, 0, X8, X9, X10, X11, X12) - SUBQ $64, CX - JMP done - -less_than_64: - // R14 contains the num of bytes already xor'd - ADDQ R14, SI - ADDQ R14, DI - SUBQ R14, CX - MOVOU X8, 0(BX) - MOVOU X9, 16(BX) - MOVOU X10, 32(BX) - MOVOU X11, 48(BX) - XORQ R11, R11 - XORQ R12, R12 - MOVQ CX, BP - -xor_loop: - MOVB 0(SI), R11 - MOVB 0(BX), R12 - XORQ R11, R12 - MOVB R12, 0(DI) - INCQ SI - INCQ BX - INCQ DI - DECQ BP - JA xor_loop - -done: - MOVQ R9, SP - MOVOU X3, 48(AX) - MOVQ CX, ret+72(FP) - RET - -// func supportsSSSE3() bool -TEXT ·supportsSSSE3(SB), NOSPLIT, $0-1 - XORQ AX, AX - INCQ AX - CPUID - SHRQ $9, CX - ANDQ $1, CX - MOVB CX, ret+0(FP) - RET - -// func initialize(state *[64]byte, key []byte, nonce *[16]byte) -TEXT ·initialize(SB), 4, $0-40 - MOVQ state+0(FP), DI - MOVQ key+8(FP), AX - MOVQ nonce+32(FP), BX - - MOVOU ·sigma<>(SB), X0 - MOVOU 0(AX), X1 - MOVOU 16(AX), X2 - MOVOU 0(BX), X3 - - MOVOU X0, 0(DI) - MOVOU X1, 16(DI) - MOVOU X2, 32(DI) - MOVOU X3, 48(DI) - RET - -// func hChaCha20SSE2(out *[32]byte, nonce *[16]byte, key *[32]byte) -TEXT ·hChaCha20SSE2(SB), 4, $0-24 - MOVQ out+0(FP), DI - MOVQ nonce+8(FP), AX - MOVQ key+16(FP), BX - - MOVOU ·sigma<>(SB), X0 - MOVOU 0(BX), X1 - MOVOU 16(BX), X2 - MOVOU 0(AX), X3 - - MOVQ $20, CX - -chacha_loop: - CHACHA_QROUND_SSE2(X0, X1, X2, X3, X4) - CHACHA_SHUFFLE(X1, X2, X3) - CHACHA_QROUND_SSE2(X0, X1, X2, X3, X4) - CHACHA_SHUFFLE(X3, X2, X1) - SUBQ $2, CX - JNZ chacha_loop - - MOVOU X0, 0(DI) - MOVOU X3, 16(DI) - RET - -// func hChaCha20SSSE3(out *[32]byte, nonce *[16]byte, key *[32]byte) -TEXT ·hChaCha20SSSE3(SB), 4, $0-24 - MOVQ out+0(FP), DI - MOVQ nonce+8(FP), AX - MOVQ key+16(FP), BX - - MOVOU ·sigma<>(SB), X0 - MOVOU 0(BX), X1 - MOVOU 16(BX), X2 - MOVOU 0(AX), X3 - MOVOU ·rol16<>(SB), X5 - MOVOU ·rol8<>(SB), X6 - - MOVQ $20, CX - -chacha_loop: - CHACHA_QROUND_SSSE3(X0, X1, X2, X3, X4, X5, X6) - CHACHA_SHUFFLE(X1, X2, X3) - CHACHA_QROUND_SSSE3(X0, X1, X2, X3, X4, X5, X6) - CHACHA_SHUFFLE(X3, X2, X1) - SUBQ $2, CX - JNZ chacha_loop - - MOVOU X0, 0(DI) - MOVOU X3, 16(DI) - RET diff --git a/vendor/github.com/aead/chacha20/chacha/chacha_generic.go b/vendor/github.com/aead/chacha20/chacha/chacha_generic.go deleted file mode 100644 index 8832d5bce4d..00000000000 --- a/vendor/github.com/aead/chacha20/chacha/chacha_generic.go +++ /dev/null @@ -1,319 +0,0 @@ -// Copyright (c) 2016 Andreas Auernhammer. All rights reserved. -// Use of this source code is governed by a license that can be -// found in the LICENSE file. - -package chacha - -import "encoding/binary" - -var sigma = [4]uint32{0x61707865, 0x3320646e, 0x79622d32, 0x6b206574} - -func xorKeyStreamGeneric(dst, src []byte, block, state *[64]byte, rounds int) int { - for len(src) >= 64 { - chachaGeneric(block, state, rounds) - - for i, v := range block { - dst[i] = src[i] ^ v - } - src = src[64:] - dst = dst[64:] - } - - n := len(src) - if n > 0 { - chachaGeneric(block, state, rounds) - for i, v := range src { - dst[i] = v ^ block[i] - } - } - return n -} - -func chachaGeneric(dst *[64]byte, state *[64]byte, rounds int) { - v00 := binary.LittleEndian.Uint32(state[0:]) - v01 := binary.LittleEndian.Uint32(state[4:]) - v02 := binary.LittleEndian.Uint32(state[8:]) - v03 := binary.LittleEndian.Uint32(state[12:]) - v04 := binary.LittleEndian.Uint32(state[16:]) - v05 := binary.LittleEndian.Uint32(state[20:]) - v06 := binary.LittleEndian.Uint32(state[24:]) - v07 := binary.LittleEndian.Uint32(state[28:]) - v08 := binary.LittleEndian.Uint32(state[32:]) - v09 := binary.LittleEndian.Uint32(state[36:]) - v10 := binary.LittleEndian.Uint32(state[40:]) - v11 := binary.LittleEndian.Uint32(state[44:]) - v12 := binary.LittleEndian.Uint32(state[48:]) - v13 := binary.LittleEndian.Uint32(state[52:]) - v14 := binary.LittleEndian.Uint32(state[56:]) - v15 := binary.LittleEndian.Uint32(state[60:]) - - s00, s01, s02, s03, s04, s05, s06, s07 := v00, v01, v02, v03, v04, v05, v06, v07 - s08, s09, s10, s11, s12, s13, s14, s15 := v08, v09, v10, v11, v12, v13, v14, v15 - - for i := 0; i < rounds; i += 2 { - v00 += v04 - v12 ^= v00 - v12 = (v12 << 16) | (v12 >> 16) - v08 += v12 - v04 ^= v08 - v04 = (v04 << 12) | (v04 >> 20) - v00 += v04 - v12 ^= v00 - v12 = (v12 << 8) | (v12 >> 24) - v08 += v12 - v04 ^= v08 - v04 = (v04 << 7) | (v04 >> 25) - v01 += v05 - v13 ^= v01 - v13 = (v13 << 16) | (v13 >> 16) - v09 += v13 - v05 ^= v09 - v05 = (v05 << 12) | (v05 >> 20) - v01 += v05 - v13 ^= v01 - v13 = (v13 << 8) | (v13 >> 24) - v09 += v13 - v05 ^= v09 - v05 = (v05 << 7) | (v05 >> 25) - v02 += v06 - v14 ^= v02 - v14 = (v14 << 16) | (v14 >> 16) - v10 += v14 - v06 ^= v10 - v06 = (v06 << 12) | (v06 >> 20) - v02 += v06 - v14 ^= v02 - v14 = (v14 << 8) | (v14 >> 24) - v10 += v14 - v06 ^= v10 - v06 = (v06 << 7) | (v06 >> 25) - v03 += v07 - v15 ^= v03 - v15 = (v15 << 16) | (v15 >> 16) - v11 += v15 - v07 ^= v11 - v07 = (v07 << 12) | (v07 >> 20) - v03 += v07 - v15 ^= v03 - v15 = (v15 << 8) | (v15 >> 24) - v11 += v15 - v07 ^= v11 - v07 = (v07 << 7) | (v07 >> 25) - v00 += v05 - v15 ^= v00 - v15 = (v15 << 16) | (v15 >> 16) - v10 += v15 - v05 ^= v10 - v05 = (v05 << 12) | (v05 >> 20) - v00 += v05 - v15 ^= v00 - v15 = (v15 << 8) | (v15 >> 24) - v10 += v15 - v05 ^= v10 - v05 = (v05 << 7) | (v05 >> 25) - v01 += v06 - v12 ^= v01 - v12 = (v12 << 16) | (v12 >> 16) - v11 += v12 - v06 ^= v11 - v06 = (v06 << 12) | (v06 >> 20) - v01 += v06 - v12 ^= v01 - v12 = (v12 << 8) | (v12 >> 24) - v11 += v12 - v06 ^= v11 - v06 = (v06 << 7) | (v06 >> 25) - v02 += v07 - v13 ^= v02 - v13 = (v13 << 16) | (v13 >> 16) - v08 += v13 - v07 ^= v08 - v07 = (v07 << 12) | (v07 >> 20) - v02 += v07 - v13 ^= v02 - v13 = (v13 << 8) | (v13 >> 24) - v08 += v13 - v07 ^= v08 - v07 = (v07 << 7) | (v07 >> 25) - v03 += v04 - v14 ^= v03 - v14 = (v14 << 16) | (v14 >> 16) - v09 += v14 - v04 ^= v09 - v04 = (v04 << 12) | (v04 >> 20) - v03 += v04 - v14 ^= v03 - v14 = (v14 << 8) | (v14 >> 24) - v09 += v14 - v04 ^= v09 - v04 = (v04 << 7) | (v04 >> 25) - } - - v00 += s00 - v01 += s01 - v02 += s02 - v03 += s03 - v04 += s04 - v05 += s05 - v06 += s06 - v07 += s07 - v08 += s08 - v09 += s09 - v10 += s10 - v11 += s11 - v12 += s12 - v13 += s13 - v14 += s14 - v15 += s15 - - s12++ - binary.LittleEndian.PutUint32(state[48:], s12) - if s12 == 0 { // indicates overflow - s13++ - binary.LittleEndian.PutUint32(state[52:], s13) - } - - binary.LittleEndian.PutUint32(dst[0:], v00) - binary.LittleEndian.PutUint32(dst[4:], v01) - binary.LittleEndian.PutUint32(dst[8:], v02) - binary.LittleEndian.PutUint32(dst[12:], v03) - binary.LittleEndian.PutUint32(dst[16:], v04) - binary.LittleEndian.PutUint32(dst[20:], v05) - binary.LittleEndian.PutUint32(dst[24:], v06) - binary.LittleEndian.PutUint32(dst[28:], v07) - binary.LittleEndian.PutUint32(dst[32:], v08) - binary.LittleEndian.PutUint32(dst[36:], v09) - binary.LittleEndian.PutUint32(dst[40:], v10) - binary.LittleEndian.PutUint32(dst[44:], v11) - binary.LittleEndian.PutUint32(dst[48:], v12) - binary.LittleEndian.PutUint32(dst[52:], v13) - binary.LittleEndian.PutUint32(dst[56:], v14) - binary.LittleEndian.PutUint32(dst[60:], v15) -} - -func hChaCha20Generic(out *[32]byte, nonce *[16]byte, key *[32]byte) { - v00 := sigma[0] - v01 := sigma[1] - v02 := sigma[2] - v03 := sigma[3] - v04 := binary.LittleEndian.Uint32(key[0:]) - v05 := binary.LittleEndian.Uint32(key[4:]) - v06 := binary.LittleEndian.Uint32(key[8:]) - v07 := binary.LittleEndian.Uint32(key[12:]) - v08 := binary.LittleEndian.Uint32(key[16:]) - v09 := binary.LittleEndian.Uint32(key[20:]) - v10 := binary.LittleEndian.Uint32(key[24:]) - v11 := binary.LittleEndian.Uint32(key[28:]) - v12 := binary.LittleEndian.Uint32(nonce[0:]) - v13 := binary.LittleEndian.Uint32(nonce[4:]) - v14 := binary.LittleEndian.Uint32(nonce[8:]) - v15 := binary.LittleEndian.Uint32(nonce[12:]) - - for i := 0; i < 20; i += 2 { - v00 += v04 - v12 ^= v00 - v12 = (v12 << 16) | (v12 >> 16) - v08 += v12 - v04 ^= v08 - v04 = (v04 << 12) | (v04 >> 20) - v00 += v04 - v12 ^= v00 - v12 = (v12 << 8) | (v12 >> 24) - v08 += v12 - v04 ^= v08 - v04 = (v04 << 7) | (v04 >> 25) - v01 += v05 - v13 ^= v01 - v13 = (v13 << 16) | (v13 >> 16) - v09 += v13 - v05 ^= v09 - v05 = (v05 << 12) | (v05 >> 20) - v01 += v05 - v13 ^= v01 - v13 = (v13 << 8) | (v13 >> 24) - v09 += v13 - v05 ^= v09 - v05 = (v05 << 7) | (v05 >> 25) - v02 += v06 - v14 ^= v02 - v14 = (v14 << 16) | (v14 >> 16) - v10 += v14 - v06 ^= v10 - v06 = (v06 << 12) | (v06 >> 20) - v02 += v06 - v14 ^= v02 - v14 = (v14 << 8) | (v14 >> 24) - v10 += v14 - v06 ^= v10 - v06 = (v06 << 7) | (v06 >> 25) - v03 += v07 - v15 ^= v03 - v15 = (v15 << 16) | (v15 >> 16) - v11 += v15 - v07 ^= v11 - v07 = (v07 << 12) | (v07 >> 20) - v03 += v07 - v15 ^= v03 - v15 = (v15 << 8) | (v15 >> 24) - v11 += v15 - v07 ^= v11 - v07 = (v07 << 7) | (v07 >> 25) - v00 += v05 - v15 ^= v00 - v15 = (v15 << 16) | (v15 >> 16) - v10 += v15 - v05 ^= v10 - v05 = (v05 << 12) | (v05 >> 20) - v00 += v05 - v15 ^= v00 - v15 = (v15 << 8) | (v15 >> 24) - v10 += v15 - v05 ^= v10 - v05 = (v05 << 7) | (v05 >> 25) - v01 += v06 - v12 ^= v01 - v12 = (v12 << 16) | (v12 >> 16) - v11 += v12 - v06 ^= v11 - v06 = (v06 << 12) | (v06 >> 20) - v01 += v06 - v12 ^= v01 - v12 = (v12 << 8) | (v12 >> 24) - v11 += v12 - v06 ^= v11 - v06 = (v06 << 7) | (v06 >> 25) - v02 += v07 - v13 ^= v02 - v13 = (v13 << 16) | (v13 >> 16) - v08 += v13 - v07 ^= v08 - v07 = (v07 << 12) | (v07 >> 20) - v02 += v07 - v13 ^= v02 - v13 = (v13 << 8) | (v13 >> 24) - v08 += v13 - v07 ^= v08 - v07 = (v07 << 7) | (v07 >> 25) - v03 += v04 - v14 ^= v03 - v14 = (v14 << 16) | (v14 >> 16) - v09 += v14 - v04 ^= v09 - v04 = (v04 << 12) | (v04 >> 20) - v03 += v04 - v14 ^= v03 - v14 = (v14 << 8) | (v14 >> 24) - v09 += v14 - v04 ^= v09 - v04 = (v04 << 7) | (v04 >> 25) - } - - binary.LittleEndian.PutUint32(out[0:], v00) - binary.LittleEndian.PutUint32(out[4:], v01) - binary.LittleEndian.PutUint32(out[8:], v02) - binary.LittleEndian.PutUint32(out[12:], v03) - binary.LittleEndian.PutUint32(out[16:], v12) - binary.LittleEndian.PutUint32(out[20:], v13) - binary.LittleEndian.PutUint32(out[24:], v14) - binary.LittleEndian.PutUint32(out[28:], v15) -} diff --git a/vendor/github.com/aead/chacha20/chacha/chacha_go16_amd64.go b/vendor/github.com/aead/chacha20/chacha/chacha_go16_amd64.go deleted file mode 100644 index 0dcb302790e..00000000000 --- a/vendor/github.com/aead/chacha20/chacha/chacha_go16_amd64.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) 2017 Andreas Auernhammer. All rights reserved. -// Use of this source code is governed by a license that can be -// found in the LICENSE file. - -// +build amd64,!gccgo,!appengine,!nacl,!go1.7 - -package chacha - -func init() { - useSSE2 = true - useSSSE3 = supportsSSSE3() - useAVX2 = false -} - -// This function is implemented in chacha_amd64.s -//go:noescape -func initialize(state *[64]byte, key []byte, nonce *[16]byte) - -// This function is implemented in chacha_amd64.s -//go:noescape -func supportsSSSE3() bool - -// This function is implemented in chacha_amd64.s -//go:noescape -func hChaCha20SSE2(out *[32]byte, nonce *[16]byte, key *[32]byte) - -// This function is implemented in chacha_amd64.s -//go:noescape -func hChaCha20SSSE3(out *[32]byte, nonce *[16]byte, key *[32]byte) - -// This function is implemented in chacha_amd64.s -//go:noescape -func xorKeyStreamSSE2(dst, src []byte, block, state *[64]byte, rounds int) int - -// This function is implemented in chacha_amd64.s -//go:noescape -func xorKeyStreamSSSE3(dst, src []byte, block, state *[64]byte, rounds int) int - -func hChaCha20(out *[32]byte, nonce *[16]byte, key *[32]byte) { - if useSSSE3 { - hChaCha20SSSE3(out, nonce, key) - } else if useSSE2 { // on amd64 this is always true - used to test generic on amd64 - hChaCha20SSE2(out, nonce, key) - } else { - hChaCha20Generic(out, nonce, key) - } -} - -func xorKeyStream(dst, src []byte, block, state *[64]byte, rounds int) int { - if useSSSE3 { - return xorKeyStreamSSSE3(dst, src, block, state, rounds) - } else if useSSE2 { // on amd64 this is always true - used to test generic on amd64 - return xorKeyStreamSSE2(dst, src, block, state, rounds) - } - return xorKeyStreamGeneric(dst, src, block, state, rounds) -} diff --git a/vendor/github.com/aead/chacha20/chacha/chacha_go17_amd64.go b/vendor/github.com/aead/chacha20/chacha/chacha_go17_amd64.go deleted file mode 100644 index 9ff41cf2850..00000000000 --- a/vendor/github.com/aead/chacha20/chacha/chacha_go17_amd64.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 2017 Andreas Auernhammer. All rights reserved. -// Use of this source code is governed by a license that can be -// found in the LICENSE file. - -// +build go1.7,amd64,!gccgo,!appengine,!nacl - -package chacha - -func init() { - useSSE2 = true - useSSSE3 = supportsSSSE3() - useAVX2 = supportsAVX2() -} - -// This function is implemented in chacha_amd64.s -//go:noescape -func initialize(state *[64]byte, key []byte, nonce *[16]byte) - -// This function is implemented in chacha_amd64.s -//go:noescape -func supportsSSSE3() bool - -// This function is implemented in chachaAVX2_amd64.s -//go:noescape -func supportsAVX2() bool - -// This function is implemented in chacha_amd64.s -//go:noescape -func hChaCha20SSE2(out *[32]byte, nonce *[16]byte, key *[32]byte) - -// This function is implemented in chacha_amd64.s -//go:noescape -func hChaCha20SSSE3(out *[32]byte, nonce *[16]byte, key *[32]byte) - -// This function is implemented in chachaAVX2_amd64.s -//go:noescape -func hChaCha20AVX(out *[32]byte, nonce *[16]byte, key *[32]byte) - -// This function is implemented in chacha_amd64.s -//go:noescape -func xorKeyStreamSSE2(dst, src []byte, block, state *[64]byte, rounds int) int - -// This function is implemented in chacha_amd64.s -//go:noescape -func xorKeyStreamSSSE3(dst, src []byte, block, state *[64]byte, rounds int) int - -// This function is implemented in chachaAVX2_amd64.s -//go:noescape -func xorKeyStreamAVX2(dst, src []byte, block, state *[64]byte, rounds int) int - -func hChaCha20(out *[32]byte, nonce *[16]byte, key *[32]byte) { - if useAVX2 { - hChaCha20AVX(out, nonce, key) - } else if useSSSE3 { - hChaCha20SSSE3(out, nonce, key) - } else if useSSE2 { // on amd64 this is always true - neccessary for testing generic on amd64 - hChaCha20SSE2(out, nonce, key) - } else { - hChaCha20Generic(out, nonce, key) - } -} - -func xorKeyStream(dst, src []byte, block, state *[64]byte, rounds int) int { - if useAVX2 { - return xorKeyStreamAVX2(dst, src, block, state, rounds) - } else if useSSSE3 { - return xorKeyStreamSSSE3(dst, src, block, state, rounds) - } else if useSSE2 { // on amd64 this is always true - neccessary for testing generic on amd64 - return xorKeyStreamSSE2(dst, src, block, state, rounds) - } - return xorKeyStreamGeneric(dst, src, block, state, rounds) -} diff --git a/vendor/github.com/aead/chacha20/chacha/chacha_ref.go b/vendor/github.com/aead/chacha20/chacha/chacha_ref.go deleted file mode 100644 index 2c95a0c8961..00000000000 --- a/vendor/github.com/aead/chacha20/chacha/chacha_ref.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2016 Andreas Auernhammer. All rights reserved. -// Use of this source code is governed by a license that can be -// found in the LICENSE file. - -// +build !amd64,!386 gccgo appengine nacl - -package chacha - -import "encoding/binary" - -func initialize(state *[64]byte, key []byte, nonce *[16]byte) { - binary.LittleEndian.PutUint32(state[0:], sigma[0]) - binary.LittleEndian.PutUint32(state[4:], sigma[1]) - binary.LittleEndian.PutUint32(state[8:], sigma[2]) - binary.LittleEndian.PutUint32(state[12:], sigma[3]) - copy(state[16:], key[:]) - copy(state[48:], nonce[:]) -} - -func xorKeyStream(dst, src []byte, block, state *[64]byte, rounds int) int { - return xorKeyStreamGeneric(dst, src, block, state, rounds) -} - -func hChaCha20(out *[32]byte, nonce *[16]byte, key *[32]byte) { - hChaCha20Generic(out, nonce, key) -} diff --git a/vendor/github.com/aead/chacha20/chacha20.go b/vendor/github.com/aead/chacha20/chacha20.go deleted file mode 100644 index df6ddd23181..00000000000 --- a/vendor/github.com/aead/chacha20/chacha20.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2016 Andreas Auernhammer. All rights reserved. -// Use of this source code is governed by a license that can be -// found in the LICENSE file. - -// Package chacha20 implements the ChaCha20 / XChaCha20 stream chipher. -// Notice that one specific key-nonce combination must be unique for all time. -// -// There are three versions of ChaCha20: -// - ChaCha20 with a 64 bit nonce (en/decrypt up to 2^64 * 64 bytes for one key-nonce combination) -// - ChaCha20 with a 96 bit nonce (en/decrypt up to 2^32 * 64 bytes (~256 GB) for one key-nonce combination) -// - XChaCha20 with a 192 bit nonce (en/decrypt up to 2^64 * 64 bytes for one key-nonce combination) -package chacha20 // import "github.com/aead/chacha20" - -import ( - "crypto/cipher" - - "github.com/aead/chacha20/chacha" -) - -// XORKeyStream crypts bytes from src to dst using the given nonce and key. -// The length of the nonce determinds the version of ChaCha20: -// - 8 bytes: ChaCha20 with a 64 bit nonce and a 2^64 * 64 byte period. -// - 12 bytes: ChaCha20 as defined in RFC 7539 and a 2^32 * 64 byte period. -// - 24 bytes: XChaCha20 with a 192 bit nonce and a 2^64 * 64 byte period. -// Src and dst may be the same slice but otherwise should not overlap. -// If len(dst) < len(src) this function panics. -// If the nonce is neither 64, 96 nor 192 bits long, this function panics. -func XORKeyStream(dst, src, nonce, key []byte) { - chacha.XORKeyStream(dst, src, nonce, key, 20) -} - -// NewCipher returns a new cipher.Stream implementing a ChaCha20 version. -// The nonce must be unique for one key for all time. -// The length of the nonce determinds the version of ChaCha20: -// - 8 bytes: ChaCha20 with a 64 bit nonce and a 2^64 * 64 byte period. -// - 12 bytes: ChaCha20 as defined in RFC 7539 and a 2^32 * 64 byte period. -// - 24 bytes: XChaCha20 with a 192 bit nonce and a 2^64 * 64 byte period. -// If the nonce is neither 64, 96 nor 192 bits long, a non-nil error is returned. -func NewCipher(nonce, key []byte) (cipher.Stream, error) { - return chacha.NewCipher(nonce, key, 20) -} diff --git a/vendor/github.com/alecthomas/template/LICENSE b/vendor/github.com/alecthomas/template/LICENSE deleted file mode 100644 index 74487567632..00000000000 --- a/vendor/github.com/alecthomas/template/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2012 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/alecthomas/template/doc.go b/vendor/github.com/alecthomas/template/doc.go deleted file mode 100644 index 223c595c25d..00000000000 --- a/vendor/github.com/alecthomas/template/doc.go +++ /dev/null @@ -1,406 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* -Package template implements data-driven templates for generating textual output. - -To generate HTML output, see package html/template, which has the same interface -as this package but automatically secures HTML output against certain attacks. - -Templates are executed by applying them to a data structure. Annotations in the -template refer to elements of the data structure (typically a field of a struct -or a key in a map) to control execution and derive values to be displayed. -Execution of the template walks the structure and sets the cursor, represented -by a period '.' and called "dot", to the value at the current location in the -structure as execution proceeds. - -The input text for a template is UTF-8-encoded text in any format. -"Actions"--data evaluations or control structures--are delimited by -"{{" and "}}"; all text outside actions is copied to the output unchanged. -Actions may not span newlines, although comments can. - -Once parsed, a template may be executed safely in parallel. - -Here is a trivial example that prints "17 items are made of wool". - - type Inventory struct { - Material string - Count uint - } - sweaters := Inventory{"wool", 17} - tmpl, err := template.New("test").Parse("{{.Count}} items are made of {{.Material}}") - if err != nil { panic(err) } - err = tmpl.Execute(os.Stdout, sweaters) - if err != nil { panic(err) } - -More intricate examples appear below. - -Actions - -Here is the list of actions. "Arguments" and "pipelines" are evaluations of -data, defined in detail below. - -*/ -// {{/* a comment */}} -// A comment; discarded. May contain newlines. -// Comments do not nest and must start and end at the -// delimiters, as shown here. -/* - - {{pipeline}} - The default textual representation of the value of the pipeline - is copied to the output. - - {{if pipeline}} T1 {{end}} - If the value of the pipeline is empty, no output is generated; - otherwise, T1 is executed. The empty values are false, 0, any - nil pointer or interface value, and any array, slice, map, or - string of length zero. - Dot is unaffected. - - {{if pipeline}} T1 {{else}} T0 {{end}} - If the value of the pipeline is empty, T0 is executed; - otherwise, T1 is executed. Dot is unaffected. - - {{if pipeline}} T1 {{else if pipeline}} T0 {{end}} - To simplify the appearance of if-else chains, the else action - of an if may include another if directly; the effect is exactly - the same as writing - {{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}} - - {{range pipeline}} T1 {{end}} - The value of the pipeline must be an array, slice, map, or channel. - If the value of the pipeline has length zero, nothing is output; - otherwise, dot is set to the successive elements of the array, - slice, or map and T1 is executed. If the value is a map and the - keys are of basic type with a defined order ("comparable"), the - elements will be visited in sorted key order. - - {{range pipeline}} T1 {{else}} T0 {{end}} - The value of the pipeline must be an array, slice, map, or channel. - If the value of the pipeline has length zero, dot is unaffected and - T0 is executed; otherwise, dot is set to the successive elements - of the array, slice, or map and T1 is executed. - - {{template "name"}} - The template with the specified name is executed with nil data. - - {{template "name" pipeline}} - The template with the specified name is executed with dot set - to the value of the pipeline. - - {{with pipeline}} T1 {{end}} - If the value of the pipeline is empty, no output is generated; - otherwise, dot is set to the value of the pipeline and T1 is - executed. - - {{with pipeline}} T1 {{else}} T0 {{end}} - If the value of the pipeline is empty, dot is unaffected and T0 - is executed; otherwise, dot is set to the value of the pipeline - and T1 is executed. - -Arguments - -An argument is a simple value, denoted by one of the following. - - - A boolean, string, character, integer, floating-point, imaginary - or complex constant in Go syntax. These behave like Go's untyped - constants, although raw strings may not span newlines. - - The keyword nil, representing an untyped Go nil. - - The character '.' (period): - . - The result is the value of dot. - - A variable name, which is a (possibly empty) alphanumeric string - preceded by a dollar sign, such as - $piOver2 - or - $ - The result is the value of the variable. - Variables are described below. - - The name of a field of the data, which must be a struct, preceded - by a period, such as - .Field - The result is the value of the field. Field invocations may be - chained: - .Field1.Field2 - Fields can also be evaluated on variables, including chaining: - $x.Field1.Field2 - - The name of a key of the data, which must be a map, preceded - by a period, such as - .Key - The result is the map element value indexed by the key. - Key invocations may be chained and combined with fields to any - depth: - .Field1.Key1.Field2.Key2 - Although the key must be an alphanumeric identifier, unlike with - field names they do not need to start with an upper case letter. - Keys can also be evaluated on variables, including chaining: - $x.key1.key2 - - The name of a niladic method of the data, preceded by a period, - such as - .Method - The result is the value of invoking the method with dot as the - receiver, dot.Method(). Such a method must have one return value (of - any type) or two return values, the second of which is an error. - If it has two and the returned error is non-nil, execution terminates - and an error is returned to the caller as the value of Execute. - Method invocations may be chained and combined with fields and keys - to any depth: - .Field1.Key1.Method1.Field2.Key2.Method2 - Methods can also be evaluated on variables, including chaining: - $x.Method1.Field - - The name of a niladic function, such as - fun - The result is the value of invoking the function, fun(). The return - types and values behave as in methods. Functions and function - names are described below. - - A parenthesized instance of one the above, for grouping. The result - may be accessed by a field or map key invocation. - print (.F1 arg1) (.F2 arg2) - (.StructValuedMethod "arg").Field - -Arguments may evaluate to any type; if they are pointers the implementation -automatically indirects to the base type when required. -If an evaluation yields a function value, such as a function-valued -field of a struct, the function is not invoked automatically, but it -can be used as a truth value for an if action and the like. To invoke -it, use the call function, defined below. - -A pipeline is a possibly chained sequence of "commands". A command is a simple -value (argument) or a function or method call, possibly with multiple arguments: - - Argument - The result is the value of evaluating the argument. - .Method [Argument...] - The method can be alone or the last element of a chain but, - unlike methods in the middle of a chain, it can take arguments. - The result is the value of calling the method with the - arguments: - dot.Method(Argument1, etc.) - functionName [Argument...] - The result is the value of calling the function associated - with the name: - function(Argument1, etc.) - Functions and function names are described below. - -Pipelines - -A pipeline may be "chained" by separating a sequence of commands with pipeline -characters '|'. In a chained pipeline, the result of the each command is -passed as the last argument of the following command. The output of the final -command in the pipeline is the value of the pipeline. - -The output of a command will be either one value or two values, the second of -which has type error. If that second value is present and evaluates to -non-nil, execution terminates and the error is returned to the caller of -Execute. - -Variables - -A pipeline inside an action may initialize a variable to capture the result. -The initialization has syntax - - $variable := pipeline - -where $variable is the name of the variable. An action that declares a -variable produces no output. - -If a "range" action initializes a variable, the variable is set to the -successive elements of the iteration. Also, a "range" may declare two -variables, separated by a comma: - - range $index, $element := pipeline - -in which case $index and $element are set to the successive values of the -array/slice index or map key and element, respectively. Note that if there is -only one variable, it is assigned the element; this is opposite to the -convention in Go range clauses. - -A variable's scope extends to the "end" action of the control structure ("if", -"with", or "range") in which it is declared, or to the end of the template if -there is no such control structure. A template invocation does not inherit -variables from the point of its invocation. - -When execution begins, $ is set to the data argument passed to Execute, that is, -to the starting value of dot. - -Examples - -Here are some example one-line templates demonstrating pipelines and variables. -All produce the quoted word "output": - - {{"\"output\""}} - A string constant. - {{`"output"`}} - A raw string constant. - {{printf "%q" "output"}} - A function call. - {{"output" | printf "%q"}} - A function call whose final argument comes from the previous - command. - {{printf "%q" (print "out" "put")}} - A parenthesized argument. - {{"put" | printf "%s%s" "out" | printf "%q"}} - A more elaborate call. - {{"output" | printf "%s" | printf "%q"}} - A longer chain. - {{with "output"}}{{printf "%q" .}}{{end}} - A with action using dot. - {{with $x := "output" | printf "%q"}}{{$x}}{{end}} - A with action that creates and uses a variable. - {{with $x := "output"}}{{printf "%q" $x}}{{end}} - A with action that uses the variable in another action. - {{with $x := "output"}}{{$x | printf "%q"}}{{end}} - The same, but pipelined. - -Functions - -During execution functions are found in two function maps: first in the -template, then in the global function map. By default, no functions are defined -in the template but the Funcs method can be used to add them. - -Predefined global functions are named as follows. - - and - Returns the boolean AND of its arguments by returning the - first empty argument or the last argument, that is, - "and x y" behaves as "if x then y else x". All the - arguments are evaluated. - call - Returns the result of calling the first argument, which - must be a function, with the remaining arguments as parameters. - Thus "call .X.Y 1 2" is, in Go notation, dot.X.Y(1, 2) where - Y is a func-valued field, map entry, or the like. - The first argument must be the result of an evaluation - that yields a value of function type (as distinct from - a predefined function such as print). The function must - return either one or two result values, the second of which - is of type error. If the arguments don't match the function - or the returned error value is non-nil, execution stops. - html - Returns the escaped HTML equivalent of the textual - representation of its arguments. - index - Returns the result of indexing its first argument by the - following arguments. Thus "index x 1 2 3" is, in Go syntax, - x[1][2][3]. Each indexed item must be a map, slice, or array. - js - Returns the escaped JavaScript equivalent of the textual - representation of its arguments. - len - Returns the integer length of its argument. - not - Returns the boolean negation of its single argument. - or - Returns the boolean OR of its arguments by returning the - first non-empty argument or the last argument, that is, - "or x y" behaves as "if x then x else y". All the - arguments are evaluated. - print - An alias for fmt.Sprint - printf - An alias for fmt.Sprintf - println - An alias for fmt.Sprintln - urlquery - Returns the escaped value of the textual representation of - its arguments in a form suitable for embedding in a URL query. - -The boolean functions take any zero value to be false and a non-zero -value to be true. - -There is also a set of binary comparison operators defined as -functions: - - eq - Returns the boolean truth of arg1 == arg2 - ne - Returns the boolean truth of arg1 != arg2 - lt - Returns the boolean truth of arg1 < arg2 - le - Returns the boolean truth of arg1 <= arg2 - gt - Returns the boolean truth of arg1 > arg2 - ge - Returns the boolean truth of arg1 >= arg2 - -For simpler multi-way equality tests, eq (only) accepts two or more -arguments and compares the second and subsequent to the first, -returning in effect - - arg1==arg2 || arg1==arg3 || arg1==arg4 ... - -(Unlike with || in Go, however, eq is a function call and all the -arguments will be evaluated.) - -The comparison functions work on basic types only (or named basic -types, such as "type Celsius float32"). They implement the Go rules -for comparison of values, except that size and exact type are -ignored, so any integer value, signed or unsigned, may be compared -with any other integer value. (The arithmetic value is compared, -not the bit pattern, so all negative integers are less than all -unsigned integers.) However, as usual, one may not compare an int -with a float32 and so on. - -Associated templates - -Each template is named by a string specified when it is created. Also, each -template is associated with zero or more other templates that it may invoke by -name; such associations are transitive and form a name space of templates. - -A template may use a template invocation to instantiate another associated -template; see the explanation of the "template" action above. The name must be -that of a template associated with the template that contains the invocation. - -Nested template definitions - -When parsing a template, another template may be defined and associated with the -template being parsed. Template definitions must appear at the top level of the -template, much like global variables in a Go program. - -The syntax of such definitions is to surround each template declaration with a -"define" and "end" action. - -The define action names the template being created by providing a string -constant. Here is a simple example: - - `{{define "T1"}}ONE{{end}} - {{define "T2"}}TWO{{end}} - {{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}} - {{template "T3"}}` - -This defines two templates, T1 and T2, and a third T3 that invokes the other two -when it is executed. Finally it invokes T3. If executed this template will -produce the text - - ONE TWO - -By construction, a template may reside in only one association. If it's -necessary to have a template addressable from multiple associations, the -template definition must be parsed multiple times to create distinct *Template -values, or must be copied with the Clone or AddParseTree method. - -Parse may be called multiple times to assemble the various associated templates; -see the ParseFiles and ParseGlob functions and methods for simple ways to parse -related templates stored in files. - -A template may be executed directly or through ExecuteTemplate, which executes -an associated template identified by name. To invoke our example above, we -might write, - - err := tmpl.Execute(os.Stdout, "no data needed") - if err != nil { - log.Fatalf("execution failed: %s", err) - } - -or to invoke a particular template explicitly by name, - - err := tmpl.ExecuteTemplate(os.Stdout, "T2", "no data needed") - if err != nil { - log.Fatalf("execution failed: %s", err) - } - -*/ -package template diff --git a/vendor/github.com/alecthomas/template/exec.go b/vendor/github.com/alecthomas/template/exec.go deleted file mode 100644 index c3078e5d0c0..00000000000 --- a/vendor/github.com/alecthomas/template/exec.go +++ /dev/null @@ -1,845 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "bytes" - "fmt" - "io" - "reflect" - "runtime" - "sort" - "strings" - - "github.com/alecthomas/template/parse" -) - -// state represents the state of an execution. It's not part of the -// template so that multiple executions of the same template -// can execute in parallel. -type state struct { - tmpl *Template - wr io.Writer - node parse.Node // current node, for errors - vars []variable // push-down stack of variable values. -} - -// variable holds the dynamic value of a variable such as $, $x etc. -type variable struct { - name string - value reflect.Value -} - -// push pushes a new variable on the stack. -func (s *state) push(name string, value reflect.Value) { - s.vars = append(s.vars, variable{name, value}) -} - -// mark returns the length of the variable stack. -func (s *state) mark() int { - return len(s.vars) -} - -// pop pops the variable stack up to the mark. -func (s *state) pop(mark int) { - s.vars = s.vars[0:mark] -} - -// setVar overwrites the top-nth variable on the stack. Used by range iterations. -func (s *state) setVar(n int, value reflect.Value) { - s.vars[len(s.vars)-n].value = value -} - -// varValue returns the value of the named variable. -func (s *state) varValue(name string) reflect.Value { - for i := s.mark() - 1; i >= 0; i-- { - if s.vars[i].name == name { - return s.vars[i].value - } - } - s.errorf("undefined variable: %s", name) - return zero -} - -var zero reflect.Value - -// at marks the state to be on node n, for error reporting. -func (s *state) at(node parse.Node) { - s.node = node -} - -// doublePercent returns the string with %'s replaced by %%, if necessary, -// so it can be used safely inside a Printf format string. -func doublePercent(str string) string { - if strings.Contains(str, "%") { - str = strings.Replace(str, "%", "%%", -1) - } - return str -} - -// errorf formats the error and terminates processing. -func (s *state) errorf(format string, args ...interface{}) { - name := doublePercent(s.tmpl.Name()) - if s.node == nil { - format = fmt.Sprintf("template: %s: %s", name, format) - } else { - location, context := s.tmpl.ErrorContext(s.node) - format = fmt.Sprintf("template: %s: executing %q at <%s>: %s", location, name, doublePercent(context), format) - } - panic(fmt.Errorf(format, args...)) -} - -// errRecover is the handler that turns panics into returns from the top -// level of Parse. -func errRecover(errp *error) { - e := recover() - if e != nil { - switch err := e.(type) { - case runtime.Error: - panic(e) - case error: - *errp = err - default: - panic(e) - } - } -} - -// ExecuteTemplate applies the template associated with t that has the given name -// to the specified data object and writes the output to wr. -// If an error occurs executing the template or writing its output, -// execution stops, but partial results may already have been written to -// the output writer. -// A template may be executed safely in parallel. -func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error { - tmpl := t.tmpl[name] - if tmpl == nil { - return fmt.Errorf("template: no template %q associated with template %q", name, t.name) - } - return tmpl.Execute(wr, data) -} - -// Execute applies a parsed template to the specified data object, -// and writes the output to wr. -// If an error occurs executing the template or writing its output, -// execution stops, but partial results may already have been written to -// the output writer. -// A template may be executed safely in parallel. -func (t *Template) Execute(wr io.Writer, data interface{}) (err error) { - defer errRecover(&err) - value := reflect.ValueOf(data) - state := &state{ - tmpl: t, - wr: wr, - vars: []variable{{"$", value}}, - } - t.init() - if t.Tree == nil || t.Root == nil { - var b bytes.Buffer - for name, tmpl := range t.tmpl { - if tmpl.Tree == nil || tmpl.Root == nil { - continue - } - if b.Len() > 0 { - b.WriteString(", ") - } - fmt.Fprintf(&b, "%q", name) - } - var s string - if b.Len() > 0 { - s = "; defined templates are: " + b.String() - } - state.errorf("%q is an incomplete or empty template%s", t.Name(), s) - } - state.walk(value, t.Root) - return -} - -// Walk functions step through the major pieces of the template structure, -// generating output as they go. -func (s *state) walk(dot reflect.Value, node parse.Node) { - s.at(node) - switch node := node.(type) { - case *parse.ActionNode: - // Do not pop variables so they persist until next end. - // Also, if the action declares variables, don't print the result. - val := s.evalPipeline(dot, node.Pipe) - if len(node.Pipe.Decl) == 0 { - s.printValue(node, val) - } - case *parse.IfNode: - s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList) - case *parse.ListNode: - for _, node := range node.Nodes { - s.walk(dot, node) - } - case *parse.RangeNode: - s.walkRange(dot, node) - case *parse.TemplateNode: - s.walkTemplate(dot, node) - case *parse.TextNode: - if _, err := s.wr.Write(node.Text); err != nil { - s.errorf("%s", err) - } - case *parse.WithNode: - s.walkIfOrWith(parse.NodeWith, dot, node.Pipe, node.List, node.ElseList) - default: - s.errorf("unknown node: %s", node) - } -} - -// walkIfOrWith walks an 'if' or 'with' node. The two control structures -// are identical in behavior except that 'with' sets dot. -func (s *state) walkIfOrWith(typ parse.NodeType, dot reflect.Value, pipe *parse.PipeNode, list, elseList *parse.ListNode) { - defer s.pop(s.mark()) - val := s.evalPipeline(dot, pipe) - truth, ok := isTrue(val) - if !ok { - s.errorf("if/with can't use %v", val) - } - if truth { - if typ == parse.NodeWith { - s.walk(val, list) - } else { - s.walk(dot, list) - } - } else if elseList != nil { - s.walk(dot, elseList) - } -} - -// isTrue reports whether the value is 'true', in the sense of not the zero of its type, -// and whether the value has a meaningful truth value. -func isTrue(val reflect.Value) (truth, ok bool) { - if !val.IsValid() { - // Something like var x interface{}, never set. It's a form of nil. - return false, true - } - switch val.Kind() { - case reflect.Array, reflect.Map, reflect.Slice, reflect.String: - truth = val.Len() > 0 - case reflect.Bool: - truth = val.Bool() - case reflect.Complex64, reflect.Complex128: - truth = val.Complex() != 0 - case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface: - truth = !val.IsNil() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - truth = val.Int() != 0 - case reflect.Float32, reflect.Float64: - truth = val.Float() != 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - truth = val.Uint() != 0 - case reflect.Struct: - truth = true // Struct values are always true. - default: - return - } - return truth, true -} - -func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { - s.at(r) - defer s.pop(s.mark()) - val, _ := indirect(s.evalPipeline(dot, r.Pipe)) - // mark top of stack before any variables in the body are pushed. - mark := s.mark() - oneIteration := func(index, elem reflect.Value) { - // Set top var (lexically the second if there are two) to the element. - if len(r.Pipe.Decl) > 0 { - s.setVar(1, elem) - } - // Set next var (lexically the first if there are two) to the index. - if len(r.Pipe.Decl) > 1 { - s.setVar(2, index) - } - s.walk(elem, r.List) - s.pop(mark) - } - switch val.Kind() { - case reflect.Array, reflect.Slice: - if val.Len() == 0 { - break - } - for i := 0; i < val.Len(); i++ { - oneIteration(reflect.ValueOf(i), val.Index(i)) - } - return - case reflect.Map: - if val.Len() == 0 { - break - } - for _, key := range sortKeys(val.MapKeys()) { - oneIteration(key, val.MapIndex(key)) - } - return - case reflect.Chan: - if val.IsNil() { - break - } - i := 0 - for ; ; i++ { - elem, ok := val.Recv() - if !ok { - break - } - oneIteration(reflect.ValueOf(i), elem) - } - if i == 0 { - break - } - return - case reflect.Invalid: - break // An invalid value is likely a nil map, etc. and acts like an empty map. - default: - s.errorf("range can't iterate over %v", val) - } - if r.ElseList != nil { - s.walk(dot, r.ElseList) - } -} - -func (s *state) walkTemplate(dot reflect.Value, t *parse.TemplateNode) { - s.at(t) - tmpl := s.tmpl.tmpl[t.Name] - if tmpl == nil { - s.errorf("template %q not defined", t.Name) - } - // Variables declared by the pipeline persist. - dot = s.evalPipeline(dot, t.Pipe) - newState := *s - newState.tmpl = tmpl - // No dynamic scoping: template invocations inherit no variables. - newState.vars = []variable{{"$", dot}} - newState.walk(dot, tmpl.Root) -} - -// Eval functions evaluate pipelines, commands, and their elements and extract -// values from the data structure by examining fields, calling methods, and so on. -// The printing of those values happens only through walk functions. - -// evalPipeline returns the value acquired by evaluating a pipeline. If the -// pipeline has a variable declaration, the variable will be pushed on the -// stack. Callers should therefore pop the stack after they are finished -// executing commands depending on the pipeline value. -func (s *state) evalPipeline(dot reflect.Value, pipe *parse.PipeNode) (value reflect.Value) { - if pipe == nil { - return - } - s.at(pipe) - for _, cmd := range pipe.Cmds { - value = s.evalCommand(dot, cmd, value) // previous value is this one's final arg. - // If the object has type interface{}, dig down one level to the thing inside. - if value.Kind() == reflect.Interface && value.Type().NumMethod() == 0 { - value = reflect.ValueOf(value.Interface()) // lovely! - } - } - for _, variable := range pipe.Decl { - s.push(variable.Ident[0], value) - } - return value -} - -func (s *state) notAFunction(args []parse.Node, final reflect.Value) { - if len(args) > 1 || final.IsValid() { - s.errorf("can't give argument to non-function %s", args[0]) - } -} - -func (s *state) evalCommand(dot reflect.Value, cmd *parse.CommandNode, final reflect.Value) reflect.Value { - firstWord := cmd.Args[0] - switch n := firstWord.(type) { - case *parse.FieldNode: - return s.evalFieldNode(dot, n, cmd.Args, final) - case *parse.ChainNode: - return s.evalChainNode(dot, n, cmd.Args, final) - case *parse.IdentifierNode: - // Must be a function. - return s.evalFunction(dot, n, cmd, cmd.Args, final) - case *parse.PipeNode: - // Parenthesized pipeline. The arguments are all inside the pipeline; final is ignored. - return s.evalPipeline(dot, n) - case *parse.VariableNode: - return s.evalVariableNode(dot, n, cmd.Args, final) - } - s.at(firstWord) - s.notAFunction(cmd.Args, final) - switch word := firstWord.(type) { - case *parse.BoolNode: - return reflect.ValueOf(word.True) - case *parse.DotNode: - return dot - case *parse.NilNode: - s.errorf("nil is not a command") - case *parse.NumberNode: - return s.idealConstant(word) - case *parse.StringNode: - return reflect.ValueOf(word.Text) - } - s.errorf("can't evaluate command %q", firstWord) - panic("not reached") -} - -// idealConstant is called to return the value of a number in a context where -// we don't know the type. In that case, the syntax of the number tells us -// its type, and we use Go rules to resolve. Note there is no such thing as -// a uint ideal constant in this situation - the value must be of int type. -func (s *state) idealConstant(constant *parse.NumberNode) reflect.Value { - // These are ideal constants but we don't know the type - // and we have no context. (If it was a method argument, - // we'd know what we need.) The syntax guides us to some extent. - s.at(constant) - switch { - case constant.IsComplex: - return reflect.ValueOf(constant.Complex128) // incontrovertible. - case constant.IsFloat && !isHexConstant(constant.Text) && strings.IndexAny(constant.Text, ".eE") >= 0: - return reflect.ValueOf(constant.Float64) - case constant.IsInt: - n := int(constant.Int64) - if int64(n) != constant.Int64 { - s.errorf("%s overflows int", constant.Text) - } - return reflect.ValueOf(n) - case constant.IsUint: - s.errorf("%s overflows int", constant.Text) - } - return zero -} - -func isHexConstant(s string) bool { - return len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') -} - -func (s *state) evalFieldNode(dot reflect.Value, field *parse.FieldNode, args []parse.Node, final reflect.Value) reflect.Value { - s.at(field) - return s.evalFieldChain(dot, dot, field, field.Ident, args, final) -} - -func (s *state) evalChainNode(dot reflect.Value, chain *parse.ChainNode, args []parse.Node, final reflect.Value) reflect.Value { - s.at(chain) - // (pipe).Field1.Field2 has pipe as .Node, fields as .Field. Eval the pipeline, then the fields. - pipe := s.evalArg(dot, nil, chain.Node) - if len(chain.Field) == 0 { - s.errorf("internal error: no fields in evalChainNode") - } - return s.evalFieldChain(dot, pipe, chain, chain.Field, args, final) -} - -func (s *state) evalVariableNode(dot reflect.Value, variable *parse.VariableNode, args []parse.Node, final reflect.Value) reflect.Value { - // $x.Field has $x as the first ident, Field as the second. Eval the var, then the fields. - s.at(variable) - value := s.varValue(variable.Ident[0]) - if len(variable.Ident) == 1 { - s.notAFunction(args, final) - return value - } - return s.evalFieldChain(dot, value, variable, variable.Ident[1:], args, final) -} - -// evalFieldChain evaluates .X.Y.Z possibly followed by arguments. -// dot is the environment in which to evaluate arguments, while -// receiver is the value being walked along the chain. -func (s *state) evalFieldChain(dot, receiver reflect.Value, node parse.Node, ident []string, args []parse.Node, final reflect.Value) reflect.Value { - n := len(ident) - for i := 0; i < n-1; i++ { - receiver = s.evalField(dot, ident[i], node, nil, zero, receiver) - } - // Now if it's a method, it gets the arguments. - return s.evalField(dot, ident[n-1], node, args, final, receiver) -} - -func (s *state) evalFunction(dot reflect.Value, node *parse.IdentifierNode, cmd parse.Node, args []parse.Node, final reflect.Value) reflect.Value { - s.at(node) - name := node.Ident - function, ok := findFunction(name, s.tmpl) - if !ok { - s.errorf("%q is not a defined function", name) - } - return s.evalCall(dot, function, cmd, name, args, final) -} - -// evalField evaluates an expression like (.Field) or (.Field arg1 arg2). -// The 'final' argument represents the return value from the preceding -// value of the pipeline, if any. -func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, args []parse.Node, final, receiver reflect.Value) reflect.Value { - if !receiver.IsValid() { - return zero - } - typ := receiver.Type() - receiver, _ = indirect(receiver) - // Unless it's an interface, need to get to a value of type *T to guarantee - // we see all methods of T and *T. - ptr := receiver - if ptr.Kind() != reflect.Interface && ptr.CanAddr() { - ptr = ptr.Addr() - } - if method := ptr.MethodByName(fieldName); method.IsValid() { - return s.evalCall(dot, method, node, fieldName, args, final) - } - hasArgs := len(args) > 1 || final.IsValid() - // It's not a method; must be a field of a struct or an element of a map. The receiver must not be nil. - receiver, isNil := indirect(receiver) - if isNil { - s.errorf("nil pointer evaluating %s.%s", typ, fieldName) - } - switch receiver.Kind() { - case reflect.Struct: - tField, ok := receiver.Type().FieldByName(fieldName) - if ok { - field := receiver.FieldByIndex(tField.Index) - if tField.PkgPath != "" { // field is unexported - s.errorf("%s is an unexported field of struct type %s", fieldName, typ) - } - // If it's a function, we must call it. - if hasArgs { - s.errorf("%s has arguments but cannot be invoked as function", fieldName) - } - return field - } - s.errorf("%s is not a field of struct type %s", fieldName, typ) - case reflect.Map: - // If it's a map, attempt to use the field name as a key. - nameVal := reflect.ValueOf(fieldName) - if nameVal.Type().AssignableTo(receiver.Type().Key()) { - if hasArgs { - s.errorf("%s is not a method but has arguments", fieldName) - } - return receiver.MapIndex(nameVal) - } - } - s.errorf("can't evaluate field %s in type %s", fieldName, typ) - panic("not reached") -} - -var ( - errorType = reflect.TypeOf((*error)(nil)).Elem() - fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() -) - -// evalCall executes a function or method call. If it's a method, fun already has the receiver bound, so -// it looks just like a function call. The arg list, if non-nil, includes (in the manner of the shell), arg[0] -// as the function itself. -func (s *state) evalCall(dot, fun reflect.Value, node parse.Node, name string, args []parse.Node, final reflect.Value) reflect.Value { - if args != nil { - args = args[1:] // Zeroth arg is function name/node; not passed to function. - } - typ := fun.Type() - numIn := len(args) - if final.IsValid() { - numIn++ - } - numFixed := len(args) - if typ.IsVariadic() { - numFixed = typ.NumIn() - 1 // last arg is the variadic one. - if numIn < numFixed { - s.errorf("wrong number of args for %s: want at least %d got %d", name, typ.NumIn()-1, len(args)) - } - } else if numIn < typ.NumIn()-1 || !typ.IsVariadic() && numIn != typ.NumIn() { - s.errorf("wrong number of args for %s: want %d got %d", name, typ.NumIn(), len(args)) - } - if !goodFunc(typ) { - // TODO: This could still be a confusing error; maybe goodFunc should provide info. - s.errorf("can't call method/function %q with %d results", name, typ.NumOut()) - } - // Build the arg list. - argv := make([]reflect.Value, numIn) - // Args must be evaluated. Fixed args first. - i := 0 - for ; i < numFixed && i < len(args); i++ { - argv[i] = s.evalArg(dot, typ.In(i), args[i]) - } - // Now the ... args. - if typ.IsVariadic() { - argType := typ.In(typ.NumIn() - 1).Elem() // Argument is a slice. - for ; i < len(args); i++ { - argv[i] = s.evalArg(dot, argType, args[i]) - } - } - // Add final value if necessary. - if final.IsValid() { - t := typ.In(typ.NumIn() - 1) - if typ.IsVariadic() { - t = t.Elem() - } - argv[i] = s.validateType(final, t) - } - result := fun.Call(argv) - // If we have an error that is not nil, stop execution and return that error to the caller. - if len(result) == 2 && !result[1].IsNil() { - s.at(node) - s.errorf("error calling %s: %s", name, result[1].Interface().(error)) - } - return result[0] -} - -// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero. -func canBeNil(typ reflect.Type) bool { - switch typ.Kind() { - case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: - return true - } - return false -} - -// validateType guarantees that the value is valid and assignable to the type. -func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Value { - if !value.IsValid() { - if typ == nil || canBeNil(typ) { - // An untyped nil interface{}. Accept as a proper nil value. - return reflect.Zero(typ) - } - s.errorf("invalid value; expected %s", typ) - } - if typ != nil && !value.Type().AssignableTo(typ) { - if value.Kind() == reflect.Interface && !value.IsNil() { - value = value.Elem() - if value.Type().AssignableTo(typ) { - return value - } - // fallthrough - } - // Does one dereference or indirection work? We could do more, as we - // do with method receivers, but that gets messy and method receivers - // are much more constrained, so it makes more sense there than here. - // Besides, one is almost always all you need. - switch { - case value.Kind() == reflect.Ptr && value.Type().Elem().AssignableTo(typ): - value = value.Elem() - if !value.IsValid() { - s.errorf("dereference of nil pointer of type %s", typ) - } - case reflect.PtrTo(value.Type()).AssignableTo(typ) && value.CanAddr(): - value = value.Addr() - default: - s.errorf("wrong type for value; expected %s; got %s", typ, value.Type()) - } - } - return value -} - -func (s *state) evalArg(dot reflect.Value, typ reflect.Type, n parse.Node) reflect.Value { - s.at(n) - switch arg := n.(type) { - case *parse.DotNode: - return s.validateType(dot, typ) - case *parse.NilNode: - if canBeNil(typ) { - return reflect.Zero(typ) - } - s.errorf("cannot assign nil to %s", typ) - case *parse.FieldNode: - return s.validateType(s.evalFieldNode(dot, arg, []parse.Node{n}, zero), typ) - case *parse.VariableNode: - return s.validateType(s.evalVariableNode(dot, arg, nil, zero), typ) - case *parse.PipeNode: - return s.validateType(s.evalPipeline(dot, arg), typ) - case *parse.IdentifierNode: - return s.evalFunction(dot, arg, arg, nil, zero) - case *parse.ChainNode: - return s.validateType(s.evalChainNode(dot, arg, nil, zero), typ) - } - switch typ.Kind() { - case reflect.Bool: - return s.evalBool(typ, n) - case reflect.Complex64, reflect.Complex128: - return s.evalComplex(typ, n) - case reflect.Float32, reflect.Float64: - return s.evalFloat(typ, n) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return s.evalInteger(typ, n) - case reflect.Interface: - if typ.NumMethod() == 0 { - return s.evalEmptyInterface(dot, n) - } - case reflect.String: - return s.evalString(typ, n) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return s.evalUnsignedInteger(typ, n) - } - s.errorf("can't handle %s for arg of type %s", n, typ) - panic("not reached") -} - -func (s *state) evalBool(typ reflect.Type, n parse.Node) reflect.Value { - s.at(n) - if n, ok := n.(*parse.BoolNode); ok { - value := reflect.New(typ).Elem() - value.SetBool(n.True) - return value - } - s.errorf("expected bool; found %s", n) - panic("not reached") -} - -func (s *state) evalString(typ reflect.Type, n parse.Node) reflect.Value { - s.at(n) - if n, ok := n.(*parse.StringNode); ok { - value := reflect.New(typ).Elem() - value.SetString(n.Text) - return value - } - s.errorf("expected string; found %s", n) - panic("not reached") -} - -func (s *state) evalInteger(typ reflect.Type, n parse.Node) reflect.Value { - s.at(n) - if n, ok := n.(*parse.NumberNode); ok && n.IsInt { - value := reflect.New(typ).Elem() - value.SetInt(n.Int64) - return value - } - s.errorf("expected integer; found %s", n) - panic("not reached") -} - -func (s *state) evalUnsignedInteger(typ reflect.Type, n parse.Node) reflect.Value { - s.at(n) - if n, ok := n.(*parse.NumberNode); ok && n.IsUint { - value := reflect.New(typ).Elem() - value.SetUint(n.Uint64) - return value - } - s.errorf("expected unsigned integer; found %s", n) - panic("not reached") -} - -func (s *state) evalFloat(typ reflect.Type, n parse.Node) reflect.Value { - s.at(n) - if n, ok := n.(*parse.NumberNode); ok && n.IsFloat { - value := reflect.New(typ).Elem() - value.SetFloat(n.Float64) - return value - } - s.errorf("expected float; found %s", n) - panic("not reached") -} - -func (s *state) evalComplex(typ reflect.Type, n parse.Node) reflect.Value { - if n, ok := n.(*parse.NumberNode); ok && n.IsComplex { - value := reflect.New(typ).Elem() - value.SetComplex(n.Complex128) - return value - } - s.errorf("expected complex; found %s", n) - panic("not reached") -} - -func (s *state) evalEmptyInterface(dot reflect.Value, n parse.Node) reflect.Value { - s.at(n) - switch n := n.(type) { - case *parse.BoolNode: - return reflect.ValueOf(n.True) - case *parse.DotNode: - return dot - case *parse.FieldNode: - return s.evalFieldNode(dot, n, nil, zero) - case *parse.IdentifierNode: - return s.evalFunction(dot, n, n, nil, zero) - case *parse.NilNode: - // NilNode is handled in evalArg, the only place that calls here. - s.errorf("evalEmptyInterface: nil (can't happen)") - case *parse.NumberNode: - return s.idealConstant(n) - case *parse.StringNode: - return reflect.ValueOf(n.Text) - case *parse.VariableNode: - return s.evalVariableNode(dot, n, nil, zero) - case *parse.PipeNode: - return s.evalPipeline(dot, n) - } - s.errorf("can't handle assignment of %s to empty interface argument", n) - panic("not reached") -} - -// indirect returns the item at the end of indirection, and a bool to indicate if it's nil. -// We indirect through pointers and empty interfaces (only) because -// non-empty interfaces have methods we might need. -func indirect(v reflect.Value) (rv reflect.Value, isNil bool) { - for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() { - if v.IsNil() { - return v, true - } - if v.Kind() == reflect.Interface && v.NumMethod() > 0 { - break - } - } - return v, false -} - -// printValue writes the textual representation of the value to the output of -// the template. -func (s *state) printValue(n parse.Node, v reflect.Value) { - s.at(n) - iface, ok := printableValue(v) - if !ok { - s.errorf("can't print %s of type %s", n, v.Type()) - } - fmt.Fprint(s.wr, iface) -} - -// printableValue returns the, possibly indirected, interface value inside v that -// is best for a call to formatted printer. -func printableValue(v reflect.Value) (interface{}, bool) { - if v.Kind() == reflect.Ptr { - v, _ = indirect(v) // fmt.Fprint handles nil. - } - if !v.IsValid() { - return "", true - } - - if !v.Type().Implements(errorType) && !v.Type().Implements(fmtStringerType) { - if v.CanAddr() && (reflect.PtrTo(v.Type()).Implements(errorType) || reflect.PtrTo(v.Type()).Implements(fmtStringerType)) { - v = v.Addr() - } else { - switch v.Kind() { - case reflect.Chan, reflect.Func: - return nil, false - } - } - } - return v.Interface(), true -} - -// Types to help sort the keys in a map for reproducible output. - -type rvs []reflect.Value - -func (x rvs) Len() int { return len(x) } -func (x rvs) Swap(i, j int) { x[i], x[j] = x[j], x[i] } - -type rvInts struct{ rvs } - -func (x rvInts) Less(i, j int) bool { return x.rvs[i].Int() < x.rvs[j].Int() } - -type rvUints struct{ rvs } - -func (x rvUints) Less(i, j int) bool { return x.rvs[i].Uint() < x.rvs[j].Uint() } - -type rvFloats struct{ rvs } - -func (x rvFloats) Less(i, j int) bool { return x.rvs[i].Float() < x.rvs[j].Float() } - -type rvStrings struct{ rvs } - -func (x rvStrings) Less(i, j int) bool { return x.rvs[i].String() < x.rvs[j].String() } - -// sortKeys sorts (if it can) the slice of reflect.Values, which is a slice of map keys. -func sortKeys(v []reflect.Value) []reflect.Value { - if len(v) <= 1 { - return v - } - switch v[0].Kind() { - case reflect.Float32, reflect.Float64: - sort.Sort(rvFloats{v}) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - sort.Sort(rvInts{v}) - case reflect.String: - sort.Sort(rvStrings{v}) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - sort.Sort(rvUints{v}) - } - return v -} diff --git a/vendor/github.com/alecthomas/template/funcs.go b/vendor/github.com/alecthomas/template/funcs.go deleted file mode 100644 index 39ee5ed68fb..00000000000 --- a/vendor/github.com/alecthomas/template/funcs.go +++ /dev/null @@ -1,598 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "bytes" - "errors" - "fmt" - "io" - "net/url" - "reflect" - "strings" - "unicode" - "unicode/utf8" -) - -// FuncMap is the type of the map defining the mapping from names to functions. -// Each function must have either a single return value, or two return values of -// which the second has type error. In that case, if the second (error) -// return value evaluates to non-nil during execution, execution terminates and -// Execute returns that error. -type FuncMap map[string]interface{} - -var builtins = FuncMap{ - "and": and, - "call": call, - "html": HTMLEscaper, - "index": index, - "js": JSEscaper, - "len": length, - "not": not, - "or": or, - "print": fmt.Sprint, - "printf": fmt.Sprintf, - "println": fmt.Sprintln, - "urlquery": URLQueryEscaper, - - // Comparisons - "eq": eq, // == - "ge": ge, // >= - "gt": gt, // > - "le": le, // <= - "lt": lt, // < - "ne": ne, // != -} - -var builtinFuncs = createValueFuncs(builtins) - -// createValueFuncs turns a FuncMap into a map[string]reflect.Value -func createValueFuncs(funcMap FuncMap) map[string]reflect.Value { - m := make(map[string]reflect.Value) - addValueFuncs(m, funcMap) - return m -} - -// addValueFuncs adds to values the functions in funcs, converting them to reflect.Values. -func addValueFuncs(out map[string]reflect.Value, in FuncMap) { - for name, fn := range in { - v := reflect.ValueOf(fn) - if v.Kind() != reflect.Func { - panic("value for " + name + " not a function") - } - if !goodFunc(v.Type()) { - panic(fmt.Errorf("can't install method/function %q with %d results", name, v.Type().NumOut())) - } - out[name] = v - } -} - -// addFuncs adds to values the functions in funcs. It does no checking of the input - -// call addValueFuncs first. -func addFuncs(out, in FuncMap) { - for name, fn := range in { - out[name] = fn - } -} - -// goodFunc checks that the function or method has the right result signature. -func goodFunc(typ reflect.Type) bool { - // We allow functions with 1 result or 2 results where the second is an error. - switch { - case typ.NumOut() == 1: - return true - case typ.NumOut() == 2 && typ.Out(1) == errorType: - return true - } - return false -} - -// findFunction looks for a function in the template, and global map. -func findFunction(name string, tmpl *Template) (reflect.Value, bool) { - if tmpl != nil && tmpl.common != nil { - if fn := tmpl.execFuncs[name]; fn.IsValid() { - return fn, true - } - } - if fn := builtinFuncs[name]; fn.IsValid() { - return fn, true - } - return reflect.Value{}, false -} - -// Indexing. - -// index returns the result of indexing its first argument by the following -// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each -// indexed item must be a map, slice, or array. -func index(item interface{}, indices ...interface{}) (interface{}, error) { - v := reflect.ValueOf(item) - for _, i := range indices { - index := reflect.ValueOf(i) - var isNil bool - if v, isNil = indirect(v); isNil { - return nil, fmt.Errorf("index of nil pointer") - } - switch v.Kind() { - case reflect.Array, reflect.Slice, reflect.String: - var x int64 - switch index.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - x = index.Int() - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - x = int64(index.Uint()) - default: - return nil, fmt.Errorf("cannot index slice/array with type %s", index.Type()) - } - if x < 0 || x >= int64(v.Len()) { - return nil, fmt.Errorf("index out of range: %d", x) - } - v = v.Index(int(x)) - case reflect.Map: - if !index.IsValid() { - index = reflect.Zero(v.Type().Key()) - } - if !index.Type().AssignableTo(v.Type().Key()) { - return nil, fmt.Errorf("%s is not index type for %s", index.Type(), v.Type()) - } - if x := v.MapIndex(index); x.IsValid() { - v = x - } else { - v = reflect.Zero(v.Type().Elem()) - } - default: - return nil, fmt.Errorf("can't index item of type %s", v.Type()) - } - } - return v.Interface(), nil -} - -// Length - -// length returns the length of the item, with an error if it has no defined length. -func length(item interface{}) (int, error) { - v, isNil := indirect(reflect.ValueOf(item)) - if isNil { - return 0, fmt.Errorf("len of nil pointer") - } - switch v.Kind() { - case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String: - return v.Len(), nil - } - return 0, fmt.Errorf("len of type %s", v.Type()) -} - -// Function invocation - -// call returns the result of evaluating the first argument as a function. -// The function must return 1 result, or 2 results, the second of which is an error. -func call(fn interface{}, args ...interface{}) (interface{}, error) { - v := reflect.ValueOf(fn) - typ := v.Type() - if typ.Kind() != reflect.Func { - return nil, fmt.Errorf("non-function of type %s", typ) - } - if !goodFunc(typ) { - return nil, fmt.Errorf("function called with %d args; should be 1 or 2", typ.NumOut()) - } - numIn := typ.NumIn() - var dddType reflect.Type - if typ.IsVariadic() { - if len(args) < numIn-1 { - return nil, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1) - } - dddType = typ.In(numIn - 1).Elem() - } else { - if len(args) != numIn { - return nil, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn) - } - } - argv := make([]reflect.Value, len(args)) - for i, arg := range args { - value := reflect.ValueOf(arg) - // Compute the expected type. Clumsy because of variadics. - var argType reflect.Type - if !typ.IsVariadic() || i < numIn-1 { - argType = typ.In(i) - } else { - argType = dddType - } - if !value.IsValid() && canBeNil(argType) { - value = reflect.Zero(argType) - } - if !value.Type().AssignableTo(argType) { - return nil, fmt.Errorf("arg %d has type %s; should be %s", i, value.Type(), argType) - } - argv[i] = value - } - result := v.Call(argv) - if len(result) == 2 && !result[1].IsNil() { - return result[0].Interface(), result[1].Interface().(error) - } - return result[0].Interface(), nil -} - -// Boolean logic. - -func truth(a interface{}) bool { - t, _ := isTrue(reflect.ValueOf(a)) - return t -} - -// and computes the Boolean AND of its arguments, returning -// the first false argument it encounters, or the last argument. -func and(arg0 interface{}, args ...interface{}) interface{} { - if !truth(arg0) { - return arg0 - } - for i := range args { - arg0 = args[i] - if !truth(arg0) { - break - } - } - return arg0 -} - -// or computes the Boolean OR of its arguments, returning -// the first true argument it encounters, or the last argument. -func or(arg0 interface{}, args ...interface{}) interface{} { - if truth(arg0) { - return arg0 - } - for i := range args { - arg0 = args[i] - if truth(arg0) { - break - } - } - return arg0 -} - -// not returns the Boolean negation of its argument. -func not(arg interface{}) (truth bool) { - truth, _ = isTrue(reflect.ValueOf(arg)) - return !truth -} - -// Comparison. - -// TODO: Perhaps allow comparison between signed and unsigned integers. - -var ( - errBadComparisonType = errors.New("invalid type for comparison") - errBadComparison = errors.New("incompatible types for comparison") - errNoComparison = errors.New("missing argument for comparison") -) - -type kind int - -const ( - invalidKind kind = iota - boolKind - complexKind - intKind - floatKind - integerKind - stringKind - uintKind -) - -func basicKind(v reflect.Value) (kind, error) { - switch v.Kind() { - case reflect.Bool: - return boolKind, nil - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return intKind, nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return uintKind, nil - case reflect.Float32, reflect.Float64: - return floatKind, nil - case reflect.Complex64, reflect.Complex128: - return complexKind, nil - case reflect.String: - return stringKind, nil - } - return invalidKind, errBadComparisonType -} - -// eq evaluates the comparison a == b || a == c || ... -func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) { - v1 := reflect.ValueOf(arg1) - k1, err := basicKind(v1) - if err != nil { - return false, err - } - if len(arg2) == 0 { - return false, errNoComparison - } - for _, arg := range arg2 { - v2 := reflect.ValueOf(arg) - k2, err := basicKind(v2) - if err != nil { - return false, err - } - truth := false - if k1 != k2 { - // Special case: Can compare integer values regardless of type's sign. - switch { - case k1 == intKind && k2 == uintKind: - truth = v1.Int() >= 0 && uint64(v1.Int()) == v2.Uint() - case k1 == uintKind && k2 == intKind: - truth = v2.Int() >= 0 && v1.Uint() == uint64(v2.Int()) - default: - return false, errBadComparison - } - } else { - switch k1 { - case boolKind: - truth = v1.Bool() == v2.Bool() - case complexKind: - truth = v1.Complex() == v2.Complex() - case floatKind: - truth = v1.Float() == v2.Float() - case intKind: - truth = v1.Int() == v2.Int() - case stringKind: - truth = v1.String() == v2.String() - case uintKind: - truth = v1.Uint() == v2.Uint() - default: - panic("invalid kind") - } - } - if truth { - return true, nil - } - } - return false, nil -} - -// ne evaluates the comparison a != b. -func ne(arg1, arg2 interface{}) (bool, error) { - // != is the inverse of ==. - equal, err := eq(arg1, arg2) - return !equal, err -} - -// lt evaluates the comparison a < b. -func lt(arg1, arg2 interface{}) (bool, error) { - v1 := reflect.ValueOf(arg1) - k1, err := basicKind(v1) - if err != nil { - return false, err - } - v2 := reflect.ValueOf(arg2) - k2, err := basicKind(v2) - if err != nil { - return false, err - } - truth := false - if k1 != k2 { - // Special case: Can compare integer values regardless of type's sign. - switch { - case k1 == intKind && k2 == uintKind: - truth = v1.Int() < 0 || uint64(v1.Int()) < v2.Uint() - case k1 == uintKind && k2 == intKind: - truth = v2.Int() >= 0 && v1.Uint() < uint64(v2.Int()) - default: - return false, errBadComparison - } - } else { - switch k1 { - case boolKind, complexKind: - return false, errBadComparisonType - case floatKind: - truth = v1.Float() < v2.Float() - case intKind: - truth = v1.Int() < v2.Int() - case stringKind: - truth = v1.String() < v2.String() - case uintKind: - truth = v1.Uint() < v2.Uint() - default: - panic("invalid kind") - } - } - return truth, nil -} - -// le evaluates the comparison <= b. -func le(arg1, arg2 interface{}) (bool, error) { - // <= is < or ==. - lessThan, err := lt(arg1, arg2) - if lessThan || err != nil { - return lessThan, err - } - return eq(arg1, arg2) -} - -// gt evaluates the comparison a > b. -func gt(arg1, arg2 interface{}) (bool, error) { - // > is the inverse of <=. - lessOrEqual, err := le(arg1, arg2) - if err != nil { - return false, err - } - return !lessOrEqual, nil -} - -// ge evaluates the comparison a >= b. -func ge(arg1, arg2 interface{}) (bool, error) { - // >= is the inverse of <. - lessThan, err := lt(arg1, arg2) - if err != nil { - return false, err - } - return !lessThan, nil -} - -// HTML escaping. - -var ( - htmlQuot = []byte(""") // shorter than """ - htmlApos = []byte("'") // shorter than "'" and apos was not in HTML until HTML5 - htmlAmp = []byte("&") - htmlLt = []byte("<") - htmlGt = []byte(">") -) - -// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b. -func HTMLEscape(w io.Writer, b []byte) { - last := 0 - for i, c := range b { - var html []byte - switch c { - case '"': - html = htmlQuot - case '\'': - html = htmlApos - case '&': - html = htmlAmp - case '<': - html = htmlLt - case '>': - html = htmlGt - default: - continue - } - w.Write(b[last:i]) - w.Write(html) - last = i + 1 - } - w.Write(b[last:]) -} - -// HTMLEscapeString returns the escaped HTML equivalent of the plain text data s. -func HTMLEscapeString(s string) string { - // Avoid allocation if we can. - if strings.IndexAny(s, `'"&<>`) < 0 { - return s - } - var b bytes.Buffer - HTMLEscape(&b, []byte(s)) - return b.String() -} - -// HTMLEscaper returns the escaped HTML equivalent of the textual -// representation of its arguments. -func HTMLEscaper(args ...interface{}) string { - return HTMLEscapeString(evalArgs(args)) -} - -// JavaScript escaping. - -var ( - jsLowUni = []byte(`\u00`) - hex = []byte("0123456789ABCDEF") - - jsBackslash = []byte(`\\`) - jsApos = []byte(`\'`) - jsQuot = []byte(`\"`) - jsLt = []byte(`\x3C`) - jsGt = []byte(`\x3E`) -) - -// JSEscape writes to w the escaped JavaScript equivalent of the plain text data b. -func JSEscape(w io.Writer, b []byte) { - last := 0 - for i := 0; i < len(b); i++ { - c := b[i] - - if !jsIsSpecial(rune(c)) { - // fast path: nothing to do - continue - } - w.Write(b[last:i]) - - if c < utf8.RuneSelf { - // Quotes, slashes and angle brackets get quoted. - // Control characters get written as \u00XX. - switch c { - case '\\': - w.Write(jsBackslash) - case '\'': - w.Write(jsApos) - case '"': - w.Write(jsQuot) - case '<': - w.Write(jsLt) - case '>': - w.Write(jsGt) - default: - w.Write(jsLowUni) - t, b := c>>4, c&0x0f - w.Write(hex[t : t+1]) - w.Write(hex[b : b+1]) - } - } else { - // Unicode rune. - r, size := utf8.DecodeRune(b[i:]) - if unicode.IsPrint(r) { - w.Write(b[i : i+size]) - } else { - fmt.Fprintf(w, "\\u%04X", r) - } - i += size - 1 - } - last = i + 1 - } - w.Write(b[last:]) -} - -// JSEscapeString returns the escaped JavaScript equivalent of the plain text data s. -func JSEscapeString(s string) string { - // Avoid allocation if we can. - if strings.IndexFunc(s, jsIsSpecial) < 0 { - return s - } - var b bytes.Buffer - JSEscape(&b, []byte(s)) - return b.String() -} - -func jsIsSpecial(r rune) bool { - switch r { - case '\\', '\'', '"', '<', '>': - return true - } - return r < ' ' || utf8.RuneSelf <= r -} - -// JSEscaper returns the escaped JavaScript equivalent of the textual -// representation of its arguments. -func JSEscaper(args ...interface{}) string { - return JSEscapeString(evalArgs(args)) -} - -// URLQueryEscaper returns the escaped value of the textual representation of -// its arguments in a form suitable for embedding in a URL query. -func URLQueryEscaper(args ...interface{}) string { - return url.QueryEscape(evalArgs(args)) -} - -// evalArgs formats the list of arguments into a string. It is therefore equivalent to -// fmt.Sprint(args...) -// except that each argument is indirected (if a pointer), as required, -// using the same rules as the default string evaluation during template -// execution. -func evalArgs(args []interface{}) string { - ok := false - var s string - // Fast path for simple common case. - if len(args) == 1 { - s, ok = args[0].(string) - } - if !ok { - for i, arg := range args { - a, ok := printableValue(reflect.ValueOf(arg)) - if ok { - args[i] = a - } // else left fmt do its thing - } - s = fmt.Sprint(args...) - } - return s -} diff --git a/vendor/github.com/alecthomas/template/helper.go b/vendor/github.com/alecthomas/template/helper.go deleted file mode 100644 index 3636fb54d69..00000000000 --- a/vendor/github.com/alecthomas/template/helper.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Helper functions to make constructing templates easier. - -package template - -import ( - "fmt" - "io/ioutil" - "path/filepath" -) - -// Functions and methods to parse templates. - -// Must is a helper that wraps a call to a function returning (*Template, error) -// and panics if the error is non-nil. It is intended for use in variable -// initializations such as -// var t = template.Must(template.New("name").Parse("text")) -func Must(t *Template, err error) *Template { - if err != nil { - panic(err) - } - return t -} - -// ParseFiles creates a new Template and parses the template definitions from -// the named files. The returned template's name will have the (base) name and -// (parsed) contents of the first file. There must be at least one file. -// If an error occurs, parsing stops and the returned *Template is nil. -func ParseFiles(filenames ...string) (*Template, error) { - return parseFiles(nil, filenames...) -} - -// ParseFiles parses the named files and associates the resulting templates with -// t. If an error occurs, parsing stops and the returned template is nil; -// otherwise it is t. There must be at least one file. -func (t *Template) ParseFiles(filenames ...string) (*Template, error) { - return parseFiles(t, filenames...) -} - -// parseFiles is the helper for the method and function. If the argument -// template is nil, it is created from the first file. -func parseFiles(t *Template, filenames ...string) (*Template, error) { - if len(filenames) == 0 { - // Not really a problem, but be consistent. - return nil, fmt.Errorf("template: no files named in call to ParseFiles") - } - for _, filename := range filenames { - b, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - s := string(b) - name := filepath.Base(filename) - // First template becomes return value if not already defined, - // and we use that one for subsequent New calls to associate - // all the templates together. Also, if this file has the same name - // as t, this file becomes the contents of t, so - // t, err := New(name).Funcs(xxx).ParseFiles(name) - // works. Otherwise we create a new template associated with t. - var tmpl *Template - if t == nil { - t = New(name) - } - if name == t.Name() { - tmpl = t - } else { - tmpl = t.New(name) - } - _, err = tmpl.Parse(s) - if err != nil { - return nil, err - } - } - return t, nil -} - -// ParseGlob creates a new Template and parses the template definitions from the -// files identified by the pattern, which must match at least one file. The -// returned template will have the (base) name and (parsed) contents of the -// first file matched by the pattern. ParseGlob is equivalent to calling -// ParseFiles with the list of files matched by the pattern. -func ParseGlob(pattern string) (*Template, error) { - return parseGlob(nil, pattern) -} - -// ParseGlob parses the template definitions in the files identified by the -// pattern and associates the resulting templates with t. The pattern is -// processed by filepath.Glob and must match at least one file. ParseGlob is -// equivalent to calling t.ParseFiles with the list of files matched by the -// pattern. -func (t *Template) ParseGlob(pattern string) (*Template, error) { - return parseGlob(t, pattern) -} - -// parseGlob is the implementation of the function and method ParseGlob. -func parseGlob(t *Template, pattern string) (*Template, error) { - filenames, err := filepath.Glob(pattern) - if err != nil { - return nil, err - } - if len(filenames) == 0 { - return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern) - } - return parseFiles(t, filenames...) -} diff --git a/vendor/github.com/alecthomas/template/parse/lex.go b/vendor/github.com/alecthomas/template/parse/lex.go deleted file mode 100644 index 55f1c051e86..00000000000 --- a/vendor/github.com/alecthomas/template/parse/lex.go +++ /dev/null @@ -1,556 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package parse - -import ( - "fmt" - "strings" - "unicode" - "unicode/utf8" -) - -// item represents a token or text string returned from the scanner. -type item struct { - typ itemType // The type of this item. - pos Pos // The starting position, in bytes, of this item in the input string. - val string // The value of this item. -} - -func (i item) String() string { - switch { - case i.typ == itemEOF: - return "EOF" - case i.typ == itemError: - return i.val - case i.typ > itemKeyword: - return fmt.Sprintf("<%s>", i.val) - case len(i.val) > 10: - return fmt.Sprintf("%.10q...", i.val) - } - return fmt.Sprintf("%q", i.val) -} - -// itemType identifies the type of lex items. -type itemType int - -const ( - itemError itemType = iota // error occurred; value is text of error - itemBool // boolean constant - itemChar // printable ASCII character; grab bag for comma etc. - itemCharConstant // character constant - itemComplex // complex constant (1+2i); imaginary is just a number - itemColonEquals // colon-equals (':=') introducing a declaration - itemEOF - itemField // alphanumeric identifier starting with '.' - itemIdentifier // alphanumeric identifier not starting with '.' - itemLeftDelim // left action delimiter - itemLeftParen // '(' inside action - itemNumber // simple number, including imaginary - itemPipe // pipe symbol - itemRawString // raw quoted string (includes quotes) - itemRightDelim // right action delimiter - itemElideNewline // elide newline after right delim - itemRightParen // ')' inside action - itemSpace // run of spaces separating arguments - itemString // quoted string (includes quotes) - itemText // plain text - itemVariable // variable starting with '$', such as '$' or '$1' or '$hello' - // Keywords appear after all the rest. - itemKeyword // used only to delimit the keywords - itemDot // the cursor, spelled '.' - itemDefine // define keyword - itemElse // else keyword - itemEnd // end keyword - itemIf // if keyword - itemNil // the untyped nil constant, easiest to treat as a keyword - itemRange // range keyword - itemTemplate // template keyword - itemWith // with keyword -) - -var key = map[string]itemType{ - ".": itemDot, - "define": itemDefine, - "else": itemElse, - "end": itemEnd, - "if": itemIf, - "range": itemRange, - "nil": itemNil, - "template": itemTemplate, - "with": itemWith, -} - -const eof = -1 - -// stateFn represents the state of the scanner as a function that returns the next state. -type stateFn func(*lexer) stateFn - -// lexer holds the state of the scanner. -type lexer struct { - name string // the name of the input; used only for error reports - input string // the string being scanned - leftDelim string // start of action - rightDelim string // end of action - state stateFn // the next lexing function to enter - pos Pos // current position in the input - start Pos // start position of this item - width Pos // width of last rune read from input - lastPos Pos // position of most recent item returned by nextItem - items chan item // channel of scanned items - parenDepth int // nesting depth of ( ) exprs -} - -// next returns the next rune in the input. -func (l *lexer) next() rune { - if int(l.pos) >= len(l.input) { - l.width = 0 - return eof - } - r, w := utf8.DecodeRuneInString(l.input[l.pos:]) - l.width = Pos(w) - l.pos += l.width - return r -} - -// peek returns but does not consume the next rune in the input. -func (l *lexer) peek() rune { - r := l.next() - l.backup() - return r -} - -// backup steps back one rune. Can only be called once per call of next. -func (l *lexer) backup() { - l.pos -= l.width -} - -// emit passes an item back to the client. -func (l *lexer) emit(t itemType) { - l.items <- item{t, l.start, l.input[l.start:l.pos]} - l.start = l.pos -} - -// ignore skips over the pending input before this point. -func (l *lexer) ignore() { - l.start = l.pos -} - -// accept consumes the next rune if it's from the valid set. -func (l *lexer) accept(valid string) bool { - if strings.IndexRune(valid, l.next()) >= 0 { - return true - } - l.backup() - return false -} - -// acceptRun consumes a run of runes from the valid set. -func (l *lexer) acceptRun(valid string) { - for strings.IndexRune(valid, l.next()) >= 0 { - } - l.backup() -} - -// lineNumber reports which line we're on, based on the position of -// the previous item returned by nextItem. Doing it this way -// means we don't have to worry about peek double counting. -func (l *lexer) lineNumber() int { - return 1 + strings.Count(l.input[:l.lastPos], "\n") -} - -// errorf returns an error token and terminates the scan by passing -// back a nil pointer that will be the next state, terminating l.nextItem. -func (l *lexer) errorf(format string, args ...interface{}) stateFn { - l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)} - return nil -} - -// nextItem returns the next item from the input. -func (l *lexer) nextItem() item { - item := <-l.items - l.lastPos = item.pos - return item -} - -// lex creates a new scanner for the input string. -func lex(name, input, left, right string) *lexer { - if left == "" { - left = leftDelim - } - if right == "" { - right = rightDelim - } - l := &lexer{ - name: name, - input: input, - leftDelim: left, - rightDelim: right, - items: make(chan item), - } - go l.run() - return l -} - -// run runs the state machine for the lexer. -func (l *lexer) run() { - for l.state = lexText; l.state != nil; { - l.state = l.state(l) - } -} - -// state functions - -const ( - leftDelim = "{{" - rightDelim = "}}" - leftComment = "/*" - rightComment = "*/" -) - -// lexText scans until an opening action delimiter, "{{". -func lexText(l *lexer) stateFn { - for { - if strings.HasPrefix(l.input[l.pos:], l.leftDelim) { - if l.pos > l.start { - l.emit(itemText) - } - return lexLeftDelim - } - if l.next() == eof { - break - } - } - // Correctly reached EOF. - if l.pos > l.start { - l.emit(itemText) - } - l.emit(itemEOF) - return nil -} - -// lexLeftDelim scans the left delimiter, which is known to be present. -func lexLeftDelim(l *lexer) stateFn { - l.pos += Pos(len(l.leftDelim)) - if strings.HasPrefix(l.input[l.pos:], leftComment) { - return lexComment - } - l.emit(itemLeftDelim) - l.parenDepth = 0 - return lexInsideAction -} - -// lexComment scans a comment. The left comment marker is known to be present. -func lexComment(l *lexer) stateFn { - l.pos += Pos(len(leftComment)) - i := strings.Index(l.input[l.pos:], rightComment) - if i < 0 { - return l.errorf("unclosed comment") - } - l.pos += Pos(i + len(rightComment)) - if !strings.HasPrefix(l.input[l.pos:], l.rightDelim) { - return l.errorf("comment ends before closing delimiter") - - } - l.pos += Pos(len(l.rightDelim)) - l.ignore() - return lexText -} - -// lexRightDelim scans the right delimiter, which is known to be present. -func lexRightDelim(l *lexer) stateFn { - l.pos += Pos(len(l.rightDelim)) - l.emit(itemRightDelim) - if l.peek() == '\\' { - l.pos++ - l.emit(itemElideNewline) - } - return lexText -} - -// lexInsideAction scans the elements inside action delimiters. -func lexInsideAction(l *lexer) stateFn { - // Either number, quoted string, or identifier. - // Spaces separate arguments; runs of spaces turn into itemSpace. - // Pipe symbols separate and are emitted. - if strings.HasPrefix(l.input[l.pos:], l.rightDelim+"\\") || strings.HasPrefix(l.input[l.pos:], l.rightDelim) { - if l.parenDepth == 0 { - return lexRightDelim - } - return l.errorf("unclosed left paren") - } - switch r := l.next(); { - case r == eof || isEndOfLine(r): - return l.errorf("unclosed action") - case isSpace(r): - return lexSpace - case r == ':': - if l.next() != '=' { - return l.errorf("expected :=") - } - l.emit(itemColonEquals) - case r == '|': - l.emit(itemPipe) - case r == '"': - return lexQuote - case r == '`': - return lexRawQuote - case r == '$': - return lexVariable - case r == '\'': - return lexChar - case r == '.': - // special look-ahead for ".field" so we don't break l.backup(). - if l.pos < Pos(len(l.input)) { - r := l.input[l.pos] - if r < '0' || '9' < r { - return lexField - } - } - fallthrough // '.' can start a number. - case r == '+' || r == '-' || ('0' <= r && r <= '9'): - l.backup() - return lexNumber - case isAlphaNumeric(r): - l.backup() - return lexIdentifier - case r == '(': - l.emit(itemLeftParen) - l.parenDepth++ - return lexInsideAction - case r == ')': - l.emit(itemRightParen) - l.parenDepth-- - if l.parenDepth < 0 { - return l.errorf("unexpected right paren %#U", r) - } - return lexInsideAction - case r <= unicode.MaxASCII && unicode.IsPrint(r): - l.emit(itemChar) - return lexInsideAction - default: - return l.errorf("unrecognized character in action: %#U", r) - } - return lexInsideAction -} - -// lexSpace scans a run of space characters. -// One space has already been seen. -func lexSpace(l *lexer) stateFn { - for isSpace(l.peek()) { - l.next() - } - l.emit(itemSpace) - return lexInsideAction -} - -// lexIdentifier scans an alphanumeric. -func lexIdentifier(l *lexer) stateFn { -Loop: - for { - switch r := l.next(); { - case isAlphaNumeric(r): - // absorb. - default: - l.backup() - word := l.input[l.start:l.pos] - if !l.atTerminator() { - return l.errorf("bad character %#U", r) - } - switch { - case key[word] > itemKeyword: - l.emit(key[word]) - case word[0] == '.': - l.emit(itemField) - case word == "true", word == "false": - l.emit(itemBool) - default: - l.emit(itemIdentifier) - } - break Loop - } - } - return lexInsideAction -} - -// lexField scans a field: .Alphanumeric. -// The . has been scanned. -func lexField(l *lexer) stateFn { - return lexFieldOrVariable(l, itemField) -} - -// lexVariable scans a Variable: $Alphanumeric. -// The $ has been scanned. -func lexVariable(l *lexer) stateFn { - if l.atTerminator() { // Nothing interesting follows -> "$". - l.emit(itemVariable) - return lexInsideAction - } - return lexFieldOrVariable(l, itemVariable) -} - -// lexVariable scans a field or variable: [.$]Alphanumeric. -// The . or $ has been scanned. -func lexFieldOrVariable(l *lexer, typ itemType) stateFn { - if l.atTerminator() { // Nothing interesting follows -> "." or "$". - if typ == itemVariable { - l.emit(itemVariable) - } else { - l.emit(itemDot) - } - return lexInsideAction - } - var r rune - for { - r = l.next() - if !isAlphaNumeric(r) { - l.backup() - break - } - } - if !l.atTerminator() { - return l.errorf("bad character %#U", r) - } - l.emit(typ) - return lexInsideAction -} - -// atTerminator reports whether the input is at valid termination character to -// appear after an identifier. Breaks .X.Y into two pieces. Also catches cases -// like "$x+2" not being acceptable without a space, in case we decide one -// day to implement arithmetic. -func (l *lexer) atTerminator() bool { - r := l.peek() - if isSpace(r) || isEndOfLine(r) { - return true - } - switch r { - case eof, '.', ',', '|', ':', ')', '(': - return true - } - // Does r start the delimiter? This can be ambiguous (with delim=="//", $x/2 will - // succeed but should fail) but only in extremely rare cases caused by willfully - // bad choice of delimiter. - if rd, _ := utf8.DecodeRuneInString(l.rightDelim); rd == r { - return true - } - return false -} - -// lexChar scans a character constant. The initial quote is already -// scanned. Syntax checking is done by the parser. -func lexChar(l *lexer) stateFn { -Loop: - for { - switch l.next() { - case '\\': - if r := l.next(); r != eof && r != '\n' { - break - } - fallthrough - case eof, '\n': - return l.errorf("unterminated character constant") - case '\'': - break Loop - } - } - l.emit(itemCharConstant) - return lexInsideAction -} - -// lexNumber scans a number: decimal, octal, hex, float, or imaginary. This -// isn't a perfect number scanner - for instance it accepts "." and "0x0.2" -// and "089" - but when it's wrong the input is invalid and the parser (via -// strconv) will notice. -func lexNumber(l *lexer) stateFn { - if !l.scanNumber() { - return l.errorf("bad number syntax: %q", l.input[l.start:l.pos]) - } - if sign := l.peek(); sign == '+' || sign == '-' { - // Complex: 1+2i. No spaces, must end in 'i'. - if !l.scanNumber() || l.input[l.pos-1] != 'i' { - return l.errorf("bad number syntax: %q", l.input[l.start:l.pos]) - } - l.emit(itemComplex) - } else { - l.emit(itemNumber) - } - return lexInsideAction -} - -func (l *lexer) scanNumber() bool { - // Optional leading sign. - l.accept("+-") - // Is it hex? - digits := "0123456789" - if l.accept("0") && l.accept("xX") { - digits = "0123456789abcdefABCDEF" - } - l.acceptRun(digits) - if l.accept(".") { - l.acceptRun(digits) - } - if l.accept("eE") { - l.accept("+-") - l.acceptRun("0123456789") - } - // Is it imaginary? - l.accept("i") - // Next thing mustn't be alphanumeric. - if isAlphaNumeric(l.peek()) { - l.next() - return false - } - return true -} - -// lexQuote scans a quoted string. -func lexQuote(l *lexer) stateFn { -Loop: - for { - switch l.next() { - case '\\': - if r := l.next(); r != eof && r != '\n' { - break - } - fallthrough - case eof, '\n': - return l.errorf("unterminated quoted string") - case '"': - break Loop - } - } - l.emit(itemString) - return lexInsideAction -} - -// lexRawQuote scans a raw quoted string. -func lexRawQuote(l *lexer) stateFn { -Loop: - for { - switch l.next() { - case eof, '\n': - return l.errorf("unterminated raw quoted string") - case '`': - break Loop - } - } - l.emit(itemRawString) - return lexInsideAction -} - -// isSpace reports whether r is a space character. -func isSpace(r rune) bool { - return r == ' ' || r == '\t' -} - -// isEndOfLine reports whether r is an end-of-line character. -func isEndOfLine(r rune) bool { - return r == '\r' || r == '\n' -} - -// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore. -func isAlphaNumeric(r rune) bool { - return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r) -} diff --git a/vendor/github.com/alecthomas/template/parse/node.go b/vendor/github.com/alecthomas/template/parse/node.go deleted file mode 100644 index 55c37f6dbac..00000000000 --- a/vendor/github.com/alecthomas/template/parse/node.go +++ /dev/null @@ -1,834 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Parse nodes. - -package parse - -import ( - "bytes" - "fmt" - "strconv" - "strings" -) - -var textFormat = "%s" // Changed to "%q" in tests for better error messages. - -// A Node is an element in the parse tree. The interface is trivial. -// The interface contains an unexported method so that only -// types local to this package can satisfy it. -type Node interface { - Type() NodeType - String() string - // Copy does a deep copy of the Node and all its components. - // To avoid type assertions, some XxxNodes also have specialized - // CopyXxx methods that return *XxxNode. - Copy() Node - Position() Pos // byte position of start of node in full original input string - // tree returns the containing *Tree. - // It is unexported so all implementations of Node are in this package. - tree() *Tree -} - -// NodeType identifies the type of a parse tree node. -type NodeType int - -// Pos represents a byte position in the original input text from which -// this template was parsed. -type Pos int - -func (p Pos) Position() Pos { - return p -} - -// Type returns itself and provides an easy default implementation -// for embedding in a Node. Embedded in all non-trivial Nodes. -func (t NodeType) Type() NodeType { - return t -} - -const ( - NodeText NodeType = iota // Plain text. - NodeAction // A non-control action such as a field evaluation. - NodeBool // A boolean constant. - NodeChain // A sequence of field accesses. - NodeCommand // An element of a pipeline. - NodeDot // The cursor, dot. - nodeElse // An else action. Not added to tree. - nodeEnd // An end action. Not added to tree. - NodeField // A field or method name. - NodeIdentifier // An identifier; always a function name. - NodeIf // An if action. - NodeList // A list of Nodes. - NodeNil // An untyped nil constant. - NodeNumber // A numerical constant. - NodePipe // A pipeline of commands. - NodeRange // A range action. - NodeString // A string constant. - NodeTemplate // A template invocation action. - NodeVariable // A $ variable. - NodeWith // A with action. -) - -// Nodes. - -// ListNode holds a sequence of nodes. -type ListNode struct { - NodeType - Pos - tr *Tree - Nodes []Node // The element nodes in lexical order. -} - -func (t *Tree) newList(pos Pos) *ListNode { - return &ListNode{tr: t, NodeType: NodeList, Pos: pos} -} - -func (l *ListNode) append(n Node) { - l.Nodes = append(l.Nodes, n) -} - -func (l *ListNode) tree() *Tree { - return l.tr -} - -func (l *ListNode) String() string { - b := new(bytes.Buffer) - for _, n := range l.Nodes { - fmt.Fprint(b, n) - } - return b.String() -} - -func (l *ListNode) CopyList() *ListNode { - if l == nil { - return l - } - n := l.tr.newList(l.Pos) - for _, elem := range l.Nodes { - n.append(elem.Copy()) - } - return n -} - -func (l *ListNode) Copy() Node { - return l.CopyList() -} - -// TextNode holds plain text. -type TextNode struct { - NodeType - Pos - tr *Tree - Text []byte // The text; may span newlines. -} - -func (t *Tree) newText(pos Pos, text string) *TextNode { - return &TextNode{tr: t, NodeType: NodeText, Pos: pos, Text: []byte(text)} -} - -func (t *TextNode) String() string { - return fmt.Sprintf(textFormat, t.Text) -} - -func (t *TextNode) tree() *Tree { - return t.tr -} - -func (t *TextNode) Copy() Node { - return &TextNode{tr: t.tr, NodeType: NodeText, Pos: t.Pos, Text: append([]byte{}, t.Text...)} -} - -// PipeNode holds a pipeline with optional declaration -type PipeNode struct { - NodeType - Pos - tr *Tree - Line int // The line number in the input (deprecated; kept for compatibility) - Decl []*VariableNode // Variable declarations in lexical order. - Cmds []*CommandNode // The commands in lexical order. -} - -func (t *Tree) newPipeline(pos Pos, line int, decl []*VariableNode) *PipeNode { - return &PipeNode{tr: t, NodeType: NodePipe, Pos: pos, Line: line, Decl: decl} -} - -func (p *PipeNode) append(command *CommandNode) { - p.Cmds = append(p.Cmds, command) -} - -func (p *PipeNode) String() string { - s := "" - if len(p.Decl) > 0 { - for i, v := range p.Decl { - if i > 0 { - s += ", " - } - s += v.String() - } - s += " := " - } - for i, c := range p.Cmds { - if i > 0 { - s += " | " - } - s += c.String() - } - return s -} - -func (p *PipeNode) tree() *Tree { - return p.tr -} - -func (p *PipeNode) CopyPipe() *PipeNode { - if p == nil { - return p - } - var decl []*VariableNode - for _, d := range p.Decl { - decl = append(decl, d.Copy().(*VariableNode)) - } - n := p.tr.newPipeline(p.Pos, p.Line, decl) - for _, c := range p.Cmds { - n.append(c.Copy().(*CommandNode)) - } - return n -} - -func (p *PipeNode) Copy() Node { - return p.CopyPipe() -} - -// ActionNode holds an action (something bounded by delimiters). -// Control actions have their own nodes; ActionNode represents simple -// ones such as field evaluations and parenthesized pipelines. -type ActionNode struct { - NodeType - Pos - tr *Tree - Line int // The line number in the input (deprecated; kept for compatibility) - Pipe *PipeNode // The pipeline in the action. -} - -func (t *Tree) newAction(pos Pos, line int, pipe *PipeNode) *ActionNode { - return &ActionNode{tr: t, NodeType: NodeAction, Pos: pos, Line: line, Pipe: pipe} -} - -func (a *ActionNode) String() string { - return fmt.Sprintf("{{%s}}", a.Pipe) - -} - -func (a *ActionNode) tree() *Tree { - return a.tr -} - -func (a *ActionNode) Copy() Node { - return a.tr.newAction(a.Pos, a.Line, a.Pipe.CopyPipe()) - -} - -// CommandNode holds a command (a pipeline inside an evaluating action). -type CommandNode struct { - NodeType - Pos - tr *Tree - Args []Node // Arguments in lexical order: Identifier, field, or constant. -} - -func (t *Tree) newCommand(pos Pos) *CommandNode { - return &CommandNode{tr: t, NodeType: NodeCommand, Pos: pos} -} - -func (c *CommandNode) append(arg Node) { - c.Args = append(c.Args, arg) -} - -func (c *CommandNode) String() string { - s := "" - for i, arg := range c.Args { - if i > 0 { - s += " " - } - if arg, ok := arg.(*PipeNode); ok { - s += "(" + arg.String() + ")" - continue - } - s += arg.String() - } - return s -} - -func (c *CommandNode) tree() *Tree { - return c.tr -} - -func (c *CommandNode) Copy() Node { - if c == nil { - return c - } - n := c.tr.newCommand(c.Pos) - for _, c := range c.Args { - n.append(c.Copy()) - } - return n -} - -// IdentifierNode holds an identifier. -type IdentifierNode struct { - NodeType - Pos - tr *Tree - Ident string // The identifier's name. -} - -// NewIdentifier returns a new IdentifierNode with the given identifier name. -func NewIdentifier(ident string) *IdentifierNode { - return &IdentifierNode{NodeType: NodeIdentifier, Ident: ident} -} - -// SetPos sets the position. NewIdentifier is a public method so we can't modify its signature. -// Chained for convenience. -// TODO: fix one day? -func (i *IdentifierNode) SetPos(pos Pos) *IdentifierNode { - i.Pos = pos - return i -} - -// SetTree sets the parent tree for the node. NewIdentifier is a public method so we can't modify its signature. -// Chained for convenience. -// TODO: fix one day? -func (i *IdentifierNode) SetTree(t *Tree) *IdentifierNode { - i.tr = t - return i -} - -func (i *IdentifierNode) String() string { - return i.Ident -} - -func (i *IdentifierNode) tree() *Tree { - return i.tr -} - -func (i *IdentifierNode) Copy() Node { - return NewIdentifier(i.Ident).SetTree(i.tr).SetPos(i.Pos) -} - -// VariableNode holds a list of variable names, possibly with chained field -// accesses. The dollar sign is part of the (first) name. -type VariableNode struct { - NodeType - Pos - tr *Tree - Ident []string // Variable name and fields in lexical order. -} - -func (t *Tree) newVariable(pos Pos, ident string) *VariableNode { - return &VariableNode{tr: t, NodeType: NodeVariable, Pos: pos, Ident: strings.Split(ident, ".")} -} - -func (v *VariableNode) String() string { - s := "" - for i, id := range v.Ident { - if i > 0 { - s += "." - } - s += id - } - return s -} - -func (v *VariableNode) tree() *Tree { - return v.tr -} - -func (v *VariableNode) Copy() Node { - return &VariableNode{tr: v.tr, NodeType: NodeVariable, Pos: v.Pos, Ident: append([]string{}, v.Ident...)} -} - -// DotNode holds the special identifier '.'. -type DotNode struct { - NodeType - Pos - tr *Tree -} - -func (t *Tree) newDot(pos Pos) *DotNode { - return &DotNode{tr: t, NodeType: NodeDot, Pos: pos} -} - -func (d *DotNode) Type() NodeType { - // Override method on embedded NodeType for API compatibility. - // TODO: Not really a problem; could change API without effect but - // api tool complains. - return NodeDot -} - -func (d *DotNode) String() string { - return "." -} - -func (d *DotNode) tree() *Tree { - return d.tr -} - -func (d *DotNode) Copy() Node { - return d.tr.newDot(d.Pos) -} - -// NilNode holds the special identifier 'nil' representing an untyped nil constant. -type NilNode struct { - NodeType - Pos - tr *Tree -} - -func (t *Tree) newNil(pos Pos) *NilNode { - return &NilNode{tr: t, NodeType: NodeNil, Pos: pos} -} - -func (n *NilNode) Type() NodeType { - // Override method on embedded NodeType for API compatibility. - // TODO: Not really a problem; could change API without effect but - // api tool complains. - return NodeNil -} - -func (n *NilNode) String() string { - return "nil" -} - -func (n *NilNode) tree() *Tree { - return n.tr -} - -func (n *NilNode) Copy() Node { - return n.tr.newNil(n.Pos) -} - -// FieldNode holds a field (identifier starting with '.'). -// The names may be chained ('.x.y'). -// The period is dropped from each ident. -type FieldNode struct { - NodeType - Pos - tr *Tree - Ident []string // The identifiers in lexical order. -} - -func (t *Tree) newField(pos Pos, ident string) *FieldNode { - return &FieldNode{tr: t, NodeType: NodeField, Pos: pos, Ident: strings.Split(ident[1:], ".")} // [1:] to drop leading period -} - -func (f *FieldNode) String() string { - s := "" - for _, id := range f.Ident { - s += "." + id - } - return s -} - -func (f *FieldNode) tree() *Tree { - return f.tr -} - -func (f *FieldNode) Copy() Node { - return &FieldNode{tr: f.tr, NodeType: NodeField, Pos: f.Pos, Ident: append([]string{}, f.Ident...)} -} - -// ChainNode holds a term followed by a chain of field accesses (identifier starting with '.'). -// The names may be chained ('.x.y'). -// The periods are dropped from each ident. -type ChainNode struct { - NodeType - Pos - tr *Tree - Node Node - Field []string // The identifiers in lexical order. -} - -func (t *Tree) newChain(pos Pos, node Node) *ChainNode { - return &ChainNode{tr: t, NodeType: NodeChain, Pos: pos, Node: node} -} - -// Add adds the named field (which should start with a period) to the end of the chain. -func (c *ChainNode) Add(field string) { - if len(field) == 0 || field[0] != '.' { - panic("no dot in field") - } - field = field[1:] // Remove leading dot. - if field == "" { - panic("empty field") - } - c.Field = append(c.Field, field) -} - -func (c *ChainNode) String() string { - s := c.Node.String() - if _, ok := c.Node.(*PipeNode); ok { - s = "(" + s + ")" - } - for _, field := range c.Field { - s += "." + field - } - return s -} - -func (c *ChainNode) tree() *Tree { - return c.tr -} - -func (c *ChainNode) Copy() Node { - return &ChainNode{tr: c.tr, NodeType: NodeChain, Pos: c.Pos, Node: c.Node, Field: append([]string{}, c.Field...)} -} - -// BoolNode holds a boolean constant. -type BoolNode struct { - NodeType - Pos - tr *Tree - True bool // The value of the boolean constant. -} - -func (t *Tree) newBool(pos Pos, true bool) *BoolNode { - return &BoolNode{tr: t, NodeType: NodeBool, Pos: pos, True: true} -} - -func (b *BoolNode) String() string { - if b.True { - return "true" - } - return "false" -} - -func (b *BoolNode) tree() *Tree { - return b.tr -} - -func (b *BoolNode) Copy() Node { - return b.tr.newBool(b.Pos, b.True) -} - -// NumberNode holds a number: signed or unsigned integer, float, or complex. -// The value is parsed and stored under all the types that can represent the value. -// This simulates in a small amount of code the behavior of Go's ideal constants. -type NumberNode struct { - NodeType - Pos - tr *Tree - IsInt bool // Number has an integral value. - IsUint bool // Number has an unsigned integral value. - IsFloat bool // Number has a floating-point value. - IsComplex bool // Number is complex. - Int64 int64 // The signed integer value. - Uint64 uint64 // The unsigned integer value. - Float64 float64 // The floating-point value. - Complex128 complex128 // The complex value. - Text string // The original textual representation from the input. -} - -func (t *Tree) newNumber(pos Pos, text string, typ itemType) (*NumberNode, error) { - n := &NumberNode{tr: t, NodeType: NodeNumber, Pos: pos, Text: text} - switch typ { - case itemCharConstant: - rune, _, tail, err := strconv.UnquoteChar(text[1:], text[0]) - if err != nil { - return nil, err - } - if tail != "'" { - return nil, fmt.Errorf("malformed character constant: %s", text) - } - n.Int64 = int64(rune) - n.IsInt = true - n.Uint64 = uint64(rune) - n.IsUint = true - n.Float64 = float64(rune) // odd but those are the rules. - n.IsFloat = true - return n, nil - case itemComplex: - // fmt.Sscan can parse the pair, so let it do the work. - if _, err := fmt.Sscan(text, &n.Complex128); err != nil { - return nil, err - } - n.IsComplex = true - n.simplifyComplex() - return n, nil - } - // Imaginary constants can only be complex unless they are zero. - if len(text) > 0 && text[len(text)-1] == 'i' { - f, err := strconv.ParseFloat(text[:len(text)-1], 64) - if err == nil { - n.IsComplex = true - n.Complex128 = complex(0, f) - n.simplifyComplex() - return n, nil - } - } - // Do integer test first so we get 0x123 etc. - u, err := strconv.ParseUint(text, 0, 64) // will fail for -0; fixed below. - if err == nil { - n.IsUint = true - n.Uint64 = u - } - i, err := strconv.ParseInt(text, 0, 64) - if err == nil { - n.IsInt = true - n.Int64 = i - if i == 0 { - n.IsUint = true // in case of -0. - n.Uint64 = u - } - } - // If an integer extraction succeeded, promote the float. - if n.IsInt { - n.IsFloat = true - n.Float64 = float64(n.Int64) - } else if n.IsUint { - n.IsFloat = true - n.Float64 = float64(n.Uint64) - } else { - f, err := strconv.ParseFloat(text, 64) - if err == nil { - n.IsFloat = true - n.Float64 = f - // If a floating-point extraction succeeded, extract the int if needed. - if !n.IsInt && float64(int64(f)) == f { - n.IsInt = true - n.Int64 = int64(f) - } - if !n.IsUint && float64(uint64(f)) == f { - n.IsUint = true - n.Uint64 = uint64(f) - } - } - } - if !n.IsInt && !n.IsUint && !n.IsFloat { - return nil, fmt.Errorf("illegal number syntax: %q", text) - } - return n, nil -} - -// simplifyComplex pulls out any other types that are represented by the complex number. -// These all require that the imaginary part be zero. -func (n *NumberNode) simplifyComplex() { - n.IsFloat = imag(n.Complex128) == 0 - if n.IsFloat { - n.Float64 = real(n.Complex128) - n.IsInt = float64(int64(n.Float64)) == n.Float64 - if n.IsInt { - n.Int64 = int64(n.Float64) - } - n.IsUint = float64(uint64(n.Float64)) == n.Float64 - if n.IsUint { - n.Uint64 = uint64(n.Float64) - } - } -} - -func (n *NumberNode) String() string { - return n.Text -} - -func (n *NumberNode) tree() *Tree { - return n.tr -} - -func (n *NumberNode) Copy() Node { - nn := new(NumberNode) - *nn = *n // Easy, fast, correct. - return nn -} - -// StringNode holds a string constant. The value has been "unquoted". -type StringNode struct { - NodeType - Pos - tr *Tree - Quoted string // The original text of the string, with quotes. - Text string // The string, after quote processing. -} - -func (t *Tree) newString(pos Pos, orig, text string) *StringNode { - return &StringNode{tr: t, NodeType: NodeString, Pos: pos, Quoted: orig, Text: text} -} - -func (s *StringNode) String() string { - return s.Quoted -} - -func (s *StringNode) tree() *Tree { - return s.tr -} - -func (s *StringNode) Copy() Node { - return s.tr.newString(s.Pos, s.Quoted, s.Text) -} - -// endNode represents an {{end}} action. -// It does not appear in the final parse tree. -type endNode struct { - NodeType - Pos - tr *Tree -} - -func (t *Tree) newEnd(pos Pos) *endNode { - return &endNode{tr: t, NodeType: nodeEnd, Pos: pos} -} - -func (e *endNode) String() string { - return "{{end}}" -} - -func (e *endNode) tree() *Tree { - return e.tr -} - -func (e *endNode) Copy() Node { - return e.tr.newEnd(e.Pos) -} - -// elseNode represents an {{else}} action. Does not appear in the final tree. -type elseNode struct { - NodeType - Pos - tr *Tree - Line int // The line number in the input (deprecated; kept for compatibility) -} - -func (t *Tree) newElse(pos Pos, line int) *elseNode { - return &elseNode{tr: t, NodeType: nodeElse, Pos: pos, Line: line} -} - -func (e *elseNode) Type() NodeType { - return nodeElse -} - -func (e *elseNode) String() string { - return "{{else}}" -} - -func (e *elseNode) tree() *Tree { - return e.tr -} - -func (e *elseNode) Copy() Node { - return e.tr.newElse(e.Pos, e.Line) -} - -// BranchNode is the common representation of if, range, and with. -type BranchNode struct { - NodeType - Pos - tr *Tree - Line int // The line number in the input (deprecated; kept for compatibility) - Pipe *PipeNode // The pipeline to be evaluated. - List *ListNode // What to execute if the value is non-empty. - ElseList *ListNode // What to execute if the value is empty (nil if absent). -} - -func (b *BranchNode) String() string { - name := "" - switch b.NodeType { - case NodeIf: - name = "if" - case NodeRange: - name = "range" - case NodeWith: - name = "with" - default: - panic("unknown branch type") - } - if b.ElseList != nil { - return fmt.Sprintf("{{%s %s}}%s{{else}}%s{{end}}", name, b.Pipe, b.List, b.ElseList) - } - return fmt.Sprintf("{{%s %s}}%s{{end}}", name, b.Pipe, b.List) -} - -func (b *BranchNode) tree() *Tree { - return b.tr -} - -func (b *BranchNode) Copy() Node { - switch b.NodeType { - case NodeIf: - return b.tr.newIf(b.Pos, b.Line, b.Pipe, b.List, b.ElseList) - case NodeRange: - return b.tr.newRange(b.Pos, b.Line, b.Pipe, b.List, b.ElseList) - case NodeWith: - return b.tr.newWith(b.Pos, b.Line, b.Pipe, b.List, b.ElseList) - default: - panic("unknown branch type") - } -} - -// IfNode represents an {{if}} action and its commands. -type IfNode struct { - BranchNode -} - -func (t *Tree) newIf(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *IfNode { - return &IfNode{BranchNode{tr: t, NodeType: NodeIf, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}} -} - -func (i *IfNode) Copy() Node { - return i.tr.newIf(i.Pos, i.Line, i.Pipe.CopyPipe(), i.List.CopyList(), i.ElseList.CopyList()) -} - -// RangeNode represents a {{range}} action and its commands. -type RangeNode struct { - BranchNode -} - -func (t *Tree) newRange(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *RangeNode { - return &RangeNode{BranchNode{tr: t, NodeType: NodeRange, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}} -} - -func (r *RangeNode) Copy() Node { - return r.tr.newRange(r.Pos, r.Line, r.Pipe.CopyPipe(), r.List.CopyList(), r.ElseList.CopyList()) -} - -// WithNode represents a {{with}} action and its commands. -type WithNode struct { - BranchNode -} - -func (t *Tree) newWith(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *WithNode { - return &WithNode{BranchNode{tr: t, NodeType: NodeWith, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}} -} - -func (w *WithNode) Copy() Node { - return w.tr.newWith(w.Pos, w.Line, w.Pipe.CopyPipe(), w.List.CopyList(), w.ElseList.CopyList()) -} - -// TemplateNode represents a {{template}} action. -type TemplateNode struct { - NodeType - Pos - tr *Tree - Line int // The line number in the input (deprecated; kept for compatibility) - Name string // The name of the template (unquoted). - Pipe *PipeNode // The command to evaluate as dot for the template. -} - -func (t *Tree) newTemplate(pos Pos, line int, name string, pipe *PipeNode) *TemplateNode { - return &TemplateNode{tr: t, NodeType: NodeTemplate, Pos: pos, Line: line, Name: name, Pipe: pipe} -} - -func (t *TemplateNode) String() string { - if t.Pipe == nil { - return fmt.Sprintf("{{template %q}}", t.Name) - } - return fmt.Sprintf("{{template %q %s}}", t.Name, t.Pipe) -} - -func (t *TemplateNode) tree() *Tree { - return t.tr -} - -func (t *TemplateNode) Copy() Node { - return t.tr.newTemplate(t.Pos, t.Line, t.Name, t.Pipe.CopyPipe()) -} diff --git a/vendor/github.com/alecthomas/template/parse/parse.go b/vendor/github.com/alecthomas/template/parse/parse.go deleted file mode 100644 index 0d77ade8718..00000000000 --- a/vendor/github.com/alecthomas/template/parse/parse.go +++ /dev/null @@ -1,700 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package parse builds parse trees for templates as defined by text/template -// and html/template. Clients should use those packages to construct templates -// rather than this one, which provides shared internal data structures not -// intended for general use. -package parse - -import ( - "bytes" - "fmt" - "runtime" - "strconv" - "strings" -) - -// Tree is the representation of a single parsed template. -type Tree struct { - Name string // name of the template represented by the tree. - ParseName string // name of the top-level template during parsing, for error messages. - Root *ListNode // top-level root of the tree. - text string // text parsed to create the template (or its parent) - // Parsing only; cleared after parse. - funcs []map[string]interface{} - lex *lexer - token [3]item // three-token lookahead for parser. - peekCount int - vars []string // variables defined at the moment. -} - -// Copy returns a copy of the Tree. Any parsing state is discarded. -func (t *Tree) Copy() *Tree { - if t == nil { - return nil - } - return &Tree{ - Name: t.Name, - ParseName: t.ParseName, - Root: t.Root.CopyList(), - text: t.text, - } -} - -// Parse returns a map from template name to parse.Tree, created by parsing the -// templates described in the argument string. The top-level template will be -// given the specified name. If an error is encountered, parsing stops and an -// empty map is returned with the error. -func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (treeSet map[string]*Tree, err error) { - treeSet = make(map[string]*Tree) - t := New(name) - t.text = text - _, err = t.Parse(text, leftDelim, rightDelim, treeSet, funcs...) - return -} - -// next returns the next token. -func (t *Tree) next() item { - if t.peekCount > 0 { - t.peekCount-- - } else { - t.token[0] = t.lex.nextItem() - } - return t.token[t.peekCount] -} - -// backup backs the input stream up one token. -func (t *Tree) backup() { - t.peekCount++ -} - -// backup2 backs the input stream up two tokens. -// The zeroth token is already there. -func (t *Tree) backup2(t1 item) { - t.token[1] = t1 - t.peekCount = 2 -} - -// backup3 backs the input stream up three tokens -// The zeroth token is already there. -func (t *Tree) backup3(t2, t1 item) { // Reverse order: we're pushing back. - t.token[1] = t1 - t.token[2] = t2 - t.peekCount = 3 -} - -// peek returns but does not consume the next token. -func (t *Tree) peek() item { - if t.peekCount > 0 { - return t.token[t.peekCount-1] - } - t.peekCount = 1 - t.token[0] = t.lex.nextItem() - return t.token[0] -} - -// nextNonSpace returns the next non-space token. -func (t *Tree) nextNonSpace() (token item) { - for { - token = t.next() - if token.typ != itemSpace { - break - } - } - return token -} - -// peekNonSpace returns but does not consume the next non-space token. -func (t *Tree) peekNonSpace() (token item) { - for { - token = t.next() - if token.typ != itemSpace { - break - } - } - t.backup() - return token -} - -// Parsing. - -// New allocates a new parse tree with the given name. -func New(name string, funcs ...map[string]interface{}) *Tree { - return &Tree{ - Name: name, - funcs: funcs, - } -} - -// ErrorContext returns a textual representation of the location of the node in the input text. -// The receiver is only used when the node does not have a pointer to the tree inside, -// which can occur in old code. -func (t *Tree) ErrorContext(n Node) (location, context string) { - pos := int(n.Position()) - tree := n.tree() - if tree == nil { - tree = t - } - text := tree.text[:pos] - byteNum := strings.LastIndex(text, "\n") - if byteNum == -1 { - byteNum = pos // On first line. - } else { - byteNum++ // After the newline. - byteNum = pos - byteNum - } - lineNum := 1 + strings.Count(text, "\n") - context = n.String() - if len(context) > 20 { - context = fmt.Sprintf("%.20s...", context) - } - return fmt.Sprintf("%s:%d:%d", tree.ParseName, lineNum, byteNum), context -} - -// errorf formats the error and terminates processing. -func (t *Tree) errorf(format string, args ...interface{}) { - t.Root = nil - format = fmt.Sprintf("template: %s:%d: %s", t.ParseName, t.lex.lineNumber(), format) - panic(fmt.Errorf(format, args...)) -} - -// error terminates processing. -func (t *Tree) error(err error) { - t.errorf("%s", err) -} - -// expect consumes the next token and guarantees it has the required type. -func (t *Tree) expect(expected itemType, context string) item { - token := t.nextNonSpace() - if token.typ != expected { - t.unexpected(token, context) - } - return token -} - -// expectOneOf consumes the next token and guarantees it has one of the required types. -func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item { - token := t.nextNonSpace() - if token.typ != expected1 && token.typ != expected2 { - t.unexpected(token, context) - } - return token -} - -// unexpected complains about the token and terminates processing. -func (t *Tree) unexpected(token item, context string) { - t.errorf("unexpected %s in %s", token, context) -} - -// recover is the handler that turns panics into returns from the top level of Parse. -func (t *Tree) recover(errp *error) { - e := recover() - if e != nil { - if _, ok := e.(runtime.Error); ok { - panic(e) - } - if t != nil { - t.stopParse() - } - *errp = e.(error) - } - return -} - -// startParse initializes the parser, using the lexer. -func (t *Tree) startParse(funcs []map[string]interface{}, lex *lexer) { - t.Root = nil - t.lex = lex - t.vars = []string{"$"} - t.funcs = funcs -} - -// stopParse terminates parsing. -func (t *Tree) stopParse() { - t.lex = nil - t.vars = nil - t.funcs = nil -} - -// Parse parses the template definition string to construct a representation of -// the template for execution. If either action delimiter string is empty, the -// default ("{{" or "}}") is used. Embedded template definitions are added to -// the treeSet map. -func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) { - defer t.recover(&err) - t.ParseName = t.Name - t.startParse(funcs, lex(t.Name, text, leftDelim, rightDelim)) - t.text = text - t.parse(treeSet) - t.add(treeSet) - t.stopParse() - return t, nil -} - -// add adds tree to the treeSet. -func (t *Tree) add(treeSet map[string]*Tree) { - tree := treeSet[t.Name] - if tree == nil || IsEmptyTree(tree.Root) { - treeSet[t.Name] = t - return - } - if !IsEmptyTree(t.Root) { - t.errorf("template: multiple definition of template %q", t.Name) - } -} - -// IsEmptyTree reports whether this tree (node) is empty of everything but space. -func IsEmptyTree(n Node) bool { - switch n := n.(type) { - case nil: - return true - case *ActionNode: - case *IfNode: - case *ListNode: - for _, node := range n.Nodes { - if !IsEmptyTree(node) { - return false - } - } - return true - case *RangeNode: - case *TemplateNode: - case *TextNode: - return len(bytes.TrimSpace(n.Text)) == 0 - case *WithNode: - default: - panic("unknown node: " + n.String()) - } - return false -} - -// parse is the top-level parser for a template, essentially the same -// as itemList except it also parses {{define}} actions. -// It runs to EOF. -func (t *Tree) parse(treeSet map[string]*Tree) (next Node) { - t.Root = t.newList(t.peek().pos) - for t.peek().typ != itemEOF { - if t.peek().typ == itemLeftDelim { - delim := t.next() - if t.nextNonSpace().typ == itemDefine { - newT := New("definition") // name will be updated once we know it. - newT.text = t.text - newT.ParseName = t.ParseName - newT.startParse(t.funcs, t.lex) - newT.parseDefinition(treeSet) - continue - } - t.backup2(delim) - } - n := t.textOrAction() - if n.Type() == nodeEnd { - t.errorf("unexpected %s", n) - } - t.Root.append(n) - } - return nil -} - -// parseDefinition parses a {{define}} ... {{end}} template definition and -// installs the definition in the treeSet map. The "define" keyword has already -// been scanned. -func (t *Tree) parseDefinition(treeSet map[string]*Tree) { - const context = "define clause" - name := t.expectOneOf(itemString, itemRawString, context) - var err error - t.Name, err = strconv.Unquote(name.val) - if err != nil { - t.error(err) - } - t.expect(itemRightDelim, context) - var end Node - t.Root, end = t.itemList() - if end.Type() != nodeEnd { - t.errorf("unexpected %s in %s", end, context) - } - t.add(treeSet) - t.stopParse() -} - -// itemList: -// textOrAction* -// Terminates at {{end}} or {{else}}, returned separately. -func (t *Tree) itemList() (list *ListNode, next Node) { - list = t.newList(t.peekNonSpace().pos) - for t.peekNonSpace().typ != itemEOF { - n := t.textOrAction() - switch n.Type() { - case nodeEnd, nodeElse: - return list, n - } - list.append(n) - } - t.errorf("unexpected EOF") - return -} - -// textOrAction: -// text | action -func (t *Tree) textOrAction() Node { - switch token := t.nextNonSpace(); token.typ { - case itemElideNewline: - return t.elideNewline() - case itemText: - return t.newText(token.pos, token.val) - case itemLeftDelim: - return t.action() - default: - t.unexpected(token, "input") - } - return nil -} - -// elideNewline: -// Remove newlines trailing rightDelim if \\ is present. -func (t *Tree) elideNewline() Node { - token := t.peek() - if token.typ != itemText { - t.unexpected(token, "input") - return nil - } - - t.next() - stripped := strings.TrimLeft(token.val, "\n\r") - diff := len(token.val) - len(stripped) - if diff > 0 { - // This is a bit nasty. We mutate the token in-place to remove - // preceding newlines. - token.pos += Pos(diff) - token.val = stripped - } - return t.newText(token.pos, token.val) -} - -// Action: -// control -// command ("|" command)* -// Left delim is past. Now get actions. -// First word could be a keyword such as range. -func (t *Tree) action() (n Node) { - switch token := t.nextNonSpace(); token.typ { - case itemElse: - return t.elseControl() - case itemEnd: - return t.endControl() - case itemIf: - return t.ifControl() - case itemRange: - return t.rangeControl() - case itemTemplate: - return t.templateControl() - case itemWith: - return t.withControl() - } - t.backup() - // Do not pop variables; they persist until "end". - return t.newAction(t.peek().pos, t.lex.lineNumber(), t.pipeline("command")) -} - -// Pipeline: -// declarations? command ('|' command)* -func (t *Tree) pipeline(context string) (pipe *PipeNode) { - var decl []*VariableNode - pos := t.peekNonSpace().pos - // Are there declarations? - for { - if v := t.peekNonSpace(); v.typ == itemVariable { - t.next() - // Since space is a token, we need 3-token look-ahead here in the worst case: - // in "$x foo" we need to read "foo" (as opposed to ":=") to know that $x is an - // argument variable rather than a declaration. So remember the token - // adjacent to the variable so we can push it back if necessary. - tokenAfterVariable := t.peek() - if next := t.peekNonSpace(); next.typ == itemColonEquals || (next.typ == itemChar && next.val == ",") { - t.nextNonSpace() - variable := t.newVariable(v.pos, v.val) - decl = append(decl, variable) - t.vars = append(t.vars, v.val) - if next.typ == itemChar && next.val == "," { - if context == "range" && len(decl) < 2 { - continue - } - t.errorf("too many declarations in %s", context) - } - } else if tokenAfterVariable.typ == itemSpace { - t.backup3(v, tokenAfterVariable) - } else { - t.backup2(v) - } - } - break - } - pipe = t.newPipeline(pos, t.lex.lineNumber(), decl) - for { - switch token := t.nextNonSpace(); token.typ { - case itemRightDelim, itemRightParen: - if len(pipe.Cmds) == 0 { - t.errorf("missing value for %s", context) - } - if token.typ == itemRightParen { - t.backup() - } - return - case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier, - itemNumber, itemNil, itemRawString, itemString, itemVariable, itemLeftParen: - t.backup() - pipe.append(t.command()) - default: - t.unexpected(token, context) - } - } -} - -func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) { - defer t.popVars(len(t.vars)) - line = t.lex.lineNumber() - pipe = t.pipeline(context) - var next Node - list, next = t.itemList() - switch next.Type() { - case nodeEnd: //done - case nodeElse: - if allowElseIf { - // Special case for "else if". If the "else" is followed immediately by an "if", - // the elseControl will have left the "if" token pending. Treat - // {{if a}}_{{else if b}}_{{end}} - // as - // {{if a}}_{{else}}{{if b}}_{{end}}{{end}}. - // To do this, parse the if as usual and stop at it {{end}}; the subsequent{{end}} - // is assumed. This technique works even for long if-else-if chains. - // TODO: Should we allow else-if in with and range? - if t.peek().typ == itemIf { - t.next() // Consume the "if" token. - elseList = t.newList(next.Position()) - elseList.append(t.ifControl()) - // Do not consume the next item - only one {{end}} required. - break - } - } - elseList, next = t.itemList() - if next.Type() != nodeEnd { - t.errorf("expected end; found %s", next) - } - } - return pipe.Position(), line, pipe, list, elseList -} - -// If: -// {{if pipeline}} itemList {{end}} -// {{if pipeline}} itemList {{else}} itemList {{end}} -// If keyword is past. -func (t *Tree) ifControl() Node { - return t.newIf(t.parseControl(true, "if")) -} - -// Range: -// {{range pipeline}} itemList {{end}} -// {{range pipeline}} itemList {{else}} itemList {{end}} -// Range keyword is past. -func (t *Tree) rangeControl() Node { - return t.newRange(t.parseControl(false, "range")) -} - -// With: -// {{with pipeline}} itemList {{end}} -// {{with pipeline}} itemList {{else}} itemList {{end}} -// If keyword is past. -func (t *Tree) withControl() Node { - return t.newWith(t.parseControl(false, "with")) -} - -// End: -// {{end}} -// End keyword is past. -func (t *Tree) endControl() Node { - return t.newEnd(t.expect(itemRightDelim, "end").pos) -} - -// Else: -// {{else}} -// Else keyword is past. -func (t *Tree) elseControl() Node { - // Special case for "else if". - peek := t.peekNonSpace() - if peek.typ == itemIf { - // We see "{{else if ... " but in effect rewrite it to {{else}}{{if ... ". - return t.newElse(peek.pos, t.lex.lineNumber()) - } - return t.newElse(t.expect(itemRightDelim, "else").pos, t.lex.lineNumber()) -} - -// Template: -// {{template stringValue pipeline}} -// Template keyword is past. The name must be something that can evaluate -// to a string. -func (t *Tree) templateControl() Node { - var name string - token := t.nextNonSpace() - switch token.typ { - case itemString, itemRawString: - s, err := strconv.Unquote(token.val) - if err != nil { - t.error(err) - } - name = s - default: - t.unexpected(token, "template invocation") - } - var pipe *PipeNode - if t.nextNonSpace().typ != itemRightDelim { - t.backup() - // Do not pop variables; they persist until "end". - pipe = t.pipeline("template") - } - return t.newTemplate(token.pos, t.lex.lineNumber(), name, pipe) -} - -// command: -// operand (space operand)* -// space-separated arguments up to a pipeline character or right delimiter. -// we consume the pipe character but leave the right delim to terminate the action. -func (t *Tree) command() *CommandNode { - cmd := t.newCommand(t.peekNonSpace().pos) - for { - t.peekNonSpace() // skip leading spaces. - operand := t.operand() - if operand != nil { - cmd.append(operand) - } - switch token := t.next(); token.typ { - case itemSpace: - continue - case itemError: - t.errorf("%s", token.val) - case itemRightDelim, itemRightParen: - t.backup() - case itemPipe: - default: - t.errorf("unexpected %s in operand; missing space?", token) - } - break - } - if len(cmd.Args) == 0 { - t.errorf("empty command") - } - return cmd -} - -// operand: -// term .Field* -// An operand is a space-separated component of a command, -// a term possibly followed by field accesses. -// A nil return means the next item is not an operand. -func (t *Tree) operand() Node { - node := t.term() - if node == nil { - return nil - } - if t.peek().typ == itemField { - chain := t.newChain(t.peek().pos, node) - for t.peek().typ == itemField { - chain.Add(t.next().val) - } - // Compatibility with original API: If the term is of type NodeField - // or NodeVariable, just put more fields on the original. - // Otherwise, keep the Chain node. - // TODO: Switch to Chains always when we can. - switch node.Type() { - case NodeField: - node = t.newField(chain.Position(), chain.String()) - case NodeVariable: - node = t.newVariable(chain.Position(), chain.String()) - default: - node = chain - } - } - return node -} - -// term: -// literal (number, string, nil, boolean) -// function (identifier) -// . -// .Field -// $ -// '(' pipeline ')' -// A term is a simple "expression". -// A nil return means the next item is not a term. -func (t *Tree) term() Node { - switch token := t.nextNonSpace(); token.typ { - case itemError: - t.errorf("%s", token.val) - case itemIdentifier: - if !t.hasFunction(token.val) { - t.errorf("function %q not defined", token.val) - } - return NewIdentifier(token.val).SetTree(t).SetPos(token.pos) - case itemDot: - return t.newDot(token.pos) - case itemNil: - return t.newNil(token.pos) - case itemVariable: - return t.useVar(token.pos, token.val) - case itemField: - return t.newField(token.pos, token.val) - case itemBool: - return t.newBool(token.pos, token.val == "true") - case itemCharConstant, itemComplex, itemNumber: - number, err := t.newNumber(token.pos, token.val, token.typ) - if err != nil { - t.error(err) - } - return number - case itemLeftParen: - pipe := t.pipeline("parenthesized pipeline") - if token := t.next(); token.typ != itemRightParen { - t.errorf("unclosed right paren: unexpected %s", token) - } - return pipe - case itemString, itemRawString: - s, err := strconv.Unquote(token.val) - if err != nil { - t.error(err) - } - return t.newString(token.pos, token.val, s) - } - t.backup() - return nil -} - -// hasFunction reports if a function name exists in the Tree's maps. -func (t *Tree) hasFunction(name string) bool { - for _, funcMap := range t.funcs { - if funcMap == nil { - continue - } - if funcMap[name] != nil { - return true - } - } - return false -} - -// popVars trims the variable list to the specified length -func (t *Tree) popVars(n int) { - t.vars = t.vars[:n] -} - -// useVar returns a node for a variable reference. It errors if the -// variable is not defined. -func (t *Tree) useVar(pos Pos, name string) Node { - v := t.newVariable(pos, name) - for _, varName := range t.vars { - if varName == v.Ident[0] { - return v - } - } - t.errorf("undefined variable %q", v.Ident[0]) - return nil -} diff --git a/vendor/github.com/alecthomas/template/template.go b/vendor/github.com/alecthomas/template/template.go deleted file mode 100644 index 447ed2abaea..00000000000 --- a/vendor/github.com/alecthomas/template/template.go +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package template - -import ( - "fmt" - "reflect" - - "github.com/alecthomas/template/parse" -) - -// common holds the information shared by related templates. -type common struct { - tmpl map[string]*Template - // We use two maps, one for parsing and one for execution. - // This separation makes the API cleaner since it doesn't - // expose reflection to the client. - parseFuncs FuncMap - execFuncs map[string]reflect.Value -} - -// Template is the representation of a parsed template. The *parse.Tree -// field is exported only for use by html/template and should be treated -// as unexported by all other clients. -type Template struct { - name string - *parse.Tree - *common - leftDelim string - rightDelim string -} - -// New allocates a new template with the given name. -func New(name string) *Template { - return &Template{ - name: name, - } -} - -// Name returns the name of the template. -func (t *Template) Name() string { - return t.name -} - -// New allocates a new template associated with the given one and with the same -// delimiters. The association, which is transitive, allows one template to -// invoke another with a {{template}} action. -func (t *Template) New(name string) *Template { - t.init() - return &Template{ - name: name, - common: t.common, - leftDelim: t.leftDelim, - rightDelim: t.rightDelim, - } -} - -func (t *Template) init() { - if t.common == nil { - t.common = new(common) - t.tmpl = make(map[string]*Template) - t.parseFuncs = make(FuncMap) - t.execFuncs = make(map[string]reflect.Value) - } -} - -// Clone returns a duplicate of the template, including all associated -// templates. The actual representation is not copied, but the name space of -// associated templates is, so further calls to Parse in the copy will add -// templates to the copy but not to the original. Clone can be used to prepare -// common templates and use them with variant definitions for other templates -// by adding the variants after the clone is made. -func (t *Template) Clone() (*Template, error) { - nt := t.copy(nil) - nt.init() - nt.tmpl[t.name] = nt - for k, v := range t.tmpl { - if k == t.name { // Already installed. - continue - } - // The associated templates share nt's common structure. - tmpl := v.copy(nt.common) - nt.tmpl[k] = tmpl - } - for k, v := range t.parseFuncs { - nt.parseFuncs[k] = v - } - for k, v := range t.execFuncs { - nt.execFuncs[k] = v - } - return nt, nil -} - -// copy returns a shallow copy of t, with common set to the argument. -func (t *Template) copy(c *common) *Template { - nt := New(t.name) - nt.Tree = t.Tree - nt.common = c - nt.leftDelim = t.leftDelim - nt.rightDelim = t.rightDelim - return nt -} - -// AddParseTree creates a new template with the name and parse tree -// and associates it with t. -func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) { - if t.common != nil && t.tmpl[name] != nil { - return nil, fmt.Errorf("template: redefinition of template %q", name) - } - nt := t.New(name) - nt.Tree = tree - t.tmpl[name] = nt - return nt, nil -} - -// Templates returns a slice of the templates associated with t, including t -// itself. -func (t *Template) Templates() []*Template { - if t.common == nil { - return nil - } - // Return a slice so we don't expose the map. - m := make([]*Template, 0, len(t.tmpl)) - for _, v := range t.tmpl { - m = append(m, v) - } - return m -} - -// Delims sets the action delimiters to the specified strings, to be used in -// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template -// definitions will inherit the settings. An empty delimiter stands for the -// corresponding default: {{ or }}. -// The return value is the template, so calls can be chained. -func (t *Template) Delims(left, right string) *Template { - t.leftDelim = left - t.rightDelim = right - return t -} - -// Funcs adds the elements of the argument map to the template's function map. -// It panics if a value in the map is not a function with appropriate return -// type. However, it is legal to overwrite elements of the map. The return -// value is the template, so calls can be chained. -func (t *Template) Funcs(funcMap FuncMap) *Template { - t.init() - addValueFuncs(t.execFuncs, funcMap) - addFuncs(t.parseFuncs, funcMap) - return t -} - -// Lookup returns the template with the given name that is associated with t, -// or nil if there is no such template. -func (t *Template) Lookup(name string) *Template { - if t.common == nil { - return nil - } - return t.tmpl[name] -} - -// Parse parses a string into a template. Nested template definitions will be -// associated with the top-level template t. Parse may be called multiple times -// to parse definitions of templates to associate with t. It is an error if a -// resulting template is non-empty (contains content other than template -// definitions) and would replace a non-empty template with the same name. -// (In multiple calls to Parse with the same receiver template, only one call -// can contain text other than space, comments, and template definitions.) -func (t *Template) Parse(text string) (*Template, error) { - t.init() - trees, err := parse.Parse(t.name, text, t.leftDelim, t.rightDelim, t.parseFuncs, builtins) - if err != nil { - return nil, err - } - // Add the newly parsed trees, including the one for t, into our common structure. - for name, tree := range trees { - // If the name we parsed is the name of this template, overwrite this template. - // The associate method checks it's not a redefinition. - tmpl := t - if name != t.name { - tmpl = t.New(name) - } - // Even if t == tmpl, we need to install it in the common.tmpl map. - if replace, err := t.associate(tmpl, tree); err != nil { - return nil, err - } else if replace { - tmpl.Tree = tree - } - tmpl.leftDelim = t.leftDelim - tmpl.rightDelim = t.rightDelim - } - return t, nil -} - -// associate installs the new template into the group of templates associated -// with t. It is an error to reuse a name except to overwrite an empty -// template. The two are already known to share the common structure. -// The boolean return value reports wither to store this tree as t.Tree. -func (t *Template) associate(new *Template, tree *parse.Tree) (bool, error) { - if new.common != t.common { - panic("internal error: associate not common") - } - name := new.name - if old := t.tmpl[name]; old != nil { - oldIsEmpty := parse.IsEmptyTree(old.Root) - newIsEmpty := parse.IsEmptyTree(tree.Root) - if newIsEmpty { - // Whether old is empty or not, new is empty; no reason to replace old. - return false, nil - } - if !oldIsEmpty { - return false, fmt.Errorf("template: redefinition of template %q", name) - } - } - t.tmpl[name] = new - return true, nil -} diff --git a/vendor/github.com/alecthomas/units/COPYING b/vendor/github.com/alecthomas/units/COPYING deleted file mode 100644 index 2993ec085d3..00000000000 --- a/vendor/github.com/alecthomas/units/COPYING +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (C) 2014 Alec Thomas - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/alecthomas/units/bytes.go b/vendor/github.com/alecthomas/units/bytes.go deleted file mode 100644 index eaadeb8005a..00000000000 --- a/vendor/github.com/alecthomas/units/bytes.go +++ /dev/null @@ -1,83 +0,0 @@ -package units - -// Base2Bytes is the old non-SI power-of-2 byte scale (1024 bytes in a kilobyte, -// etc.). -type Base2Bytes int64 - -// Base-2 byte units. -const ( - Kibibyte Base2Bytes = 1024 - KiB = Kibibyte - Mebibyte = Kibibyte * 1024 - MiB = Mebibyte - Gibibyte = Mebibyte * 1024 - GiB = Gibibyte - Tebibyte = Gibibyte * 1024 - TiB = Tebibyte - Pebibyte = Tebibyte * 1024 - PiB = Pebibyte - Exbibyte = Pebibyte * 1024 - EiB = Exbibyte -) - -var ( - bytesUnitMap = MakeUnitMap("iB", "B", 1024) - oldBytesUnitMap = MakeUnitMap("B", "B", 1024) -) - -// ParseBase2Bytes supports both iB and B in base-2 multipliers. That is, KB -// and KiB are both 1024. -func ParseBase2Bytes(s string) (Base2Bytes, error) { - n, err := ParseUnit(s, bytesUnitMap) - if err != nil { - n, err = ParseUnit(s, oldBytesUnitMap) - } - return Base2Bytes(n), err -} - -func (b Base2Bytes) String() string { - return ToString(int64(b), 1024, "iB", "B") -} - -var ( - metricBytesUnitMap = MakeUnitMap("B", "B", 1000) -) - -// MetricBytes are SI byte units (1000 bytes in a kilobyte). -type MetricBytes SI - -// SI base-10 byte units. -const ( - Kilobyte MetricBytes = 1000 - KB = Kilobyte - Megabyte = Kilobyte * 1000 - MB = Megabyte - Gigabyte = Megabyte * 1000 - GB = Gigabyte - Terabyte = Gigabyte * 1000 - TB = Terabyte - Petabyte = Terabyte * 1000 - PB = Petabyte - Exabyte = Petabyte * 1000 - EB = Exabyte -) - -// ParseMetricBytes parses base-10 metric byte units. That is, KB is 1000 bytes. -func ParseMetricBytes(s string) (MetricBytes, error) { - n, err := ParseUnit(s, metricBytesUnitMap) - return MetricBytes(n), err -} - -func (m MetricBytes) String() string { - return ToString(int64(m), 1000, "B", "B") -} - -// ParseStrictBytes supports both iB and B suffixes for base 2 and metric, -// respectively. That is, KiB represents 1024 and KB represents 1000. -func ParseStrictBytes(s string) (int64, error) { - n, err := ParseUnit(s, bytesUnitMap) - if err != nil { - n, err = ParseUnit(s, metricBytesUnitMap) - } - return int64(n), err -} diff --git a/vendor/github.com/alecthomas/units/doc.go b/vendor/github.com/alecthomas/units/doc.go deleted file mode 100644 index 156ae386723..00000000000 --- a/vendor/github.com/alecthomas/units/doc.go +++ /dev/null @@ -1,13 +0,0 @@ -// Package units provides helpful unit multipliers and functions for Go. -// -// The goal of this package is to have functionality similar to the time [1] package. -// -// -// [1] http://golang.org/pkg/time/ -// -// It allows for code like this: -// -// n, err := ParseBase2Bytes("1KB") -// // n == 1024 -// n = units.Mebibyte * 512 -package units diff --git a/vendor/github.com/alecthomas/units/si.go b/vendor/github.com/alecthomas/units/si.go deleted file mode 100644 index 8234a9d52cb..00000000000 --- a/vendor/github.com/alecthomas/units/si.go +++ /dev/null @@ -1,26 +0,0 @@ -package units - -// SI units. -type SI int64 - -// SI unit multiples. -const ( - Kilo SI = 1000 - Mega = Kilo * 1000 - Giga = Mega * 1000 - Tera = Giga * 1000 - Peta = Tera * 1000 - Exa = Peta * 1000 -) - -func MakeUnitMap(suffix, shortSuffix string, scale int64) map[string]float64 { - return map[string]float64{ - shortSuffix: 1, - "K" + suffix: float64(scale), - "M" + suffix: float64(scale * scale), - "G" + suffix: float64(scale * scale * scale), - "T" + suffix: float64(scale * scale * scale * scale), - "P" + suffix: float64(scale * scale * scale * scale * scale), - "E" + suffix: float64(scale * scale * scale * scale * scale * scale), - } -} diff --git a/vendor/github.com/alecthomas/units/util.go b/vendor/github.com/alecthomas/units/util.go deleted file mode 100644 index 6527e92d164..00000000000 --- a/vendor/github.com/alecthomas/units/util.go +++ /dev/null @@ -1,138 +0,0 @@ -package units - -import ( - "errors" - "fmt" - "strings" -) - -var ( - siUnits = []string{"", "K", "M", "G", "T", "P", "E"} -) - -func ToString(n int64, scale int64, suffix, baseSuffix string) string { - mn := len(siUnits) - out := make([]string, mn) - for i, m := range siUnits { - if n%scale != 0 || i == 0 && n == 0 { - s := suffix - if i == 0 { - s = baseSuffix - } - out[mn-1-i] = fmt.Sprintf("%d%s%s", n%scale, m, s) - } - n /= scale - if n == 0 { - break - } - } - return strings.Join(out, "") -} - -// Below code ripped straight from http://golang.org/src/pkg/time/format.go?s=33392:33438#L1123 -var errLeadingInt = errors.New("units: bad [0-9]*") // never printed - -// leadingInt consumes the leading [0-9]* from s. -func leadingInt(s string) (x int64, rem string, err error) { - i := 0 - for ; i < len(s); i++ { - c := s[i] - if c < '0' || c > '9' { - break - } - if x >= (1<<63-10)/10 { - // overflow - return 0, "", errLeadingInt - } - x = x*10 + int64(c) - '0' - } - return x, s[i:], nil -} - -func ParseUnit(s string, unitMap map[string]float64) (int64, error) { - // [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+ - orig := s - f := float64(0) - neg := false - - // Consume [-+]? - if s != "" { - c := s[0] - if c == '-' || c == '+' { - neg = c == '-' - s = s[1:] - } - } - // Special case: if all that is left is "0", this is zero. - if s == "0" { - return 0, nil - } - if s == "" { - return 0, errors.New("units: invalid " + orig) - } - for s != "" { - g := float64(0) // this element of the sequence - - var x int64 - var err error - - // The next character must be [0-9.] - if !(s[0] == '.' || ('0' <= s[0] && s[0] <= '9')) { - return 0, errors.New("units: invalid " + orig) - } - // Consume [0-9]* - pl := len(s) - x, s, err = leadingInt(s) - if err != nil { - return 0, errors.New("units: invalid " + orig) - } - g = float64(x) - pre := pl != len(s) // whether we consumed anything before a period - - // Consume (\.[0-9]*)? - post := false - if s != "" && s[0] == '.' { - s = s[1:] - pl := len(s) - x, s, err = leadingInt(s) - if err != nil { - return 0, errors.New("units: invalid " + orig) - } - scale := 1.0 - for n := pl - len(s); n > 0; n-- { - scale *= 10 - } - g += float64(x) / scale - post = pl != len(s) - } - if !pre && !post { - // no digits (e.g. ".s" or "-.s") - return 0, errors.New("units: invalid " + orig) - } - - // Consume unit. - i := 0 - for ; i < len(s); i++ { - c := s[i] - if c == '.' || ('0' <= c && c <= '9') { - break - } - } - u := s[:i] - s = s[i:] - unit, ok := unitMap[u] - if !ok { - return 0, errors.New("units: unknown unit " + u + " in " + orig) - } - - f += g * unit - } - - if neg { - f = -f - } - if f < float64(-1<<63) || f > float64(1<<63-1) { - return 0, errors.New("units: overflow parsing unit") - } - return int64(f), nil -} diff --git a/vendor/github.com/codahale/aesnicheck/LICENSE b/vendor/github.com/codahale/aesnicheck/LICENSE deleted file mode 100644 index f9835c241fc..00000000000 --- a/vendor/github.com/codahale/aesnicheck/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Coda Hale - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/vendor/github.com/codahale/aesnicheck/asm_amd64.s b/vendor/github.com/codahale/aesnicheck/asm_amd64.s deleted file mode 100644 index d7ee7ee3820..00000000000 --- a/vendor/github.com/codahale/aesnicheck/asm_amd64.s +++ /dev/null @@ -1,9 +0,0 @@ -// func HasAESNI() bool -TEXT ·HasAESNI(SB),$0 - XORQ AX, AX - INCL AX - CPUID - SHRQ $25, CX - ANDQ $1, CX - MOVB CX, ret+0(FP) - RET diff --git a/vendor/github.com/codahale/aesnicheck/check_asm.go b/vendor/github.com/codahale/aesnicheck/check_asm.go deleted file mode 100644 index 7b4d332cd1e..00000000000 --- a/vendor/github.com/codahale/aesnicheck/check_asm.go +++ /dev/null @@ -1,6 +0,0 @@ -// +build amd64 - -package aesnicheck - -// HasAESNI returns whether AES-NI is supported by the CPU. -func HasAESNI() bool diff --git a/vendor/github.com/codahale/aesnicheck/check_generic.go b/vendor/github.com/codahale/aesnicheck/check_generic.go deleted file mode 100644 index b808160380a..00000000000 --- a/vendor/github.com/codahale/aesnicheck/check_generic.go +++ /dev/null @@ -1,8 +0,0 @@ -// +build !amd64 - -package aesnicheck - -// HasAESNI returns whether AES-NI is supported by the CPU. -func HasAESNI() bool { - return false -} diff --git a/vendor/github.com/codahale/aesnicheck/cmd/aesnicheck/aesnicheck.go b/vendor/github.com/codahale/aesnicheck/cmd/aesnicheck/aesnicheck.go deleted file mode 100644 index ecfe1ce8158..00000000000 --- a/vendor/github.com/codahale/aesnicheck/cmd/aesnicheck/aesnicheck.go +++ /dev/null @@ -1,22 +0,0 @@ -// Command aesnicheck queries the CPU for AES-NI support. If AES-NI is supported, -// aesnicheck will print "supported" and exit with a status of 0. If AES-NI is -// not supported, aesnicheck will print "unsupported" and exit with a status of -// -1. -package main - -import ( - "fmt" - "os" - - "github.com/codahale/aesnicheck" -) - -func main() { - if aesnicheck.HasAESNI() { - fmt.Println("supported") - os.Exit(0) - } else { - fmt.Println("unsupported") - os.Exit(-1) - } -} diff --git a/vendor/github.com/codahale/aesnicheck/docs.go b/vendor/github.com/codahale/aesnicheck/docs.go deleted file mode 100644 index 54fa03e618d..00000000000 --- a/vendor/github.com/codahale/aesnicheck/docs.go +++ /dev/null @@ -1,9 +0,0 @@ -// Package aesnicheck provides a simple check to see if crypto/aes is using -// AES-NI instructions or if the AES transform is being done in software. AES-NI -// is constant-time, which makes it impervious to cache-level timing attacks. For -// security-conscious deployments on public cloud infrastructure (Amazon EC2, -// Google Compute Engine, Microsoft Azure, etc.) this may be critical. -// -// See http://eprint.iacr.org/2014/248 for details on cross-VM timing attacks on -// AES keys. -package aesnicheck diff --git a/vendor/github.com/dustin/go-humanize/LICENSE b/vendor/github.com/dustin/go-humanize/LICENSE deleted file mode 100644 index 8d9a94a9068..00000000000 --- a/vendor/github.com/dustin/go-humanize/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -Copyright (c) 2005-2008 Dustin Sallings - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - diff --git a/vendor/github.com/dustin/go-humanize/big.go b/vendor/github.com/dustin/go-humanize/big.go deleted file mode 100644 index f49dc337dcd..00000000000 --- a/vendor/github.com/dustin/go-humanize/big.go +++ /dev/null @@ -1,31 +0,0 @@ -package humanize - -import ( - "math/big" -) - -// order of magnitude (to a max order) -func oomm(n, b *big.Int, maxmag int) (float64, int) { - mag := 0 - m := &big.Int{} - for n.Cmp(b) >= 0 { - n.DivMod(n, b, m) - mag++ - if mag == maxmag && maxmag >= 0 { - break - } - } - return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag -} - -// total order of magnitude -// (same as above, but with no upper limit) -func oom(n, b *big.Int) (float64, int) { - mag := 0 - m := &big.Int{} - for n.Cmp(b) >= 0 { - n.DivMod(n, b, m) - mag++ - } - return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag -} diff --git a/vendor/github.com/dustin/go-humanize/bigbytes.go b/vendor/github.com/dustin/go-humanize/bigbytes.go deleted file mode 100644 index 1a2bf617239..00000000000 --- a/vendor/github.com/dustin/go-humanize/bigbytes.go +++ /dev/null @@ -1,173 +0,0 @@ -package humanize - -import ( - "fmt" - "math/big" - "strings" - "unicode" -) - -var ( - bigIECExp = big.NewInt(1024) - - // BigByte is one byte in bit.Ints - BigByte = big.NewInt(1) - // BigKiByte is 1,024 bytes in bit.Ints - BigKiByte = (&big.Int{}).Mul(BigByte, bigIECExp) - // BigMiByte is 1,024 k bytes in bit.Ints - BigMiByte = (&big.Int{}).Mul(BigKiByte, bigIECExp) - // BigGiByte is 1,024 m bytes in bit.Ints - BigGiByte = (&big.Int{}).Mul(BigMiByte, bigIECExp) - // BigTiByte is 1,024 g bytes in bit.Ints - BigTiByte = (&big.Int{}).Mul(BigGiByte, bigIECExp) - // BigPiByte is 1,024 t bytes in bit.Ints - BigPiByte = (&big.Int{}).Mul(BigTiByte, bigIECExp) - // BigEiByte is 1,024 p bytes in bit.Ints - BigEiByte = (&big.Int{}).Mul(BigPiByte, bigIECExp) - // BigZiByte is 1,024 e bytes in bit.Ints - BigZiByte = (&big.Int{}).Mul(BigEiByte, bigIECExp) - // BigYiByte is 1,024 z bytes in bit.Ints - BigYiByte = (&big.Int{}).Mul(BigZiByte, bigIECExp) -) - -var ( - bigSIExp = big.NewInt(1000) - - // BigSIByte is one SI byte in big.Ints - BigSIByte = big.NewInt(1) - // BigKByte is 1,000 SI bytes in big.Ints - BigKByte = (&big.Int{}).Mul(BigSIByte, bigSIExp) - // BigMByte is 1,000 SI k bytes in big.Ints - BigMByte = (&big.Int{}).Mul(BigKByte, bigSIExp) - // BigGByte is 1,000 SI m bytes in big.Ints - BigGByte = (&big.Int{}).Mul(BigMByte, bigSIExp) - // BigTByte is 1,000 SI g bytes in big.Ints - BigTByte = (&big.Int{}).Mul(BigGByte, bigSIExp) - // BigPByte is 1,000 SI t bytes in big.Ints - BigPByte = (&big.Int{}).Mul(BigTByte, bigSIExp) - // BigEByte is 1,000 SI p bytes in big.Ints - BigEByte = (&big.Int{}).Mul(BigPByte, bigSIExp) - // BigZByte is 1,000 SI e bytes in big.Ints - BigZByte = (&big.Int{}).Mul(BigEByte, bigSIExp) - // BigYByte is 1,000 SI z bytes in big.Ints - BigYByte = (&big.Int{}).Mul(BigZByte, bigSIExp) -) - -var bigBytesSizeTable = map[string]*big.Int{ - "b": BigByte, - "kib": BigKiByte, - "kb": BigKByte, - "mib": BigMiByte, - "mb": BigMByte, - "gib": BigGiByte, - "gb": BigGByte, - "tib": BigTiByte, - "tb": BigTByte, - "pib": BigPiByte, - "pb": BigPByte, - "eib": BigEiByte, - "eb": BigEByte, - "zib": BigZiByte, - "zb": BigZByte, - "yib": BigYiByte, - "yb": BigYByte, - // Without suffix - "": BigByte, - "ki": BigKiByte, - "k": BigKByte, - "mi": BigMiByte, - "m": BigMByte, - "gi": BigGiByte, - "g": BigGByte, - "ti": BigTiByte, - "t": BigTByte, - "pi": BigPiByte, - "p": BigPByte, - "ei": BigEiByte, - "e": BigEByte, - "z": BigZByte, - "zi": BigZiByte, - "y": BigYByte, - "yi": BigYiByte, -} - -var ten = big.NewInt(10) - -func humanateBigBytes(s, base *big.Int, sizes []string) string { - if s.Cmp(ten) < 0 { - return fmt.Sprintf("%d B", s) - } - c := (&big.Int{}).Set(s) - val, mag := oomm(c, base, len(sizes)-1) - suffix := sizes[mag] - f := "%.0f %s" - if val < 10 { - f = "%.1f %s" - } - - return fmt.Sprintf(f, val, suffix) - -} - -// BigBytes produces a human readable representation of an SI size. -// -// See also: ParseBigBytes. -// -// BigBytes(82854982) -> 83 MB -func BigBytes(s *big.Int) string { - sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"} - return humanateBigBytes(s, bigSIExp, sizes) -} - -// BigIBytes produces a human readable representation of an IEC size. -// -// See also: ParseBigBytes. -// -// BigIBytes(82854982) -> 79 MiB -func BigIBytes(s *big.Int) string { - sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"} - return humanateBigBytes(s, bigIECExp, sizes) -} - -// ParseBigBytes parses a string representation of bytes into the number -// of bytes it represents. -// -// See also: BigBytes, BigIBytes. -// -// ParseBigBytes("42 MB") -> 42000000, nil -// ParseBigBytes("42 mib") -> 44040192, nil -func ParseBigBytes(s string) (*big.Int, error) { - lastDigit := 0 - hasComma := false - for _, r := range s { - if !(unicode.IsDigit(r) || r == '.' || r == ',') { - break - } - if r == ',' { - hasComma = true - } - lastDigit++ - } - - num := s[:lastDigit] - if hasComma { - num = strings.Replace(num, ",", "", -1) - } - - val := &big.Rat{} - _, err := fmt.Sscanf(num, "%f", val) - if err != nil { - return nil, err - } - - extra := strings.ToLower(strings.TrimSpace(s[lastDigit:])) - if m, ok := bigBytesSizeTable[extra]; ok { - mv := (&big.Rat{}).SetInt(m) - val.Mul(val, mv) - rv := &big.Int{} - rv.Div(val.Num(), val.Denom()) - return rv, nil - } - - return nil, fmt.Errorf("unhandled size name: %v", extra) -} diff --git a/vendor/github.com/dustin/go-humanize/bytes.go b/vendor/github.com/dustin/go-humanize/bytes.go deleted file mode 100644 index 0b498f4885c..00000000000 --- a/vendor/github.com/dustin/go-humanize/bytes.go +++ /dev/null @@ -1,143 +0,0 @@ -package humanize - -import ( - "fmt" - "math" - "strconv" - "strings" - "unicode" -) - -// IEC Sizes. -// kibis of bits -const ( - Byte = 1 << (iota * 10) - KiByte - MiByte - GiByte - TiByte - PiByte - EiByte -) - -// SI Sizes. -const ( - IByte = 1 - KByte = IByte * 1000 - MByte = KByte * 1000 - GByte = MByte * 1000 - TByte = GByte * 1000 - PByte = TByte * 1000 - EByte = PByte * 1000 -) - -var bytesSizeTable = map[string]uint64{ - "b": Byte, - "kib": KiByte, - "kb": KByte, - "mib": MiByte, - "mb": MByte, - "gib": GiByte, - "gb": GByte, - "tib": TiByte, - "tb": TByte, - "pib": PiByte, - "pb": PByte, - "eib": EiByte, - "eb": EByte, - // Without suffix - "": Byte, - "ki": KiByte, - "k": KByte, - "mi": MiByte, - "m": MByte, - "gi": GiByte, - "g": GByte, - "ti": TiByte, - "t": TByte, - "pi": PiByte, - "p": PByte, - "ei": EiByte, - "e": EByte, -} - -func logn(n, b float64) float64 { - return math.Log(n) / math.Log(b) -} - -func humanateBytes(s uint64, base float64, sizes []string) string { - if s < 10 { - return fmt.Sprintf("%d B", s) - } - e := math.Floor(logn(float64(s), base)) - suffix := sizes[int(e)] - val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10 - f := "%.0f %s" - if val < 10 { - f = "%.1f %s" - } - - return fmt.Sprintf(f, val, suffix) -} - -// Bytes produces a human readable representation of an SI size. -// -// See also: ParseBytes. -// -// Bytes(82854982) -> 83 MB -func Bytes(s uint64) string { - sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"} - return humanateBytes(s, 1000, sizes) -} - -// IBytes produces a human readable representation of an IEC size. -// -// See also: ParseBytes. -// -// IBytes(82854982) -> 79 MiB -func IBytes(s uint64) string { - sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"} - return humanateBytes(s, 1024, sizes) -} - -// ParseBytes parses a string representation of bytes into the number -// of bytes it represents. -// -// See Also: Bytes, IBytes. -// -// ParseBytes("42 MB") -> 42000000, nil -// ParseBytes("42 mib") -> 44040192, nil -func ParseBytes(s string) (uint64, error) { - lastDigit := 0 - hasComma := false - for _, r := range s { - if !(unicode.IsDigit(r) || r == '.' || r == ',') { - break - } - if r == ',' { - hasComma = true - } - lastDigit++ - } - - num := s[:lastDigit] - if hasComma { - num = strings.Replace(num, ",", "", -1) - } - - f, err := strconv.ParseFloat(num, 64) - if err != nil { - return 0, err - } - - extra := strings.ToLower(strings.TrimSpace(s[lastDigit:])) - if m, ok := bytesSizeTable[extra]; ok { - f *= float64(m) - if f >= math.MaxUint64 { - return 0, fmt.Errorf("too large: %v", s) - } - return uint64(f), nil - } - - return 0, fmt.Errorf("unhandled size name: %v", extra) -} diff --git a/vendor/github.com/dustin/go-humanize/comma.go b/vendor/github.com/dustin/go-humanize/comma.go deleted file mode 100644 index eb285cb9519..00000000000 --- a/vendor/github.com/dustin/go-humanize/comma.go +++ /dev/null @@ -1,108 +0,0 @@ -package humanize - -import ( - "bytes" - "math" - "math/big" - "strconv" - "strings" -) - -// Comma produces a string form of the given number in base 10 with -// commas after every three orders of magnitude. -// -// e.g. Comma(834142) -> 834,142 -func Comma(v int64) string { - sign := "" - - // minin64 can't be negated to a usable value, so it has to be special cased. - if v == math.MinInt64 { - return "-9,223,372,036,854,775,808" - } - - if v < 0 { - sign = "-" - v = 0 - v - } - - parts := []string{"", "", "", "", "", "", ""} - j := len(parts) - 1 - - for v > 999 { - parts[j] = strconv.FormatInt(v%1000, 10) - switch len(parts[j]) { - case 2: - parts[j] = "0" + parts[j] - case 1: - parts[j] = "00" + parts[j] - } - v = v / 1000 - j-- - } - parts[j] = strconv.Itoa(int(v)) - return sign + strings.Join(parts[j:], ",") -} - -// Commaf produces a string form of the given number in base 10 with -// commas after every three orders of magnitude. -// -// e.g. Commaf(834142.32) -> 834,142.32 -func Commaf(v float64) string { - buf := &bytes.Buffer{} - if v < 0 { - buf.Write([]byte{'-'}) - v = 0 - v - } - - comma := []byte{','} - - parts := strings.Split(strconv.FormatFloat(v, 'f', -1, 64), ".") - pos := 0 - if len(parts[0])%3 != 0 { - pos += len(parts[0]) % 3 - buf.WriteString(parts[0][:pos]) - buf.Write(comma) - } - for ; pos < len(parts[0]); pos += 3 { - buf.WriteString(parts[0][pos : pos+3]) - buf.Write(comma) - } - buf.Truncate(buf.Len() - 1) - - if len(parts) > 1 { - buf.Write([]byte{'.'}) - buf.WriteString(parts[1]) - } - return buf.String() -} - -// BigComma produces a string form of the given big.Int in base 10 -// with commas after every three orders of magnitude. -func BigComma(b *big.Int) string { - sign := "" - if b.Sign() < 0 { - sign = "-" - b.Abs(b) - } - - athousand := big.NewInt(1000) - c := (&big.Int{}).Set(b) - _, m := oom(c, athousand) - parts := make([]string, m+1) - j := len(parts) - 1 - - mod := &big.Int{} - for b.Cmp(athousand) >= 0 { - b.DivMod(b, athousand, mod) - parts[j] = strconv.FormatInt(mod.Int64(), 10) - switch len(parts[j]) { - case 2: - parts[j] = "0" + parts[j] - case 1: - parts[j] = "00" + parts[j] - } - j-- - } - parts[j] = strconv.Itoa(int(b.Int64())) - return sign + strings.Join(parts[j:], ",") -} diff --git a/vendor/github.com/dustin/go-humanize/commaf.go b/vendor/github.com/dustin/go-humanize/commaf.go deleted file mode 100644 index 620690dec7d..00000000000 --- a/vendor/github.com/dustin/go-humanize/commaf.go +++ /dev/null @@ -1,40 +0,0 @@ -// +build go1.6 - -package humanize - -import ( - "bytes" - "math/big" - "strings" -) - -// BigCommaf produces a string form of the given big.Float in base 10 -// with commas after every three orders of magnitude. -func BigCommaf(v *big.Float) string { - buf := &bytes.Buffer{} - if v.Sign() < 0 { - buf.Write([]byte{'-'}) - v.Abs(v) - } - - comma := []byte{','} - - parts := strings.Split(v.Text('f', -1), ".") - pos := 0 - if len(parts[0])%3 != 0 { - pos += len(parts[0]) % 3 - buf.WriteString(parts[0][:pos]) - buf.Write(comma) - } - for ; pos < len(parts[0]); pos += 3 { - buf.WriteString(parts[0][pos : pos+3]) - buf.Write(comma) - } - buf.Truncate(buf.Len() - 1) - - if len(parts) > 1 { - buf.Write([]byte{'.'}) - buf.WriteString(parts[1]) - } - return buf.String() -} diff --git a/vendor/github.com/dustin/go-humanize/ftoa.go b/vendor/github.com/dustin/go-humanize/ftoa.go deleted file mode 100644 index c76190b1067..00000000000 --- a/vendor/github.com/dustin/go-humanize/ftoa.go +++ /dev/null @@ -1,23 +0,0 @@ -package humanize - -import "strconv" - -func stripTrailingZeros(s string) string { - offset := len(s) - 1 - for offset > 0 { - if s[offset] == '.' { - offset-- - break - } - if s[offset] != '0' { - break - } - offset-- - } - return s[:offset+1] -} - -// Ftoa converts a float to a string with no trailing zeros. -func Ftoa(num float64) string { - return stripTrailingZeros(strconv.FormatFloat(num, 'f', 6, 64)) -} diff --git a/vendor/github.com/dustin/go-humanize/humanize.go b/vendor/github.com/dustin/go-humanize/humanize.go deleted file mode 100644 index a2c2da31ef1..00000000000 --- a/vendor/github.com/dustin/go-humanize/humanize.go +++ /dev/null @@ -1,8 +0,0 @@ -/* -Package humanize converts boring ugly numbers to human-friendly strings and back. - -Durations can be turned into strings such as "3 days ago", numbers -representing sizes like 82854982 into useful strings like, "83 MB" or -"79 MiB" (whichever you prefer). -*/ -package humanize diff --git a/vendor/github.com/dustin/go-humanize/number.go b/vendor/github.com/dustin/go-humanize/number.go deleted file mode 100644 index dec61865996..00000000000 --- a/vendor/github.com/dustin/go-humanize/number.go +++ /dev/null @@ -1,192 +0,0 @@ -package humanize - -/* -Slightly adapted from the source to fit go-humanize. - -Author: https://github.com/gorhill -Source: https://gist.github.com/gorhill/5285193 - -*/ - -import ( - "math" - "strconv" -) - -var ( - renderFloatPrecisionMultipliers = [...]float64{ - 1, - 10, - 100, - 1000, - 10000, - 100000, - 1000000, - 10000000, - 100000000, - 1000000000, - } - - renderFloatPrecisionRounders = [...]float64{ - 0.5, - 0.05, - 0.005, - 0.0005, - 0.00005, - 0.000005, - 0.0000005, - 0.00000005, - 0.000000005, - 0.0000000005, - } -) - -// FormatFloat produces a formatted number as string based on the following user-specified criteria: -// * thousands separator -// * decimal separator -// * decimal precision -// -// Usage: s := RenderFloat(format, n) -// The format parameter tells how to render the number n. -// -// See examples: http://play.golang.org/p/LXc1Ddm1lJ -// -// Examples of format strings, given n = 12345.6789: -// "#,###.##" => "12,345.67" -// "#,###." => "12,345" -// "#,###" => "12345,678" -// "#\u202F###,##" => "12 345,68" -// "#.###,###### => 12.345,678900 -// "" (aka default format) => 12,345.67 -// -// The highest precision allowed is 9 digits after the decimal symbol. -// There is also a version for integer number, FormatInteger(), -// which is convenient for calls within template. -func FormatFloat(format string, n float64) string { - // Special cases: - // NaN = "NaN" - // +Inf = "+Infinity" - // -Inf = "-Infinity" - if math.IsNaN(n) { - return "NaN" - } - if n > math.MaxFloat64 { - return "Infinity" - } - if n < -math.MaxFloat64 { - return "-Infinity" - } - - // default format - precision := 2 - decimalStr := "." - thousandStr := "," - positiveStr := "" - negativeStr := "-" - - if len(format) > 0 { - format := []rune(format) - - // If there is an explicit format directive, - // then default values are these: - precision = 9 - thousandStr = "" - - // collect indices of meaningful formatting directives - formatIndx := []int{} - for i, char := range format { - if char != '#' && char != '0' { - formatIndx = append(formatIndx, i) - } - } - - if len(formatIndx) > 0 { - // Directive at index 0: - // Must be a '+' - // Raise an error if not the case - // index: 0123456789 - // +0.000,000 - // +000,000.0 - // +0000.00 - // +0000 - if formatIndx[0] == 0 { - if format[formatIndx[0]] != '+' { - panic("RenderFloat(): invalid positive sign directive") - } - positiveStr = "+" - formatIndx = formatIndx[1:] - } - - // Two directives: - // First is thousands separator - // Raise an error if not followed by 3-digit - // 0123456789 - // 0.000,000 - // 000,000.00 - if len(formatIndx) == 2 { - if (formatIndx[1] - formatIndx[0]) != 4 { - panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers") - } - thousandStr = string(format[formatIndx[0]]) - formatIndx = formatIndx[1:] - } - - // One directive: - // Directive is decimal separator - // The number of digit-specifier following the separator indicates wanted precision - // 0123456789 - // 0.00 - // 000,0000 - if len(formatIndx) == 1 { - decimalStr = string(format[formatIndx[0]]) - precision = len(format) - formatIndx[0] - 1 - } - } - } - - // generate sign part - var signStr string - if n >= 0.000000001 { - signStr = positiveStr - } else if n <= -0.000000001 { - signStr = negativeStr - n = -n - } else { - signStr = "" - n = 0.0 - } - - // split number into integer and fractional parts - intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision]) - - // generate integer part string - intStr := strconv.FormatInt(int64(intf), 10) - - // add thousand separator if required - if len(thousandStr) > 0 { - for i := len(intStr); i > 3; { - i -= 3 - intStr = intStr[:i] + thousandStr + intStr[i:] - } - } - - // no fractional part, we can leave now - if precision == 0 { - return signStr + intStr - } - - // generate fractional part - fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision])) - // may need padding - if len(fracStr) < precision { - fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr - } - - return signStr + intStr + decimalStr + fracStr -} - -// FormatInteger produces a formatted number as string. -// See FormatFloat. -func FormatInteger(format string, n int) string { - return FormatFloat(format, float64(n)) -} diff --git a/vendor/github.com/dustin/go-humanize/ordinals.go b/vendor/github.com/dustin/go-humanize/ordinals.go deleted file mode 100644 index 43d88a86195..00000000000 --- a/vendor/github.com/dustin/go-humanize/ordinals.go +++ /dev/null @@ -1,25 +0,0 @@ -package humanize - -import "strconv" - -// Ordinal gives you the input number in a rank/ordinal format. -// -// Ordinal(3) -> 3rd -func Ordinal(x int) string { - suffix := "th" - switch x % 10 { - case 1: - if x%100 != 11 { - suffix = "st" - } - case 2: - if x%100 != 12 { - suffix = "nd" - } - case 3: - if x%100 != 13 { - suffix = "rd" - } - } - return strconv.Itoa(x) + suffix -} diff --git a/vendor/github.com/dustin/go-humanize/si.go b/vendor/github.com/dustin/go-humanize/si.go deleted file mode 100644 index b24e48169f4..00000000000 --- a/vendor/github.com/dustin/go-humanize/si.go +++ /dev/null @@ -1,113 +0,0 @@ -package humanize - -import ( - "errors" - "math" - "regexp" - "strconv" -) - -var siPrefixTable = map[float64]string{ - -24: "y", // yocto - -21: "z", // zepto - -18: "a", // atto - -15: "f", // femto - -12: "p", // pico - -9: "n", // nano - -6: "µ", // micro - -3: "m", // milli - 0: "", - 3: "k", // kilo - 6: "M", // mega - 9: "G", // giga - 12: "T", // tera - 15: "P", // peta - 18: "E", // exa - 21: "Z", // zetta - 24: "Y", // yotta -} - -var revSIPrefixTable = revfmap(siPrefixTable) - -// revfmap reverses the map and precomputes the power multiplier -func revfmap(in map[float64]string) map[string]float64 { - rv := map[string]float64{} - for k, v := range in { - rv[v] = math.Pow(10, k) - } - return rv -} - -var riParseRegex *regexp.Regexp - -func init() { - ri := `^([\-0-9.]+)\s?([` - for _, v := range siPrefixTable { - ri += v - } - ri += `]?)(.*)` - - riParseRegex = regexp.MustCompile(ri) -} - -// ComputeSI finds the most appropriate SI prefix for the given number -// and returns the prefix along with the value adjusted to be within -// that prefix. -// -// See also: SI, ParseSI. -// -// e.g. ComputeSI(2.2345e-12) -> (2.2345, "p") -func ComputeSI(input float64) (float64, string) { - if input == 0 { - return 0, "" - } - mag := math.Abs(input) - exponent := math.Floor(logn(mag, 10)) - exponent = math.Floor(exponent/3) * 3 - - value := mag / math.Pow(10, exponent) - - // Handle special case where value is exactly 1000.0 - // Should return 1 M instead of 1000 k - if value == 1000.0 { - exponent += 3 - value = mag / math.Pow(10, exponent) - } - - value = math.Copysign(value, input) - - prefix := siPrefixTable[exponent] - return value, prefix -} - -// SI returns a string with default formatting. -// -// SI uses Ftoa to format float value, removing trailing zeros. -// -// See also: ComputeSI, ParseSI. -// -// e.g. SI(1000000, "B") -> 1 MB -// e.g. SI(2.2345e-12, "F") -> 2.2345 pF -func SI(input float64, unit string) string { - value, prefix := ComputeSI(input) - return Ftoa(value) + " " + prefix + unit -} - -var errInvalid = errors.New("invalid input") - -// ParseSI parses an SI string back into the number and unit. -// -// See also: SI, ComputeSI. -// -// e.g. ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil) -func ParseSI(input string) (float64, string, error) { - found := riParseRegex.FindStringSubmatch(input) - if len(found) != 4 { - return 0, "", errInvalid - } - mag := revSIPrefixTable[found[2]] - unit := found[3] - - base, err := strconv.ParseFloat(found[1], 64) - return base * mag, unit, err -} diff --git a/vendor/github.com/dustin/go-humanize/times.go b/vendor/github.com/dustin/go-humanize/times.go deleted file mode 100644 index b311f11c802..00000000000 --- a/vendor/github.com/dustin/go-humanize/times.go +++ /dev/null @@ -1,117 +0,0 @@ -package humanize - -import ( - "fmt" - "math" - "sort" - "time" -) - -// Seconds-based time units -const ( - Day = 24 * time.Hour - Week = 7 * Day - Month = 30 * Day - Year = 12 * Month - LongTime = 37 * Year -) - -// Time formats a time into a relative string. -// -// Time(someT) -> "3 weeks ago" -func Time(then time.Time) string { - return RelTime(then, time.Now(), "ago", "from now") -} - -// A RelTimeMagnitude struct contains a relative time point at which -// the relative format of time will switch to a new format string. A -// slice of these in ascending order by their "D" field is passed to -// CustomRelTime to format durations. -// -// The Format field is a string that may contain a "%s" which will be -// replaced with the appropriate signed label (e.g. "ago" or "from -// now") and a "%d" that will be replaced by the quantity. -// -// The DivBy field is the amount of time the time difference must be -// divided by in order to display correctly. -// -// e.g. if D is 2*time.Minute and you want to display "%d minutes %s" -// DivBy should be time.Minute so whatever the duration is will be -// expressed in minutes. -type RelTimeMagnitude struct { - D time.Duration - Format string - DivBy time.Duration -} - -var defaultMagnitudes = []RelTimeMagnitude{ - {time.Second, "now", time.Second}, - {2 * time.Second, "1 second %s", 1}, - {time.Minute, "%d seconds %s", time.Second}, - {2 * time.Minute, "1 minute %s", 1}, - {time.Hour, "%d minutes %s", time.Minute}, - {2 * time.Hour, "1 hour %s", 1}, - {Day, "%d hours %s", time.Hour}, - {2 * Day, "1 day %s", 1}, - {Week, "%d days %s", Day}, - {2 * Week, "1 week %s", 1}, - {Month, "%d weeks %s", Week}, - {2 * Month, "1 month %s", 1}, - {Year, "%d months %s", Month}, - {18 * Month, "1 year %s", 1}, - {2 * Year, "2 years %s", 1}, - {LongTime, "%d years %s", Year}, - {math.MaxInt64, "a long while %s", 1}, -} - -// RelTime formats a time into a relative string. -// -// It takes two times and two labels. In addition to the generic time -// delta string (e.g. 5 minutes), the labels are used applied so that -// the label corresponding to the smaller time is applied. -// -// RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier" -func RelTime(a, b time.Time, albl, blbl string) string { - return CustomRelTime(a, b, albl, blbl, defaultMagnitudes) -} - -// CustomRelTime formats a time into a relative string. -// -// It takes two times two labels and a table of relative time formats. -// In addition to the generic time delta string (e.g. 5 minutes), the -// labels are used applied so that the label corresponding to the -// smaller time is applied. -func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnitude) string { - lbl := albl - diff := b.Sub(a) - - if a.After(b) { - lbl = blbl - diff = a.Sub(b) - } - - n := sort.Search(len(magnitudes), func(i int) bool { - return magnitudes[i].D >= diff - }) - - if n >= len(magnitudes) { - n = len(magnitudes) - 1 - } - mag := magnitudes[n] - args := []interface{}{} - escaped := false - for _, ch := range mag.Format { - if escaped { - switch ch { - case 's': - args = append(args, lbl) - case 'd': - args = append(args, diff/mag.DivBy) - } - escaped = false - } else { - escaped = ch == '%' - } - } - return fmt.Sprintf(mag.Format, args...) -} diff --git a/vendor/github.com/flynn/go-shlex/COPYING b/vendor/github.com/flynn/go-shlex/COPYING deleted file mode 100644 index d6456956733..00000000000 --- a/vendor/github.com/flynn/go-shlex/COPYING +++ /dev/null @@ -1,202 +0,0 @@ - - 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. diff --git a/vendor/github.com/flynn/go-shlex/shlex.go b/vendor/github.com/flynn/go-shlex/shlex.go deleted file mode 100644 index 7aeace801e8..00000000000 --- a/vendor/github.com/flynn/go-shlex/shlex.go +++ /dev/null @@ -1,457 +0,0 @@ -/* -Copyright 2012 Google Inc. All Rights Reserved. - -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 shlex - -/* -Package shlex implements a simple lexer which splits input in to tokens using -shell-style rules for quoting and commenting. -*/ -import ( - "bufio" - "errors" - "fmt" - "io" - "strings" -) - -/* -A TokenType is a top-level token; a word, space, comment, unknown. -*/ -type TokenType int - -/* -A RuneTokenType is the type of a UTF-8 character; a character, quote, space, escape. -*/ -type RuneTokenType int - -type lexerState int - -type Token struct { - tokenType TokenType - value string -} - -/* -Two tokens are equal if both their types and values are equal. A nil token can -never equal another token. -*/ -func (a *Token) Equal(b *Token) bool { - if a == nil || b == nil { - return false - } - if a.tokenType != b.tokenType { - return false - } - return a.value == b.value -} - -const ( - RUNE_CHAR string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._-,/@$*()+=><:;&^%~|!?[]{}" - RUNE_SPACE string = " \t\r\n" - RUNE_ESCAPING_QUOTE string = "\"" - RUNE_NONESCAPING_QUOTE string = "'" - RUNE_ESCAPE = "\\" - RUNE_COMMENT = "#" - - RUNETOKEN_UNKNOWN RuneTokenType = 0 - RUNETOKEN_CHAR RuneTokenType = 1 - RUNETOKEN_SPACE RuneTokenType = 2 - RUNETOKEN_ESCAPING_QUOTE RuneTokenType = 3 - RUNETOKEN_NONESCAPING_QUOTE RuneTokenType = 4 - RUNETOKEN_ESCAPE RuneTokenType = 5 - RUNETOKEN_COMMENT RuneTokenType = 6 - RUNETOKEN_EOF RuneTokenType = 7 - - TOKEN_UNKNOWN TokenType = 0 - TOKEN_WORD TokenType = 1 - TOKEN_SPACE TokenType = 2 - TOKEN_COMMENT TokenType = 3 - - STATE_START lexerState = 0 - STATE_INWORD lexerState = 1 - STATE_ESCAPING lexerState = 2 - STATE_ESCAPING_QUOTED lexerState = 3 - STATE_QUOTED_ESCAPING lexerState = 4 - STATE_QUOTED lexerState = 5 - STATE_COMMENT lexerState = 6 - - INITIAL_TOKEN_CAPACITY int = 100 -) - -/* -A type for classifying characters. This allows for different sorts of -classifiers - those accepting extended non-ascii chars, or strict posix -compatibility, for example. -*/ -type TokenClassifier struct { - typeMap map[int32]RuneTokenType -} - -func addRuneClass(typeMap *map[int32]RuneTokenType, runes string, tokenType RuneTokenType) { - for _, rune := range runes { - (*typeMap)[int32(rune)] = tokenType - } -} - -/* -Create a new classifier for basic ASCII characters. -*/ -func NewDefaultClassifier() *TokenClassifier { - typeMap := map[int32]RuneTokenType{} - addRuneClass(&typeMap, RUNE_CHAR, RUNETOKEN_CHAR) - addRuneClass(&typeMap, RUNE_SPACE, RUNETOKEN_SPACE) - addRuneClass(&typeMap, RUNE_ESCAPING_QUOTE, RUNETOKEN_ESCAPING_QUOTE) - addRuneClass(&typeMap, RUNE_NONESCAPING_QUOTE, RUNETOKEN_NONESCAPING_QUOTE) - addRuneClass(&typeMap, RUNE_ESCAPE, RUNETOKEN_ESCAPE) - addRuneClass(&typeMap, RUNE_COMMENT, RUNETOKEN_COMMENT) - return &TokenClassifier{ - typeMap: typeMap} -} - -func (classifier *TokenClassifier) ClassifyRune(rune int32) RuneTokenType { - return classifier.typeMap[rune] -} - -/* -A type for turning an input stream in to a sequence of strings. Whitespace and -comments are skipped. -*/ -type Lexer struct { - tokenizer *Tokenizer -} - -/* -Create a new lexer. -*/ -func NewLexer(r io.Reader) (*Lexer, error) { - - tokenizer, err := NewTokenizer(r) - if err != nil { - return nil, err - } - lexer := &Lexer{tokenizer: tokenizer} - return lexer, nil -} - -/* -Return the next word, and an error value. If there are no more words, the error -will be io.EOF. -*/ -func (l *Lexer) NextWord() (string, error) { - var token *Token - var err error - for { - token, err = l.tokenizer.NextToken() - if err != nil { - return "", err - } - switch token.tokenType { - case TOKEN_WORD: - { - return token.value, nil - } - case TOKEN_COMMENT: - { - // skip comments - } - default: - { - panic(fmt.Sprintf("Unknown token type: %v", token.tokenType)) - } - } - } - return "", io.EOF -} - -/* -A type for turning an input stream in to a sequence of typed tokens. -*/ -type Tokenizer struct { - input *bufio.Reader - classifier *TokenClassifier -} - -/* -Create a new tokenizer. -*/ -func NewTokenizer(r io.Reader) (*Tokenizer, error) { - input := bufio.NewReader(r) - classifier := NewDefaultClassifier() - tokenizer := &Tokenizer{ - input: input, - classifier: classifier} - return tokenizer, nil -} - -/* -Scan the stream for the next token. - -This uses an internal state machine. It will panic if it encounters a character -which it does not know how to handle. -*/ -func (t *Tokenizer) scanStream() (*Token, error) { - state := STATE_START - var tokenType TokenType - value := make([]int32, 0, INITIAL_TOKEN_CAPACITY) - var ( - nextRune int32 - nextRuneType RuneTokenType - err error - ) -SCAN: - for { - nextRune, _, err = t.input.ReadRune() - nextRuneType = t.classifier.ClassifyRune(nextRune) - if err != nil { - if err == io.EOF { - nextRuneType = RUNETOKEN_EOF - err = nil - } else { - return nil, err - } - } - switch state { - case STATE_START: // no runes read yet - { - switch nextRuneType { - case RUNETOKEN_EOF: - { - return nil, io.EOF - } - case RUNETOKEN_CHAR: - { - tokenType = TOKEN_WORD - value = append(value, nextRune) - state = STATE_INWORD - } - case RUNETOKEN_SPACE: - { - } - case RUNETOKEN_ESCAPING_QUOTE: - { - tokenType = TOKEN_WORD - state = STATE_QUOTED_ESCAPING - } - case RUNETOKEN_NONESCAPING_QUOTE: - { - tokenType = TOKEN_WORD - state = STATE_QUOTED - } - case RUNETOKEN_ESCAPE: - { - tokenType = TOKEN_WORD - state = STATE_ESCAPING - } - case RUNETOKEN_COMMENT: - { - tokenType = TOKEN_COMMENT - state = STATE_COMMENT - } - default: - { - return nil, errors.New(fmt.Sprintf("Unknown rune: %v", nextRune)) - } - } - } - case STATE_INWORD: // in a regular word - { - switch nextRuneType { - case RUNETOKEN_EOF: - { - break SCAN - } - case RUNETOKEN_CHAR, RUNETOKEN_COMMENT: - { - value = append(value, nextRune) - } - case RUNETOKEN_SPACE: - { - t.input.UnreadRune() - break SCAN - } - case RUNETOKEN_ESCAPING_QUOTE: - { - state = STATE_QUOTED_ESCAPING - } - case RUNETOKEN_NONESCAPING_QUOTE: - { - state = STATE_QUOTED - } - case RUNETOKEN_ESCAPE: - { - state = STATE_ESCAPING - } - default: - { - return nil, errors.New(fmt.Sprintf("Uknown rune: %v", nextRune)) - } - } - } - case STATE_ESCAPING: // the next rune after an escape character - { - switch nextRuneType { - case RUNETOKEN_EOF: - { - err = errors.New("EOF found after escape character") - break SCAN - } - case RUNETOKEN_CHAR, RUNETOKEN_SPACE, RUNETOKEN_ESCAPING_QUOTE, RUNETOKEN_NONESCAPING_QUOTE, RUNETOKEN_ESCAPE, RUNETOKEN_COMMENT: - { - state = STATE_INWORD - value = append(value, nextRune) - } - default: - { - return nil, errors.New(fmt.Sprintf("Uknown rune: %v", nextRune)) - } - } - } - case STATE_ESCAPING_QUOTED: // the next rune after an escape character, in double quotes - { - switch nextRuneType { - case RUNETOKEN_EOF: - { - err = errors.New("EOF found after escape character") - break SCAN - } - case RUNETOKEN_CHAR, RUNETOKEN_SPACE, RUNETOKEN_ESCAPING_QUOTE, RUNETOKEN_NONESCAPING_QUOTE, RUNETOKEN_ESCAPE, RUNETOKEN_COMMENT: - { - state = STATE_QUOTED_ESCAPING - value = append(value, nextRune) - } - default: - { - return nil, errors.New(fmt.Sprintf("Uknown rune: %v", nextRune)) - } - } - } - case STATE_QUOTED_ESCAPING: // in escaping double quotes - { - switch nextRuneType { - case RUNETOKEN_EOF: - { - err = errors.New("EOF found when expecting closing quote.") - break SCAN - } - case RUNETOKEN_CHAR, RUNETOKEN_UNKNOWN, RUNETOKEN_SPACE, RUNETOKEN_NONESCAPING_QUOTE, RUNETOKEN_COMMENT: - { - value = append(value, nextRune) - } - case RUNETOKEN_ESCAPING_QUOTE: - { - state = STATE_INWORD - } - case RUNETOKEN_ESCAPE: - { - state = STATE_ESCAPING_QUOTED - } - default: - { - return nil, errors.New(fmt.Sprintf("Uknown rune: %v", nextRune)) - } - } - } - case STATE_QUOTED: // in non-escaping single quotes - { - switch nextRuneType { - case RUNETOKEN_EOF: - { - err = errors.New("EOF found when expecting closing quote.") - break SCAN - } - case RUNETOKEN_CHAR, RUNETOKEN_UNKNOWN, RUNETOKEN_SPACE, RUNETOKEN_ESCAPING_QUOTE, RUNETOKEN_ESCAPE, RUNETOKEN_COMMENT: - { - value = append(value, nextRune) - } - case RUNETOKEN_NONESCAPING_QUOTE: - { - state = STATE_INWORD - } - default: - { - return nil, errors.New(fmt.Sprintf("Uknown rune: %v", nextRune)) - } - } - } - case STATE_COMMENT: - { - switch nextRuneType { - case RUNETOKEN_EOF: - { - break SCAN - } - case RUNETOKEN_CHAR, RUNETOKEN_UNKNOWN, RUNETOKEN_ESCAPING_QUOTE, RUNETOKEN_ESCAPE, RUNETOKEN_COMMENT, RUNETOKEN_NONESCAPING_QUOTE: - { - value = append(value, nextRune) - } - case RUNETOKEN_SPACE: - { - if nextRune == '\n' { - state = STATE_START - break SCAN - } else { - value = append(value, nextRune) - } - } - default: - { - return nil, errors.New(fmt.Sprintf("Uknown rune: %v", nextRune)) - } - } - } - default: - { - panic(fmt.Sprintf("Unexpected state: %v", state)) - } - } - } - token := &Token{ - tokenType: tokenType, - value: string(value)} - return token, err -} - -/* -Return the next token in the stream, and an error value. If there are no more -tokens available, the error value will be io.EOF. -*/ -func (t *Tokenizer) NextToken() (*Token, error) { - return t.scanStream() -} - -/* -Split a string in to a slice of strings, based upon shell-style rules for -quoting, escaping, and spaces. -*/ -func Split(s string) ([]string, error) { - l, err := NewLexer(strings.NewReader(s)) - if err != nil { - return nil, err - } - subStrings := []string{} - for { - word, err := l.NextWord() - if err != nil { - if err == io.EOF { - return subStrings, nil - } - return subStrings, err - } - subStrings = append(subStrings, word) - } - return subStrings, nil -} diff --git a/vendor/github.com/golang/protobuf/proto/LICENSE b/vendor/github.com/golang/protobuf/proto/LICENSE deleted file mode 100644 index 1b1b1921efa..00000000000 --- a/vendor/github.com/golang/protobuf/proto/LICENSE +++ /dev/null @@ -1,31 +0,0 @@ -Go support for Protocol Buffers - Google's data interchange format - -Copyright 2010 The Go Authors. All rights reserved. -https://github.com/golang/protobuf - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - diff --git a/vendor/github.com/golang/protobuf/proto/clone.go b/vendor/github.com/golang/protobuf/proto/clone.go deleted file mode 100644 index e392575b353..00000000000 --- a/vendor/github.com/golang/protobuf/proto/clone.go +++ /dev/null @@ -1,229 +0,0 @@ -// Go support for Protocol Buffers - Google's data interchange format -// -// Copyright 2011 The Go Authors. All rights reserved. -// https://github.com/golang/protobuf -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// Protocol buffer deep copy and merge. -// TODO: RawMessage. - -package proto - -import ( - "log" - "reflect" - "strings" -) - -// Clone returns a deep copy of a protocol buffer. -func Clone(pb Message) Message { - in := reflect.ValueOf(pb) - if in.IsNil() { - return pb - } - - out := reflect.New(in.Type().Elem()) - // out is empty so a merge is a deep copy. - mergeStruct(out.Elem(), in.Elem()) - return out.Interface().(Message) -} - -// Merge merges src into dst. -// Required and optional fields that are set in src will be set to that value in dst. -// Elements of repeated fields will be appended. -// Merge panics if src and dst are not the same type, or if dst is nil. -func Merge(dst, src Message) { - in := reflect.ValueOf(src) - out := reflect.ValueOf(dst) - if out.IsNil() { - panic("proto: nil destination") - } - if in.Type() != out.Type() { - // Explicit test prior to mergeStruct so that mistyped nils will fail - panic("proto: type mismatch") - } - if in.IsNil() { - // Merging nil into non-nil is a quiet no-op - return - } - mergeStruct(out.Elem(), in.Elem()) -} - -func mergeStruct(out, in reflect.Value) { - sprop := GetProperties(in.Type()) - for i := 0; i < in.NumField(); i++ { - f := in.Type().Field(i) - if strings.HasPrefix(f.Name, "XXX_") { - continue - } - mergeAny(out.Field(i), in.Field(i), false, sprop.Prop[i]) - } - - if emIn, ok := extendable(in.Addr().Interface()); ok { - emOut, _ := extendable(out.Addr().Interface()) - mIn, muIn := emIn.extensionsRead() - if mIn != nil { - mOut := emOut.extensionsWrite() - muIn.Lock() - mergeExtension(mOut, mIn) - muIn.Unlock() - } - } - - uf := in.FieldByName("XXX_unrecognized") - if !uf.IsValid() { - return - } - uin := uf.Bytes() - if len(uin) > 0 { - out.FieldByName("XXX_unrecognized").SetBytes(append([]byte(nil), uin...)) - } -} - -// mergeAny performs a merge between two values of the same type. -// viaPtr indicates whether the values were indirected through a pointer (implying proto2). -// prop is set if this is a struct field (it may be nil). -func mergeAny(out, in reflect.Value, viaPtr bool, prop *Properties) { - if in.Type() == protoMessageType { - if !in.IsNil() { - if out.IsNil() { - out.Set(reflect.ValueOf(Clone(in.Interface().(Message)))) - } else { - Merge(out.Interface().(Message), in.Interface().(Message)) - } - } - return - } - switch in.Kind() { - case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64, - reflect.String, reflect.Uint32, reflect.Uint64: - if !viaPtr && isProto3Zero(in) { - return - } - out.Set(in) - case reflect.Interface: - // Probably a oneof field; copy non-nil values. - if in.IsNil() { - return - } - // Allocate destination if it is not set, or set to a different type. - // Otherwise we will merge as normal. - if out.IsNil() || out.Elem().Type() != in.Elem().Type() { - out.Set(reflect.New(in.Elem().Elem().Type())) // interface -> *T -> T -> new(T) - } - mergeAny(out.Elem(), in.Elem(), false, nil) - case reflect.Map: - if in.Len() == 0 { - return - } - if out.IsNil() { - out.Set(reflect.MakeMap(in.Type())) - } - // For maps with value types of *T or []byte we need to deep copy each value. - elemKind := in.Type().Elem().Kind() - for _, key := range in.MapKeys() { - var val reflect.Value - switch elemKind { - case reflect.Ptr: - val = reflect.New(in.Type().Elem().Elem()) - mergeAny(val, in.MapIndex(key), false, nil) - case reflect.Slice: - val = in.MapIndex(key) - val = reflect.ValueOf(append([]byte{}, val.Bytes()...)) - default: - val = in.MapIndex(key) - } - out.SetMapIndex(key, val) - } - case reflect.Ptr: - if in.IsNil() { - return - } - if out.IsNil() { - out.Set(reflect.New(in.Elem().Type())) - } - mergeAny(out.Elem(), in.Elem(), true, nil) - case reflect.Slice: - if in.IsNil() { - return - } - if in.Type().Elem().Kind() == reflect.Uint8 { - // []byte is a scalar bytes field, not a repeated field. - - // Edge case: if this is in a proto3 message, a zero length - // bytes field is considered the zero value, and should not - // be merged. - if prop != nil && prop.proto3 && in.Len() == 0 { - return - } - - // Make a deep copy. - // Append to []byte{} instead of []byte(nil) so that we never end up - // with a nil result. - out.SetBytes(append([]byte{}, in.Bytes()...)) - return - } - n := in.Len() - if out.IsNil() { - out.Set(reflect.MakeSlice(in.Type(), 0, n)) - } - switch in.Type().Elem().Kind() { - case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64, - reflect.String, reflect.Uint32, reflect.Uint64: - out.Set(reflect.AppendSlice(out, in)) - default: - for i := 0; i < n; i++ { - x := reflect.Indirect(reflect.New(in.Type().Elem())) - mergeAny(x, in.Index(i), false, nil) - out.Set(reflect.Append(out, x)) - } - } - case reflect.Struct: - mergeStruct(out, in) - default: - // unknown type, so not a protocol buffer - log.Printf("proto: don't know how to copy %v", in) - } -} - -func mergeExtension(out, in map[int32]Extension) { - for extNum, eIn := range in { - eOut := Extension{desc: eIn.desc} - if eIn.value != nil { - v := reflect.New(reflect.TypeOf(eIn.value)).Elem() - mergeAny(v, reflect.ValueOf(eIn.value), false, nil) - eOut.value = v.Interface() - } - if eIn.enc != nil { - eOut.enc = make([]byte, len(eIn.enc)) - copy(eOut.enc, eIn.enc) - } - - out[extNum] = eOut - } -} diff --git a/vendor/github.com/golang/protobuf/proto/decode.go b/vendor/github.com/golang/protobuf/proto/decode.go deleted file mode 100644 index aa207298f99..00000000000 --- a/vendor/github.com/golang/protobuf/proto/decode.go +++ /dev/null @@ -1,970 +0,0 @@ -// Go support for Protocol Buffers - Google's data interchange format -// -// Copyright 2010 The Go Authors. All rights reserved. -// https://github.com/golang/protobuf -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package proto - -/* - * Routines for decoding protocol buffer data to construct in-memory representations. - */ - -import ( - "errors" - "fmt" - "io" - "os" - "reflect" -) - -// errOverflow is returned when an integer is too large to be represented. -var errOverflow = errors.New("proto: integer overflow") - -// ErrInternalBadWireType is returned by generated code when an incorrect -// wire type is encountered. It does not get returned to user code. -var ErrInternalBadWireType = errors.New("proto: internal error: bad wiretype for oneof") - -// The fundamental decoders that interpret bytes on the wire. -// Those that take integer types all return uint64 and are -// therefore of type valueDecoder. - -// DecodeVarint reads a varint-encoded integer from the slice. -// It returns the integer and the number of bytes consumed, or -// zero if there is not enough. -// This is the format for the -// int32, int64, uint32, uint64, bool, and enum -// protocol buffer types. -func DecodeVarint(buf []byte) (x uint64, n int) { - for shift := uint(0); shift < 64; shift += 7 { - if n >= len(buf) { - return 0, 0 - } - b := uint64(buf[n]) - n++ - x |= (b & 0x7F) << shift - if (b & 0x80) == 0 { - return x, n - } - } - - // The number is too large to represent in a 64-bit value. - return 0, 0 -} - -func (p *Buffer) decodeVarintSlow() (x uint64, err error) { - i := p.index - l := len(p.buf) - - for shift := uint(0); shift < 64; shift += 7 { - if i >= l { - err = io.ErrUnexpectedEOF - return - } - b := p.buf[i] - i++ - x |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - p.index = i - return - } - } - - // The number is too large to represent in a 64-bit value. - err = errOverflow - return -} - -// DecodeVarint reads a varint-encoded integer from the Buffer. -// This is the format for the -// int32, int64, uint32, uint64, bool, and enum -// protocol buffer types. -func (p *Buffer) DecodeVarint() (x uint64, err error) { - i := p.index - buf := p.buf - - if i >= len(buf) { - return 0, io.ErrUnexpectedEOF - } else if buf[i] < 0x80 { - p.index++ - return uint64(buf[i]), nil - } else if len(buf)-i < 10 { - return p.decodeVarintSlow() - } - - var b uint64 - // we already checked the first byte - x = uint64(buf[i]) - 0x80 - i++ - - b = uint64(buf[i]) - i++ - x += b << 7 - if b&0x80 == 0 { - goto done - } - x -= 0x80 << 7 - - b = uint64(buf[i]) - i++ - x += b << 14 - if b&0x80 == 0 { - goto done - } - x -= 0x80 << 14 - - b = uint64(buf[i]) - i++ - x += b << 21 - if b&0x80 == 0 { - goto done - } - x -= 0x80 << 21 - - b = uint64(buf[i]) - i++ - x += b << 28 - if b&0x80 == 0 { - goto done - } - x -= 0x80 << 28 - - b = uint64(buf[i]) - i++ - x += b << 35 - if b&0x80 == 0 { - goto done - } - x -= 0x80 << 35 - - b = uint64(buf[i]) - i++ - x += b << 42 - if b&0x80 == 0 { - goto done - } - x -= 0x80 << 42 - - b = uint64(buf[i]) - i++ - x += b << 49 - if b&0x80 == 0 { - goto done - } - x -= 0x80 << 49 - - b = uint64(buf[i]) - i++ - x += b << 56 - if b&0x80 == 0 { - goto done - } - x -= 0x80 << 56 - - b = uint64(buf[i]) - i++ - x += b << 63 - if b&0x80 == 0 { - goto done - } - // x -= 0x80 << 63 // Always zero. - - return 0, errOverflow - -done: - p.index = i - return x, nil -} - -// DecodeFixed64 reads a 64-bit integer from the Buffer. -// This is the format for the -// fixed64, sfixed64, and double protocol buffer types. -func (p *Buffer) DecodeFixed64() (x uint64, err error) { - // x, err already 0 - i := p.index + 8 - if i < 0 || i > len(p.buf) { - err = io.ErrUnexpectedEOF - return - } - p.index = i - - x = uint64(p.buf[i-8]) - x |= uint64(p.buf[i-7]) << 8 - x |= uint64(p.buf[i-6]) << 16 - x |= uint64(p.buf[i-5]) << 24 - x |= uint64(p.buf[i-4]) << 32 - x |= uint64(p.buf[i-3]) << 40 - x |= uint64(p.buf[i-2]) << 48 - x |= uint64(p.buf[i-1]) << 56 - return -} - -// DecodeFixed32 reads a 32-bit integer from the Buffer. -// This is the format for the -// fixed32, sfixed32, and float protocol buffer types. -func (p *Buffer) DecodeFixed32() (x uint64, err error) { - // x, err already 0 - i := p.index + 4 - if i < 0 || i > len(p.buf) { - err = io.ErrUnexpectedEOF - return - } - p.index = i - - x = uint64(p.buf[i-4]) - x |= uint64(p.buf[i-3]) << 8 - x |= uint64(p.buf[i-2]) << 16 - x |= uint64(p.buf[i-1]) << 24 - return -} - -// DecodeZigzag64 reads a zigzag-encoded 64-bit integer -// from the Buffer. -// This is the format used for the sint64 protocol buffer type. -func (p *Buffer) DecodeZigzag64() (x uint64, err error) { - x, err = p.DecodeVarint() - if err != nil { - return - } - x = (x >> 1) ^ uint64((int64(x&1)<<63)>>63) - return -} - -// DecodeZigzag32 reads a zigzag-encoded 32-bit integer -// from the Buffer. -// This is the format used for the sint32 protocol buffer type. -func (p *Buffer) DecodeZigzag32() (x uint64, err error) { - x, err = p.DecodeVarint() - if err != nil { - return - } - x = uint64((uint32(x) >> 1) ^ uint32((int32(x&1)<<31)>>31)) - return -} - -// These are not ValueDecoders: they produce an array of bytes or a string. -// bytes, embedded messages - -// DecodeRawBytes reads a count-delimited byte buffer from the Buffer. -// This is the format used for the bytes protocol buffer -// type and for embedded messages. -func (p *Buffer) DecodeRawBytes(alloc bool) (buf []byte, err error) { - n, err := p.DecodeVarint() - if err != nil { - return nil, err - } - - nb := int(n) - if nb < 0 { - return nil, fmt.Errorf("proto: bad byte length %d", nb) - } - end := p.index + nb - if end < p.index || end > len(p.buf) { - return nil, io.ErrUnexpectedEOF - } - - if !alloc { - // todo: check if can get more uses of alloc=false - buf = p.buf[p.index:end] - p.index += nb - return - } - - buf = make([]byte, nb) - copy(buf, p.buf[p.index:]) - p.index += nb - return -} - -// DecodeStringBytes reads an encoded string from the Buffer. -// This is the format used for the proto2 string type. -func (p *Buffer) DecodeStringBytes() (s string, err error) { - buf, err := p.DecodeRawBytes(false) - if err != nil { - return - } - return string(buf), nil -} - -// Skip the next item in the buffer. Its wire type is decoded and presented as an argument. -// If the protocol buffer has extensions, and the field matches, add it as an extension. -// Otherwise, if the XXX_unrecognized field exists, append the skipped data there. -func (o *Buffer) skipAndSave(t reflect.Type, tag, wire int, base structPointer, unrecField field) error { - oi := o.index - - err := o.skip(t, tag, wire) - if err != nil { - return err - } - - if !unrecField.IsValid() { - return nil - } - - ptr := structPointer_Bytes(base, unrecField) - - // Add the skipped field to struct field - obuf := o.buf - - o.buf = *ptr - o.EncodeVarint(uint64(tag<<3 | wire)) - *ptr = append(o.buf, obuf[oi:o.index]...) - - o.buf = obuf - - return nil -} - -// Skip the next item in the buffer. Its wire type is decoded and presented as an argument. -func (o *Buffer) skip(t reflect.Type, tag, wire int) error { - - var u uint64 - var err error - - switch wire { - case WireVarint: - _, err = o.DecodeVarint() - case WireFixed64: - _, err = o.DecodeFixed64() - case WireBytes: - _, err = o.DecodeRawBytes(false) - case WireFixed32: - _, err = o.DecodeFixed32() - case WireStartGroup: - for { - u, err = o.DecodeVarint() - if err != nil { - break - } - fwire := int(u & 0x7) - if fwire == WireEndGroup { - break - } - ftag := int(u >> 3) - err = o.skip(t, ftag, fwire) - if err != nil { - break - } - } - default: - err = fmt.Errorf("proto: can't skip unknown wire type %d for %s", wire, t) - } - return err -} - -// Unmarshaler is the interface representing objects that can -// unmarshal themselves. The method should reset the receiver before -// decoding starts. The argument points to data that may be -// overwritten, so implementations should not keep references to the -// buffer. -type Unmarshaler interface { - Unmarshal([]byte) error -} - -// Unmarshal parses the protocol buffer representation in buf and places the -// decoded result in pb. If the struct underlying pb does not match -// the data in buf, the results can be unpredictable. -// -// Unmarshal resets pb before starting to unmarshal, so any -// existing data in pb is always removed. Use UnmarshalMerge -// to preserve and append to existing data. -func Unmarshal(buf []byte, pb Message) error { - pb.Reset() - return UnmarshalMerge(buf, pb) -} - -// UnmarshalMerge parses the protocol buffer representation in buf and -// writes the decoded result to pb. If the struct underlying pb does not match -// the data in buf, the results can be unpredictable. -// -// UnmarshalMerge merges into existing data in pb. -// Most code should use Unmarshal instead. -func UnmarshalMerge(buf []byte, pb Message) error { - // If the object can unmarshal itself, let it. - if u, ok := pb.(Unmarshaler); ok { - return u.Unmarshal(buf) - } - return NewBuffer(buf).Unmarshal(pb) -} - -// DecodeMessage reads a count-delimited message from the Buffer. -func (p *Buffer) DecodeMessage(pb Message) error { - enc, err := p.DecodeRawBytes(false) - if err != nil { - return err - } - return NewBuffer(enc).Unmarshal(pb) -} - -// DecodeGroup reads a tag-delimited group from the Buffer. -func (p *Buffer) DecodeGroup(pb Message) error { - typ, base, err := getbase(pb) - if err != nil { - return err - } - return p.unmarshalType(typ.Elem(), GetProperties(typ.Elem()), true, base) -} - -// Unmarshal parses the protocol buffer representation in the -// Buffer and places the decoded result in pb. If the struct -// underlying pb does not match the data in the buffer, the results can be -// unpredictable. -// -// Unlike proto.Unmarshal, this does not reset pb before starting to unmarshal. -func (p *Buffer) Unmarshal(pb Message) error { - // If the object can unmarshal itself, let it. - if u, ok := pb.(Unmarshaler); ok { - err := u.Unmarshal(p.buf[p.index:]) - p.index = len(p.buf) - return err - } - - typ, base, err := getbase(pb) - if err != nil { - return err - } - - err = p.unmarshalType(typ.Elem(), GetProperties(typ.Elem()), false, base) - - if collectStats { - stats.Decode++ - } - - return err -} - -// unmarshalType does the work of unmarshaling a structure. -func (o *Buffer) unmarshalType(st reflect.Type, prop *StructProperties, is_group bool, base structPointer) error { - var state errorState - required, reqFields := prop.reqCount, uint64(0) - - var err error - for err == nil && o.index < len(o.buf) { - oi := o.index - var u uint64 - u, err = o.DecodeVarint() - if err != nil { - break - } - wire := int(u & 0x7) - if wire == WireEndGroup { - if is_group { - if required > 0 { - // Not enough information to determine the exact field. - // (See below.) - return &RequiredNotSetError{"{Unknown}"} - } - return nil // input is satisfied - } - return fmt.Errorf("proto: %s: wiretype end group for non-group", st) - } - tag := int(u >> 3) - if tag <= 0 { - return fmt.Errorf("proto: %s: illegal tag %d (wire type %d)", st, tag, wire) - } - fieldnum, ok := prop.decoderTags.get(tag) - if !ok { - // Maybe it's an extension? - if prop.extendable { - if e, _ := extendable(structPointer_Interface(base, st)); isExtensionField(e, int32(tag)) { - if err = o.skip(st, tag, wire); err == nil { - extmap := e.extensionsWrite() - ext := extmap[int32(tag)] // may be missing - ext.enc = append(ext.enc, o.buf[oi:o.index]...) - extmap[int32(tag)] = ext - } - continue - } - } - // Maybe it's a oneof? - if prop.oneofUnmarshaler != nil { - m := structPointer_Interface(base, st).(Message) - // First return value indicates whether tag is a oneof field. - ok, err = prop.oneofUnmarshaler(m, tag, wire, o) - if err == ErrInternalBadWireType { - // Map the error to something more descriptive. - // Do the formatting here to save generated code space. - err = fmt.Errorf("bad wiretype for oneof field in %T", m) - } - if ok { - continue - } - } - err = o.skipAndSave(st, tag, wire, base, prop.unrecField) - continue - } - p := prop.Prop[fieldnum] - - if p.dec == nil { - fmt.Fprintf(os.Stderr, "proto: no protobuf decoder for %s.%s\n", st, st.Field(fieldnum).Name) - continue - } - dec := p.dec - if wire != WireStartGroup && wire != p.WireType { - if wire == WireBytes && p.packedDec != nil { - // a packable field - dec = p.packedDec - } else { - err = fmt.Errorf("proto: bad wiretype for field %s.%s: got wiretype %d, want %d", st, st.Field(fieldnum).Name, wire, p.WireType) - continue - } - } - decErr := dec(o, p, base) - if decErr != nil && !state.shouldContinue(decErr, p) { - err = decErr - } - if err == nil && p.Required { - // Successfully decoded a required field. - if tag <= 64 { - // use bitmap for fields 1-64 to catch field reuse. - var mask uint64 = 1 << uint64(tag-1) - if reqFields&mask == 0 { - // new required field - reqFields |= mask - required-- - } - } else { - // This is imprecise. It can be fooled by a required field - // with a tag > 64 that is encoded twice; that's very rare. - // A fully correct implementation would require allocating - // a data structure, which we would like to avoid. - required-- - } - } - } - if err == nil { - if is_group { - return io.ErrUnexpectedEOF - } - if state.err != nil { - return state.err - } - if required > 0 { - // Not enough information to determine the exact field. If we use extra - // CPU, we could determine the field only if the missing required field - // has a tag <= 64 and we check reqFields. - return &RequiredNotSetError{"{Unknown}"} - } - } - return err -} - -// Individual type decoders -// For each, -// u is the decoded value, -// v is a pointer to the field (pointer) in the struct - -// Sizes of the pools to allocate inside the Buffer. -// The goal is modest amortization and allocation -// on at least 16-byte boundaries. -const ( - boolPoolSize = 16 - uint32PoolSize = 8 - uint64PoolSize = 4 -) - -// Decode a bool. -func (o *Buffer) dec_bool(p *Properties, base structPointer) error { - u, err := p.valDec(o) - if err != nil { - return err - } - if len(o.bools) == 0 { - o.bools = make([]bool, boolPoolSize) - } - o.bools[0] = u != 0 - *structPointer_Bool(base, p.field) = &o.bools[0] - o.bools = o.bools[1:] - return nil -} - -func (o *Buffer) dec_proto3_bool(p *Properties, base structPointer) error { - u, err := p.valDec(o) - if err != nil { - return err - } - *structPointer_BoolVal(base, p.field) = u != 0 - return nil -} - -// Decode an int32. -func (o *Buffer) dec_int32(p *Properties, base structPointer) error { - u, err := p.valDec(o) - if err != nil { - return err - } - word32_Set(structPointer_Word32(base, p.field), o, uint32(u)) - return nil -} - -func (o *Buffer) dec_proto3_int32(p *Properties, base structPointer) error { - u, err := p.valDec(o) - if err != nil { - return err - } - word32Val_Set(structPointer_Word32Val(base, p.field), uint32(u)) - return nil -} - -// Decode an int64. -func (o *Buffer) dec_int64(p *Properties, base structPointer) error { - u, err := p.valDec(o) - if err != nil { - return err - } - word64_Set(structPointer_Word64(base, p.field), o, u) - return nil -} - -func (o *Buffer) dec_proto3_int64(p *Properties, base structPointer) error { - u, err := p.valDec(o) - if err != nil { - return err - } - word64Val_Set(structPointer_Word64Val(base, p.field), o, u) - return nil -} - -// Decode a string. -func (o *Buffer) dec_string(p *Properties, base structPointer) error { - s, err := o.DecodeStringBytes() - if err != nil { - return err - } - *structPointer_String(base, p.field) = &s - return nil -} - -func (o *Buffer) dec_proto3_string(p *Properties, base structPointer) error { - s, err := o.DecodeStringBytes() - if err != nil { - return err - } - *structPointer_StringVal(base, p.field) = s - return nil -} - -// Decode a slice of bytes ([]byte). -func (o *Buffer) dec_slice_byte(p *Properties, base structPointer) error { - b, err := o.DecodeRawBytes(true) - if err != nil { - return err - } - *structPointer_Bytes(base, p.field) = b - return nil -} - -// Decode a slice of bools ([]bool). -func (o *Buffer) dec_slice_bool(p *Properties, base structPointer) error { - u, err := p.valDec(o) - if err != nil { - return err - } - v := structPointer_BoolSlice(base, p.field) - *v = append(*v, u != 0) - return nil -} - -// Decode a slice of bools ([]bool) in packed format. -func (o *Buffer) dec_slice_packed_bool(p *Properties, base structPointer) error { - v := structPointer_BoolSlice(base, p.field) - - nn, err := o.DecodeVarint() - if err != nil { - return err - } - nb := int(nn) // number of bytes of encoded bools - fin := o.index + nb - if fin < o.index { - return errOverflow - } - - y := *v - for o.index < fin { - u, err := p.valDec(o) - if err != nil { - return err - } - y = append(y, u != 0) - } - - *v = y - return nil -} - -// Decode a slice of int32s ([]int32). -func (o *Buffer) dec_slice_int32(p *Properties, base structPointer) error { - u, err := p.valDec(o) - if err != nil { - return err - } - structPointer_Word32Slice(base, p.field).Append(uint32(u)) - return nil -} - -// Decode a slice of int32s ([]int32) in packed format. -func (o *Buffer) dec_slice_packed_int32(p *Properties, base structPointer) error { - v := structPointer_Word32Slice(base, p.field) - - nn, err := o.DecodeVarint() - if err != nil { - return err - } - nb := int(nn) // number of bytes of encoded int32s - - fin := o.index + nb - if fin < o.index { - return errOverflow - } - for o.index < fin { - u, err := p.valDec(o) - if err != nil { - return err - } - v.Append(uint32(u)) - } - return nil -} - -// Decode a slice of int64s ([]int64). -func (o *Buffer) dec_slice_int64(p *Properties, base structPointer) error { - u, err := p.valDec(o) - if err != nil { - return err - } - - structPointer_Word64Slice(base, p.field).Append(u) - return nil -} - -// Decode a slice of int64s ([]int64) in packed format. -func (o *Buffer) dec_slice_packed_int64(p *Properties, base structPointer) error { - v := structPointer_Word64Slice(base, p.field) - - nn, err := o.DecodeVarint() - if err != nil { - return err - } - nb := int(nn) // number of bytes of encoded int64s - - fin := o.index + nb - if fin < o.index { - return errOverflow - } - for o.index < fin { - u, err := p.valDec(o) - if err != nil { - return err - } - v.Append(u) - } - return nil -} - -// Decode a slice of strings ([]string). -func (o *Buffer) dec_slice_string(p *Properties, base structPointer) error { - s, err := o.DecodeStringBytes() - if err != nil { - return err - } - v := structPointer_StringSlice(base, p.field) - *v = append(*v, s) - return nil -} - -// Decode a slice of slice of bytes ([][]byte). -func (o *Buffer) dec_slice_slice_byte(p *Properties, base structPointer) error { - b, err := o.DecodeRawBytes(true) - if err != nil { - return err - } - v := structPointer_BytesSlice(base, p.field) - *v = append(*v, b) - return nil -} - -// Decode a map field. -func (o *Buffer) dec_new_map(p *Properties, base structPointer) error { - raw, err := o.DecodeRawBytes(false) - if err != nil { - return err - } - oi := o.index // index at the end of this map entry - o.index -= len(raw) // move buffer back to start of map entry - - mptr := structPointer_NewAt(base, p.field, p.mtype) // *map[K]V - if mptr.Elem().IsNil() { - mptr.Elem().Set(reflect.MakeMap(mptr.Type().Elem())) - } - v := mptr.Elem() // map[K]V - - // Prepare addressable doubly-indirect placeholders for the key and value types. - // See enc_new_map for why. - keyptr := reflect.New(reflect.PtrTo(p.mtype.Key())).Elem() // addressable *K - keybase := toStructPointer(keyptr.Addr()) // **K - - var valbase structPointer - var valptr reflect.Value - switch p.mtype.Elem().Kind() { - case reflect.Slice: - // []byte - var dummy []byte - valptr = reflect.ValueOf(&dummy) // *[]byte - valbase = toStructPointer(valptr) // *[]byte - case reflect.Ptr: - // message; valptr is **Msg; need to allocate the intermediate pointer - valptr = reflect.New(reflect.PtrTo(p.mtype.Elem())).Elem() // addressable *V - valptr.Set(reflect.New(valptr.Type().Elem())) - valbase = toStructPointer(valptr) - default: - // everything else - valptr = reflect.New(reflect.PtrTo(p.mtype.Elem())).Elem() // addressable *V - valbase = toStructPointer(valptr.Addr()) // **V - } - - // Decode. - // This parses a restricted wire format, namely the encoding of a message - // with two fields. See enc_new_map for the format. - for o.index < oi { - // tagcode for key and value properties are always a single byte - // because they have tags 1 and 2. - tagcode := o.buf[o.index] - o.index++ - switch tagcode { - case p.mkeyprop.tagcode[0]: - if err := p.mkeyprop.dec(o, p.mkeyprop, keybase); err != nil { - return err - } - case p.mvalprop.tagcode[0]: - if err := p.mvalprop.dec(o, p.mvalprop, valbase); err != nil { - return err - } - default: - // TODO: Should we silently skip this instead? - return fmt.Errorf("proto: bad map data tag %d", raw[0]) - } - } - keyelem, valelem := keyptr.Elem(), valptr.Elem() - if !keyelem.IsValid() { - keyelem = reflect.Zero(p.mtype.Key()) - } - if !valelem.IsValid() { - valelem = reflect.Zero(p.mtype.Elem()) - } - - v.SetMapIndex(keyelem, valelem) - return nil -} - -// Decode a group. -func (o *Buffer) dec_struct_group(p *Properties, base structPointer) error { - bas := structPointer_GetStructPointer(base, p.field) - if structPointer_IsNil(bas) { - // allocate new nested message - bas = toStructPointer(reflect.New(p.stype)) - structPointer_SetStructPointer(base, p.field, bas) - } - return o.unmarshalType(p.stype, p.sprop, true, bas) -} - -// Decode an embedded message. -func (o *Buffer) dec_struct_message(p *Properties, base structPointer) (err error) { - raw, e := o.DecodeRawBytes(false) - if e != nil { - return e - } - - bas := structPointer_GetStructPointer(base, p.field) - if structPointer_IsNil(bas) { - // allocate new nested message - bas = toStructPointer(reflect.New(p.stype)) - structPointer_SetStructPointer(base, p.field, bas) - } - - // If the object can unmarshal itself, let it. - if p.isUnmarshaler { - iv := structPointer_Interface(bas, p.stype) - return iv.(Unmarshaler).Unmarshal(raw) - } - - obuf := o.buf - oi := o.index - o.buf = raw - o.index = 0 - - err = o.unmarshalType(p.stype, p.sprop, false, bas) - o.buf = obuf - o.index = oi - - return err -} - -// Decode a slice of embedded messages. -func (o *Buffer) dec_slice_struct_message(p *Properties, base structPointer) error { - return o.dec_slice_struct(p, false, base) -} - -// Decode a slice of embedded groups. -func (o *Buffer) dec_slice_struct_group(p *Properties, base structPointer) error { - return o.dec_slice_struct(p, true, base) -} - -// Decode a slice of structs ([]*struct). -func (o *Buffer) dec_slice_struct(p *Properties, is_group bool, base structPointer) error { - v := reflect.New(p.stype) - bas := toStructPointer(v) - structPointer_StructPointerSlice(base, p.field).Append(bas) - - if is_group { - err := o.unmarshalType(p.stype, p.sprop, is_group, bas) - return err - } - - raw, err := o.DecodeRawBytes(false) - if err != nil { - return err - } - - // If the object can unmarshal itself, let it. - if p.isUnmarshaler { - iv := v.Interface() - return iv.(Unmarshaler).Unmarshal(raw) - } - - obuf := o.buf - oi := o.index - o.buf = raw - o.index = 0 - - err = o.unmarshalType(p.stype, p.sprop, is_group, bas) - - o.buf = obuf - o.index = oi - - return err -} diff --git a/vendor/github.com/golang/protobuf/proto/encode.go b/vendor/github.com/golang/protobuf/proto/encode.go deleted file mode 100644 index 8b84d1b22d4..00000000000 --- a/vendor/github.com/golang/protobuf/proto/encode.go +++ /dev/null @@ -1,1362 +0,0 @@ -// Go support for Protocol Buffers - Google's data interchange format -// -// Copyright 2010 The Go Authors. All rights reserved. -// https://github.com/golang/protobuf -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package proto - -/* - * Routines for encoding data into the wire format for protocol buffers. - */ - -import ( - "errors" - "fmt" - "reflect" - "sort" -) - -// RequiredNotSetError is the error returned if Marshal is called with -// a protocol buffer struct whose required fields have not -// all been initialized. It is also the error returned if Unmarshal is -// called with an encoded protocol buffer that does not include all the -// required fields. -// -// When printed, RequiredNotSetError reports the first unset required field in a -// message. If the field cannot be precisely determined, it is reported as -// "{Unknown}". -type RequiredNotSetError struct { - field string -} - -func (e *RequiredNotSetError) Error() string { - return fmt.Sprintf("proto: required field %q not set", e.field) -} - -var ( - // errRepeatedHasNil is the error returned if Marshal is called with - // a struct with a repeated field containing a nil element. - errRepeatedHasNil = errors.New("proto: repeated field has nil element") - - // errOneofHasNil is the error returned if Marshal is called with - // a struct with a oneof field containing a nil element. - errOneofHasNil = errors.New("proto: oneof field has nil value") - - // ErrNil is the error returned if Marshal is called with nil. - ErrNil = errors.New("proto: Marshal called with nil") - - // ErrTooLarge is the error returned if Marshal is called with a - // message that encodes to >2GB. - ErrTooLarge = errors.New("proto: message encodes to over 2 GB") -) - -// The fundamental encoders that put bytes on the wire. -// Those that take integer types all accept uint64 and are -// therefore of type valueEncoder. - -const maxVarintBytes = 10 // maximum length of a varint - -// maxMarshalSize is the largest allowed size of an encoded protobuf, -// since C++ and Java use signed int32s for the size. -const maxMarshalSize = 1<<31 - 1 - -// EncodeVarint returns the varint encoding of x. -// This is the format for the -// int32, int64, uint32, uint64, bool, and enum -// protocol buffer types. -// Not used by the package itself, but helpful to clients -// wishing to use the same encoding. -func EncodeVarint(x uint64) []byte { - var buf [maxVarintBytes]byte - var n int - for n = 0; x > 127; n++ { - buf[n] = 0x80 | uint8(x&0x7F) - x >>= 7 - } - buf[n] = uint8(x) - n++ - return buf[0:n] -} - -// EncodeVarint writes a varint-encoded integer to the Buffer. -// This is the format for the -// int32, int64, uint32, uint64, bool, and enum -// protocol buffer types. -func (p *Buffer) EncodeVarint(x uint64) error { - for x >= 1<<7 { - p.buf = append(p.buf, uint8(x&0x7f|0x80)) - x >>= 7 - } - p.buf = append(p.buf, uint8(x)) - return nil -} - -// SizeVarint returns the varint encoding size of an integer. -func SizeVarint(x uint64) int { - return sizeVarint(x) -} - -func sizeVarint(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } - } - return n -} - -// EncodeFixed64 writes a 64-bit integer to the Buffer. -// This is the format for the -// fixed64, sfixed64, and double protocol buffer types. -func (p *Buffer) EncodeFixed64(x uint64) error { - p.buf = append(p.buf, - uint8(x), - uint8(x>>8), - uint8(x>>16), - uint8(x>>24), - uint8(x>>32), - uint8(x>>40), - uint8(x>>48), - uint8(x>>56)) - return nil -} - -func sizeFixed64(x uint64) int { - return 8 -} - -// EncodeFixed32 writes a 32-bit integer to the Buffer. -// This is the format for the -// fixed32, sfixed32, and float protocol buffer types. -func (p *Buffer) EncodeFixed32(x uint64) error { - p.buf = append(p.buf, - uint8(x), - uint8(x>>8), - uint8(x>>16), - uint8(x>>24)) - return nil -} - -func sizeFixed32(x uint64) int { - return 4 -} - -// EncodeZigzag64 writes a zigzag-encoded 64-bit integer -// to the Buffer. -// This is the format used for the sint64 protocol buffer type. -func (p *Buffer) EncodeZigzag64(x uint64) error { - // use signed number to get arithmetic right shift. - return p.EncodeVarint((x << 1) ^ uint64((int64(x) >> 63))) -} - -func sizeZigzag64(x uint64) int { - return sizeVarint((x << 1) ^ uint64((int64(x) >> 63))) -} - -// EncodeZigzag32 writes a zigzag-encoded 32-bit integer -// to the Buffer. -// This is the format used for the sint32 protocol buffer type. -func (p *Buffer) EncodeZigzag32(x uint64) error { - // use signed number to get arithmetic right shift. - return p.EncodeVarint(uint64((uint32(x) << 1) ^ uint32((int32(x) >> 31)))) -} - -func sizeZigzag32(x uint64) int { - return sizeVarint(uint64((uint32(x) << 1) ^ uint32((int32(x) >> 31)))) -} - -// EncodeRawBytes writes a count-delimited byte buffer to the Buffer. -// This is the format used for the bytes protocol buffer -// type and for embedded messages. -func (p *Buffer) EncodeRawBytes(b []byte) error { - p.EncodeVarint(uint64(len(b))) - p.buf = append(p.buf, b...) - return nil -} - -func sizeRawBytes(b []byte) int { - return sizeVarint(uint64(len(b))) + - len(b) -} - -// EncodeStringBytes writes an encoded string to the Buffer. -// This is the format used for the proto2 string type. -func (p *Buffer) EncodeStringBytes(s string) error { - p.EncodeVarint(uint64(len(s))) - p.buf = append(p.buf, s...) - return nil -} - -func sizeStringBytes(s string) int { - return sizeVarint(uint64(len(s))) + - len(s) -} - -// Marshaler is the interface representing objects that can marshal themselves. -type Marshaler interface { - Marshal() ([]byte, error) -} - -// Marshal takes the protocol buffer -// and encodes it into the wire format, returning the data. -func Marshal(pb Message) ([]byte, error) { - // Can the object marshal itself? - if m, ok := pb.(Marshaler); ok { - return m.Marshal() - } - p := NewBuffer(nil) - err := p.Marshal(pb) - if p.buf == nil && err == nil { - // Return a non-nil slice on success. - return []byte{}, nil - } - return p.buf, err -} - -// EncodeMessage writes the protocol buffer to the Buffer, -// prefixed by a varint-encoded length. -func (p *Buffer) EncodeMessage(pb Message) error { - t, base, err := getbase(pb) - if structPointer_IsNil(base) { - return ErrNil - } - if err == nil { - var state errorState - err = p.enc_len_struct(GetProperties(t.Elem()), base, &state) - } - return err -} - -// Marshal takes the protocol buffer -// and encodes it into the wire format, writing the result to the -// Buffer. -func (p *Buffer) Marshal(pb Message) error { - // Can the object marshal itself? - if m, ok := pb.(Marshaler); ok { - data, err := m.Marshal() - p.buf = append(p.buf, data...) - return err - } - - t, base, err := getbase(pb) - if structPointer_IsNil(base) { - return ErrNil - } - if err == nil { - err = p.enc_struct(GetProperties(t.Elem()), base) - } - - if collectStats { - (stats).Encode++ // Parens are to work around a goimports bug. - } - - if len(p.buf) > maxMarshalSize { - return ErrTooLarge - } - return err -} - -// Size returns the encoded size of a protocol buffer. -func Size(pb Message) (n int) { - // Can the object marshal itself? If so, Size is slow. - // TODO: add Size to Marshaler, or add a Sizer interface. - if m, ok := pb.(Marshaler); ok { - b, _ := m.Marshal() - return len(b) - } - - t, base, err := getbase(pb) - if structPointer_IsNil(base) { - return 0 - } - if err == nil { - n = size_struct(GetProperties(t.Elem()), base) - } - - if collectStats { - (stats).Size++ // Parens are to work around a goimports bug. - } - - return -} - -// Individual type encoders. - -// Encode a bool. -func (o *Buffer) enc_bool(p *Properties, base structPointer) error { - v := *structPointer_Bool(base, p.field) - if v == nil { - return ErrNil - } - x := 0 - if *v { - x = 1 - } - o.buf = append(o.buf, p.tagcode...) - p.valEnc(o, uint64(x)) - return nil -} - -func (o *Buffer) enc_proto3_bool(p *Properties, base structPointer) error { - v := *structPointer_BoolVal(base, p.field) - if !v { - return ErrNil - } - o.buf = append(o.buf, p.tagcode...) - p.valEnc(o, 1) - return nil -} - -func size_bool(p *Properties, base structPointer) int { - v := *structPointer_Bool(base, p.field) - if v == nil { - return 0 - } - return len(p.tagcode) + 1 // each bool takes exactly one byte -} - -func size_proto3_bool(p *Properties, base structPointer) int { - v := *structPointer_BoolVal(base, p.field) - if !v && !p.oneof { - return 0 - } - return len(p.tagcode) + 1 // each bool takes exactly one byte -} - -// Encode an int32. -func (o *Buffer) enc_int32(p *Properties, base structPointer) error { - v := structPointer_Word32(base, p.field) - if word32_IsNil(v) { - return ErrNil - } - x := int32(word32_Get(v)) // permit sign extension to use full 64-bit range - o.buf = append(o.buf, p.tagcode...) - p.valEnc(o, uint64(x)) - return nil -} - -func (o *Buffer) enc_proto3_int32(p *Properties, base structPointer) error { - v := structPointer_Word32Val(base, p.field) - x := int32(word32Val_Get(v)) // permit sign extension to use full 64-bit range - if x == 0 { - return ErrNil - } - o.buf = append(o.buf, p.tagcode...) - p.valEnc(o, uint64(x)) - return nil -} - -func size_int32(p *Properties, base structPointer) (n int) { - v := structPointer_Word32(base, p.field) - if word32_IsNil(v) { - return 0 - } - x := int32(word32_Get(v)) // permit sign extension to use full 64-bit range - n += len(p.tagcode) - n += p.valSize(uint64(x)) - return -} - -func size_proto3_int32(p *Properties, base structPointer) (n int) { - v := structPointer_Word32Val(base, p.field) - x := int32(word32Val_Get(v)) // permit sign extension to use full 64-bit range - if x == 0 && !p.oneof { - return 0 - } - n += len(p.tagcode) - n += p.valSize(uint64(x)) - return -} - -// Encode a uint32. -// Exactly the same as int32, except for no sign extension. -func (o *Buffer) enc_uint32(p *Properties, base structPointer) error { - v := structPointer_Word32(base, p.field) - if word32_IsNil(v) { - return ErrNil - } - x := word32_Get(v) - o.buf = append(o.buf, p.tagcode...) - p.valEnc(o, uint64(x)) - return nil -} - -func (o *Buffer) enc_proto3_uint32(p *Properties, base structPointer) error { - v := structPointer_Word32Val(base, p.field) - x := word32Val_Get(v) - if x == 0 { - return ErrNil - } - o.buf = append(o.buf, p.tagcode...) - p.valEnc(o, uint64(x)) - return nil -} - -func size_uint32(p *Properties, base structPointer) (n int) { - v := structPointer_Word32(base, p.field) - if word32_IsNil(v) { - return 0 - } - x := word32_Get(v) - n += len(p.tagcode) - n += p.valSize(uint64(x)) - return -} - -func size_proto3_uint32(p *Properties, base structPointer) (n int) { - v := structPointer_Word32Val(base, p.field) - x := word32Val_Get(v) - if x == 0 && !p.oneof { - return 0 - } - n += len(p.tagcode) - n += p.valSize(uint64(x)) - return -} - -// Encode an int64. -func (o *Buffer) enc_int64(p *Properties, base structPointer) error { - v := structPointer_Word64(base, p.field) - if word64_IsNil(v) { - return ErrNil - } - x := word64_Get(v) - o.buf = append(o.buf, p.tagcode...) - p.valEnc(o, x) - return nil -} - -func (o *Buffer) enc_proto3_int64(p *Properties, base structPointer) error { - v := structPointer_Word64Val(base, p.field) - x := word64Val_Get(v) - if x == 0 { - return ErrNil - } - o.buf = append(o.buf, p.tagcode...) - p.valEnc(o, x) - return nil -} - -func size_int64(p *Properties, base structPointer) (n int) { - v := structPointer_Word64(base, p.field) - if word64_IsNil(v) { - return 0 - } - x := word64_Get(v) - n += len(p.tagcode) - n += p.valSize(x) - return -} - -func size_proto3_int64(p *Properties, base structPointer) (n int) { - v := structPointer_Word64Val(base, p.field) - x := word64Val_Get(v) - if x == 0 && !p.oneof { - return 0 - } - n += len(p.tagcode) - n += p.valSize(x) - return -} - -// Encode a string. -func (o *Buffer) enc_string(p *Properties, base structPointer) error { - v := *structPointer_String(base, p.field) - if v == nil { - return ErrNil - } - x := *v - o.buf = append(o.buf, p.tagcode...) - o.EncodeStringBytes(x) - return nil -} - -func (o *Buffer) enc_proto3_string(p *Properties, base structPointer) error { - v := *structPointer_StringVal(base, p.field) - if v == "" { - return ErrNil - } - o.buf = append(o.buf, p.tagcode...) - o.EncodeStringBytes(v) - return nil -} - -func size_string(p *Properties, base structPointer) (n int) { - v := *structPointer_String(base, p.field) - if v == nil { - return 0 - } - x := *v - n += len(p.tagcode) - n += sizeStringBytes(x) - return -} - -func size_proto3_string(p *Properties, base structPointer) (n int) { - v := *structPointer_StringVal(base, p.field) - if v == "" && !p.oneof { - return 0 - } - n += len(p.tagcode) - n += sizeStringBytes(v) - return -} - -// All protocol buffer fields are nillable, but be careful. -func isNil(v reflect.Value) bool { - switch v.Kind() { - case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: - return v.IsNil() - } - return false -} - -// Encode a message struct. -func (o *Buffer) enc_struct_message(p *Properties, base structPointer) error { - var state errorState - structp := structPointer_GetStructPointer(base, p.field) - if structPointer_IsNil(structp) { - return ErrNil - } - - // Can the object marshal itself? - if p.isMarshaler { - m := structPointer_Interface(structp, p.stype).(Marshaler) - data, err := m.Marshal() - if err != nil && !state.shouldContinue(err, nil) { - return err - } - o.buf = append(o.buf, p.tagcode...) - o.EncodeRawBytes(data) - return state.err - } - - o.buf = append(o.buf, p.tagcode...) - return o.enc_len_struct(p.sprop, structp, &state) -} - -func size_struct_message(p *Properties, base structPointer) int { - structp := structPointer_GetStructPointer(base, p.field) - if structPointer_IsNil(structp) { - return 0 - } - - // Can the object marshal itself? - if p.isMarshaler { - m := structPointer_Interface(structp, p.stype).(Marshaler) - data, _ := m.Marshal() - n0 := len(p.tagcode) - n1 := sizeRawBytes(data) - return n0 + n1 - } - - n0 := len(p.tagcode) - n1 := size_struct(p.sprop, structp) - n2 := sizeVarint(uint64(n1)) // size of encoded length - return n0 + n1 + n2 -} - -// Encode a group struct. -func (o *Buffer) enc_struct_group(p *Properties, base structPointer) error { - var state errorState - b := structPointer_GetStructPointer(base, p.field) - if structPointer_IsNil(b) { - return ErrNil - } - - o.EncodeVarint(uint64((p.Tag << 3) | WireStartGroup)) - err := o.enc_struct(p.sprop, b) - if err != nil && !state.shouldContinue(err, nil) { - return err - } - o.EncodeVarint(uint64((p.Tag << 3) | WireEndGroup)) - return state.err -} - -func size_struct_group(p *Properties, base structPointer) (n int) { - b := structPointer_GetStructPointer(base, p.field) - if structPointer_IsNil(b) { - return 0 - } - - n += sizeVarint(uint64((p.Tag << 3) | WireStartGroup)) - n += size_struct(p.sprop, b) - n += sizeVarint(uint64((p.Tag << 3) | WireEndGroup)) - return -} - -// Encode a slice of bools ([]bool). -func (o *Buffer) enc_slice_bool(p *Properties, base structPointer) error { - s := *structPointer_BoolSlice(base, p.field) - l := len(s) - if l == 0 { - return ErrNil - } - for _, x := range s { - o.buf = append(o.buf, p.tagcode...) - v := uint64(0) - if x { - v = 1 - } - p.valEnc(o, v) - } - return nil -} - -func size_slice_bool(p *Properties, base structPointer) int { - s := *structPointer_BoolSlice(base, p.field) - l := len(s) - if l == 0 { - return 0 - } - return l * (len(p.tagcode) + 1) // each bool takes exactly one byte -} - -// Encode a slice of bools ([]bool) in packed format. -func (o *Buffer) enc_slice_packed_bool(p *Properties, base structPointer) error { - s := *structPointer_BoolSlice(base, p.field) - l := len(s) - if l == 0 { - return ErrNil - } - o.buf = append(o.buf, p.tagcode...) - o.EncodeVarint(uint64(l)) // each bool takes exactly one byte - for _, x := range s { - v := uint64(0) - if x { - v = 1 - } - p.valEnc(o, v) - } - return nil -} - -func size_slice_packed_bool(p *Properties, base structPointer) (n int) { - s := *structPointer_BoolSlice(base, p.field) - l := len(s) - if l == 0 { - return 0 - } - n += len(p.tagcode) - n += sizeVarint(uint64(l)) - n += l // each bool takes exactly one byte - return -} - -// Encode a slice of bytes ([]byte). -func (o *Buffer) enc_slice_byte(p *Properties, base structPointer) error { - s := *structPointer_Bytes(base, p.field) - if s == nil { - return ErrNil - } - o.buf = append(o.buf, p.tagcode...) - o.EncodeRawBytes(s) - return nil -} - -func (o *Buffer) enc_proto3_slice_byte(p *Properties, base structPointer) error { - s := *structPointer_Bytes(base, p.field) - if len(s) == 0 { - return ErrNil - } - o.buf = append(o.buf, p.tagcode...) - o.EncodeRawBytes(s) - return nil -} - -func size_slice_byte(p *Properties, base structPointer) (n int) { - s := *structPointer_Bytes(base, p.field) - if s == nil && !p.oneof { - return 0 - } - n += len(p.tagcode) - n += sizeRawBytes(s) - return -} - -func size_proto3_slice_byte(p *Properties, base structPointer) (n int) { - s := *structPointer_Bytes(base, p.field) - if len(s) == 0 && !p.oneof { - return 0 - } - n += len(p.tagcode) - n += sizeRawBytes(s) - return -} - -// Encode a slice of int32s ([]int32). -func (o *Buffer) enc_slice_int32(p *Properties, base structPointer) error { - s := structPointer_Word32Slice(base, p.field) - l := s.Len() - if l == 0 { - return ErrNil - } - for i := 0; i < l; i++ { - o.buf = append(o.buf, p.tagcode...) - x := int32(s.Index(i)) // permit sign extension to use full 64-bit range - p.valEnc(o, uint64(x)) - } - return nil -} - -func size_slice_int32(p *Properties, base structPointer) (n int) { - s := structPointer_Word32Slice(base, p.field) - l := s.Len() - if l == 0 { - return 0 - } - for i := 0; i < l; i++ { - n += len(p.tagcode) - x := int32(s.Index(i)) // permit sign extension to use full 64-bit range - n += p.valSize(uint64(x)) - } - return -} - -// Encode a slice of int32s ([]int32) in packed format. -func (o *Buffer) enc_slice_packed_int32(p *Properties, base structPointer) error { - s := structPointer_Word32Slice(base, p.field) - l := s.Len() - if l == 0 { - return ErrNil - } - // TODO: Reuse a Buffer. - buf := NewBuffer(nil) - for i := 0; i < l; i++ { - x := int32(s.Index(i)) // permit sign extension to use full 64-bit range - p.valEnc(buf, uint64(x)) - } - - o.buf = append(o.buf, p.tagcode...) - o.EncodeVarint(uint64(len(buf.buf))) - o.buf = append(o.buf, buf.buf...) - return nil -} - -func size_slice_packed_int32(p *Properties, base structPointer) (n int) { - s := structPointer_Word32Slice(base, p.field) - l := s.Len() - if l == 0 { - return 0 - } - var bufSize int - for i := 0; i < l; i++ { - x := int32(s.Index(i)) // permit sign extension to use full 64-bit range - bufSize += p.valSize(uint64(x)) - } - - n += len(p.tagcode) - n += sizeVarint(uint64(bufSize)) - n += bufSize - return -} - -// Encode a slice of uint32s ([]uint32). -// Exactly the same as int32, except for no sign extension. -func (o *Buffer) enc_slice_uint32(p *Properties, base structPointer) error { - s := structPointer_Word32Slice(base, p.field) - l := s.Len() - if l == 0 { - return ErrNil - } - for i := 0; i < l; i++ { - o.buf = append(o.buf, p.tagcode...) - x := s.Index(i) - p.valEnc(o, uint64(x)) - } - return nil -} - -func size_slice_uint32(p *Properties, base structPointer) (n int) { - s := structPointer_Word32Slice(base, p.field) - l := s.Len() - if l == 0 { - return 0 - } - for i := 0; i < l; i++ { - n += len(p.tagcode) - x := s.Index(i) - n += p.valSize(uint64(x)) - } - return -} - -// Encode a slice of uint32s ([]uint32) in packed format. -// Exactly the same as int32, except for no sign extension. -func (o *Buffer) enc_slice_packed_uint32(p *Properties, base structPointer) error { - s := structPointer_Word32Slice(base, p.field) - l := s.Len() - if l == 0 { - return ErrNil - } - // TODO: Reuse a Buffer. - buf := NewBuffer(nil) - for i := 0; i < l; i++ { - p.valEnc(buf, uint64(s.Index(i))) - } - - o.buf = append(o.buf, p.tagcode...) - o.EncodeVarint(uint64(len(buf.buf))) - o.buf = append(o.buf, buf.buf...) - return nil -} - -func size_slice_packed_uint32(p *Properties, base structPointer) (n int) { - s := structPointer_Word32Slice(base, p.field) - l := s.Len() - if l == 0 { - return 0 - } - var bufSize int - for i := 0; i < l; i++ { - bufSize += p.valSize(uint64(s.Index(i))) - } - - n += len(p.tagcode) - n += sizeVarint(uint64(bufSize)) - n += bufSize - return -} - -// Encode a slice of int64s ([]int64). -func (o *Buffer) enc_slice_int64(p *Properties, base structPointer) error { - s := structPointer_Word64Slice(base, p.field) - l := s.Len() - if l == 0 { - return ErrNil - } - for i := 0; i < l; i++ { - o.buf = append(o.buf, p.tagcode...) - p.valEnc(o, s.Index(i)) - } - return nil -} - -func size_slice_int64(p *Properties, base structPointer) (n int) { - s := structPointer_Word64Slice(base, p.field) - l := s.Len() - if l == 0 { - return 0 - } - for i := 0; i < l; i++ { - n += len(p.tagcode) - n += p.valSize(s.Index(i)) - } - return -} - -// Encode a slice of int64s ([]int64) in packed format. -func (o *Buffer) enc_slice_packed_int64(p *Properties, base structPointer) error { - s := structPointer_Word64Slice(base, p.field) - l := s.Len() - if l == 0 { - return ErrNil - } - // TODO: Reuse a Buffer. - buf := NewBuffer(nil) - for i := 0; i < l; i++ { - p.valEnc(buf, s.Index(i)) - } - - o.buf = append(o.buf, p.tagcode...) - o.EncodeVarint(uint64(len(buf.buf))) - o.buf = append(o.buf, buf.buf...) - return nil -} - -func size_slice_packed_int64(p *Properties, base structPointer) (n int) { - s := structPointer_Word64Slice(base, p.field) - l := s.Len() - if l == 0 { - return 0 - } - var bufSize int - for i := 0; i < l; i++ { - bufSize += p.valSize(s.Index(i)) - } - - n += len(p.tagcode) - n += sizeVarint(uint64(bufSize)) - n += bufSize - return -} - -// Encode a slice of slice of bytes ([][]byte). -func (o *Buffer) enc_slice_slice_byte(p *Properties, base structPointer) error { - ss := *structPointer_BytesSlice(base, p.field) - l := len(ss) - if l == 0 { - return ErrNil - } - for i := 0; i < l; i++ { - o.buf = append(o.buf, p.tagcode...) - o.EncodeRawBytes(ss[i]) - } - return nil -} - -func size_slice_slice_byte(p *Properties, base structPointer) (n int) { - ss := *structPointer_BytesSlice(base, p.field) - l := len(ss) - if l == 0 { - return 0 - } - n += l * len(p.tagcode) - for i := 0; i < l; i++ { - n += sizeRawBytes(ss[i]) - } - return -} - -// Encode a slice of strings ([]string). -func (o *Buffer) enc_slice_string(p *Properties, base structPointer) error { - ss := *structPointer_StringSlice(base, p.field) - l := len(ss) - for i := 0; i < l; i++ { - o.buf = append(o.buf, p.tagcode...) - o.EncodeStringBytes(ss[i]) - } - return nil -} - -func size_slice_string(p *Properties, base structPointer) (n int) { - ss := *structPointer_StringSlice(base, p.field) - l := len(ss) - n += l * len(p.tagcode) - for i := 0; i < l; i++ { - n += sizeStringBytes(ss[i]) - } - return -} - -// Encode a slice of message structs ([]*struct). -func (o *Buffer) enc_slice_struct_message(p *Properties, base structPointer) error { - var state errorState - s := structPointer_StructPointerSlice(base, p.field) - l := s.Len() - - for i := 0; i < l; i++ { - structp := s.Index(i) - if structPointer_IsNil(structp) { - return errRepeatedHasNil - } - - // Can the object marshal itself? - if p.isMarshaler { - m := structPointer_Interface(structp, p.stype).(Marshaler) - data, err := m.Marshal() - if err != nil && !state.shouldContinue(err, nil) { - return err - } - o.buf = append(o.buf, p.tagcode...) - o.EncodeRawBytes(data) - continue - } - - o.buf = append(o.buf, p.tagcode...) - err := o.enc_len_struct(p.sprop, structp, &state) - if err != nil && !state.shouldContinue(err, nil) { - if err == ErrNil { - return errRepeatedHasNil - } - return err - } - } - return state.err -} - -func size_slice_struct_message(p *Properties, base structPointer) (n int) { - s := structPointer_StructPointerSlice(base, p.field) - l := s.Len() - n += l * len(p.tagcode) - for i := 0; i < l; i++ { - structp := s.Index(i) - if structPointer_IsNil(structp) { - return // return the size up to this point - } - - // Can the object marshal itself? - if p.isMarshaler { - m := structPointer_Interface(structp, p.stype).(Marshaler) - data, _ := m.Marshal() - n += sizeRawBytes(data) - continue - } - - n0 := size_struct(p.sprop, structp) - n1 := sizeVarint(uint64(n0)) // size of encoded length - n += n0 + n1 - } - return -} - -// Encode a slice of group structs ([]*struct). -func (o *Buffer) enc_slice_struct_group(p *Properties, base structPointer) error { - var state errorState - s := structPointer_StructPointerSlice(base, p.field) - l := s.Len() - - for i := 0; i < l; i++ { - b := s.Index(i) - if structPointer_IsNil(b) { - return errRepeatedHasNil - } - - o.EncodeVarint(uint64((p.Tag << 3) | WireStartGroup)) - - err := o.enc_struct(p.sprop, b) - - if err != nil && !state.shouldContinue(err, nil) { - if err == ErrNil { - return errRepeatedHasNil - } - return err - } - - o.EncodeVarint(uint64((p.Tag << 3) | WireEndGroup)) - } - return state.err -} - -func size_slice_struct_group(p *Properties, base structPointer) (n int) { - s := structPointer_StructPointerSlice(base, p.field) - l := s.Len() - - n += l * sizeVarint(uint64((p.Tag<<3)|WireStartGroup)) - n += l * sizeVarint(uint64((p.Tag<<3)|WireEndGroup)) - for i := 0; i < l; i++ { - b := s.Index(i) - if structPointer_IsNil(b) { - return // return size up to this point - } - - n += size_struct(p.sprop, b) - } - return -} - -// Encode an extension map. -func (o *Buffer) enc_map(p *Properties, base structPointer) error { - exts := structPointer_ExtMap(base, p.field) - if err := encodeExtensionsMap(*exts); err != nil { - return err - } - - return o.enc_map_body(*exts) -} - -func (o *Buffer) enc_exts(p *Properties, base structPointer) error { - exts := structPointer_Extensions(base, p.field) - - v, mu := exts.extensionsRead() - if v == nil { - return nil - } - - mu.Lock() - defer mu.Unlock() - if err := encodeExtensionsMap(v); err != nil { - return err - } - - return o.enc_map_body(v) -} - -func (o *Buffer) enc_map_body(v map[int32]Extension) error { - // Fast-path for common cases: zero or one extensions. - if len(v) <= 1 { - for _, e := range v { - o.buf = append(o.buf, e.enc...) - } - return nil - } - - // Sort keys to provide a deterministic encoding. - keys := make([]int, 0, len(v)) - for k := range v { - keys = append(keys, int(k)) - } - sort.Ints(keys) - - for _, k := range keys { - o.buf = append(o.buf, v[int32(k)].enc...) - } - return nil -} - -func size_map(p *Properties, base structPointer) int { - v := structPointer_ExtMap(base, p.field) - return extensionsMapSize(*v) -} - -func size_exts(p *Properties, base structPointer) int { - v := structPointer_Extensions(base, p.field) - return extensionsSize(v) -} - -// Encode a map field. -func (o *Buffer) enc_new_map(p *Properties, base structPointer) error { - var state errorState // XXX: or do we need to plumb this through? - - /* - A map defined as - map map_field = N; - is encoded in the same way as - message MapFieldEntry { - key_type key = 1; - value_type value = 2; - } - repeated MapFieldEntry map_field = N; - */ - - v := structPointer_NewAt(base, p.field, p.mtype).Elem() // map[K]V - if v.Len() == 0 { - return nil - } - - keycopy, valcopy, keybase, valbase := mapEncodeScratch(p.mtype) - - enc := func() error { - if err := p.mkeyprop.enc(o, p.mkeyprop, keybase); err != nil { - return err - } - if err := p.mvalprop.enc(o, p.mvalprop, valbase); err != nil && err != ErrNil { - return err - } - return nil - } - - // Don't sort map keys. It is not required by the spec, and C++ doesn't do it. - for _, key := range v.MapKeys() { - val := v.MapIndex(key) - - keycopy.Set(key) - valcopy.Set(val) - - o.buf = append(o.buf, p.tagcode...) - if err := o.enc_len_thing(enc, &state); err != nil { - return err - } - } - return nil -} - -func size_new_map(p *Properties, base structPointer) int { - v := structPointer_NewAt(base, p.field, p.mtype).Elem() // map[K]V - - keycopy, valcopy, keybase, valbase := mapEncodeScratch(p.mtype) - - n := 0 - for _, key := range v.MapKeys() { - val := v.MapIndex(key) - keycopy.Set(key) - valcopy.Set(val) - - // Tag codes for key and val are the responsibility of the sub-sizer. - keysize := p.mkeyprop.size(p.mkeyprop, keybase) - valsize := p.mvalprop.size(p.mvalprop, valbase) - entry := keysize + valsize - // Add on tag code and length of map entry itself. - n += len(p.tagcode) + sizeVarint(uint64(entry)) + entry - } - return n -} - -// mapEncodeScratch returns a new reflect.Value matching the map's value type, -// and a structPointer suitable for passing to an encoder or sizer. -func mapEncodeScratch(mapType reflect.Type) (keycopy, valcopy reflect.Value, keybase, valbase structPointer) { - // Prepare addressable doubly-indirect placeholders for the key and value types. - // This is needed because the element-type encoders expect **T, but the map iteration produces T. - - keycopy = reflect.New(mapType.Key()).Elem() // addressable K - keyptr := reflect.New(reflect.PtrTo(keycopy.Type())).Elem() // addressable *K - keyptr.Set(keycopy.Addr()) // - keybase = toStructPointer(keyptr.Addr()) // **K - - // Value types are more varied and require special handling. - switch mapType.Elem().Kind() { - case reflect.Slice: - // []byte - var dummy []byte - valcopy = reflect.ValueOf(&dummy).Elem() // addressable []byte - valbase = toStructPointer(valcopy.Addr()) - case reflect.Ptr: - // message; the generated field type is map[K]*Msg (so V is *Msg), - // so we only need one level of indirection. - valcopy = reflect.New(mapType.Elem()).Elem() // addressable V - valbase = toStructPointer(valcopy.Addr()) - default: - // everything else - valcopy = reflect.New(mapType.Elem()).Elem() // addressable V - valptr := reflect.New(reflect.PtrTo(valcopy.Type())).Elem() // addressable *V - valptr.Set(valcopy.Addr()) // - valbase = toStructPointer(valptr.Addr()) // **V - } - return -} - -// Encode a struct. -func (o *Buffer) enc_struct(prop *StructProperties, base structPointer) error { - var state errorState - // Encode fields in tag order so that decoders may use optimizations - // that depend on the ordering. - // https://developers.google.com/protocol-buffers/docs/encoding#order - for _, i := range prop.order { - p := prop.Prop[i] - if p.enc != nil { - err := p.enc(o, p, base) - if err != nil { - if err == ErrNil { - if p.Required && state.err == nil { - state.err = &RequiredNotSetError{p.Name} - } - } else if err == errRepeatedHasNil { - // Give more context to nil values in repeated fields. - return errors.New("repeated field " + p.OrigName + " has nil element") - } else if !state.shouldContinue(err, p) { - return err - } - } - if len(o.buf) > maxMarshalSize { - return ErrTooLarge - } - } - } - - // Do oneof fields. - if prop.oneofMarshaler != nil { - m := structPointer_Interface(base, prop.stype).(Message) - if err := prop.oneofMarshaler(m, o); err == ErrNil { - return errOneofHasNil - } else if err != nil { - return err - } - } - - // Add unrecognized fields at the end. - if prop.unrecField.IsValid() { - v := *structPointer_Bytes(base, prop.unrecField) - if len(o.buf)+len(v) > maxMarshalSize { - return ErrTooLarge - } - if len(v) > 0 { - o.buf = append(o.buf, v...) - } - } - - return state.err -} - -func size_struct(prop *StructProperties, base structPointer) (n int) { - for _, i := range prop.order { - p := prop.Prop[i] - if p.size != nil { - n += p.size(p, base) - } - } - - // Add unrecognized fields at the end. - if prop.unrecField.IsValid() { - v := *structPointer_Bytes(base, prop.unrecField) - n += len(v) - } - - // Factor in any oneof fields. - if prop.oneofSizer != nil { - m := structPointer_Interface(base, prop.stype).(Message) - n += prop.oneofSizer(m) - } - - return -} - -var zeroes [20]byte // longer than any conceivable sizeVarint - -// Encode a struct, preceded by its encoded length (as a varint). -func (o *Buffer) enc_len_struct(prop *StructProperties, base structPointer, state *errorState) error { - return o.enc_len_thing(func() error { return o.enc_struct(prop, base) }, state) -} - -// Encode something, preceded by its encoded length (as a varint). -func (o *Buffer) enc_len_thing(enc func() error, state *errorState) error { - iLen := len(o.buf) - o.buf = append(o.buf, 0, 0, 0, 0) // reserve four bytes for length - iMsg := len(o.buf) - err := enc() - if err != nil && !state.shouldContinue(err, nil) { - return err - } - lMsg := len(o.buf) - iMsg - lLen := sizeVarint(uint64(lMsg)) - switch x := lLen - (iMsg - iLen); { - case x > 0: // actual length is x bytes larger than the space we reserved - // Move msg x bytes right. - o.buf = append(o.buf, zeroes[:x]...) - copy(o.buf[iMsg+x:], o.buf[iMsg:iMsg+lMsg]) - case x < 0: // actual length is x bytes smaller than the space we reserved - // Move msg x bytes left. - copy(o.buf[iMsg+x:], o.buf[iMsg:iMsg+lMsg]) - o.buf = o.buf[:len(o.buf)+x] // x is negative - } - // Encode the length in the reserved space. - o.buf = o.buf[:iLen] - o.EncodeVarint(uint64(lMsg)) - o.buf = o.buf[:len(o.buf)+lMsg] - return state.err -} - -// errorState maintains the first error that occurs and updates that error -// with additional context. -type errorState struct { - err error -} - -// shouldContinue reports whether encoding should continue upon encountering the -// given error. If the error is RequiredNotSetError, shouldContinue returns true -// and, if this is the first appearance of that error, remembers it for future -// reporting. -// -// If prop is not nil, it may update any error with additional context about the -// field with the error. -func (s *errorState) shouldContinue(err error, prop *Properties) bool { - // Ignore unset required fields. - reqNotSet, ok := err.(*RequiredNotSetError) - if !ok { - return false - } - if s.err == nil { - if prop != nil { - err = &RequiredNotSetError{prop.Name + "." + reqNotSet.field} - } - s.err = err - } - return true -} diff --git a/vendor/github.com/golang/protobuf/proto/equal.go b/vendor/github.com/golang/protobuf/proto/equal.go deleted file mode 100644 index 2ed1cf59666..00000000000 --- a/vendor/github.com/golang/protobuf/proto/equal.go +++ /dev/null @@ -1,300 +0,0 @@ -// Go support for Protocol Buffers - Google's data interchange format -// -// Copyright 2011 The Go Authors. All rights reserved. -// https://github.com/golang/protobuf -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// Protocol buffer comparison. - -package proto - -import ( - "bytes" - "log" - "reflect" - "strings" -) - -/* -Equal returns true iff protocol buffers a and b are equal. -The arguments must both be pointers to protocol buffer structs. - -Equality is defined in this way: - - Two messages are equal iff they are the same type, - corresponding fields are equal, unknown field sets - are equal, and extensions sets are equal. - - Two set scalar fields are equal iff their values are equal. - If the fields are of a floating-point type, remember that - NaN != x for all x, including NaN. If the message is defined - in a proto3 .proto file, fields are not "set"; specifically, - zero length proto3 "bytes" fields are equal (nil == {}). - - Two repeated fields are equal iff their lengths are the same, - and their corresponding elements are equal. Note a "bytes" field, - although represented by []byte, is not a repeated field and the - rule for the scalar fields described above applies. - - Two unset fields are equal. - - Two unknown field sets are equal if their current - encoded state is equal. - - Two extension sets are equal iff they have corresponding - elements that are pairwise equal. - - Two map fields are equal iff their lengths are the same, - and they contain the same set of elements. Zero-length map - fields are equal. - - Every other combination of things are not equal. - -The return value is undefined if a and b are not protocol buffers. -*/ -func Equal(a, b Message) bool { - if a == nil || b == nil { - return a == b - } - v1, v2 := reflect.ValueOf(a), reflect.ValueOf(b) - if v1.Type() != v2.Type() { - return false - } - if v1.Kind() == reflect.Ptr { - if v1.IsNil() { - return v2.IsNil() - } - if v2.IsNil() { - return false - } - v1, v2 = v1.Elem(), v2.Elem() - } - if v1.Kind() != reflect.Struct { - return false - } - return equalStruct(v1, v2) -} - -// v1 and v2 are known to have the same type. -func equalStruct(v1, v2 reflect.Value) bool { - sprop := GetProperties(v1.Type()) - for i := 0; i < v1.NumField(); i++ { - f := v1.Type().Field(i) - if strings.HasPrefix(f.Name, "XXX_") { - continue - } - f1, f2 := v1.Field(i), v2.Field(i) - if f.Type.Kind() == reflect.Ptr { - if n1, n2 := f1.IsNil(), f2.IsNil(); n1 && n2 { - // both unset - continue - } else if n1 != n2 { - // set/unset mismatch - return false - } - b1, ok := f1.Interface().(raw) - if ok { - b2 := f2.Interface().(raw) - // RawMessage - if !bytes.Equal(b1.Bytes(), b2.Bytes()) { - return false - } - continue - } - f1, f2 = f1.Elem(), f2.Elem() - } - if !equalAny(f1, f2, sprop.Prop[i]) { - return false - } - } - - if em1 := v1.FieldByName("XXX_InternalExtensions"); em1.IsValid() { - em2 := v2.FieldByName("XXX_InternalExtensions") - if !equalExtensions(v1.Type(), em1.Interface().(XXX_InternalExtensions), em2.Interface().(XXX_InternalExtensions)) { - return false - } - } - - if em1 := v1.FieldByName("XXX_extensions"); em1.IsValid() { - em2 := v2.FieldByName("XXX_extensions") - if !equalExtMap(v1.Type(), em1.Interface().(map[int32]Extension), em2.Interface().(map[int32]Extension)) { - return false - } - } - - uf := v1.FieldByName("XXX_unrecognized") - if !uf.IsValid() { - return true - } - - u1 := uf.Bytes() - u2 := v2.FieldByName("XXX_unrecognized").Bytes() - if !bytes.Equal(u1, u2) { - return false - } - - return true -} - -// v1 and v2 are known to have the same type. -// prop may be nil. -func equalAny(v1, v2 reflect.Value, prop *Properties) bool { - if v1.Type() == protoMessageType { - m1, _ := v1.Interface().(Message) - m2, _ := v2.Interface().(Message) - return Equal(m1, m2) - } - switch v1.Kind() { - case reflect.Bool: - return v1.Bool() == v2.Bool() - case reflect.Float32, reflect.Float64: - return v1.Float() == v2.Float() - case reflect.Int32, reflect.Int64: - return v1.Int() == v2.Int() - case reflect.Interface: - // Probably a oneof field; compare the inner values. - n1, n2 := v1.IsNil(), v2.IsNil() - if n1 || n2 { - return n1 == n2 - } - e1, e2 := v1.Elem(), v2.Elem() - if e1.Type() != e2.Type() { - return false - } - return equalAny(e1, e2, nil) - case reflect.Map: - if v1.Len() != v2.Len() { - return false - } - for _, key := range v1.MapKeys() { - val2 := v2.MapIndex(key) - if !val2.IsValid() { - // This key was not found in the second map. - return false - } - if !equalAny(v1.MapIndex(key), val2, nil) { - return false - } - } - return true - case reflect.Ptr: - // Maps may have nil values in them, so check for nil. - if v1.IsNil() && v2.IsNil() { - return true - } - if v1.IsNil() != v2.IsNil() { - return false - } - return equalAny(v1.Elem(), v2.Elem(), prop) - case reflect.Slice: - if v1.Type().Elem().Kind() == reflect.Uint8 { - // short circuit: []byte - - // Edge case: if this is in a proto3 message, a zero length - // bytes field is considered the zero value. - if prop != nil && prop.proto3 && v1.Len() == 0 && v2.Len() == 0 { - return true - } - if v1.IsNil() != v2.IsNil() { - return false - } - return bytes.Equal(v1.Interface().([]byte), v2.Interface().([]byte)) - } - - if v1.Len() != v2.Len() { - return false - } - for i := 0; i < v1.Len(); i++ { - if !equalAny(v1.Index(i), v2.Index(i), prop) { - return false - } - } - return true - case reflect.String: - return v1.Interface().(string) == v2.Interface().(string) - case reflect.Struct: - return equalStruct(v1, v2) - case reflect.Uint32, reflect.Uint64: - return v1.Uint() == v2.Uint() - } - - // unknown type, so not a protocol buffer - log.Printf("proto: don't know how to compare %v", v1) - return false -} - -// base is the struct type that the extensions are based on. -// x1 and x2 are InternalExtensions. -func equalExtensions(base reflect.Type, x1, x2 XXX_InternalExtensions) bool { - em1, _ := x1.extensionsRead() - em2, _ := x2.extensionsRead() - return equalExtMap(base, em1, em2) -} - -func equalExtMap(base reflect.Type, em1, em2 map[int32]Extension) bool { - if len(em1) != len(em2) { - return false - } - - for extNum, e1 := range em1 { - e2, ok := em2[extNum] - if !ok { - return false - } - - m1, m2 := e1.value, e2.value - - if m1 != nil && m2 != nil { - // Both are unencoded. - if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2), nil) { - return false - } - continue - } - - // At least one is encoded. To do a semantically correct comparison - // we need to unmarshal them first. - var desc *ExtensionDesc - if m := extensionMaps[base]; m != nil { - desc = m[extNum] - } - if desc == nil { - log.Printf("proto: don't know how to compare extension %d of %v", extNum, base) - continue - } - var err error - if m1 == nil { - m1, err = decodeExtension(e1.enc, desc) - } - if m2 == nil && err == nil { - m2, err = decodeExtension(e2.enc, desc) - } - if err != nil { - // The encoded form is invalid. - log.Printf("proto: badly encoded extension %d of %v: %v", extNum, base, err) - return false - } - if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2), nil) { - return false - } - } - - return true -} diff --git a/vendor/github.com/golang/protobuf/proto/extensions.go b/vendor/github.com/golang/protobuf/proto/extensions.go deleted file mode 100644 index eaad2183126..00000000000 --- a/vendor/github.com/golang/protobuf/proto/extensions.go +++ /dev/null @@ -1,587 +0,0 @@ -// Go support for Protocol Buffers - Google's data interchange format -// -// Copyright 2010 The Go Authors. All rights reserved. -// https://github.com/golang/protobuf -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package proto - -/* - * Types and routines for supporting protocol buffer extensions. - */ - -import ( - "errors" - "fmt" - "reflect" - "strconv" - "sync" -) - -// ErrMissingExtension is the error returned by GetExtension if the named extension is not in the message. -var ErrMissingExtension = errors.New("proto: missing extension") - -// ExtensionRange represents a range of message extensions for a protocol buffer. -// Used in code generated by the protocol compiler. -type ExtensionRange struct { - Start, End int32 // both inclusive -} - -// extendableProto is an interface implemented by any protocol buffer generated by the current -// proto compiler that may be extended. -type extendableProto interface { - Message - ExtensionRangeArray() []ExtensionRange - extensionsWrite() map[int32]Extension - extensionsRead() (map[int32]Extension, sync.Locker) -} - -// extendableProtoV1 is an interface implemented by a protocol buffer generated by the previous -// version of the proto compiler that may be extended. -type extendableProtoV1 interface { - Message - ExtensionRangeArray() []ExtensionRange - ExtensionMap() map[int32]Extension -} - -// extensionAdapter is a wrapper around extendableProtoV1 that implements extendableProto. -type extensionAdapter struct { - extendableProtoV1 -} - -func (e extensionAdapter) extensionsWrite() map[int32]Extension { - return e.ExtensionMap() -} - -func (e extensionAdapter) extensionsRead() (map[int32]Extension, sync.Locker) { - return e.ExtensionMap(), notLocker{} -} - -// notLocker is a sync.Locker whose Lock and Unlock methods are nops. -type notLocker struct{} - -func (n notLocker) Lock() {} -func (n notLocker) Unlock() {} - -// extendable returns the extendableProto interface for the given generated proto message. -// If the proto message has the old extension format, it returns a wrapper that implements -// the extendableProto interface. -func extendable(p interface{}) (extendableProto, bool) { - if ep, ok := p.(extendableProto); ok { - return ep, ok - } - if ep, ok := p.(extendableProtoV1); ok { - return extensionAdapter{ep}, ok - } - return nil, false -} - -// XXX_InternalExtensions is an internal representation of proto extensions. -// -// Each generated message struct type embeds an anonymous XXX_InternalExtensions field, -// thus gaining the unexported 'extensions' method, which can be called only from the proto package. -// -// The methods of XXX_InternalExtensions are not concurrency safe in general, -// but calls to logically read-only methods such as has and get may be executed concurrently. -type XXX_InternalExtensions struct { - // The struct must be indirect so that if a user inadvertently copies a - // generated message and its embedded XXX_InternalExtensions, they - // avoid the mayhem of a copied mutex. - // - // The mutex serializes all logically read-only operations to p.extensionMap. - // It is up to the client to ensure that write operations to p.extensionMap are - // mutually exclusive with other accesses. - p *struct { - mu sync.Mutex - extensionMap map[int32]Extension - } -} - -// extensionsWrite returns the extension map, creating it on first use. -func (e *XXX_InternalExtensions) extensionsWrite() map[int32]Extension { - if e.p == nil { - e.p = new(struct { - mu sync.Mutex - extensionMap map[int32]Extension - }) - e.p.extensionMap = make(map[int32]Extension) - } - return e.p.extensionMap -} - -// extensionsRead returns the extensions map for read-only use. It may be nil. -// The caller must hold the returned mutex's lock when accessing Elements within the map. -func (e *XXX_InternalExtensions) extensionsRead() (map[int32]Extension, sync.Locker) { - if e.p == nil { - return nil, nil - } - return e.p.extensionMap, &e.p.mu -} - -var extendableProtoType = reflect.TypeOf((*extendableProto)(nil)).Elem() -var extendableProtoV1Type = reflect.TypeOf((*extendableProtoV1)(nil)).Elem() - -// ExtensionDesc represents an extension specification. -// Used in generated code from the protocol compiler. -type ExtensionDesc struct { - ExtendedType Message // nil pointer to the type that is being extended - ExtensionType interface{} // nil pointer to the extension type - Field int32 // field number - Name string // fully-qualified name of extension, for text formatting - Tag string // protobuf tag style - Filename string // name of the file in which the extension is defined -} - -func (ed *ExtensionDesc) repeated() bool { - t := reflect.TypeOf(ed.ExtensionType) - return t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8 -} - -// Extension represents an extension in a message. -type Extension struct { - // When an extension is stored in a message using SetExtension - // only desc and value are set. When the message is marshaled - // enc will be set to the encoded form of the message. - // - // When a message is unmarshaled and contains extensions, each - // extension will have only enc set. When such an extension is - // accessed using GetExtension (or GetExtensions) desc and value - // will be set. - desc *ExtensionDesc - value interface{} - enc []byte -} - -// SetRawExtension is for testing only. -func SetRawExtension(base Message, id int32, b []byte) { - epb, ok := extendable(base) - if !ok { - return - } - extmap := epb.extensionsWrite() - extmap[id] = Extension{enc: b} -} - -// isExtensionField returns true iff the given field number is in an extension range. -func isExtensionField(pb extendableProto, field int32) bool { - for _, er := range pb.ExtensionRangeArray() { - if er.Start <= field && field <= er.End { - return true - } - } - return false -} - -// checkExtensionTypes checks that the given extension is valid for pb. -func checkExtensionTypes(pb extendableProto, extension *ExtensionDesc) error { - var pbi interface{} = pb - // Check the extended type. - if ea, ok := pbi.(extensionAdapter); ok { - pbi = ea.extendableProtoV1 - } - if a, b := reflect.TypeOf(pbi), reflect.TypeOf(extension.ExtendedType); a != b { - return errors.New("proto: bad extended type; " + b.String() + " does not extend " + a.String()) - } - // Check the range. - if !isExtensionField(pb, extension.Field) { - return errors.New("proto: bad extension number; not in declared ranges") - } - return nil -} - -// extPropKey is sufficient to uniquely identify an extension. -type extPropKey struct { - base reflect.Type - field int32 -} - -var extProp = struct { - sync.RWMutex - m map[extPropKey]*Properties -}{ - m: make(map[extPropKey]*Properties), -} - -func extensionProperties(ed *ExtensionDesc) *Properties { - key := extPropKey{base: reflect.TypeOf(ed.ExtendedType), field: ed.Field} - - extProp.RLock() - if prop, ok := extProp.m[key]; ok { - extProp.RUnlock() - return prop - } - extProp.RUnlock() - - extProp.Lock() - defer extProp.Unlock() - // Check again. - if prop, ok := extProp.m[key]; ok { - return prop - } - - prop := new(Properties) - prop.Init(reflect.TypeOf(ed.ExtensionType), "unknown_name", ed.Tag, nil) - extProp.m[key] = prop - return prop -} - -// encode encodes any unmarshaled (unencoded) extensions in e. -func encodeExtensions(e *XXX_InternalExtensions) error { - m, mu := e.extensionsRead() - if m == nil { - return nil // fast path - } - mu.Lock() - defer mu.Unlock() - return encodeExtensionsMap(m) -} - -// encode encodes any unmarshaled (unencoded) extensions in e. -func encodeExtensionsMap(m map[int32]Extension) error { - for k, e := range m { - if e.value == nil || e.desc == nil { - // Extension is only in its encoded form. - continue - } - - // We don't skip extensions that have an encoded form set, - // because the extension value may have been mutated after - // the last time this function was called. - - et := reflect.TypeOf(e.desc.ExtensionType) - props := extensionProperties(e.desc) - - p := NewBuffer(nil) - // If e.value has type T, the encoder expects a *struct{ X T }. - // Pass a *T with a zero field and hope it all works out. - x := reflect.New(et) - x.Elem().Set(reflect.ValueOf(e.value)) - if err := props.enc(p, props, toStructPointer(x)); err != nil { - return err - } - e.enc = p.buf - m[k] = e - } - return nil -} - -func extensionsSize(e *XXX_InternalExtensions) (n int) { - m, mu := e.extensionsRead() - if m == nil { - return 0 - } - mu.Lock() - defer mu.Unlock() - return extensionsMapSize(m) -} - -func extensionsMapSize(m map[int32]Extension) (n int) { - for _, e := range m { - if e.value == nil || e.desc == nil { - // Extension is only in its encoded form. - n += len(e.enc) - continue - } - - // We don't skip extensions that have an encoded form set, - // because the extension value may have been mutated after - // the last time this function was called. - - et := reflect.TypeOf(e.desc.ExtensionType) - props := extensionProperties(e.desc) - - // If e.value has type T, the encoder expects a *struct{ X T }. - // Pass a *T with a zero field and hope it all works out. - x := reflect.New(et) - x.Elem().Set(reflect.ValueOf(e.value)) - n += props.size(props, toStructPointer(x)) - } - return -} - -// HasExtension returns whether the given extension is present in pb. -func HasExtension(pb Message, extension *ExtensionDesc) bool { - // TODO: Check types, field numbers, etc.? - epb, ok := extendable(pb) - if !ok { - return false - } - extmap, mu := epb.extensionsRead() - if extmap == nil { - return false - } - mu.Lock() - _, ok = extmap[extension.Field] - mu.Unlock() - return ok -} - -// ClearExtension removes the given extension from pb. -func ClearExtension(pb Message, extension *ExtensionDesc) { - epb, ok := extendable(pb) - if !ok { - return - } - // TODO: Check types, field numbers, etc.? - extmap := epb.extensionsWrite() - delete(extmap, extension.Field) -} - -// GetExtension parses and returns the given extension of pb. -// If the extension is not present and has no default value it returns ErrMissingExtension. -func GetExtension(pb Message, extension *ExtensionDesc) (interface{}, error) { - epb, ok := extendable(pb) - if !ok { - return nil, errors.New("proto: not an extendable proto") - } - - if err := checkExtensionTypes(epb, extension); err != nil { - return nil, err - } - - emap, mu := epb.extensionsRead() - if emap == nil { - return defaultExtensionValue(extension) - } - mu.Lock() - defer mu.Unlock() - e, ok := emap[extension.Field] - if !ok { - // defaultExtensionValue returns the default value or - // ErrMissingExtension if there is no default. - return defaultExtensionValue(extension) - } - - if e.value != nil { - // Already decoded. Check the descriptor, though. - if e.desc != extension { - // This shouldn't happen. If it does, it means that - // GetExtension was called twice with two different - // descriptors with the same field number. - return nil, errors.New("proto: descriptor conflict") - } - return e.value, nil - } - - v, err := decodeExtension(e.enc, extension) - if err != nil { - return nil, err - } - - // Remember the decoded version and drop the encoded version. - // That way it is safe to mutate what we return. - e.value = v - e.desc = extension - e.enc = nil - emap[extension.Field] = e - return e.value, nil -} - -// defaultExtensionValue returns the default value for extension. -// If no default for an extension is defined ErrMissingExtension is returned. -func defaultExtensionValue(extension *ExtensionDesc) (interface{}, error) { - t := reflect.TypeOf(extension.ExtensionType) - props := extensionProperties(extension) - - sf, _, err := fieldDefault(t, props) - if err != nil { - return nil, err - } - - if sf == nil || sf.value == nil { - // There is no default value. - return nil, ErrMissingExtension - } - - if t.Kind() != reflect.Ptr { - // We do not need to return a Ptr, we can directly return sf.value. - return sf.value, nil - } - - // We need to return an interface{} that is a pointer to sf.value. - value := reflect.New(t).Elem() - value.Set(reflect.New(value.Type().Elem())) - if sf.kind == reflect.Int32 { - // We may have an int32 or an enum, but the underlying data is int32. - // Since we can't set an int32 into a non int32 reflect.value directly - // set it as a int32. - value.Elem().SetInt(int64(sf.value.(int32))) - } else { - value.Elem().Set(reflect.ValueOf(sf.value)) - } - return value.Interface(), nil -} - -// decodeExtension decodes an extension encoded in b. -func decodeExtension(b []byte, extension *ExtensionDesc) (interface{}, error) { - o := NewBuffer(b) - - t := reflect.TypeOf(extension.ExtensionType) - - props := extensionProperties(extension) - - // t is a pointer to a struct, pointer to basic type or a slice. - // Allocate a "field" to store the pointer/slice itself; the - // pointer/slice will be stored here. We pass - // the address of this field to props.dec. - // This passes a zero field and a *t and lets props.dec - // interpret it as a *struct{ x t }. - value := reflect.New(t).Elem() - - for { - // Discard wire type and field number varint. It isn't needed. - if _, err := o.DecodeVarint(); err != nil { - return nil, err - } - - if err := props.dec(o, props, toStructPointer(value.Addr())); err != nil { - return nil, err - } - - if o.index >= len(o.buf) { - break - } - } - return value.Interface(), nil -} - -// GetExtensions returns a slice of the extensions present in pb that are also listed in es. -// The returned slice has the same length as es; missing extensions will appear as nil elements. -func GetExtensions(pb Message, es []*ExtensionDesc) (extensions []interface{}, err error) { - epb, ok := extendable(pb) - if !ok { - return nil, errors.New("proto: not an extendable proto") - } - extensions = make([]interface{}, len(es)) - for i, e := range es { - extensions[i], err = GetExtension(epb, e) - if err == ErrMissingExtension { - err = nil - } - if err != nil { - return - } - } - return -} - -// ExtensionDescs returns a new slice containing pb's extension descriptors, in undefined order. -// For non-registered extensions, ExtensionDescs returns an incomplete descriptor containing -// just the Field field, which defines the extension's field number. -func ExtensionDescs(pb Message) ([]*ExtensionDesc, error) { - epb, ok := extendable(pb) - if !ok { - return nil, fmt.Errorf("proto: %T is not an extendable proto.Message", pb) - } - registeredExtensions := RegisteredExtensions(pb) - - emap, mu := epb.extensionsRead() - if emap == nil { - return nil, nil - } - mu.Lock() - defer mu.Unlock() - extensions := make([]*ExtensionDesc, 0, len(emap)) - for extid, e := range emap { - desc := e.desc - if desc == nil { - desc = registeredExtensions[extid] - if desc == nil { - desc = &ExtensionDesc{Field: extid} - } - } - - extensions = append(extensions, desc) - } - return extensions, nil -} - -// SetExtension sets the specified extension of pb to the specified value. -func SetExtension(pb Message, extension *ExtensionDesc, value interface{}) error { - epb, ok := extendable(pb) - if !ok { - return errors.New("proto: not an extendable proto") - } - if err := checkExtensionTypes(epb, extension); err != nil { - return err - } - typ := reflect.TypeOf(extension.ExtensionType) - if typ != reflect.TypeOf(value) { - return errors.New("proto: bad extension value type") - } - // nil extension values need to be caught early, because the - // encoder can't distinguish an ErrNil due to a nil extension - // from an ErrNil due to a missing field. Extensions are - // always optional, so the encoder would just swallow the error - // and drop all the extensions from the encoded message. - if reflect.ValueOf(value).IsNil() { - return fmt.Errorf("proto: SetExtension called with nil value of type %T", value) - } - - extmap := epb.extensionsWrite() - extmap[extension.Field] = Extension{desc: extension, value: value} - return nil -} - -// ClearAllExtensions clears all extensions from pb. -func ClearAllExtensions(pb Message) { - epb, ok := extendable(pb) - if !ok { - return - } - m := epb.extensionsWrite() - for k := range m { - delete(m, k) - } -} - -// A global registry of extensions. -// The generated code will register the generated descriptors by calling RegisterExtension. - -var extensionMaps = make(map[reflect.Type]map[int32]*ExtensionDesc) - -// RegisterExtension is called from the generated code. -func RegisterExtension(desc *ExtensionDesc) { - st := reflect.TypeOf(desc.ExtendedType).Elem() - m := extensionMaps[st] - if m == nil { - m = make(map[int32]*ExtensionDesc) - extensionMaps[st] = m - } - if _, ok := m[desc.Field]; ok { - panic("proto: duplicate extension registered: " + st.String() + " " + strconv.Itoa(int(desc.Field))) - } - m[desc.Field] = desc -} - -// RegisteredExtensions returns a map of the registered extensions of a -// protocol buffer struct, indexed by the extension number. -// The argument pb should be a nil pointer to the struct type. -func RegisteredExtensions(pb Message) map[int32]*ExtensionDesc { - return extensionMaps[reflect.TypeOf(pb).Elem()] -} diff --git a/vendor/github.com/golang/protobuf/proto/lib.go b/vendor/github.com/golang/protobuf/proto/lib.go deleted file mode 100644 index 1c225504a01..00000000000 --- a/vendor/github.com/golang/protobuf/proto/lib.go +++ /dev/null @@ -1,897 +0,0 @@ -// Go support for Protocol Buffers - Google's data interchange format -// -// Copyright 2010 The Go Authors. All rights reserved. -// https://github.com/golang/protobuf -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -/* -Package proto converts data structures to and from the wire format of -protocol buffers. It works in concert with the Go source code generated -for .proto files by the protocol compiler. - -A summary of the properties of the protocol buffer interface -for a protocol buffer variable v: - - - Names are turned from camel_case to CamelCase for export. - - There are no methods on v to set fields; just treat - them as structure fields. - - There are getters that return a field's value if set, - and return the field's default value if unset. - The getters work even if the receiver is a nil message. - - The zero value for a struct is its correct initialization state. - All desired fields must be set before marshaling. - - A Reset() method will restore a protobuf struct to its zero state. - - Non-repeated fields are pointers to the values; nil means unset. - That is, optional or required field int32 f becomes F *int32. - - Repeated fields are slices. - - Helper functions are available to aid the setting of fields. - msg.Foo = proto.String("hello") // set field - - Constants are defined to hold the default values of all fields that - have them. They have the form Default_StructName_FieldName. - Because the getter methods handle defaulted values, - direct use of these constants should be rare. - - Enums are given type names and maps from names to values. - Enum values are prefixed by the enclosing message's name, or by the - enum's type name if it is a top-level enum. Enum types have a String - method, and a Enum method to assist in message construction. - - Nested messages, groups and enums have type names prefixed with the name of - the surrounding message type. - - Extensions are given descriptor names that start with E_, - followed by an underscore-delimited list of the nested messages - that contain it (if any) followed by the CamelCased name of the - extension field itself. HasExtension, ClearExtension, GetExtension - and SetExtension are functions for manipulating extensions. - - Oneof field sets are given a single field in their message, - with distinguished wrapper types for each possible field value. - - Marshal and Unmarshal are functions to encode and decode the wire format. - -When the .proto file specifies `syntax="proto3"`, there are some differences: - - - Non-repeated fields of non-message type are values instead of pointers. - - Enum types do not get an Enum method. - -The simplest way to describe this is to see an example. -Given file test.proto, containing - - package example; - - enum FOO { X = 17; } - - message Test { - required string label = 1; - optional int32 type = 2 [default=77]; - repeated int64 reps = 3; - optional group OptionalGroup = 4 { - required string RequiredField = 5; - } - oneof union { - int32 number = 6; - string name = 7; - } - } - -The resulting file, test.pb.go, is: - - package example - - import proto "github.com/golang/protobuf/proto" - import math "math" - - type FOO int32 - const ( - FOO_X FOO = 17 - ) - var FOO_name = map[int32]string{ - 17: "X", - } - var FOO_value = map[string]int32{ - "X": 17, - } - - func (x FOO) Enum() *FOO { - p := new(FOO) - *p = x - return p - } - func (x FOO) String() string { - return proto.EnumName(FOO_name, int32(x)) - } - func (x *FOO) UnmarshalJSON(data []byte) error { - value, err := proto.UnmarshalJSONEnum(FOO_value, data) - if err != nil { - return err - } - *x = FOO(value) - return nil - } - - type Test struct { - Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"` - Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"` - Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"` - Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup" json:"optionalgroup,omitempty"` - // Types that are valid to be assigned to Union: - // *Test_Number - // *Test_Name - Union isTest_Union `protobuf_oneof:"union"` - XXX_unrecognized []byte `json:"-"` - } - func (m *Test) Reset() { *m = Test{} } - func (m *Test) String() string { return proto.CompactTextString(m) } - func (*Test) ProtoMessage() {} - - type isTest_Union interface { - isTest_Union() - } - - type Test_Number struct { - Number int32 `protobuf:"varint,6,opt,name=number"` - } - type Test_Name struct { - Name string `protobuf:"bytes,7,opt,name=name"` - } - - func (*Test_Number) isTest_Union() {} - func (*Test_Name) isTest_Union() {} - - func (m *Test) GetUnion() isTest_Union { - if m != nil { - return m.Union - } - return nil - } - const Default_Test_Type int32 = 77 - - func (m *Test) GetLabel() string { - if m != nil && m.Label != nil { - return *m.Label - } - return "" - } - - func (m *Test) GetType() int32 { - if m != nil && m.Type != nil { - return *m.Type - } - return Default_Test_Type - } - - func (m *Test) GetOptionalgroup() *Test_OptionalGroup { - if m != nil { - return m.Optionalgroup - } - return nil - } - - type Test_OptionalGroup struct { - RequiredField *string `protobuf:"bytes,5,req" json:"RequiredField,omitempty"` - } - func (m *Test_OptionalGroup) Reset() { *m = Test_OptionalGroup{} } - func (m *Test_OptionalGroup) String() string { return proto.CompactTextString(m) } - - func (m *Test_OptionalGroup) GetRequiredField() string { - if m != nil && m.RequiredField != nil { - return *m.RequiredField - } - return "" - } - - func (m *Test) GetNumber() int32 { - if x, ok := m.GetUnion().(*Test_Number); ok { - return x.Number - } - return 0 - } - - func (m *Test) GetName() string { - if x, ok := m.GetUnion().(*Test_Name); ok { - return x.Name - } - return "" - } - - func init() { - proto.RegisterEnum("example.FOO", FOO_name, FOO_value) - } - -To create and play with a Test object: - - package main - - import ( - "log" - - "github.com/golang/protobuf/proto" - pb "./example.pb" - ) - - func main() { - test := &pb.Test{ - Label: proto.String("hello"), - Type: proto.Int32(17), - Reps: []int64{1, 2, 3}, - Optionalgroup: &pb.Test_OptionalGroup{ - RequiredField: proto.String("good bye"), - }, - Union: &pb.Test_Name{"fred"}, - } - data, err := proto.Marshal(test) - if err != nil { - log.Fatal("marshaling error: ", err) - } - newTest := &pb.Test{} - err = proto.Unmarshal(data, newTest) - if err != nil { - log.Fatal("unmarshaling error: ", err) - } - // Now test and newTest contain the same data. - if test.GetLabel() != newTest.GetLabel() { - log.Fatalf("data mismatch %q != %q", test.GetLabel(), newTest.GetLabel()) - } - // Use a type switch to determine which oneof was set. - switch u := test.Union.(type) { - case *pb.Test_Number: // u.Number contains the number. - case *pb.Test_Name: // u.Name contains the string. - } - // etc. - } -*/ -package proto - -import ( - "encoding/json" - "fmt" - "log" - "reflect" - "sort" - "strconv" - "sync" -) - -// Message is implemented by generated protocol buffer messages. -type Message interface { - Reset() - String() string - ProtoMessage() -} - -// Stats records allocation details about the protocol buffer encoders -// and decoders. Useful for tuning the library itself. -type Stats struct { - Emalloc uint64 // mallocs in encode - Dmalloc uint64 // mallocs in decode - Encode uint64 // number of encodes - Decode uint64 // number of decodes - Chit uint64 // number of cache hits - Cmiss uint64 // number of cache misses - Size uint64 // number of sizes -} - -// Set to true to enable stats collection. -const collectStats = false - -var stats Stats - -// GetStats returns a copy of the global Stats structure. -func GetStats() Stats { return stats } - -// A Buffer is a buffer manager for marshaling and unmarshaling -// protocol buffers. It may be reused between invocations to -// reduce memory usage. It is not necessary to use a Buffer; -// the global functions Marshal and Unmarshal create a -// temporary Buffer and are fine for most applications. -type Buffer struct { - buf []byte // encode/decode byte stream - index int // read point - - // pools of basic types to amortize allocation. - bools []bool - uint32s []uint32 - uint64s []uint64 - - // extra pools, only used with pointer_reflect.go - int32s []int32 - int64s []int64 - float32s []float32 - float64s []float64 -} - -// NewBuffer allocates a new Buffer and initializes its internal data to -// the contents of the argument slice. -func NewBuffer(e []byte) *Buffer { - return &Buffer{buf: e} -} - -// Reset resets the Buffer, ready for marshaling a new protocol buffer. -func (p *Buffer) Reset() { - p.buf = p.buf[0:0] // for reading/writing - p.index = 0 // for reading -} - -// SetBuf replaces the internal buffer with the slice, -// ready for unmarshaling the contents of the slice. -func (p *Buffer) SetBuf(s []byte) { - p.buf = s - p.index = 0 -} - -// Bytes returns the contents of the Buffer. -func (p *Buffer) Bytes() []byte { return p.buf } - -/* - * Helper routines for simplifying the creation of optional fields of basic type. - */ - -// Bool is a helper routine that allocates a new bool value -// to store v and returns a pointer to it. -func Bool(v bool) *bool { - return &v -} - -// Int32 is a helper routine that allocates a new int32 value -// to store v and returns a pointer to it. -func Int32(v int32) *int32 { - return &v -} - -// Int is a helper routine that allocates a new int32 value -// to store v and returns a pointer to it, but unlike Int32 -// its argument value is an int. -func Int(v int) *int32 { - p := new(int32) - *p = int32(v) - return p -} - -// Int64 is a helper routine that allocates a new int64 value -// to store v and returns a pointer to it. -func Int64(v int64) *int64 { - return &v -} - -// Float32 is a helper routine that allocates a new float32 value -// to store v and returns a pointer to it. -func Float32(v float32) *float32 { - return &v -} - -// Float64 is a helper routine that allocates a new float64 value -// to store v and returns a pointer to it. -func Float64(v float64) *float64 { - return &v -} - -// Uint32 is a helper routine that allocates a new uint32 value -// to store v and returns a pointer to it. -func Uint32(v uint32) *uint32 { - return &v -} - -// Uint64 is a helper routine that allocates a new uint64 value -// to store v and returns a pointer to it. -func Uint64(v uint64) *uint64 { - return &v -} - -// String is a helper routine that allocates a new string value -// to store v and returns a pointer to it. -func String(v string) *string { - return &v -} - -// EnumName is a helper function to simplify printing protocol buffer enums -// by name. Given an enum map and a value, it returns a useful string. -func EnumName(m map[int32]string, v int32) string { - s, ok := m[v] - if ok { - return s - } - return strconv.Itoa(int(v)) -} - -// UnmarshalJSONEnum is a helper function to simplify recovering enum int values -// from their JSON-encoded representation. Given a map from the enum's symbolic -// names to its int values, and a byte buffer containing the JSON-encoded -// value, it returns an int32 that can be cast to the enum type by the caller. -// -// The function can deal with both JSON representations, numeric and symbolic. -func UnmarshalJSONEnum(m map[string]int32, data []byte, enumName string) (int32, error) { - if data[0] == '"' { - // New style: enums are strings. - var repr string - if err := json.Unmarshal(data, &repr); err != nil { - return -1, err - } - val, ok := m[repr] - if !ok { - return 0, fmt.Errorf("unrecognized enum %s value %q", enumName, repr) - } - return val, nil - } - // Old style: enums are ints. - var val int32 - if err := json.Unmarshal(data, &val); err != nil { - return 0, fmt.Errorf("cannot unmarshal %#q into enum %s", data, enumName) - } - return val, nil -} - -// DebugPrint dumps the encoded data in b in a debugging format with a header -// including the string s. Used in testing but made available for general debugging. -func (p *Buffer) DebugPrint(s string, b []byte) { - var u uint64 - - obuf := p.buf - index := p.index - p.buf = b - p.index = 0 - depth := 0 - - fmt.Printf("\n--- %s ---\n", s) - -out: - for { - for i := 0; i < depth; i++ { - fmt.Print(" ") - } - - index := p.index - if index == len(p.buf) { - break - } - - op, err := p.DecodeVarint() - if err != nil { - fmt.Printf("%3d: fetching op err %v\n", index, err) - break out - } - tag := op >> 3 - wire := op & 7 - - switch wire { - default: - fmt.Printf("%3d: t=%3d unknown wire=%d\n", - index, tag, wire) - break out - - case WireBytes: - var r []byte - - r, err = p.DecodeRawBytes(false) - if err != nil { - break out - } - fmt.Printf("%3d: t=%3d bytes [%d]", index, tag, len(r)) - if len(r) <= 6 { - for i := 0; i < len(r); i++ { - fmt.Printf(" %.2x", r[i]) - } - } else { - for i := 0; i < 3; i++ { - fmt.Printf(" %.2x", r[i]) - } - fmt.Printf(" ..") - for i := len(r) - 3; i < len(r); i++ { - fmt.Printf(" %.2x", r[i]) - } - } - fmt.Printf("\n") - - case WireFixed32: - u, err = p.DecodeFixed32() - if err != nil { - fmt.Printf("%3d: t=%3d fix32 err %v\n", index, tag, err) - break out - } - fmt.Printf("%3d: t=%3d fix32 %d\n", index, tag, u) - - case WireFixed64: - u, err = p.DecodeFixed64() - if err != nil { - fmt.Printf("%3d: t=%3d fix64 err %v\n", index, tag, err) - break out - } - fmt.Printf("%3d: t=%3d fix64 %d\n", index, tag, u) - - case WireVarint: - u, err = p.DecodeVarint() - if err != nil { - fmt.Printf("%3d: t=%3d varint err %v\n", index, tag, err) - break out - } - fmt.Printf("%3d: t=%3d varint %d\n", index, tag, u) - - case WireStartGroup: - fmt.Printf("%3d: t=%3d start\n", index, tag) - depth++ - - case WireEndGroup: - depth-- - fmt.Printf("%3d: t=%3d end\n", index, tag) - } - } - - if depth != 0 { - fmt.Printf("%3d: start-end not balanced %d\n", p.index, depth) - } - fmt.Printf("\n") - - p.buf = obuf - p.index = index -} - -// SetDefaults sets unset protocol buffer fields to their default values. -// It only modifies fields that are both unset and have defined defaults. -// It recursively sets default values in any non-nil sub-messages. -func SetDefaults(pb Message) { - setDefaults(reflect.ValueOf(pb), true, false) -} - -// v is a pointer to a struct. -func setDefaults(v reflect.Value, recur, zeros bool) { - v = v.Elem() - - defaultMu.RLock() - dm, ok := defaults[v.Type()] - defaultMu.RUnlock() - if !ok { - dm = buildDefaultMessage(v.Type()) - defaultMu.Lock() - defaults[v.Type()] = dm - defaultMu.Unlock() - } - - for _, sf := range dm.scalars { - f := v.Field(sf.index) - if !f.IsNil() { - // field already set - continue - } - dv := sf.value - if dv == nil && !zeros { - // no explicit default, and don't want to set zeros - continue - } - fptr := f.Addr().Interface() // **T - // TODO: Consider batching the allocations we do here. - switch sf.kind { - case reflect.Bool: - b := new(bool) - if dv != nil { - *b = dv.(bool) - } - *(fptr.(**bool)) = b - case reflect.Float32: - f := new(float32) - if dv != nil { - *f = dv.(float32) - } - *(fptr.(**float32)) = f - case reflect.Float64: - f := new(float64) - if dv != nil { - *f = dv.(float64) - } - *(fptr.(**float64)) = f - case reflect.Int32: - // might be an enum - if ft := f.Type(); ft != int32PtrType { - // enum - f.Set(reflect.New(ft.Elem())) - if dv != nil { - f.Elem().SetInt(int64(dv.(int32))) - } - } else { - // int32 field - i := new(int32) - if dv != nil { - *i = dv.(int32) - } - *(fptr.(**int32)) = i - } - case reflect.Int64: - i := new(int64) - if dv != nil { - *i = dv.(int64) - } - *(fptr.(**int64)) = i - case reflect.String: - s := new(string) - if dv != nil { - *s = dv.(string) - } - *(fptr.(**string)) = s - case reflect.Uint8: - // exceptional case: []byte - var b []byte - if dv != nil { - db := dv.([]byte) - b = make([]byte, len(db)) - copy(b, db) - } else { - b = []byte{} - } - *(fptr.(*[]byte)) = b - case reflect.Uint32: - u := new(uint32) - if dv != nil { - *u = dv.(uint32) - } - *(fptr.(**uint32)) = u - case reflect.Uint64: - u := new(uint64) - if dv != nil { - *u = dv.(uint64) - } - *(fptr.(**uint64)) = u - default: - log.Printf("proto: can't set default for field %v (sf.kind=%v)", f, sf.kind) - } - } - - for _, ni := range dm.nested { - f := v.Field(ni) - // f is *T or []*T or map[T]*T - switch f.Kind() { - case reflect.Ptr: - if f.IsNil() { - continue - } - setDefaults(f, recur, zeros) - - case reflect.Slice: - for i := 0; i < f.Len(); i++ { - e := f.Index(i) - if e.IsNil() { - continue - } - setDefaults(e, recur, zeros) - } - - case reflect.Map: - for _, k := range f.MapKeys() { - e := f.MapIndex(k) - if e.IsNil() { - continue - } - setDefaults(e, recur, zeros) - } - } - } -} - -var ( - // defaults maps a protocol buffer struct type to a slice of the fields, - // with its scalar fields set to their proto-declared non-zero default values. - defaultMu sync.RWMutex - defaults = make(map[reflect.Type]defaultMessage) - - int32PtrType = reflect.TypeOf((*int32)(nil)) -) - -// defaultMessage represents information about the default values of a message. -type defaultMessage struct { - scalars []scalarField - nested []int // struct field index of nested messages -} - -type scalarField struct { - index int // struct field index - kind reflect.Kind // element type (the T in *T or []T) - value interface{} // the proto-declared default value, or nil -} - -// t is a struct type. -func buildDefaultMessage(t reflect.Type) (dm defaultMessage) { - sprop := GetProperties(t) - for _, prop := range sprop.Prop { - fi, ok := sprop.decoderTags.get(prop.Tag) - if !ok { - // XXX_unrecognized - continue - } - ft := t.Field(fi).Type - - sf, nested, err := fieldDefault(ft, prop) - switch { - case err != nil: - log.Print(err) - case nested: - dm.nested = append(dm.nested, fi) - case sf != nil: - sf.index = fi - dm.scalars = append(dm.scalars, *sf) - } - } - - return dm -} - -// fieldDefault returns the scalarField for field type ft. -// sf will be nil if the field can not have a default. -// nestedMessage will be true if this is a nested message. -// Note that sf.index is not set on return. -func fieldDefault(ft reflect.Type, prop *Properties) (sf *scalarField, nestedMessage bool, err error) { - var canHaveDefault bool - switch ft.Kind() { - case reflect.Ptr: - if ft.Elem().Kind() == reflect.Struct { - nestedMessage = true - } else { - canHaveDefault = true // proto2 scalar field - } - - case reflect.Slice: - switch ft.Elem().Kind() { - case reflect.Ptr: - nestedMessage = true // repeated message - case reflect.Uint8: - canHaveDefault = true // bytes field - } - - case reflect.Map: - if ft.Elem().Kind() == reflect.Ptr { - nestedMessage = true // map with message values - } - } - - if !canHaveDefault { - if nestedMessage { - return nil, true, nil - } - return nil, false, nil - } - - // We now know that ft is a pointer or slice. - sf = &scalarField{kind: ft.Elem().Kind()} - - // scalar fields without defaults - if !prop.HasDefault { - return sf, false, nil - } - - // a scalar field: either *T or []byte - switch ft.Elem().Kind() { - case reflect.Bool: - x, err := strconv.ParseBool(prop.Default) - if err != nil { - return nil, false, fmt.Errorf("proto: bad default bool %q: %v", prop.Default, err) - } - sf.value = x - case reflect.Float32: - x, err := strconv.ParseFloat(prop.Default, 32) - if err != nil { - return nil, false, fmt.Errorf("proto: bad default float32 %q: %v", prop.Default, err) - } - sf.value = float32(x) - case reflect.Float64: - x, err := strconv.ParseFloat(prop.Default, 64) - if err != nil { - return nil, false, fmt.Errorf("proto: bad default float64 %q: %v", prop.Default, err) - } - sf.value = x - case reflect.Int32: - x, err := strconv.ParseInt(prop.Default, 10, 32) - if err != nil { - return nil, false, fmt.Errorf("proto: bad default int32 %q: %v", prop.Default, err) - } - sf.value = int32(x) - case reflect.Int64: - x, err := strconv.ParseInt(prop.Default, 10, 64) - if err != nil { - return nil, false, fmt.Errorf("proto: bad default int64 %q: %v", prop.Default, err) - } - sf.value = x - case reflect.String: - sf.value = prop.Default - case reflect.Uint8: - // []byte (not *uint8) - sf.value = []byte(prop.Default) - case reflect.Uint32: - x, err := strconv.ParseUint(prop.Default, 10, 32) - if err != nil { - return nil, false, fmt.Errorf("proto: bad default uint32 %q: %v", prop.Default, err) - } - sf.value = uint32(x) - case reflect.Uint64: - x, err := strconv.ParseUint(prop.Default, 10, 64) - if err != nil { - return nil, false, fmt.Errorf("proto: bad default uint64 %q: %v", prop.Default, err) - } - sf.value = x - default: - return nil, false, fmt.Errorf("proto: unhandled def kind %v", ft.Elem().Kind()) - } - - return sf, false, nil -} - -// Map fields may have key types of non-float scalars, strings and enums. -// The easiest way to sort them in some deterministic order is to use fmt. -// If this turns out to be inefficient we can always consider other options, -// such as doing a Schwartzian transform. - -func mapKeys(vs []reflect.Value) sort.Interface { - s := mapKeySorter{ - vs: vs, - // default Less function: textual comparison - less: func(a, b reflect.Value) bool { - return fmt.Sprint(a.Interface()) < fmt.Sprint(b.Interface()) - }, - } - - // Type specialization per https://developers.google.com/protocol-buffers/docs/proto#maps; - // numeric keys are sorted numerically. - if len(vs) == 0 { - return s - } - switch vs[0].Kind() { - case reflect.Int32, reflect.Int64: - s.less = func(a, b reflect.Value) bool { return a.Int() < b.Int() } - case reflect.Uint32, reflect.Uint64: - s.less = func(a, b reflect.Value) bool { return a.Uint() < b.Uint() } - } - - return s -} - -type mapKeySorter struct { - vs []reflect.Value - less func(a, b reflect.Value) bool -} - -func (s mapKeySorter) Len() int { return len(s.vs) } -func (s mapKeySorter) Swap(i, j int) { s.vs[i], s.vs[j] = s.vs[j], s.vs[i] } -func (s mapKeySorter) Less(i, j int) bool { - return s.less(s.vs[i], s.vs[j]) -} - -// isProto3Zero reports whether v is a zero proto3 value. -func isProto3Zero(v reflect.Value) bool { - switch v.Kind() { - case reflect.Bool: - return !v.Bool() - case reflect.Int32, reflect.Int64: - return v.Int() == 0 - case reflect.Uint32, reflect.Uint64: - return v.Uint() == 0 - case reflect.Float32, reflect.Float64: - return v.Float() == 0 - case reflect.String: - return v.String() == "" - } - return false -} - -// ProtoPackageIsVersion2 is referenced from generated protocol buffer files -// to assert that that code is compatible with this version of the proto package. -const ProtoPackageIsVersion2 = true - -// ProtoPackageIsVersion1 is referenced from generated protocol buffer files -// to assert that that code is compatible with this version of the proto package. -const ProtoPackageIsVersion1 = true diff --git a/vendor/github.com/golang/protobuf/proto/message_set.go b/vendor/github.com/golang/protobuf/proto/message_set.go deleted file mode 100644 index fd982decd66..00000000000 --- a/vendor/github.com/golang/protobuf/proto/message_set.go +++ /dev/null @@ -1,311 +0,0 @@ -// Go support for Protocol Buffers - Google's data interchange format -// -// Copyright 2010 The Go Authors. All rights reserved. -// https://github.com/golang/protobuf -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package proto - -/* - * Support for message sets. - */ - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "reflect" - "sort" -) - -// errNoMessageTypeID occurs when a protocol buffer does not have a message type ID. -// A message type ID is required for storing a protocol buffer in a message set. -var errNoMessageTypeID = errors.New("proto does not have a message type ID") - -// The first two types (_MessageSet_Item and messageSet) -// model what the protocol compiler produces for the following protocol message: -// message MessageSet { -// repeated group Item = 1 { -// required int32 type_id = 2; -// required string message = 3; -// }; -// } -// That is the MessageSet wire format. We can't use a proto to generate these -// because that would introduce a circular dependency between it and this package. - -type _MessageSet_Item struct { - TypeId *int32 `protobuf:"varint,2,req,name=type_id"` - Message []byte `protobuf:"bytes,3,req,name=message"` -} - -type messageSet struct { - Item []*_MessageSet_Item `protobuf:"group,1,rep"` - XXX_unrecognized []byte - // TODO: caching? -} - -// Make sure messageSet is a Message. -var _ Message = (*messageSet)(nil) - -// messageTypeIder is an interface satisfied by a protocol buffer type -// that may be stored in a MessageSet. -type messageTypeIder interface { - MessageTypeId() int32 -} - -func (ms *messageSet) find(pb Message) *_MessageSet_Item { - mti, ok := pb.(messageTypeIder) - if !ok { - return nil - } - id := mti.MessageTypeId() - for _, item := range ms.Item { - if *item.TypeId == id { - return item - } - } - return nil -} - -func (ms *messageSet) Has(pb Message) bool { - if ms.find(pb) != nil { - return true - } - return false -} - -func (ms *messageSet) Unmarshal(pb Message) error { - if item := ms.find(pb); item != nil { - return Unmarshal(item.Message, pb) - } - if _, ok := pb.(messageTypeIder); !ok { - return errNoMessageTypeID - } - return nil // TODO: return error instead? -} - -func (ms *messageSet) Marshal(pb Message) error { - msg, err := Marshal(pb) - if err != nil { - return err - } - if item := ms.find(pb); item != nil { - // reuse existing item - item.Message = msg - return nil - } - - mti, ok := pb.(messageTypeIder) - if !ok { - return errNoMessageTypeID - } - - mtid := mti.MessageTypeId() - ms.Item = append(ms.Item, &_MessageSet_Item{ - TypeId: &mtid, - Message: msg, - }) - return nil -} - -func (ms *messageSet) Reset() { *ms = messageSet{} } -func (ms *messageSet) String() string { return CompactTextString(ms) } -func (*messageSet) ProtoMessage() {} - -// Support for the message_set_wire_format message option. - -func skipVarint(buf []byte) []byte { - i := 0 - for ; buf[i]&0x80 != 0; i++ { - } - return buf[i+1:] -} - -// MarshalMessageSet encodes the extension map represented by m in the message set wire format. -// It is called by generated Marshal methods on protocol buffer messages with the message_set_wire_format option. -func MarshalMessageSet(exts interface{}) ([]byte, error) { - var m map[int32]Extension - switch exts := exts.(type) { - case *XXX_InternalExtensions: - if err := encodeExtensions(exts); err != nil { - return nil, err - } - m, _ = exts.extensionsRead() - case map[int32]Extension: - if err := encodeExtensionsMap(exts); err != nil { - return nil, err - } - m = exts - default: - return nil, errors.New("proto: not an extension map") - } - - // Sort extension IDs to provide a deterministic encoding. - // See also enc_map in encode.go. - ids := make([]int, 0, len(m)) - for id := range m { - ids = append(ids, int(id)) - } - sort.Ints(ids) - - ms := &messageSet{Item: make([]*_MessageSet_Item, 0, len(m))} - for _, id := range ids { - e := m[int32(id)] - // Remove the wire type and field number varint, as well as the length varint. - msg := skipVarint(skipVarint(e.enc)) - - ms.Item = append(ms.Item, &_MessageSet_Item{ - TypeId: Int32(int32(id)), - Message: msg, - }) - } - return Marshal(ms) -} - -// UnmarshalMessageSet decodes the extension map encoded in buf in the message set wire format. -// It is called by generated Unmarshal methods on protocol buffer messages with the message_set_wire_format option. -func UnmarshalMessageSet(buf []byte, exts interface{}) error { - var m map[int32]Extension - switch exts := exts.(type) { - case *XXX_InternalExtensions: - m = exts.extensionsWrite() - case map[int32]Extension: - m = exts - default: - return errors.New("proto: not an extension map") - } - - ms := new(messageSet) - if err := Unmarshal(buf, ms); err != nil { - return err - } - for _, item := range ms.Item { - id := *item.TypeId - msg := item.Message - - // Restore wire type and field number varint, plus length varint. - // Be careful to preserve duplicate items. - b := EncodeVarint(uint64(id)<<3 | WireBytes) - if ext, ok := m[id]; ok { - // Existing data; rip off the tag and length varint - // so we join the new data correctly. - // We can assume that ext.enc is set because we are unmarshaling. - o := ext.enc[len(b):] // skip wire type and field number - _, n := DecodeVarint(o) // calculate length of length varint - o = o[n:] // skip length varint - msg = append(o, msg...) // join old data and new data - } - b = append(b, EncodeVarint(uint64(len(msg)))...) - b = append(b, msg...) - - m[id] = Extension{enc: b} - } - return nil -} - -// MarshalMessageSetJSON encodes the extension map represented by m in JSON format. -// It is called by generated MarshalJSON methods on protocol buffer messages with the message_set_wire_format option. -func MarshalMessageSetJSON(exts interface{}) ([]byte, error) { - var m map[int32]Extension - switch exts := exts.(type) { - case *XXX_InternalExtensions: - m, _ = exts.extensionsRead() - case map[int32]Extension: - m = exts - default: - return nil, errors.New("proto: not an extension map") - } - var b bytes.Buffer - b.WriteByte('{') - - // Process the map in key order for deterministic output. - ids := make([]int32, 0, len(m)) - for id := range m { - ids = append(ids, id) - } - sort.Sort(int32Slice(ids)) // int32Slice defined in text.go - - for i, id := range ids { - ext := m[id] - if i > 0 { - b.WriteByte(',') - } - - msd, ok := messageSetMap[id] - if !ok { - // Unknown type; we can't render it, so skip it. - continue - } - fmt.Fprintf(&b, `"[%s]":`, msd.name) - - x := ext.value - if x == nil { - x = reflect.New(msd.t.Elem()).Interface() - if err := Unmarshal(ext.enc, x.(Message)); err != nil { - return nil, err - } - } - d, err := json.Marshal(x) - if err != nil { - return nil, err - } - b.Write(d) - } - b.WriteByte('}') - return b.Bytes(), nil -} - -// UnmarshalMessageSetJSON decodes the extension map encoded in buf in JSON format. -// It is called by generated UnmarshalJSON methods on protocol buffer messages with the message_set_wire_format option. -func UnmarshalMessageSetJSON(buf []byte, exts interface{}) error { - // Common-case fast path. - if len(buf) == 0 || bytes.Equal(buf, []byte("{}")) { - return nil - } - - // This is fairly tricky, and it's not clear that it is needed. - return errors.New("TODO: UnmarshalMessageSetJSON not yet implemented") -} - -// A global registry of types that can be used in a MessageSet. - -var messageSetMap = make(map[int32]messageSetDesc) - -type messageSetDesc struct { - t reflect.Type // pointer to struct - name string -} - -// RegisterMessageSetType is called from the generated code. -func RegisterMessageSetType(m Message, fieldNum int32, name string) { - messageSetMap[fieldNum] = messageSetDesc{ - t: reflect.TypeOf(m), - name: name, - } -} diff --git a/vendor/github.com/golang/protobuf/proto/pointer_reflect.go b/vendor/github.com/golang/protobuf/proto/pointer_reflect.go deleted file mode 100644 index fb512e2e16d..00000000000 --- a/vendor/github.com/golang/protobuf/proto/pointer_reflect.go +++ /dev/null @@ -1,484 +0,0 @@ -// Go support for Protocol Buffers - Google's data interchange format -// -// Copyright 2012 The Go Authors. All rights reserved. -// https://github.com/golang/protobuf -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// +build appengine js - -// This file contains an implementation of proto field accesses using package reflect. -// It is slower than the code in pointer_unsafe.go but it avoids package unsafe and can -// be used on App Engine. - -package proto - -import ( - "math" - "reflect" -) - -// A structPointer is a pointer to a struct. -type structPointer struct { - v reflect.Value -} - -// toStructPointer returns a structPointer equivalent to the given reflect value. -// The reflect value must itself be a pointer to a struct. -func toStructPointer(v reflect.Value) structPointer { - return structPointer{v} -} - -// IsNil reports whether p is nil. -func structPointer_IsNil(p structPointer) bool { - return p.v.IsNil() -} - -// Interface returns the struct pointer as an interface value. -func structPointer_Interface(p structPointer, _ reflect.Type) interface{} { - return p.v.Interface() -} - -// A field identifies a field in a struct, accessible from a structPointer. -// In this implementation, a field is identified by the sequence of field indices -// passed to reflect's FieldByIndex. -type field []int - -// toField returns a field equivalent to the given reflect field. -func toField(f *reflect.StructField) field { - return f.Index -} - -// invalidField is an invalid field identifier. -var invalidField = field(nil) - -// IsValid reports whether the field identifier is valid. -func (f field) IsValid() bool { return f != nil } - -// field returns the given field in the struct as a reflect value. -func structPointer_field(p structPointer, f field) reflect.Value { - // Special case: an extension map entry with a value of type T - // passes a *T to the struct-handling code with a zero field, - // expecting that it will be treated as equivalent to *struct{ X T }, - // which has the same memory layout. We have to handle that case - // specially, because reflect will panic if we call FieldByIndex on a - // non-struct. - if f == nil { - return p.v.Elem() - } - - return p.v.Elem().FieldByIndex(f) -} - -// ifield returns the given field in the struct as an interface value. -func structPointer_ifield(p structPointer, f field) interface{} { - return structPointer_field(p, f).Addr().Interface() -} - -// Bytes returns the address of a []byte field in the struct. -func structPointer_Bytes(p structPointer, f field) *[]byte { - return structPointer_ifield(p, f).(*[]byte) -} - -// BytesSlice returns the address of a [][]byte field in the struct. -func structPointer_BytesSlice(p structPointer, f field) *[][]byte { - return structPointer_ifield(p, f).(*[][]byte) -} - -// Bool returns the address of a *bool field in the struct. -func structPointer_Bool(p structPointer, f field) **bool { - return structPointer_ifield(p, f).(**bool) -} - -// BoolVal returns the address of a bool field in the struct. -func structPointer_BoolVal(p structPointer, f field) *bool { - return structPointer_ifield(p, f).(*bool) -} - -// BoolSlice returns the address of a []bool field in the struct. -func structPointer_BoolSlice(p structPointer, f field) *[]bool { - return structPointer_ifield(p, f).(*[]bool) -} - -// String returns the address of a *string field in the struct. -func structPointer_String(p structPointer, f field) **string { - return structPointer_ifield(p, f).(**string) -} - -// StringVal returns the address of a string field in the struct. -func structPointer_StringVal(p structPointer, f field) *string { - return structPointer_ifield(p, f).(*string) -} - -// StringSlice returns the address of a []string field in the struct. -func structPointer_StringSlice(p structPointer, f field) *[]string { - return structPointer_ifield(p, f).(*[]string) -} - -// Extensions returns the address of an extension map field in the struct. -func structPointer_Extensions(p structPointer, f field) *XXX_InternalExtensions { - return structPointer_ifield(p, f).(*XXX_InternalExtensions) -} - -// ExtMap returns the address of an extension map field in the struct. -func structPointer_ExtMap(p structPointer, f field) *map[int32]Extension { - return structPointer_ifield(p, f).(*map[int32]Extension) -} - -// NewAt returns the reflect.Value for a pointer to a field in the struct. -func structPointer_NewAt(p structPointer, f field, typ reflect.Type) reflect.Value { - return structPointer_field(p, f).Addr() -} - -// SetStructPointer writes a *struct field in the struct. -func structPointer_SetStructPointer(p structPointer, f field, q structPointer) { - structPointer_field(p, f).Set(q.v) -} - -// GetStructPointer reads a *struct field in the struct. -func structPointer_GetStructPointer(p structPointer, f field) structPointer { - return structPointer{structPointer_field(p, f)} -} - -// StructPointerSlice the address of a []*struct field in the struct. -func structPointer_StructPointerSlice(p structPointer, f field) structPointerSlice { - return structPointerSlice{structPointer_field(p, f)} -} - -// A structPointerSlice represents the address of a slice of pointers to structs -// (themselves messages or groups). That is, v.Type() is *[]*struct{...}. -type structPointerSlice struct { - v reflect.Value -} - -func (p structPointerSlice) Len() int { return p.v.Len() } -func (p structPointerSlice) Index(i int) structPointer { return structPointer{p.v.Index(i)} } -func (p structPointerSlice) Append(q structPointer) { - p.v.Set(reflect.Append(p.v, q.v)) -} - -var ( - int32Type = reflect.TypeOf(int32(0)) - uint32Type = reflect.TypeOf(uint32(0)) - float32Type = reflect.TypeOf(float32(0)) - int64Type = reflect.TypeOf(int64(0)) - uint64Type = reflect.TypeOf(uint64(0)) - float64Type = reflect.TypeOf(float64(0)) -) - -// A word32 represents a field of type *int32, *uint32, *float32, or *enum. -// That is, v.Type() is *int32, *uint32, *float32, or *enum and v is assignable. -type word32 struct { - v reflect.Value -} - -// IsNil reports whether p is nil. -func word32_IsNil(p word32) bool { - return p.v.IsNil() -} - -// Set sets p to point at a newly allocated word with bits set to x. -func word32_Set(p word32, o *Buffer, x uint32) { - t := p.v.Type().Elem() - switch t { - case int32Type: - if len(o.int32s) == 0 { - o.int32s = make([]int32, uint32PoolSize) - } - o.int32s[0] = int32(x) - p.v.Set(reflect.ValueOf(&o.int32s[0])) - o.int32s = o.int32s[1:] - return - case uint32Type: - if len(o.uint32s) == 0 { - o.uint32s = make([]uint32, uint32PoolSize) - } - o.uint32s[0] = x - p.v.Set(reflect.ValueOf(&o.uint32s[0])) - o.uint32s = o.uint32s[1:] - return - case float32Type: - if len(o.float32s) == 0 { - o.float32s = make([]float32, uint32PoolSize) - } - o.float32s[0] = math.Float32frombits(x) - p.v.Set(reflect.ValueOf(&o.float32s[0])) - o.float32s = o.float32s[1:] - return - } - - // must be enum - p.v.Set(reflect.New(t)) - p.v.Elem().SetInt(int64(int32(x))) -} - -// Get gets the bits pointed at by p, as a uint32. -func word32_Get(p word32) uint32 { - elem := p.v.Elem() - switch elem.Kind() { - case reflect.Int32: - return uint32(elem.Int()) - case reflect.Uint32: - return uint32(elem.Uint()) - case reflect.Float32: - return math.Float32bits(float32(elem.Float())) - } - panic("unreachable") -} - -// Word32 returns a reference to a *int32, *uint32, *float32, or *enum field in the struct. -func structPointer_Word32(p structPointer, f field) word32 { - return word32{structPointer_field(p, f)} -} - -// A word32Val represents a field of type int32, uint32, float32, or enum. -// That is, v.Type() is int32, uint32, float32, or enum and v is assignable. -type word32Val struct { - v reflect.Value -} - -// Set sets *p to x. -func word32Val_Set(p word32Val, x uint32) { - switch p.v.Type() { - case int32Type: - p.v.SetInt(int64(x)) - return - case uint32Type: - p.v.SetUint(uint64(x)) - return - case float32Type: - p.v.SetFloat(float64(math.Float32frombits(x))) - return - } - - // must be enum - p.v.SetInt(int64(int32(x))) -} - -// Get gets the bits pointed at by p, as a uint32. -func word32Val_Get(p word32Val) uint32 { - elem := p.v - switch elem.Kind() { - case reflect.Int32: - return uint32(elem.Int()) - case reflect.Uint32: - return uint32(elem.Uint()) - case reflect.Float32: - return math.Float32bits(float32(elem.Float())) - } - panic("unreachable") -} - -// Word32Val returns a reference to a int32, uint32, float32, or enum field in the struct. -func structPointer_Word32Val(p structPointer, f field) word32Val { - return word32Val{structPointer_field(p, f)} -} - -// A word32Slice is a slice of 32-bit values. -// That is, v.Type() is []int32, []uint32, []float32, or []enum. -type word32Slice struct { - v reflect.Value -} - -func (p word32Slice) Append(x uint32) { - n, m := p.v.Len(), p.v.Cap() - if n < m { - p.v.SetLen(n + 1) - } else { - t := p.v.Type().Elem() - p.v.Set(reflect.Append(p.v, reflect.Zero(t))) - } - elem := p.v.Index(n) - switch elem.Kind() { - case reflect.Int32: - elem.SetInt(int64(int32(x))) - case reflect.Uint32: - elem.SetUint(uint64(x)) - case reflect.Float32: - elem.SetFloat(float64(math.Float32frombits(x))) - } -} - -func (p word32Slice) Len() int { - return p.v.Len() -} - -func (p word32Slice) Index(i int) uint32 { - elem := p.v.Index(i) - switch elem.Kind() { - case reflect.Int32: - return uint32(elem.Int()) - case reflect.Uint32: - return uint32(elem.Uint()) - case reflect.Float32: - return math.Float32bits(float32(elem.Float())) - } - panic("unreachable") -} - -// Word32Slice returns a reference to a []int32, []uint32, []float32, or []enum field in the struct. -func structPointer_Word32Slice(p structPointer, f field) word32Slice { - return word32Slice{structPointer_field(p, f)} -} - -// word64 is like word32 but for 64-bit values. -type word64 struct { - v reflect.Value -} - -func word64_Set(p word64, o *Buffer, x uint64) { - t := p.v.Type().Elem() - switch t { - case int64Type: - if len(o.int64s) == 0 { - o.int64s = make([]int64, uint64PoolSize) - } - o.int64s[0] = int64(x) - p.v.Set(reflect.ValueOf(&o.int64s[0])) - o.int64s = o.int64s[1:] - return - case uint64Type: - if len(o.uint64s) == 0 { - o.uint64s = make([]uint64, uint64PoolSize) - } - o.uint64s[0] = x - p.v.Set(reflect.ValueOf(&o.uint64s[0])) - o.uint64s = o.uint64s[1:] - return - case float64Type: - if len(o.float64s) == 0 { - o.float64s = make([]float64, uint64PoolSize) - } - o.float64s[0] = math.Float64frombits(x) - p.v.Set(reflect.ValueOf(&o.float64s[0])) - o.float64s = o.float64s[1:] - return - } - panic("unreachable") -} - -func word64_IsNil(p word64) bool { - return p.v.IsNil() -} - -func word64_Get(p word64) uint64 { - elem := p.v.Elem() - switch elem.Kind() { - case reflect.Int64: - return uint64(elem.Int()) - case reflect.Uint64: - return elem.Uint() - case reflect.Float64: - return math.Float64bits(elem.Float()) - } - panic("unreachable") -} - -func structPointer_Word64(p structPointer, f field) word64 { - return word64{structPointer_field(p, f)} -} - -// word64Val is like word32Val but for 64-bit values. -type word64Val struct { - v reflect.Value -} - -func word64Val_Set(p word64Val, o *Buffer, x uint64) { - switch p.v.Type() { - case int64Type: - p.v.SetInt(int64(x)) - return - case uint64Type: - p.v.SetUint(x) - return - case float64Type: - p.v.SetFloat(math.Float64frombits(x)) - return - } - panic("unreachable") -} - -func word64Val_Get(p word64Val) uint64 { - elem := p.v - switch elem.Kind() { - case reflect.Int64: - return uint64(elem.Int()) - case reflect.Uint64: - return elem.Uint() - case reflect.Float64: - return math.Float64bits(elem.Float()) - } - panic("unreachable") -} - -func structPointer_Word64Val(p structPointer, f field) word64Val { - return word64Val{structPointer_field(p, f)} -} - -type word64Slice struct { - v reflect.Value -} - -func (p word64Slice) Append(x uint64) { - n, m := p.v.Len(), p.v.Cap() - if n < m { - p.v.SetLen(n + 1) - } else { - t := p.v.Type().Elem() - p.v.Set(reflect.Append(p.v, reflect.Zero(t))) - } - elem := p.v.Index(n) - switch elem.Kind() { - case reflect.Int64: - elem.SetInt(int64(int64(x))) - case reflect.Uint64: - elem.SetUint(uint64(x)) - case reflect.Float64: - elem.SetFloat(float64(math.Float64frombits(x))) - } -} - -func (p word64Slice) Len() int { - return p.v.Len() -} - -func (p word64Slice) Index(i int) uint64 { - elem := p.v.Index(i) - switch elem.Kind() { - case reflect.Int64: - return uint64(elem.Int()) - case reflect.Uint64: - return uint64(elem.Uint()) - case reflect.Float64: - return math.Float64bits(float64(elem.Float())) - } - panic("unreachable") -} - -func structPointer_Word64Slice(p structPointer, f field) word64Slice { - return word64Slice{structPointer_field(p, f)} -} diff --git a/vendor/github.com/golang/protobuf/proto/pointer_unsafe.go b/vendor/github.com/golang/protobuf/proto/pointer_unsafe.go deleted file mode 100644 index 6b5567d47cd..00000000000 --- a/vendor/github.com/golang/protobuf/proto/pointer_unsafe.go +++ /dev/null @@ -1,270 +0,0 @@ -// Go support for Protocol Buffers - Google's data interchange format -// -// Copyright 2012 The Go Authors. All rights reserved. -// https://github.com/golang/protobuf -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// +build !appengine,!js - -// This file contains the implementation of the proto field accesses using package unsafe. - -package proto - -import ( - "reflect" - "unsafe" -) - -// NOTE: These type_Foo functions would more idiomatically be methods, -// but Go does not allow methods on pointer types, and we must preserve -// some pointer type for the garbage collector. We use these -// funcs with clunky names as our poor approximation to methods. -// -// An alternative would be -// type structPointer struct { p unsafe.Pointer } -// but that does not registerize as well. - -// A structPointer is a pointer to a struct. -type structPointer unsafe.Pointer - -// toStructPointer returns a structPointer equivalent to the given reflect value. -func toStructPointer(v reflect.Value) structPointer { - return structPointer(unsafe.Pointer(v.Pointer())) -} - -// IsNil reports whether p is nil. -func structPointer_IsNil(p structPointer) bool { - return p == nil -} - -// Interface returns the struct pointer, assumed to have element type t, -// as an interface value. -func structPointer_Interface(p structPointer, t reflect.Type) interface{} { - return reflect.NewAt(t, unsafe.Pointer(p)).Interface() -} - -// A field identifies a field in a struct, accessible from a structPointer. -// In this implementation, a field is identified by its byte offset from the start of the struct. -type field uintptr - -// toField returns a field equivalent to the given reflect field. -func toField(f *reflect.StructField) field { - return field(f.Offset) -} - -// invalidField is an invalid field identifier. -const invalidField = ^field(0) - -// IsValid reports whether the field identifier is valid. -func (f field) IsValid() bool { - return f != ^field(0) -} - -// Bytes returns the address of a []byte field in the struct. -func structPointer_Bytes(p structPointer, f field) *[]byte { - return (*[]byte)(unsafe.Pointer(uintptr(p) + uintptr(f))) -} - -// BytesSlice returns the address of a [][]byte field in the struct. -func structPointer_BytesSlice(p structPointer, f field) *[][]byte { - return (*[][]byte)(unsafe.Pointer(uintptr(p) + uintptr(f))) -} - -// Bool returns the address of a *bool field in the struct. -func structPointer_Bool(p structPointer, f field) **bool { - return (**bool)(unsafe.Pointer(uintptr(p) + uintptr(f))) -} - -// BoolVal returns the address of a bool field in the struct. -func structPointer_BoolVal(p structPointer, f field) *bool { - return (*bool)(unsafe.Pointer(uintptr(p) + uintptr(f))) -} - -// BoolSlice returns the address of a []bool field in the struct. -func structPointer_BoolSlice(p structPointer, f field) *[]bool { - return (*[]bool)(unsafe.Pointer(uintptr(p) + uintptr(f))) -} - -// String returns the address of a *string field in the struct. -func structPointer_String(p structPointer, f field) **string { - return (**string)(unsafe.Pointer(uintptr(p) + uintptr(f))) -} - -// StringVal returns the address of a string field in the struct. -func structPointer_StringVal(p structPointer, f field) *string { - return (*string)(unsafe.Pointer(uintptr(p) + uintptr(f))) -} - -// StringSlice returns the address of a []string field in the struct. -func structPointer_StringSlice(p structPointer, f field) *[]string { - return (*[]string)(unsafe.Pointer(uintptr(p) + uintptr(f))) -} - -// ExtMap returns the address of an extension map field in the struct. -func structPointer_Extensions(p structPointer, f field) *XXX_InternalExtensions { - return (*XXX_InternalExtensions)(unsafe.Pointer(uintptr(p) + uintptr(f))) -} - -func structPointer_ExtMap(p structPointer, f field) *map[int32]Extension { - return (*map[int32]Extension)(unsafe.Pointer(uintptr(p) + uintptr(f))) -} - -// NewAt returns the reflect.Value for a pointer to a field in the struct. -func structPointer_NewAt(p structPointer, f field, typ reflect.Type) reflect.Value { - return reflect.NewAt(typ, unsafe.Pointer(uintptr(p)+uintptr(f))) -} - -// SetStructPointer writes a *struct field in the struct. -func structPointer_SetStructPointer(p structPointer, f field, q structPointer) { - *(*structPointer)(unsafe.Pointer(uintptr(p) + uintptr(f))) = q -} - -// GetStructPointer reads a *struct field in the struct. -func structPointer_GetStructPointer(p structPointer, f field) structPointer { - return *(*structPointer)(unsafe.Pointer(uintptr(p) + uintptr(f))) -} - -// StructPointerSlice the address of a []*struct field in the struct. -func structPointer_StructPointerSlice(p structPointer, f field) *structPointerSlice { - return (*structPointerSlice)(unsafe.Pointer(uintptr(p) + uintptr(f))) -} - -// A structPointerSlice represents a slice of pointers to structs (themselves submessages or groups). -type structPointerSlice []structPointer - -func (v *structPointerSlice) Len() int { return len(*v) } -func (v *structPointerSlice) Index(i int) structPointer { return (*v)[i] } -func (v *structPointerSlice) Append(p structPointer) { *v = append(*v, p) } - -// A word32 is the address of a "pointer to 32-bit value" field. -type word32 **uint32 - -// IsNil reports whether *v is nil. -func word32_IsNil(p word32) bool { - return *p == nil -} - -// Set sets *v to point at a newly allocated word set to x. -func word32_Set(p word32, o *Buffer, x uint32) { - if len(o.uint32s) == 0 { - o.uint32s = make([]uint32, uint32PoolSize) - } - o.uint32s[0] = x - *p = &o.uint32s[0] - o.uint32s = o.uint32s[1:] -} - -// Get gets the value pointed at by *v. -func word32_Get(p word32) uint32 { - return **p -} - -// Word32 returns the address of a *int32, *uint32, *float32, or *enum field in the struct. -func structPointer_Word32(p structPointer, f field) word32 { - return word32((**uint32)(unsafe.Pointer(uintptr(p) + uintptr(f)))) -} - -// A word32Val is the address of a 32-bit value field. -type word32Val *uint32 - -// Set sets *p to x. -func word32Val_Set(p word32Val, x uint32) { - *p = x -} - -// Get gets the value pointed at by p. -func word32Val_Get(p word32Val) uint32 { - return *p -} - -// Word32Val returns the address of a *int32, *uint32, *float32, or *enum field in the struct. -func structPointer_Word32Val(p structPointer, f field) word32Val { - return word32Val((*uint32)(unsafe.Pointer(uintptr(p) + uintptr(f)))) -} - -// A word32Slice is a slice of 32-bit values. -type word32Slice []uint32 - -func (v *word32Slice) Append(x uint32) { *v = append(*v, x) } -func (v *word32Slice) Len() int { return len(*v) } -func (v *word32Slice) Index(i int) uint32 { return (*v)[i] } - -// Word32Slice returns the address of a []int32, []uint32, []float32, or []enum field in the struct. -func structPointer_Word32Slice(p structPointer, f field) *word32Slice { - return (*word32Slice)(unsafe.Pointer(uintptr(p) + uintptr(f))) -} - -// word64 is like word32 but for 64-bit values. -type word64 **uint64 - -func word64_Set(p word64, o *Buffer, x uint64) { - if len(o.uint64s) == 0 { - o.uint64s = make([]uint64, uint64PoolSize) - } - o.uint64s[0] = x - *p = &o.uint64s[0] - o.uint64s = o.uint64s[1:] -} - -func word64_IsNil(p word64) bool { - return *p == nil -} - -func word64_Get(p word64) uint64 { - return **p -} - -func structPointer_Word64(p structPointer, f field) word64 { - return word64((**uint64)(unsafe.Pointer(uintptr(p) + uintptr(f)))) -} - -// word64Val is like word32Val but for 64-bit values. -type word64Val *uint64 - -func word64Val_Set(p word64Val, o *Buffer, x uint64) { - *p = x -} - -func word64Val_Get(p word64Val) uint64 { - return *p -} - -func structPointer_Word64Val(p structPointer, f field) word64Val { - return word64Val((*uint64)(unsafe.Pointer(uintptr(p) + uintptr(f)))) -} - -// word64Slice is like word32Slice but for 64-bit values. -type word64Slice []uint64 - -func (v *word64Slice) Append(x uint64) { *v = append(*v, x) } -func (v *word64Slice) Len() int { return len(*v) } -func (v *word64Slice) Index(i int) uint64 { return (*v)[i] } - -func structPointer_Word64Slice(p structPointer, f field) *word64Slice { - return (*word64Slice)(unsafe.Pointer(uintptr(p) + uintptr(f))) -} diff --git a/vendor/github.com/golang/protobuf/proto/properties.go b/vendor/github.com/golang/protobuf/proto/properties.go deleted file mode 100644 index ec2289c0058..00000000000 --- a/vendor/github.com/golang/protobuf/proto/properties.go +++ /dev/null @@ -1,872 +0,0 @@ -// Go support for Protocol Buffers - Google's data interchange format -// -// Copyright 2010 The Go Authors. All rights reserved. -// https://github.com/golang/protobuf -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package proto - -/* - * Routines for encoding data into the wire format for protocol buffers. - */ - -import ( - "fmt" - "log" - "os" - "reflect" - "sort" - "strconv" - "strings" - "sync" -) - -const debug bool = false - -// Constants that identify the encoding of a value on the wire. -const ( - WireVarint = 0 - WireFixed64 = 1 - WireBytes = 2 - WireStartGroup = 3 - WireEndGroup = 4 - WireFixed32 = 5 -) - -const startSize = 10 // initial slice/string sizes - -// Encoders are defined in encode.go -// An encoder outputs the full representation of a field, including its -// tag and encoder type. -type encoder func(p *Buffer, prop *Properties, base structPointer) error - -// A valueEncoder encodes a single integer in a particular encoding. -type valueEncoder func(o *Buffer, x uint64) error - -// Sizers are defined in encode.go -// A sizer returns the encoded size of a field, including its tag and encoder -// type. -type sizer func(prop *Properties, base structPointer) int - -// A valueSizer returns the encoded size of a single integer in a particular -// encoding. -type valueSizer func(x uint64) int - -// Decoders are defined in decode.go -// A decoder creates a value from its wire representation. -// Unrecognized subelements are saved in unrec. -type decoder func(p *Buffer, prop *Properties, base structPointer) error - -// A valueDecoder decodes a single integer in a particular encoding. -type valueDecoder func(o *Buffer) (x uint64, err error) - -// A oneofMarshaler does the marshaling for all oneof fields in a message. -type oneofMarshaler func(Message, *Buffer) error - -// A oneofUnmarshaler does the unmarshaling for a oneof field in a message. -type oneofUnmarshaler func(Message, int, int, *Buffer) (bool, error) - -// A oneofSizer does the sizing for all oneof fields in a message. -type oneofSizer func(Message) int - -// tagMap is an optimization over map[int]int for typical protocol buffer -// use-cases. Encoded protocol buffers are often in tag order with small tag -// numbers. -type tagMap struct { - fastTags []int - slowTags map[int]int -} - -// tagMapFastLimit is the upper bound on the tag number that will be stored in -// the tagMap slice rather than its map. -const tagMapFastLimit = 1024 - -func (p *tagMap) get(t int) (int, bool) { - if t > 0 && t < tagMapFastLimit { - if t >= len(p.fastTags) { - return 0, false - } - fi := p.fastTags[t] - return fi, fi >= 0 - } - fi, ok := p.slowTags[t] - return fi, ok -} - -func (p *tagMap) put(t int, fi int) { - if t > 0 && t < tagMapFastLimit { - for len(p.fastTags) < t+1 { - p.fastTags = append(p.fastTags, -1) - } - p.fastTags[t] = fi - return - } - if p.slowTags == nil { - p.slowTags = make(map[int]int) - } - p.slowTags[t] = fi -} - -// StructProperties represents properties for all the fields of a struct. -// decoderTags and decoderOrigNames should only be used by the decoder. -type StructProperties struct { - Prop []*Properties // properties for each field - reqCount int // required count - decoderTags tagMap // map from proto tag to struct field number - decoderOrigNames map[string]int // map from original name to struct field number - order []int // list of struct field numbers in tag order - unrecField field // field id of the XXX_unrecognized []byte field - extendable bool // is this an extendable proto - - oneofMarshaler oneofMarshaler - oneofUnmarshaler oneofUnmarshaler - oneofSizer oneofSizer - stype reflect.Type - - // OneofTypes contains information about the oneof fields in this message. - // It is keyed by the original name of a field. - OneofTypes map[string]*OneofProperties -} - -// OneofProperties represents information about a specific field in a oneof. -type OneofProperties struct { - Type reflect.Type // pointer to generated struct type for this oneof field - Field int // struct field number of the containing oneof in the message - Prop *Properties -} - -// Implement the sorting interface so we can sort the fields in tag order, as recommended by the spec. -// See encode.go, (*Buffer).enc_struct. - -func (sp *StructProperties) Len() int { return len(sp.order) } -func (sp *StructProperties) Less(i, j int) bool { - return sp.Prop[sp.order[i]].Tag < sp.Prop[sp.order[j]].Tag -} -func (sp *StructProperties) Swap(i, j int) { sp.order[i], sp.order[j] = sp.order[j], sp.order[i] } - -// Properties represents the protocol-specific behavior of a single struct field. -type Properties struct { - Name string // name of the field, for error messages - OrigName string // original name before protocol compiler (always set) - JSONName string // name to use for JSON; determined by protoc - Wire string - WireType int - Tag int - Required bool - Optional bool - Repeated bool - Packed bool // relevant for repeated primitives only - Enum string // set for enum types only - proto3 bool // whether this is known to be a proto3 field; set for []byte only - oneof bool // whether this is a oneof field - - Default string // default value - HasDefault bool // whether an explicit default was provided - def_uint64 uint64 - - enc encoder - valEnc valueEncoder // set for bool and numeric types only - field field - tagcode []byte // encoding of EncodeVarint((Tag<<3)|WireType) - tagbuf [8]byte - stype reflect.Type // set for struct types only - sprop *StructProperties // set for struct types only - isMarshaler bool - isUnmarshaler bool - - mtype reflect.Type // set for map types only - mkeyprop *Properties // set for map types only - mvalprop *Properties // set for map types only - - size sizer - valSize valueSizer // set for bool and numeric types only - - dec decoder - valDec valueDecoder // set for bool and numeric types only - - // If this is a packable field, this will be the decoder for the packed version of the field. - packedDec decoder -} - -// String formats the properties in the protobuf struct field tag style. -func (p *Properties) String() string { - s := p.Wire - s = "," - s += strconv.Itoa(p.Tag) - if p.Required { - s += ",req" - } - if p.Optional { - s += ",opt" - } - if p.Repeated { - s += ",rep" - } - if p.Packed { - s += ",packed" - } - s += ",name=" + p.OrigName - if p.JSONName != p.OrigName { - s += ",json=" + p.JSONName - } - if p.proto3 { - s += ",proto3" - } - if p.oneof { - s += ",oneof" - } - if len(p.Enum) > 0 { - s += ",enum=" + p.Enum - } - if p.HasDefault { - s += ",def=" + p.Default - } - return s -} - -// Parse populates p by parsing a string in the protobuf struct field tag style. -func (p *Properties) Parse(s string) { - // "bytes,49,opt,name=foo,def=hello!" - fields := strings.Split(s, ",") // breaks def=, but handled below. - if len(fields) < 2 { - fmt.Fprintf(os.Stderr, "proto: tag has too few fields: %q\n", s) - return - } - - p.Wire = fields[0] - switch p.Wire { - case "varint": - p.WireType = WireVarint - p.valEnc = (*Buffer).EncodeVarint - p.valDec = (*Buffer).DecodeVarint - p.valSize = sizeVarint - case "fixed32": - p.WireType = WireFixed32 - p.valEnc = (*Buffer).EncodeFixed32 - p.valDec = (*Buffer).DecodeFixed32 - p.valSize = sizeFixed32 - case "fixed64": - p.WireType = WireFixed64 - p.valEnc = (*Buffer).EncodeFixed64 - p.valDec = (*Buffer).DecodeFixed64 - p.valSize = sizeFixed64 - case "zigzag32": - p.WireType = WireVarint - p.valEnc = (*Buffer).EncodeZigzag32 - p.valDec = (*Buffer).DecodeZigzag32 - p.valSize = sizeZigzag32 - case "zigzag64": - p.WireType = WireVarint - p.valEnc = (*Buffer).EncodeZigzag64 - p.valDec = (*Buffer).DecodeZigzag64 - p.valSize = sizeZigzag64 - case "bytes", "group": - p.WireType = WireBytes - // no numeric converter for non-numeric types - default: - fmt.Fprintf(os.Stderr, "proto: tag has unknown wire type: %q\n", s) - return - } - - var err error - p.Tag, err = strconv.Atoi(fields[1]) - if err != nil { - return - } - - for i := 2; i < len(fields); i++ { - f := fields[i] - switch { - case f == "req": - p.Required = true - case f == "opt": - p.Optional = true - case f == "rep": - p.Repeated = true - case f == "packed": - p.Packed = true - case strings.HasPrefix(f, "name="): - p.OrigName = f[5:] - case strings.HasPrefix(f, "json="): - p.JSONName = f[5:] - case strings.HasPrefix(f, "enum="): - p.Enum = f[5:] - case f == "proto3": - p.proto3 = true - case f == "oneof": - p.oneof = true - case strings.HasPrefix(f, "def="): - p.HasDefault = true - p.Default = f[4:] // rest of string - if i+1 < len(fields) { - // Commas aren't escaped, and def is always last. - p.Default += "," + strings.Join(fields[i+1:], ",") - break - } - } - } -} - -func logNoSliceEnc(t1, t2 reflect.Type) { - fmt.Fprintf(os.Stderr, "proto: no slice oenc for %T = []%T\n", t1, t2) -} - -var protoMessageType = reflect.TypeOf((*Message)(nil)).Elem() - -// Initialize the fields for encoding and decoding. -func (p *Properties) setEncAndDec(typ reflect.Type, f *reflect.StructField, lockGetProp bool) { - p.enc = nil - p.dec = nil - p.size = nil - - switch t1 := typ; t1.Kind() { - default: - fmt.Fprintf(os.Stderr, "proto: no coders for %v\n", t1) - - // proto3 scalar types - - case reflect.Bool: - p.enc = (*Buffer).enc_proto3_bool - p.dec = (*Buffer).dec_proto3_bool - p.size = size_proto3_bool - case reflect.Int32: - p.enc = (*Buffer).enc_proto3_int32 - p.dec = (*Buffer).dec_proto3_int32 - p.size = size_proto3_int32 - case reflect.Uint32: - p.enc = (*Buffer).enc_proto3_uint32 - p.dec = (*Buffer).dec_proto3_int32 // can reuse - p.size = size_proto3_uint32 - case reflect.Int64, reflect.Uint64: - p.enc = (*Buffer).enc_proto3_int64 - p.dec = (*Buffer).dec_proto3_int64 - p.size = size_proto3_int64 - case reflect.Float32: - p.enc = (*Buffer).enc_proto3_uint32 // can just treat them as bits - p.dec = (*Buffer).dec_proto3_int32 - p.size = size_proto3_uint32 - case reflect.Float64: - p.enc = (*Buffer).enc_proto3_int64 // can just treat them as bits - p.dec = (*Buffer).dec_proto3_int64 - p.size = size_proto3_int64 - case reflect.String: - p.enc = (*Buffer).enc_proto3_string - p.dec = (*Buffer).dec_proto3_string - p.size = size_proto3_string - - case reflect.Ptr: - switch t2 := t1.Elem(); t2.Kind() { - default: - fmt.Fprintf(os.Stderr, "proto: no encoder function for %v -> %v\n", t1, t2) - break - case reflect.Bool: - p.enc = (*Buffer).enc_bool - p.dec = (*Buffer).dec_bool - p.size = size_bool - case reflect.Int32: - p.enc = (*Buffer).enc_int32 - p.dec = (*Buffer).dec_int32 - p.size = size_int32 - case reflect.Uint32: - p.enc = (*Buffer).enc_uint32 - p.dec = (*Buffer).dec_int32 // can reuse - p.size = size_uint32 - case reflect.Int64, reflect.Uint64: - p.enc = (*Buffer).enc_int64 - p.dec = (*Buffer).dec_int64 - p.size = size_int64 - case reflect.Float32: - p.enc = (*Buffer).enc_uint32 // can just treat them as bits - p.dec = (*Buffer).dec_int32 - p.size = size_uint32 - case reflect.Float64: - p.enc = (*Buffer).enc_int64 // can just treat them as bits - p.dec = (*Buffer).dec_int64 - p.size = size_int64 - case reflect.String: - p.enc = (*Buffer).enc_string - p.dec = (*Buffer).dec_string - p.size = size_string - case reflect.Struct: - p.stype = t1.Elem() - p.isMarshaler = isMarshaler(t1) - p.isUnmarshaler = isUnmarshaler(t1) - if p.Wire == "bytes" { - p.enc = (*Buffer).enc_struct_message - p.dec = (*Buffer).dec_struct_message - p.size = size_struct_message - } else { - p.enc = (*Buffer).enc_struct_group - p.dec = (*Buffer).dec_struct_group - p.size = size_struct_group - } - } - - case reflect.Slice: - switch t2 := t1.Elem(); t2.Kind() { - default: - logNoSliceEnc(t1, t2) - break - case reflect.Bool: - if p.Packed { - p.enc = (*Buffer).enc_slice_packed_bool - p.size = size_slice_packed_bool - } else { - p.enc = (*Buffer).enc_slice_bool - p.size = size_slice_bool - } - p.dec = (*Buffer).dec_slice_bool - p.packedDec = (*Buffer).dec_slice_packed_bool - case reflect.Int32: - if p.Packed { - p.enc = (*Buffer).enc_slice_packed_int32 - p.size = size_slice_packed_int32 - } else { - p.enc = (*Buffer).enc_slice_int32 - p.size = size_slice_int32 - } - p.dec = (*Buffer).dec_slice_int32 - p.packedDec = (*Buffer).dec_slice_packed_int32 - case reflect.Uint32: - if p.Packed { - p.enc = (*Buffer).enc_slice_packed_uint32 - p.size = size_slice_packed_uint32 - } else { - p.enc = (*Buffer).enc_slice_uint32 - p.size = size_slice_uint32 - } - p.dec = (*Buffer).dec_slice_int32 - p.packedDec = (*Buffer).dec_slice_packed_int32 - case reflect.Int64, reflect.Uint64: - if p.Packed { - p.enc = (*Buffer).enc_slice_packed_int64 - p.size = size_slice_packed_int64 - } else { - p.enc = (*Buffer).enc_slice_int64 - p.size = size_slice_int64 - } - p.dec = (*Buffer).dec_slice_int64 - p.packedDec = (*Buffer).dec_slice_packed_int64 - case reflect.Uint8: - p.dec = (*Buffer).dec_slice_byte - if p.proto3 { - p.enc = (*Buffer).enc_proto3_slice_byte - p.size = size_proto3_slice_byte - } else { - p.enc = (*Buffer).enc_slice_byte - p.size = size_slice_byte - } - case reflect.Float32, reflect.Float64: - switch t2.Bits() { - case 32: - // can just treat them as bits - if p.Packed { - p.enc = (*Buffer).enc_slice_packed_uint32 - p.size = size_slice_packed_uint32 - } else { - p.enc = (*Buffer).enc_slice_uint32 - p.size = size_slice_uint32 - } - p.dec = (*Buffer).dec_slice_int32 - p.packedDec = (*Buffer).dec_slice_packed_int32 - case 64: - // can just treat them as bits - if p.Packed { - p.enc = (*Buffer).enc_slice_packed_int64 - p.size = size_slice_packed_int64 - } else { - p.enc = (*Buffer).enc_slice_int64 - p.size = size_slice_int64 - } - p.dec = (*Buffer).dec_slice_int64 - p.packedDec = (*Buffer).dec_slice_packed_int64 - default: - logNoSliceEnc(t1, t2) - break - } - case reflect.String: - p.enc = (*Buffer).enc_slice_string - p.dec = (*Buffer).dec_slice_string - p.size = size_slice_string - case reflect.Ptr: - switch t3 := t2.Elem(); t3.Kind() { - default: - fmt.Fprintf(os.Stderr, "proto: no ptr oenc for %T -> %T -> %T\n", t1, t2, t3) - break - case reflect.Struct: - p.stype = t2.Elem() - p.isMarshaler = isMarshaler(t2) - p.isUnmarshaler = isUnmarshaler(t2) - if p.Wire == "bytes" { - p.enc = (*Buffer).enc_slice_struct_message - p.dec = (*Buffer).dec_slice_struct_message - p.size = size_slice_struct_message - } else { - p.enc = (*Buffer).enc_slice_struct_group - p.dec = (*Buffer).dec_slice_struct_group - p.size = size_slice_struct_group - } - } - case reflect.Slice: - switch t2.Elem().Kind() { - default: - fmt.Fprintf(os.Stderr, "proto: no slice elem oenc for %T -> %T -> %T\n", t1, t2, t2.Elem()) - break - case reflect.Uint8: - p.enc = (*Buffer).enc_slice_slice_byte - p.dec = (*Buffer).dec_slice_slice_byte - p.size = size_slice_slice_byte - } - } - - case reflect.Map: - p.enc = (*Buffer).enc_new_map - p.dec = (*Buffer).dec_new_map - p.size = size_new_map - - p.mtype = t1 - p.mkeyprop = &Properties{} - p.mkeyprop.init(reflect.PtrTo(p.mtype.Key()), "Key", f.Tag.Get("protobuf_key"), nil, lockGetProp) - p.mvalprop = &Properties{} - vtype := p.mtype.Elem() - if vtype.Kind() != reflect.Ptr && vtype.Kind() != reflect.Slice { - // The value type is not a message (*T) or bytes ([]byte), - // so we need encoders for the pointer to this type. - vtype = reflect.PtrTo(vtype) - } - p.mvalprop.init(vtype, "Value", f.Tag.Get("protobuf_val"), nil, lockGetProp) - } - - // precalculate tag code - wire := p.WireType - if p.Packed { - wire = WireBytes - } - x := uint32(p.Tag)<<3 | uint32(wire) - i := 0 - for i = 0; x > 127; i++ { - p.tagbuf[i] = 0x80 | uint8(x&0x7F) - x >>= 7 - } - p.tagbuf[i] = uint8(x) - p.tagcode = p.tagbuf[0 : i+1] - - if p.stype != nil { - if lockGetProp { - p.sprop = GetProperties(p.stype) - } else { - p.sprop = getPropertiesLocked(p.stype) - } - } -} - -var ( - marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem() - unmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem() -) - -// isMarshaler reports whether type t implements Marshaler. -func isMarshaler(t reflect.Type) bool { - // We're checking for (likely) pointer-receiver methods - // so if t is not a pointer, something is very wrong. - // The calls above only invoke isMarshaler on pointer types. - if t.Kind() != reflect.Ptr { - panic("proto: misuse of isMarshaler") - } - return t.Implements(marshalerType) -} - -// isUnmarshaler reports whether type t implements Unmarshaler. -func isUnmarshaler(t reflect.Type) bool { - // We're checking for (likely) pointer-receiver methods - // so if t is not a pointer, something is very wrong. - // The calls above only invoke isUnmarshaler on pointer types. - if t.Kind() != reflect.Ptr { - panic("proto: misuse of isUnmarshaler") - } - return t.Implements(unmarshalerType) -} - -// Init populates the properties from a protocol buffer struct tag. -func (p *Properties) Init(typ reflect.Type, name, tag string, f *reflect.StructField) { - p.init(typ, name, tag, f, true) -} - -func (p *Properties) init(typ reflect.Type, name, tag string, f *reflect.StructField, lockGetProp bool) { - // "bytes,49,opt,def=hello!" - p.Name = name - p.OrigName = name - if f != nil { - p.field = toField(f) - } - if tag == "" { - return - } - p.Parse(tag) - p.setEncAndDec(typ, f, lockGetProp) -} - -var ( - propertiesMu sync.RWMutex - propertiesMap = make(map[reflect.Type]*StructProperties) -) - -// GetProperties returns the list of properties for the type represented by t. -// t must represent a generated struct type of a protocol message. -func GetProperties(t reflect.Type) *StructProperties { - if t.Kind() != reflect.Struct { - panic("proto: type must have kind struct") - } - - // Most calls to GetProperties in a long-running program will be - // retrieving details for types we have seen before. - propertiesMu.RLock() - sprop, ok := propertiesMap[t] - propertiesMu.RUnlock() - if ok { - if collectStats { - stats.Chit++ - } - return sprop - } - - propertiesMu.Lock() - sprop = getPropertiesLocked(t) - propertiesMu.Unlock() - return sprop -} - -// getPropertiesLocked requires that propertiesMu is held. -func getPropertiesLocked(t reflect.Type) *StructProperties { - if prop, ok := propertiesMap[t]; ok { - if collectStats { - stats.Chit++ - } - return prop - } - if collectStats { - stats.Cmiss++ - } - - prop := new(StructProperties) - // in case of recursive protos, fill this in now. - propertiesMap[t] = prop - - // build properties - prop.extendable = reflect.PtrTo(t).Implements(extendableProtoType) || - reflect.PtrTo(t).Implements(extendableProtoV1Type) - prop.unrecField = invalidField - prop.Prop = make([]*Properties, t.NumField()) - prop.order = make([]int, t.NumField()) - - for i := 0; i < t.NumField(); i++ { - f := t.Field(i) - p := new(Properties) - name := f.Name - p.init(f.Type, name, f.Tag.Get("protobuf"), &f, false) - - if f.Name == "XXX_InternalExtensions" { // special case - p.enc = (*Buffer).enc_exts - p.dec = nil // not needed - p.size = size_exts - } else if f.Name == "XXX_extensions" { // special case - p.enc = (*Buffer).enc_map - p.dec = nil // not needed - p.size = size_map - } else if f.Name == "XXX_unrecognized" { // special case - prop.unrecField = toField(&f) - } - oneof := f.Tag.Get("protobuf_oneof") // special case - if oneof != "" { - // Oneof fields don't use the traditional protobuf tag. - p.OrigName = oneof - } - prop.Prop[i] = p - prop.order[i] = i - if debug { - print(i, " ", f.Name, " ", t.String(), " ") - if p.Tag > 0 { - print(p.String()) - } - print("\n") - } - if p.enc == nil && !strings.HasPrefix(f.Name, "XXX_") && oneof == "" { - fmt.Fprintln(os.Stderr, "proto: no encoder for", f.Name, f.Type.String(), "[GetProperties]") - } - } - - // Re-order prop.order. - sort.Sort(prop) - - type oneofMessage interface { - XXX_OneofFuncs() (func(Message, *Buffer) error, func(Message, int, int, *Buffer) (bool, error), func(Message) int, []interface{}) - } - if om, ok := reflect.Zero(reflect.PtrTo(t)).Interface().(oneofMessage); ok { - var oots []interface{} - prop.oneofMarshaler, prop.oneofUnmarshaler, prop.oneofSizer, oots = om.XXX_OneofFuncs() - prop.stype = t - - // Interpret oneof metadata. - prop.OneofTypes = make(map[string]*OneofProperties) - for _, oot := range oots { - oop := &OneofProperties{ - Type: reflect.ValueOf(oot).Type(), // *T - Prop: new(Properties), - } - sft := oop.Type.Elem().Field(0) - oop.Prop.Name = sft.Name - oop.Prop.Parse(sft.Tag.Get("protobuf")) - // There will be exactly one interface field that - // this new value is assignable to. - for i := 0; i < t.NumField(); i++ { - f := t.Field(i) - if f.Type.Kind() != reflect.Interface { - continue - } - if !oop.Type.AssignableTo(f.Type) { - continue - } - oop.Field = i - break - } - prop.OneofTypes[oop.Prop.OrigName] = oop - } - } - - // build required counts - // build tags - reqCount := 0 - prop.decoderOrigNames = make(map[string]int) - for i, p := range prop.Prop { - if strings.HasPrefix(p.Name, "XXX_") { - // Internal fields should not appear in tags/origNames maps. - // They are handled specially when encoding and decoding. - continue - } - if p.Required { - reqCount++ - } - prop.decoderTags.put(p.Tag, i) - prop.decoderOrigNames[p.OrigName] = i - } - prop.reqCount = reqCount - - return prop -} - -// Return the Properties object for the x[0]'th field of the structure. -func propByIndex(t reflect.Type, x []int) *Properties { - if len(x) != 1 { - fmt.Fprintf(os.Stderr, "proto: field index dimension %d (not 1) for type %s\n", len(x), t) - return nil - } - prop := GetProperties(t) - return prop.Prop[x[0]] -} - -// Get the address and type of a pointer to a struct from an interface. -func getbase(pb Message) (t reflect.Type, b structPointer, err error) { - if pb == nil { - err = ErrNil - return - } - // get the reflect type of the pointer to the struct. - t = reflect.TypeOf(pb) - // get the address of the struct. - value := reflect.ValueOf(pb) - b = toStructPointer(value) - return -} - -// A global registry of enum types. -// The generated code will register the generated maps by calling RegisterEnum. - -var enumValueMaps = make(map[string]map[string]int32) - -// RegisterEnum is called from the generated code to install the enum descriptor -// maps into the global table to aid parsing text format protocol buffers. -func RegisterEnum(typeName string, unusedNameMap map[int32]string, valueMap map[string]int32) { - if _, ok := enumValueMaps[typeName]; ok { - panic("proto: duplicate enum registered: " + typeName) - } - enumValueMaps[typeName] = valueMap -} - -// EnumValueMap returns the mapping from names to integers of the -// enum type enumType, or a nil if not found. -func EnumValueMap(enumType string) map[string]int32 { - return enumValueMaps[enumType] -} - -// A registry of all linked message types. -// The string is a fully-qualified proto name ("pkg.Message"). -var ( - protoTypes = make(map[string]reflect.Type) - revProtoTypes = make(map[reflect.Type]string) -) - -// RegisterType is called from generated code and maps from the fully qualified -// proto name to the type (pointer to struct) of the protocol buffer. -func RegisterType(x Message, name string) { - if _, ok := protoTypes[name]; ok { - // TODO: Some day, make this a panic. - log.Printf("proto: duplicate proto type registered: %s", name) - return - } - t := reflect.TypeOf(x) - protoTypes[name] = t - revProtoTypes[t] = name -} - -// MessageName returns the fully-qualified proto name for the given message type. -func MessageName(x Message) string { - type xname interface { - XXX_MessageName() string - } - if m, ok := x.(xname); ok { - return m.XXX_MessageName() - } - return revProtoTypes[reflect.TypeOf(x)] -} - -// MessageType returns the message type (pointer to struct) for a named message. -func MessageType(name string) reflect.Type { return protoTypes[name] } - -// A registry of all linked proto files. -var ( - protoFiles = make(map[string][]byte) // file name => fileDescriptor -) - -// RegisterFile is called from generated code and maps from the -// full file name of a .proto file to its compressed FileDescriptorProto. -func RegisterFile(filename string, fileDescriptor []byte) { - protoFiles[filename] = fileDescriptor -} - -// FileDescriptor returns the compressed FileDescriptorProto for a .proto file. -func FileDescriptor(filename string) []byte { return protoFiles[filename] } diff --git a/vendor/github.com/golang/protobuf/proto/proto3_proto/proto3.pb.go b/vendor/github.com/golang/protobuf/proto/proto3_proto/proto3.pb.go deleted file mode 100644 index cc4d0489f13..00000000000 --- a/vendor/github.com/golang/protobuf/proto/proto3_proto/proto3.pb.go +++ /dev/null @@ -1,347 +0,0 @@ -// Code generated by protoc-gen-go. -// source: proto3_proto/proto3.proto -// DO NOT EDIT! - -/* -Package proto3_proto is a generated protocol buffer package. - -It is generated from these files: - proto3_proto/proto3.proto - -It has these top-level messages: - Message - Nested - MessageWithMap - IntMap - IntMaps -*/ -package proto3_proto - -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" -import google_protobuf "github.com/golang/protobuf/ptypes/any" -import testdata "github.com/golang/protobuf/proto/testdata" - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package - -type Message_Humour int32 - -const ( - Message_UNKNOWN Message_Humour = 0 - Message_PUNS Message_Humour = 1 - Message_SLAPSTICK Message_Humour = 2 - Message_BILL_BAILEY Message_Humour = 3 -) - -var Message_Humour_name = map[int32]string{ - 0: "UNKNOWN", - 1: "PUNS", - 2: "SLAPSTICK", - 3: "BILL_BAILEY", -} -var Message_Humour_value = map[string]int32{ - "UNKNOWN": 0, - "PUNS": 1, - "SLAPSTICK": 2, - "BILL_BAILEY": 3, -} - -func (x Message_Humour) String() string { - return proto.EnumName(Message_Humour_name, int32(x)) -} -func (Message_Humour) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 0} } - -type Message struct { - Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` - Hilarity Message_Humour `protobuf:"varint,2,opt,name=hilarity,enum=proto3_proto.Message_Humour" json:"hilarity,omitempty"` - HeightInCm uint32 `protobuf:"varint,3,opt,name=height_in_cm,json=heightInCm" json:"height_in_cm,omitempty"` - Data []byte `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"` - ResultCount int64 `protobuf:"varint,7,opt,name=result_count,json=resultCount" json:"result_count,omitempty"` - TrueScotsman bool `protobuf:"varint,8,opt,name=true_scotsman,json=trueScotsman" json:"true_scotsman,omitempty"` - Score float32 `protobuf:"fixed32,9,opt,name=score" json:"score,omitempty"` - Key []uint64 `protobuf:"varint,5,rep,packed,name=key" json:"key,omitempty"` - ShortKey []int32 `protobuf:"varint,19,rep,packed,name=short_key,json=shortKey" json:"short_key,omitempty"` - Nested *Nested `protobuf:"bytes,6,opt,name=nested" json:"nested,omitempty"` - RFunny []Message_Humour `protobuf:"varint,16,rep,packed,name=r_funny,json=rFunny,enum=proto3_proto.Message_Humour" json:"r_funny,omitempty"` - Terrain map[string]*Nested `protobuf:"bytes,10,rep,name=terrain" json:"terrain,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - Proto2Field *testdata.SubDefaults `protobuf:"bytes,11,opt,name=proto2_field,json=proto2Field" json:"proto2_field,omitempty"` - Proto2Value map[string]*testdata.SubDefaults `protobuf:"bytes,13,rep,name=proto2_value,json=proto2Value" json:"proto2_value,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - Anything *google_protobuf.Any `protobuf:"bytes,14,opt,name=anything" json:"anything,omitempty"` - ManyThings []*google_protobuf.Any `protobuf:"bytes,15,rep,name=many_things,json=manyThings" json:"many_things,omitempty"` - Submessage *Message `protobuf:"bytes,17,opt,name=submessage" json:"submessage,omitempty"` - Children []*Message `protobuf:"bytes,18,rep,name=children" json:"children,omitempty"` -} - -func (m *Message) Reset() { *m = Message{} } -func (m *Message) String() string { return proto.CompactTextString(m) } -func (*Message) ProtoMessage() {} -func (*Message) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } - -func (m *Message) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -func (m *Message) GetHilarity() Message_Humour { - if m != nil { - return m.Hilarity - } - return Message_UNKNOWN -} - -func (m *Message) GetHeightInCm() uint32 { - if m != nil { - return m.HeightInCm - } - return 0 -} - -func (m *Message) GetData() []byte { - if m != nil { - return m.Data - } - return nil -} - -func (m *Message) GetResultCount() int64 { - if m != nil { - return m.ResultCount - } - return 0 -} - -func (m *Message) GetTrueScotsman() bool { - if m != nil { - return m.TrueScotsman - } - return false -} - -func (m *Message) GetScore() float32 { - if m != nil { - return m.Score - } - return 0 -} - -func (m *Message) GetKey() []uint64 { - if m != nil { - return m.Key - } - return nil -} - -func (m *Message) GetShortKey() []int32 { - if m != nil { - return m.ShortKey - } - return nil -} - -func (m *Message) GetNested() *Nested { - if m != nil { - return m.Nested - } - return nil -} - -func (m *Message) GetRFunny() []Message_Humour { - if m != nil { - return m.RFunny - } - return nil -} - -func (m *Message) GetTerrain() map[string]*Nested { - if m != nil { - return m.Terrain - } - return nil -} - -func (m *Message) GetProto2Field() *testdata.SubDefaults { - if m != nil { - return m.Proto2Field - } - return nil -} - -func (m *Message) GetProto2Value() map[string]*testdata.SubDefaults { - if m != nil { - return m.Proto2Value - } - return nil -} - -func (m *Message) GetAnything() *google_protobuf.Any { - if m != nil { - return m.Anything - } - return nil -} - -func (m *Message) GetManyThings() []*google_protobuf.Any { - if m != nil { - return m.ManyThings - } - return nil -} - -func (m *Message) GetSubmessage() *Message { - if m != nil { - return m.Submessage - } - return nil -} - -func (m *Message) GetChildren() []*Message { - if m != nil { - return m.Children - } - return nil -} - -type Nested struct { - Bunny string `protobuf:"bytes,1,opt,name=bunny" json:"bunny,omitempty"` - Cute bool `protobuf:"varint,2,opt,name=cute" json:"cute,omitempty"` -} - -func (m *Nested) Reset() { *m = Nested{} } -func (m *Nested) String() string { return proto.CompactTextString(m) } -func (*Nested) ProtoMessage() {} -func (*Nested) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } - -func (m *Nested) GetBunny() string { - if m != nil { - return m.Bunny - } - return "" -} - -func (m *Nested) GetCute() bool { - if m != nil { - return m.Cute - } - return false -} - -type MessageWithMap struct { - ByteMapping map[bool][]byte `protobuf:"bytes,1,rep,name=byte_mapping,json=byteMapping" json:"byte_mapping,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value,proto3"` -} - -func (m *MessageWithMap) Reset() { *m = MessageWithMap{} } -func (m *MessageWithMap) String() string { return proto.CompactTextString(m) } -func (*MessageWithMap) ProtoMessage() {} -func (*MessageWithMap) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } - -func (m *MessageWithMap) GetByteMapping() map[bool][]byte { - if m != nil { - return m.ByteMapping - } - return nil -} - -type IntMap struct { - Rtt map[int32]int32 `protobuf:"bytes,1,rep,name=rtt" json:"rtt,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` -} - -func (m *IntMap) Reset() { *m = IntMap{} } -func (m *IntMap) String() string { return proto.CompactTextString(m) } -func (*IntMap) ProtoMessage() {} -func (*IntMap) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } - -func (m *IntMap) GetRtt() map[int32]int32 { - if m != nil { - return m.Rtt - } - return nil -} - -type IntMaps struct { - Maps []*IntMap `protobuf:"bytes,1,rep,name=maps" json:"maps,omitempty"` -} - -func (m *IntMaps) Reset() { *m = IntMaps{} } -func (m *IntMaps) String() string { return proto.CompactTextString(m) } -func (*IntMaps) ProtoMessage() {} -func (*IntMaps) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } - -func (m *IntMaps) GetMaps() []*IntMap { - if m != nil { - return m.Maps - } - return nil -} - -func init() { - proto.RegisterType((*Message)(nil), "proto3_proto.Message") - proto.RegisterType((*Nested)(nil), "proto3_proto.Nested") - proto.RegisterType((*MessageWithMap)(nil), "proto3_proto.MessageWithMap") - proto.RegisterType((*IntMap)(nil), "proto3_proto.IntMap") - proto.RegisterType((*IntMaps)(nil), "proto3_proto.IntMaps") - proto.RegisterEnum("proto3_proto.Message_Humour", Message_Humour_name, Message_Humour_value) -} - -func init() { proto.RegisterFile("proto3_proto/proto3.proto", fileDescriptor0) } - -var fileDescriptor0 = []byte{ - // 733 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x53, 0x6d, 0x6f, 0xf3, 0x34, - 0x14, 0x25, 0x4d, 0x5f, 0xd2, 0x9b, 0x74, 0x0b, 0x5e, 0x91, 0xbc, 0x02, 0x52, 0x28, 0x12, 0x8a, - 0x78, 0x49, 0xa1, 0xd3, 0xd0, 0x84, 0x10, 0x68, 0x1b, 0x9b, 0xa8, 0xd6, 0x95, 0xca, 0xdd, 0x98, - 0xf8, 0x14, 0xa5, 0xad, 0xdb, 0x46, 0x34, 0x4e, 0x49, 0x1c, 0xa4, 0xfc, 0x1d, 0xfe, 0x28, 0x8f, - 0x6c, 0xa7, 0x5d, 0x36, 0x65, 0xcf, 0xf3, 0x29, 0xf6, 0xf1, 0xb9, 0xf7, 0x9c, 0x1c, 0x5f, 0xc3, - 0xe9, 0x2e, 0x89, 0x79, 0x7c, 0xe6, 0xcb, 0xcf, 0x40, 0x6d, 0x3c, 0xf9, 0x41, 0x56, 0xf9, 0xa8, - 0x77, 0xba, 0x8e, 0xe3, 0xf5, 0x96, 0x2a, 0xca, 0x3c, 0x5b, 0x0d, 0x02, 0x96, 0x2b, 0x62, 0xef, - 0x84, 0xd3, 0x94, 0x2f, 0x03, 0x1e, 0x0c, 0xc4, 0x42, 0x81, 0xfd, 0xff, 0x5b, 0xd0, 0xba, 0xa7, - 0x69, 0x1a, 0xac, 0x29, 0x42, 0x50, 0x67, 0x41, 0x44, 0xb1, 0xe6, 0x68, 0x6e, 0x9b, 0xc8, 0x35, - 0xba, 0x00, 0x63, 0x13, 0x6e, 0x83, 0x24, 0xe4, 0x39, 0xae, 0x39, 0x9a, 0x7b, 0x34, 0xfc, 0xcc, - 0x2b, 0x0b, 0x7a, 0x45, 0xb1, 0xf7, 0x7b, 0x16, 0xc5, 0x59, 0x42, 0x0e, 0x6c, 0xe4, 0x80, 0xb5, - 0xa1, 0xe1, 0x7a, 0xc3, 0xfd, 0x90, 0xf9, 0x8b, 0x08, 0xeb, 0x8e, 0xe6, 0x76, 0x08, 0x28, 0x6c, - 0xc4, 0xae, 0x23, 0xa1, 0x27, 0xec, 0xe0, 0xba, 0xa3, 0xb9, 0x16, 0x91, 0x6b, 0xf4, 0x05, 0x58, - 0x09, 0x4d, 0xb3, 0x2d, 0xf7, 0x17, 0x71, 0xc6, 0x38, 0x6e, 0x39, 0x9a, 0xab, 0x13, 0x53, 0x61, - 0xd7, 0x02, 0x42, 0x5f, 0x42, 0x87, 0x27, 0x19, 0xf5, 0xd3, 0x45, 0xcc, 0xd3, 0x28, 0x60, 0xd8, - 0x70, 0x34, 0xd7, 0x20, 0x96, 0x00, 0x67, 0x05, 0x86, 0xba, 0xd0, 0x48, 0x17, 0x71, 0x42, 0x71, - 0xdb, 0xd1, 0xdc, 0x1a, 0x51, 0x1b, 0x64, 0x83, 0xfe, 0x37, 0xcd, 0x71, 0xc3, 0xd1, 0xdd, 0x3a, - 0x11, 0x4b, 0xf4, 0x29, 0xb4, 0xd3, 0x4d, 0x9c, 0x70, 0x5f, 0xe0, 0x27, 0x8e, 0xee, 0x36, 0x88, - 0x21, 0x81, 0x3b, 0x9a, 0xa3, 0x6f, 0xa1, 0xc9, 0x68, 0xca, 0xe9, 0x12, 0x37, 0x1d, 0xcd, 0x35, - 0x87, 0xdd, 0x97, 0xbf, 0x3e, 0x91, 0x67, 0xa4, 0xe0, 0xa0, 0x73, 0x68, 0x25, 0xfe, 0x2a, 0x63, - 0x2c, 0xc7, 0xb6, 0xa3, 0x7f, 0x30, 0xa9, 0x66, 0x72, 0x2b, 0xb8, 0xe8, 0x67, 0x68, 0x71, 0x9a, - 0x24, 0x41, 0xc8, 0x30, 0x38, 0xba, 0x6b, 0x0e, 0xfb, 0xd5, 0x65, 0x0f, 0x8a, 0x74, 0xc3, 0x78, - 0x92, 0x93, 0x7d, 0x09, 0xba, 0x00, 0x75, 0xff, 0x43, 0x7f, 0x15, 0xd2, 0xed, 0x12, 0x9b, 0xd2, - 0xe8, 0x27, 0xde, 0xfe, 0xae, 0xbd, 0x59, 0x36, 0xff, 0x8d, 0xae, 0x82, 0x6c, 0xcb, 0x53, 0x62, - 0x2a, 0xea, 0xad, 0x60, 0xa2, 0xd1, 0xa1, 0xf2, 0xdf, 0x60, 0x9b, 0x51, 0xdc, 0x91, 0xe2, 0x5f, - 0x55, 0x8b, 0x4f, 0x25, 0xf3, 0x4f, 0x41, 0x54, 0x06, 0x8a, 0x56, 0x12, 0x41, 0xdf, 0x83, 0x11, - 0xb0, 0x9c, 0x6f, 0x42, 0xb6, 0xc6, 0x47, 0x45, 0x52, 0x6a, 0x0e, 0xbd, 0xfd, 0x1c, 0x7a, 0x97, - 0x2c, 0x27, 0x07, 0x16, 0x3a, 0x07, 0x33, 0x0a, 0x58, 0xee, 0xcb, 0x5d, 0x8a, 0x8f, 0xa5, 0x76, - 0x75, 0x11, 0x08, 0xe2, 0x83, 0xe4, 0xa1, 0x73, 0x80, 0x34, 0x9b, 0x47, 0xca, 0x14, 0xfe, 0xb8, - 0xf8, 0xd7, 0x2a, 0xc7, 0xa4, 0x44, 0x44, 0x3f, 0x80, 0xb1, 0xd8, 0x84, 0xdb, 0x65, 0x42, 0x19, - 0x46, 0x52, 0xea, 0x8d, 0xa2, 0x03, 0xad, 0x37, 0x05, 0xab, 0x1c, 0xf8, 0x7e, 0x72, 0xd4, 0xd3, - 0x90, 0x93, 0xf3, 0x35, 0x34, 0x54, 0x70, 0xb5, 0xf7, 0xcc, 0x86, 0xa2, 0xfc, 0x54, 0xbb, 0xd0, - 0x7a, 0x8f, 0x60, 0xbf, 0x4e, 0xb1, 0xa2, 0xeb, 0x37, 0x2f, 0xbb, 0xbe, 0x71, 0x91, 0xcf, 0x6d, - 0xfb, 0xbf, 0x42, 0x53, 0x0d, 0x14, 0x32, 0xa1, 0xf5, 0x38, 0xb9, 0x9b, 0xfc, 0xf1, 0x34, 0xb1, - 0x3f, 0x42, 0x06, 0xd4, 0xa7, 0x8f, 0x93, 0x99, 0xad, 0xa1, 0x0e, 0xb4, 0x67, 0xe3, 0xcb, 0xe9, - 0xec, 0x61, 0x74, 0x7d, 0x67, 0xd7, 0xd0, 0x31, 0x98, 0x57, 0xa3, 0xf1, 0xd8, 0xbf, 0xba, 0x1c, - 0x8d, 0x6f, 0xfe, 0xb2, 0xf5, 0xfe, 0x10, 0x9a, 0xca, 0xac, 0x78, 0x33, 0x73, 0x39, 0xbe, 0xca, - 0x8f, 0xda, 0x88, 0x57, 0xba, 0xc8, 0xb8, 0x32, 0x64, 0x10, 0xb9, 0xee, 0xff, 0xa7, 0xc1, 0x51, - 0x91, 0xd9, 0x53, 0xc8, 0x37, 0xf7, 0xc1, 0x0e, 0x4d, 0xc1, 0x9a, 0xe7, 0x9c, 0xfa, 0x51, 0xb0, - 0xdb, 0x89, 0x39, 0xd0, 0x64, 0xce, 0xdf, 0x55, 0xe6, 0x5c, 0xd4, 0x78, 0x57, 0x39, 0xa7, 0xf7, - 0x8a, 0x5f, 0x4c, 0xd5, 0xfc, 0x19, 0xe9, 0xfd, 0x02, 0xf6, 0x6b, 0x42, 0x39, 0x30, 0x43, 0x05, - 0xd6, 0x2d, 0x07, 0x66, 0x95, 0x93, 0xf9, 0x07, 0x9a, 0x23, 0xc6, 0x85, 0xb7, 0x01, 0xe8, 0x09, - 0xe7, 0x85, 0xa5, 0xcf, 0x5f, 0x5a, 0x52, 0x14, 0x8f, 0x70, 0xae, 0x2c, 0x08, 0x66, 0xef, 0x47, - 0x30, 0xf6, 0x40, 0x59, 0xb2, 0x51, 0x21, 0xd9, 0x28, 0x4b, 0x9e, 0x41, 0x4b, 0xf5, 0x4b, 0x91, - 0x0b, 0xf5, 0x28, 0xd8, 0xa5, 0x85, 0x68, 0xb7, 0x4a, 0x94, 0x48, 0xc6, 0xbc, 0xa9, 0x8e, 0xde, - 0x05, 0x00, 0x00, 0xff, 0xff, 0x75, 0x38, 0xad, 0x84, 0xe4, 0x05, 0x00, 0x00, -} diff --git a/vendor/github.com/golang/protobuf/proto/text.go b/vendor/github.com/golang/protobuf/proto/text.go deleted file mode 100644 index 965876bf033..00000000000 --- a/vendor/github.com/golang/protobuf/proto/text.go +++ /dev/null @@ -1,854 +0,0 @@ -// Go support for Protocol Buffers - Google's data interchange format -// -// Copyright 2010 The Go Authors. All rights reserved. -// https://github.com/golang/protobuf -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package proto - -// Functions for writing the text protocol buffer format. - -import ( - "bufio" - "bytes" - "encoding" - "errors" - "fmt" - "io" - "log" - "math" - "reflect" - "sort" - "strings" -) - -var ( - newline = []byte("\n") - spaces = []byte(" ") - gtNewline = []byte(">\n") - endBraceNewline = []byte("}\n") - backslashN = []byte{'\\', 'n'} - backslashR = []byte{'\\', 'r'} - backslashT = []byte{'\\', 't'} - backslashDQ = []byte{'\\', '"'} - backslashBS = []byte{'\\', '\\'} - posInf = []byte("inf") - negInf = []byte("-inf") - nan = []byte("nan") -) - -type writer interface { - io.Writer - WriteByte(byte) error -} - -// textWriter is an io.Writer that tracks its indentation level. -type textWriter struct { - ind int - complete bool // if the current position is a complete line - compact bool // whether to write out as a one-liner - w writer -} - -func (w *textWriter) WriteString(s string) (n int, err error) { - if !strings.Contains(s, "\n") { - if !w.compact && w.complete { - w.writeIndent() - } - w.complete = false - return io.WriteString(w.w, s) - } - // WriteString is typically called without newlines, so this - // codepath and its copy are rare. We copy to avoid - // duplicating all of Write's logic here. - return w.Write([]byte(s)) -} - -func (w *textWriter) Write(p []byte) (n int, err error) { - newlines := bytes.Count(p, newline) - if newlines == 0 { - if !w.compact && w.complete { - w.writeIndent() - } - n, err = w.w.Write(p) - w.complete = false - return n, err - } - - frags := bytes.SplitN(p, newline, newlines+1) - if w.compact { - for i, frag := range frags { - if i > 0 { - if err := w.w.WriteByte(' '); err != nil { - return n, err - } - n++ - } - nn, err := w.w.Write(frag) - n += nn - if err != nil { - return n, err - } - } - return n, nil - } - - for i, frag := range frags { - if w.complete { - w.writeIndent() - } - nn, err := w.w.Write(frag) - n += nn - if err != nil { - return n, err - } - if i+1 < len(frags) { - if err := w.w.WriteByte('\n'); err != nil { - return n, err - } - n++ - } - } - w.complete = len(frags[len(frags)-1]) == 0 - return n, nil -} - -func (w *textWriter) WriteByte(c byte) error { - if w.compact && c == '\n' { - c = ' ' - } - if !w.compact && w.complete { - w.writeIndent() - } - err := w.w.WriteByte(c) - w.complete = c == '\n' - return err -} - -func (w *textWriter) indent() { w.ind++ } - -func (w *textWriter) unindent() { - if w.ind == 0 { - log.Print("proto: textWriter unindented too far") - return - } - w.ind-- -} - -func writeName(w *textWriter, props *Properties) error { - if _, err := w.WriteString(props.OrigName); err != nil { - return err - } - if props.Wire != "group" { - return w.WriteByte(':') - } - return nil -} - -// raw is the interface satisfied by RawMessage. -type raw interface { - Bytes() []byte -} - -func requiresQuotes(u string) bool { - // When type URL contains any characters except [0-9A-Za-z./\-]*, it must be quoted. - for _, ch := range u { - switch { - case ch == '.' || ch == '/' || ch == '_': - continue - case '0' <= ch && ch <= '9': - continue - case 'A' <= ch && ch <= 'Z': - continue - case 'a' <= ch && ch <= 'z': - continue - default: - return true - } - } - return false -} - -// isAny reports whether sv is a google.protobuf.Any message -func isAny(sv reflect.Value) bool { - type wkt interface { - XXX_WellKnownType() string - } - t, ok := sv.Addr().Interface().(wkt) - return ok && t.XXX_WellKnownType() == "Any" -} - -// writeProto3Any writes an expanded google.protobuf.Any message. -// -// It returns (false, nil) if sv value can't be unmarshaled (e.g. because -// required messages are not linked in). -// -// It returns (true, error) when sv was written in expanded format or an error -// was encountered. -func (tm *TextMarshaler) writeProto3Any(w *textWriter, sv reflect.Value) (bool, error) { - turl := sv.FieldByName("TypeUrl") - val := sv.FieldByName("Value") - if !turl.IsValid() || !val.IsValid() { - return true, errors.New("proto: invalid google.protobuf.Any message") - } - - b, ok := val.Interface().([]byte) - if !ok { - return true, errors.New("proto: invalid google.protobuf.Any message") - } - - parts := strings.Split(turl.String(), "/") - mt := MessageType(parts[len(parts)-1]) - if mt == nil { - return false, nil - } - m := reflect.New(mt.Elem()) - if err := Unmarshal(b, m.Interface().(Message)); err != nil { - return false, nil - } - w.Write([]byte("[")) - u := turl.String() - if requiresQuotes(u) { - writeString(w, u) - } else { - w.Write([]byte(u)) - } - if w.compact { - w.Write([]byte("]:<")) - } else { - w.Write([]byte("]: <\n")) - w.ind++ - } - if err := tm.writeStruct(w, m.Elem()); err != nil { - return true, err - } - if w.compact { - w.Write([]byte("> ")) - } else { - w.ind-- - w.Write([]byte(">\n")) - } - return true, nil -} - -func (tm *TextMarshaler) writeStruct(w *textWriter, sv reflect.Value) error { - if tm.ExpandAny && isAny(sv) { - if canExpand, err := tm.writeProto3Any(w, sv); canExpand { - return err - } - } - st := sv.Type() - sprops := GetProperties(st) - for i := 0; i < sv.NumField(); i++ { - fv := sv.Field(i) - props := sprops.Prop[i] - name := st.Field(i).Name - - if strings.HasPrefix(name, "XXX_") { - // There are two XXX_ fields: - // XXX_unrecognized []byte - // XXX_extensions map[int32]proto.Extension - // The first is handled here; - // the second is handled at the bottom of this function. - if name == "XXX_unrecognized" && !fv.IsNil() { - if err := writeUnknownStruct(w, fv.Interface().([]byte)); err != nil { - return err - } - } - continue - } - if fv.Kind() == reflect.Ptr && fv.IsNil() { - // Field not filled in. This could be an optional field or - // a required field that wasn't filled in. Either way, there - // isn't anything we can show for it. - continue - } - if fv.Kind() == reflect.Slice && fv.IsNil() { - // Repeated field that is empty, or a bytes field that is unused. - continue - } - - if props.Repeated && fv.Kind() == reflect.Slice { - // Repeated field. - for j := 0; j < fv.Len(); j++ { - if err := writeName(w, props); err != nil { - return err - } - if !w.compact { - if err := w.WriteByte(' '); err != nil { - return err - } - } - v := fv.Index(j) - if v.Kind() == reflect.Ptr && v.IsNil() { - // A nil message in a repeated field is not valid, - // but we can handle that more gracefully than panicking. - if _, err := w.Write([]byte("\n")); err != nil { - return err - } - continue - } - if err := tm.writeAny(w, v, props); err != nil { - return err - } - if err := w.WriteByte('\n'); err != nil { - return err - } - } - continue - } - if fv.Kind() == reflect.Map { - // Map fields are rendered as a repeated struct with key/value fields. - keys := fv.MapKeys() - sort.Sort(mapKeys(keys)) - for _, key := range keys { - val := fv.MapIndex(key) - if err := writeName(w, props); err != nil { - return err - } - if !w.compact { - if err := w.WriteByte(' '); err != nil { - return err - } - } - // open struct - if err := w.WriteByte('<'); err != nil { - return err - } - if !w.compact { - if err := w.WriteByte('\n'); err != nil { - return err - } - } - w.indent() - // key - if _, err := w.WriteString("key:"); err != nil { - return err - } - if !w.compact { - if err := w.WriteByte(' '); err != nil { - return err - } - } - if err := tm.writeAny(w, key, props.mkeyprop); err != nil { - return err - } - if err := w.WriteByte('\n'); err != nil { - return err - } - // nil values aren't legal, but we can avoid panicking because of them. - if val.Kind() != reflect.Ptr || !val.IsNil() { - // value - if _, err := w.WriteString("value:"); err != nil { - return err - } - if !w.compact { - if err := w.WriteByte(' '); err != nil { - return err - } - } - if err := tm.writeAny(w, val, props.mvalprop); err != nil { - return err - } - if err := w.WriteByte('\n'); err != nil { - return err - } - } - // close struct - w.unindent() - if err := w.WriteByte('>'); err != nil { - return err - } - if err := w.WriteByte('\n'); err != nil { - return err - } - } - continue - } - if props.proto3 && fv.Kind() == reflect.Slice && fv.Len() == 0 { - // empty bytes field - continue - } - if fv.Kind() != reflect.Ptr && fv.Kind() != reflect.Slice { - // proto3 non-repeated scalar field; skip if zero value - if isProto3Zero(fv) { - continue - } - } - - if fv.Kind() == reflect.Interface { - // Check if it is a oneof. - if st.Field(i).Tag.Get("protobuf_oneof") != "" { - // fv is nil, or holds a pointer to generated struct. - // That generated struct has exactly one field, - // which has a protobuf struct tag. - if fv.IsNil() { - continue - } - inner := fv.Elem().Elem() // interface -> *T -> T - tag := inner.Type().Field(0).Tag.Get("protobuf") - props = new(Properties) // Overwrite the outer props var, but not its pointee. - props.Parse(tag) - // Write the value in the oneof, not the oneof itself. - fv = inner.Field(0) - - // Special case to cope with malformed messages gracefully: - // If the value in the oneof is a nil pointer, don't panic - // in writeAny. - if fv.Kind() == reflect.Ptr && fv.IsNil() { - // Use errors.New so writeAny won't render quotes. - msg := errors.New("/* nil */") - fv = reflect.ValueOf(&msg).Elem() - } - } - } - - if err := writeName(w, props); err != nil { - return err - } - if !w.compact { - if err := w.WriteByte(' '); err != nil { - return err - } - } - if b, ok := fv.Interface().(raw); ok { - if err := writeRaw(w, b.Bytes()); err != nil { - return err - } - continue - } - - // Enums have a String method, so writeAny will work fine. - if err := tm.writeAny(w, fv, props); err != nil { - return err - } - - if err := w.WriteByte('\n'); err != nil { - return err - } - } - - // Extensions (the XXX_extensions field). - pv := sv.Addr() - if _, ok := extendable(pv.Interface()); ok { - if err := tm.writeExtensions(w, pv); err != nil { - return err - } - } - - return nil -} - -// writeRaw writes an uninterpreted raw message. -func writeRaw(w *textWriter, b []byte) error { - if err := w.WriteByte('<'); err != nil { - return err - } - if !w.compact { - if err := w.WriteByte('\n'); err != nil { - return err - } - } - w.indent() - if err := writeUnknownStruct(w, b); err != nil { - return err - } - w.unindent() - if err := w.WriteByte('>'); err != nil { - return err - } - return nil -} - -// writeAny writes an arbitrary field. -func (tm *TextMarshaler) writeAny(w *textWriter, v reflect.Value, props *Properties) error { - v = reflect.Indirect(v) - - // Floats have special cases. - if v.Kind() == reflect.Float32 || v.Kind() == reflect.Float64 { - x := v.Float() - var b []byte - switch { - case math.IsInf(x, 1): - b = posInf - case math.IsInf(x, -1): - b = negInf - case math.IsNaN(x): - b = nan - } - if b != nil { - _, err := w.Write(b) - return err - } - // Other values are handled below. - } - - // We don't attempt to serialise every possible value type; only those - // that can occur in protocol buffers. - switch v.Kind() { - case reflect.Slice: - // Should only be a []byte; repeated fields are handled in writeStruct. - if err := writeString(w, string(v.Bytes())); err != nil { - return err - } - case reflect.String: - if err := writeString(w, v.String()); err != nil { - return err - } - case reflect.Struct: - // Required/optional group/message. - var bra, ket byte = '<', '>' - if props != nil && props.Wire == "group" { - bra, ket = '{', '}' - } - if err := w.WriteByte(bra); err != nil { - return err - } - if !w.compact { - if err := w.WriteByte('\n'); err != nil { - return err - } - } - w.indent() - if etm, ok := v.Interface().(encoding.TextMarshaler); ok { - text, err := etm.MarshalText() - if err != nil { - return err - } - if _, err = w.Write(text); err != nil { - return err - } - } else if err := tm.writeStruct(w, v); err != nil { - return err - } - w.unindent() - if err := w.WriteByte(ket); err != nil { - return err - } - default: - _, err := fmt.Fprint(w, v.Interface()) - return err - } - return nil -} - -// equivalent to C's isprint. -func isprint(c byte) bool { - return c >= 0x20 && c < 0x7f -} - -// writeString writes a string in the protocol buffer text format. -// It is similar to strconv.Quote except we don't use Go escape sequences, -// we treat the string as a byte sequence, and we use octal escapes. -// These differences are to maintain interoperability with the other -// languages' implementations of the text format. -func writeString(w *textWriter, s string) error { - // use WriteByte here to get any needed indent - if err := w.WriteByte('"'); err != nil { - return err - } - // Loop over the bytes, not the runes. - for i := 0; i < len(s); i++ { - var err error - // Divergence from C++: we don't escape apostrophes. - // There's no need to escape them, and the C++ parser - // copes with a naked apostrophe. - switch c := s[i]; c { - case '\n': - _, err = w.w.Write(backslashN) - case '\r': - _, err = w.w.Write(backslashR) - case '\t': - _, err = w.w.Write(backslashT) - case '"': - _, err = w.w.Write(backslashDQ) - case '\\': - _, err = w.w.Write(backslashBS) - default: - if isprint(c) { - err = w.w.WriteByte(c) - } else { - _, err = fmt.Fprintf(w.w, "\\%03o", c) - } - } - if err != nil { - return err - } - } - return w.WriteByte('"') -} - -func writeUnknownStruct(w *textWriter, data []byte) (err error) { - if !w.compact { - if _, err := fmt.Fprintf(w, "/* %d unknown bytes */\n", len(data)); err != nil { - return err - } - } - b := NewBuffer(data) - for b.index < len(b.buf) { - x, err := b.DecodeVarint() - if err != nil { - _, err := fmt.Fprintf(w, "/* %v */\n", err) - return err - } - wire, tag := x&7, x>>3 - if wire == WireEndGroup { - w.unindent() - if _, err := w.Write(endBraceNewline); err != nil { - return err - } - continue - } - if _, err := fmt.Fprint(w, tag); err != nil { - return err - } - if wire != WireStartGroup { - if err := w.WriteByte(':'); err != nil { - return err - } - } - if !w.compact || wire == WireStartGroup { - if err := w.WriteByte(' '); err != nil { - return err - } - } - switch wire { - case WireBytes: - buf, e := b.DecodeRawBytes(false) - if e == nil { - _, err = fmt.Fprintf(w, "%q", buf) - } else { - _, err = fmt.Fprintf(w, "/* %v */", e) - } - case WireFixed32: - x, err = b.DecodeFixed32() - err = writeUnknownInt(w, x, err) - case WireFixed64: - x, err = b.DecodeFixed64() - err = writeUnknownInt(w, x, err) - case WireStartGroup: - err = w.WriteByte('{') - w.indent() - case WireVarint: - x, err = b.DecodeVarint() - err = writeUnknownInt(w, x, err) - default: - _, err = fmt.Fprintf(w, "/* unknown wire type %d */", wire) - } - if err != nil { - return err - } - if err = w.WriteByte('\n'); err != nil { - return err - } - } - return nil -} - -func writeUnknownInt(w *textWriter, x uint64, err error) error { - if err == nil { - _, err = fmt.Fprint(w, x) - } else { - _, err = fmt.Fprintf(w, "/* %v */", err) - } - return err -} - -type int32Slice []int32 - -func (s int32Slice) Len() int { return len(s) } -func (s int32Slice) Less(i, j int) bool { return s[i] < s[j] } -func (s int32Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -// writeExtensions writes all the extensions in pv. -// pv is assumed to be a pointer to a protocol message struct that is extendable. -func (tm *TextMarshaler) writeExtensions(w *textWriter, pv reflect.Value) error { - emap := extensionMaps[pv.Type().Elem()] - ep, _ := extendable(pv.Interface()) - - // Order the extensions by ID. - // This isn't strictly necessary, but it will give us - // canonical output, which will also make testing easier. - m, mu := ep.extensionsRead() - if m == nil { - return nil - } - mu.Lock() - ids := make([]int32, 0, len(m)) - for id := range m { - ids = append(ids, id) - } - sort.Sort(int32Slice(ids)) - mu.Unlock() - - for _, extNum := range ids { - ext := m[extNum] - var desc *ExtensionDesc - if emap != nil { - desc = emap[extNum] - } - if desc == nil { - // Unknown extension. - if err := writeUnknownStruct(w, ext.enc); err != nil { - return err - } - continue - } - - pb, err := GetExtension(ep, desc) - if err != nil { - return fmt.Errorf("failed getting extension: %v", err) - } - - // Repeated extensions will appear as a slice. - if !desc.repeated() { - if err := tm.writeExtension(w, desc.Name, pb); err != nil { - return err - } - } else { - v := reflect.ValueOf(pb) - for i := 0; i < v.Len(); i++ { - if err := tm.writeExtension(w, desc.Name, v.Index(i).Interface()); err != nil { - return err - } - } - } - } - return nil -} - -func (tm *TextMarshaler) writeExtension(w *textWriter, name string, pb interface{}) error { - if _, err := fmt.Fprintf(w, "[%s]:", name); err != nil { - return err - } - if !w.compact { - if err := w.WriteByte(' '); err != nil { - return err - } - } - if err := tm.writeAny(w, reflect.ValueOf(pb), nil); err != nil { - return err - } - if err := w.WriteByte('\n'); err != nil { - return err - } - return nil -} - -func (w *textWriter) writeIndent() { - if !w.complete { - return - } - remain := w.ind * 2 - for remain > 0 { - n := remain - if n > len(spaces) { - n = len(spaces) - } - w.w.Write(spaces[:n]) - remain -= n - } - w.complete = false -} - -// TextMarshaler is a configurable text format marshaler. -type TextMarshaler struct { - Compact bool // use compact text format (one line). - ExpandAny bool // expand google.protobuf.Any messages of known types -} - -// Marshal writes a given protocol buffer in text format. -// The only errors returned are from w. -func (tm *TextMarshaler) Marshal(w io.Writer, pb Message) error { - val := reflect.ValueOf(pb) - if pb == nil || val.IsNil() { - w.Write([]byte("")) - return nil - } - var bw *bufio.Writer - ww, ok := w.(writer) - if !ok { - bw = bufio.NewWriter(w) - ww = bw - } - aw := &textWriter{ - w: ww, - complete: true, - compact: tm.Compact, - } - - if etm, ok := pb.(encoding.TextMarshaler); ok { - text, err := etm.MarshalText() - if err != nil { - return err - } - if _, err = aw.Write(text); err != nil { - return err - } - if bw != nil { - return bw.Flush() - } - return nil - } - // Dereference the received pointer so we don't have outer < and >. - v := reflect.Indirect(val) - if err := tm.writeStruct(aw, v); err != nil { - return err - } - if bw != nil { - return bw.Flush() - } - return nil -} - -// Text is the same as Marshal, but returns the string directly. -func (tm *TextMarshaler) Text(pb Message) string { - var buf bytes.Buffer - tm.Marshal(&buf, pb) - return buf.String() -} - -var ( - defaultTextMarshaler = TextMarshaler{} - compactTextMarshaler = TextMarshaler{Compact: true} -) - -// TODO: consider removing some of the Marshal functions below. - -// MarshalText writes a given protocol buffer in text format. -// The only errors returned are from w. -func MarshalText(w io.Writer, pb Message) error { return defaultTextMarshaler.Marshal(w, pb) } - -// MarshalTextString is the same as MarshalText, but returns the string directly. -func MarshalTextString(pb Message) string { return defaultTextMarshaler.Text(pb) } - -// CompactText writes a given protocol buffer in compact text format (one line). -func CompactText(w io.Writer, pb Message) error { return compactTextMarshaler.Marshal(w, pb) } - -// CompactTextString is the same as CompactText, but returns the string directly. -func CompactTextString(pb Message) string { return compactTextMarshaler.Text(pb) } diff --git a/vendor/github.com/golang/protobuf/proto/text_parser.go b/vendor/github.com/golang/protobuf/proto/text_parser.go deleted file mode 100644 index 5e14513f28c..00000000000 --- a/vendor/github.com/golang/protobuf/proto/text_parser.go +++ /dev/null @@ -1,895 +0,0 @@ -// Go support for Protocol Buffers - Google's data interchange format -// -// Copyright 2010 The Go Authors. All rights reserved. -// https://github.com/golang/protobuf -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package proto - -// Functions for parsing the Text protocol buffer format. -// TODO: message sets. - -import ( - "encoding" - "errors" - "fmt" - "reflect" - "strconv" - "strings" - "unicode/utf8" -) - -// Error string emitted when deserializing Any and fields are already set -const anyRepeatedlyUnpacked = "Any message unpacked multiple times, or %q already set" - -type ParseError struct { - Message string - Line int // 1-based line number - Offset int // 0-based byte offset from start of input -} - -func (p *ParseError) Error() string { - if p.Line == 1 { - // show offset only for first line - return fmt.Sprintf("line 1.%d: %v", p.Offset, p.Message) - } - return fmt.Sprintf("line %d: %v", p.Line, p.Message) -} - -type token struct { - value string - err *ParseError - line int // line number - offset int // byte number from start of input, not start of line - unquoted string // the unquoted version of value, if it was a quoted string -} - -func (t *token) String() string { - if t.err == nil { - return fmt.Sprintf("%q (line=%d, offset=%d)", t.value, t.line, t.offset) - } - return fmt.Sprintf("parse error: %v", t.err) -} - -type textParser struct { - s string // remaining input - done bool // whether the parsing is finished (success or error) - backed bool // whether back() was called - offset, line int - cur token -} - -func newTextParser(s string) *textParser { - p := new(textParser) - p.s = s - p.line = 1 - p.cur.line = 1 - return p -} - -func (p *textParser) errorf(format string, a ...interface{}) *ParseError { - pe := &ParseError{fmt.Sprintf(format, a...), p.cur.line, p.cur.offset} - p.cur.err = pe - p.done = true - return pe -} - -// Numbers and identifiers are matched by [-+._A-Za-z0-9] -func isIdentOrNumberChar(c byte) bool { - switch { - case 'A' <= c && c <= 'Z', 'a' <= c && c <= 'z': - return true - case '0' <= c && c <= '9': - return true - } - switch c { - case '-', '+', '.', '_': - return true - } - return false -} - -func isWhitespace(c byte) bool { - switch c { - case ' ', '\t', '\n', '\r': - return true - } - return false -} - -func isQuote(c byte) bool { - switch c { - case '"', '\'': - return true - } - return false -} - -func (p *textParser) skipWhitespace() { - i := 0 - for i < len(p.s) && (isWhitespace(p.s[i]) || p.s[i] == '#') { - if p.s[i] == '#' { - // comment; skip to end of line or input - for i < len(p.s) && p.s[i] != '\n' { - i++ - } - if i == len(p.s) { - break - } - } - if p.s[i] == '\n' { - p.line++ - } - i++ - } - p.offset += i - p.s = p.s[i:len(p.s)] - if len(p.s) == 0 { - p.done = true - } -} - -func (p *textParser) advance() { - // Skip whitespace - p.skipWhitespace() - if p.done { - return - } - - // Start of non-whitespace - p.cur.err = nil - p.cur.offset, p.cur.line = p.offset, p.line - p.cur.unquoted = "" - switch p.s[0] { - case '<', '>', '{', '}', ':', '[', ']', ';', ',', '/': - // Single symbol - p.cur.value, p.s = p.s[0:1], p.s[1:len(p.s)] - case '"', '\'': - // Quoted string - i := 1 - for i < len(p.s) && p.s[i] != p.s[0] && p.s[i] != '\n' { - if p.s[i] == '\\' && i+1 < len(p.s) { - // skip escaped char - i++ - } - i++ - } - if i >= len(p.s) || p.s[i] != p.s[0] { - p.errorf("unmatched quote") - return - } - unq, err := unquoteC(p.s[1:i], rune(p.s[0])) - if err != nil { - p.errorf("invalid quoted string %s: %v", p.s[0:i+1], err) - return - } - p.cur.value, p.s = p.s[0:i+1], p.s[i+1:len(p.s)] - p.cur.unquoted = unq - default: - i := 0 - for i < len(p.s) && isIdentOrNumberChar(p.s[i]) { - i++ - } - if i == 0 { - p.errorf("unexpected byte %#x", p.s[0]) - return - } - p.cur.value, p.s = p.s[0:i], p.s[i:len(p.s)] - } - p.offset += len(p.cur.value) -} - -var ( - errBadUTF8 = errors.New("proto: bad UTF-8") - errBadHex = errors.New("proto: bad hexadecimal") -) - -func unquoteC(s string, quote rune) (string, error) { - // This is based on C++'s tokenizer.cc. - // Despite its name, this is *not* parsing C syntax. - // For instance, "\0" is an invalid quoted string. - - // Avoid allocation in trivial cases. - simple := true - for _, r := range s { - if r == '\\' || r == quote { - simple = false - break - } - } - if simple { - return s, nil - } - - buf := make([]byte, 0, 3*len(s)/2) - for len(s) > 0 { - r, n := utf8.DecodeRuneInString(s) - if r == utf8.RuneError && n == 1 { - return "", errBadUTF8 - } - s = s[n:] - if r != '\\' { - if r < utf8.RuneSelf { - buf = append(buf, byte(r)) - } else { - buf = append(buf, string(r)...) - } - continue - } - - ch, tail, err := unescape(s) - if err != nil { - return "", err - } - buf = append(buf, ch...) - s = tail - } - return string(buf), nil -} - -func unescape(s string) (ch string, tail string, err error) { - r, n := utf8.DecodeRuneInString(s) - if r == utf8.RuneError && n == 1 { - return "", "", errBadUTF8 - } - s = s[n:] - switch r { - case 'a': - return "\a", s, nil - case 'b': - return "\b", s, nil - case 'f': - return "\f", s, nil - case 'n': - return "\n", s, nil - case 'r': - return "\r", s, nil - case 't': - return "\t", s, nil - case 'v': - return "\v", s, nil - case '?': - return "?", s, nil // trigraph workaround - case '\'', '"', '\\': - return string(r), s, nil - case '0', '1', '2', '3', '4', '5', '6', '7', 'x', 'X': - if len(s) < 2 { - return "", "", fmt.Errorf(`\%c requires 2 following digits`, r) - } - base := 8 - ss := s[:2] - s = s[2:] - if r == 'x' || r == 'X' { - base = 16 - } else { - ss = string(r) + ss - } - i, err := strconv.ParseUint(ss, base, 8) - if err != nil { - return "", "", err - } - return string([]byte{byte(i)}), s, nil - case 'u', 'U': - n := 4 - if r == 'U' { - n = 8 - } - if len(s) < n { - return "", "", fmt.Errorf(`\%c requires %d digits`, r, n) - } - - bs := make([]byte, n/2) - for i := 0; i < n; i += 2 { - a, ok1 := unhex(s[i]) - b, ok2 := unhex(s[i+1]) - if !ok1 || !ok2 { - return "", "", errBadHex - } - bs[i/2] = a<<4 | b - } - s = s[n:] - return string(bs), s, nil - } - return "", "", fmt.Errorf(`unknown escape \%c`, r) -} - -// Adapted from src/pkg/strconv/quote.go. -func unhex(b byte) (v byte, ok bool) { - switch { - case '0' <= b && b <= '9': - return b - '0', true - case 'a' <= b && b <= 'f': - return b - 'a' + 10, true - case 'A' <= b && b <= 'F': - return b - 'A' + 10, true - } - return 0, false -} - -// Back off the parser by one token. Can only be done between calls to next(). -// It makes the next advance() a no-op. -func (p *textParser) back() { p.backed = true } - -// Advances the parser and returns the new current token. -func (p *textParser) next() *token { - if p.backed || p.done { - p.backed = false - return &p.cur - } - p.advance() - if p.done { - p.cur.value = "" - } else if len(p.cur.value) > 0 && isQuote(p.cur.value[0]) { - // Look for multiple quoted strings separated by whitespace, - // and concatenate them. - cat := p.cur - for { - p.skipWhitespace() - if p.done || !isQuote(p.s[0]) { - break - } - p.advance() - if p.cur.err != nil { - return &p.cur - } - cat.value += " " + p.cur.value - cat.unquoted += p.cur.unquoted - } - p.done = false // parser may have seen EOF, but we want to return cat - p.cur = cat - } - return &p.cur -} - -func (p *textParser) consumeToken(s string) error { - tok := p.next() - if tok.err != nil { - return tok.err - } - if tok.value != s { - p.back() - return p.errorf("expected %q, found %q", s, tok.value) - } - return nil -} - -// Return a RequiredNotSetError indicating which required field was not set. -func (p *textParser) missingRequiredFieldError(sv reflect.Value) *RequiredNotSetError { - st := sv.Type() - sprops := GetProperties(st) - for i := 0; i < st.NumField(); i++ { - if !isNil(sv.Field(i)) { - continue - } - - props := sprops.Prop[i] - if props.Required { - return &RequiredNotSetError{fmt.Sprintf("%v.%v", st, props.OrigName)} - } - } - return &RequiredNotSetError{fmt.Sprintf("%v.", st)} // should not happen -} - -// Returns the index in the struct for the named field, as well as the parsed tag properties. -func structFieldByName(sprops *StructProperties, name string) (int, *Properties, bool) { - i, ok := sprops.decoderOrigNames[name] - if ok { - return i, sprops.Prop[i], true - } - return -1, nil, false -} - -// Consume a ':' from the input stream (if the next token is a colon), -// returning an error if a colon is needed but not present. -func (p *textParser) checkForColon(props *Properties, typ reflect.Type) *ParseError { - tok := p.next() - if tok.err != nil { - return tok.err - } - if tok.value != ":" { - // Colon is optional when the field is a group or message. - needColon := true - switch props.Wire { - case "group": - needColon = false - case "bytes": - // A "bytes" field is either a message, a string, or a repeated field; - // those three become *T, *string and []T respectively, so we can check for - // this field being a pointer to a non-string. - if typ.Kind() == reflect.Ptr { - // *T or *string - if typ.Elem().Kind() == reflect.String { - break - } - } else if typ.Kind() == reflect.Slice { - // []T or []*T - if typ.Elem().Kind() != reflect.Ptr { - break - } - } else if typ.Kind() == reflect.String { - // The proto3 exception is for a string field, - // which requires a colon. - break - } - needColon = false - } - if needColon { - return p.errorf("expected ':', found %q", tok.value) - } - p.back() - } - return nil -} - -func (p *textParser) readStruct(sv reflect.Value, terminator string) error { - st := sv.Type() - sprops := GetProperties(st) - reqCount := sprops.reqCount - var reqFieldErr error - fieldSet := make(map[string]bool) - // A struct is a sequence of "name: value", terminated by one of - // '>' or '}', or the end of the input. A name may also be - // "[extension]" or "[type/url]". - // - // The whole struct can also be an expanded Any message, like: - // [type/url] < ... struct contents ... > - for { - tok := p.next() - if tok.err != nil { - return tok.err - } - if tok.value == terminator { - break - } - if tok.value == "[" { - // Looks like an extension or an Any. - // - // TODO: Check whether we need to handle - // namespace rooted names (e.g. ".something.Foo"). - extName, err := p.consumeExtName() - if err != nil { - return err - } - - if s := strings.LastIndex(extName, "/"); s >= 0 { - // If it contains a slash, it's an Any type URL. - messageName := extName[s+1:] - mt := MessageType(messageName) - if mt == nil { - return p.errorf("unrecognized message %q in google.protobuf.Any", messageName) - } - tok = p.next() - if tok.err != nil { - return tok.err - } - // consume an optional colon - if tok.value == ":" { - tok = p.next() - if tok.err != nil { - return tok.err - } - } - var terminator string - switch tok.value { - case "<": - terminator = ">" - case "{": - terminator = "}" - default: - return p.errorf("expected '{' or '<', found %q", tok.value) - } - v := reflect.New(mt.Elem()) - if pe := p.readStruct(v.Elem(), terminator); pe != nil { - return pe - } - b, err := Marshal(v.Interface().(Message)) - if err != nil { - return p.errorf("failed to marshal message of type %q: %v", messageName, err) - } - if fieldSet["type_url"] { - return p.errorf(anyRepeatedlyUnpacked, "type_url") - } - if fieldSet["value"] { - return p.errorf(anyRepeatedlyUnpacked, "value") - } - sv.FieldByName("TypeUrl").SetString(extName) - sv.FieldByName("Value").SetBytes(b) - fieldSet["type_url"] = true - fieldSet["value"] = true - continue - } - - var desc *ExtensionDesc - // This could be faster, but it's functional. - // TODO: Do something smarter than a linear scan. - for _, d := range RegisteredExtensions(reflect.New(st).Interface().(Message)) { - if d.Name == extName { - desc = d - break - } - } - if desc == nil { - return p.errorf("unrecognized extension %q", extName) - } - - props := &Properties{} - props.Parse(desc.Tag) - - typ := reflect.TypeOf(desc.ExtensionType) - if err := p.checkForColon(props, typ); err != nil { - return err - } - - rep := desc.repeated() - - // Read the extension structure, and set it in - // the value we're constructing. - var ext reflect.Value - if !rep { - ext = reflect.New(typ).Elem() - } else { - ext = reflect.New(typ.Elem()).Elem() - } - if err := p.readAny(ext, props); err != nil { - if _, ok := err.(*RequiredNotSetError); !ok { - return err - } - reqFieldErr = err - } - ep := sv.Addr().Interface().(Message) - if !rep { - SetExtension(ep, desc, ext.Interface()) - } else { - old, err := GetExtension(ep, desc) - var sl reflect.Value - if err == nil { - sl = reflect.ValueOf(old) // existing slice - } else { - sl = reflect.MakeSlice(typ, 0, 1) - } - sl = reflect.Append(sl, ext) - SetExtension(ep, desc, sl.Interface()) - } - if err := p.consumeOptionalSeparator(); err != nil { - return err - } - continue - } - - // This is a normal, non-extension field. - name := tok.value - var dst reflect.Value - fi, props, ok := structFieldByName(sprops, name) - if ok { - dst = sv.Field(fi) - } else if oop, ok := sprops.OneofTypes[name]; ok { - // It is a oneof. - props = oop.Prop - nv := reflect.New(oop.Type.Elem()) - dst = nv.Elem().Field(0) - field := sv.Field(oop.Field) - if !field.IsNil() { - return p.errorf("field '%s' would overwrite already parsed oneof '%s'", name, sv.Type().Field(oop.Field).Name) - } - field.Set(nv) - } - if !dst.IsValid() { - return p.errorf("unknown field name %q in %v", name, st) - } - - if dst.Kind() == reflect.Map { - // Consume any colon. - if err := p.checkForColon(props, dst.Type()); err != nil { - return err - } - - // Construct the map if it doesn't already exist. - if dst.IsNil() { - dst.Set(reflect.MakeMap(dst.Type())) - } - key := reflect.New(dst.Type().Key()).Elem() - val := reflect.New(dst.Type().Elem()).Elem() - - // The map entry should be this sequence of tokens: - // < key : KEY value : VALUE > - // However, implementations may omit key or value, and technically - // we should support them in any order. See b/28924776 for a time - // this went wrong. - - tok := p.next() - var terminator string - switch tok.value { - case "<": - terminator = ">" - case "{": - terminator = "}" - default: - return p.errorf("expected '{' or '<', found %q", tok.value) - } - for { - tok := p.next() - if tok.err != nil { - return tok.err - } - if tok.value == terminator { - break - } - switch tok.value { - case "key": - if err := p.consumeToken(":"); err != nil { - return err - } - if err := p.readAny(key, props.mkeyprop); err != nil { - return err - } - if err := p.consumeOptionalSeparator(); err != nil { - return err - } - case "value": - if err := p.checkForColon(props.mvalprop, dst.Type().Elem()); err != nil { - return err - } - if err := p.readAny(val, props.mvalprop); err != nil { - return err - } - if err := p.consumeOptionalSeparator(); err != nil { - return err - } - default: - p.back() - return p.errorf(`expected "key", "value", or %q, found %q`, terminator, tok.value) - } - } - - dst.SetMapIndex(key, val) - continue - } - - // Check that it's not already set if it's not a repeated field. - if !props.Repeated && fieldSet[name] { - return p.errorf("non-repeated field %q was repeated", name) - } - - if err := p.checkForColon(props, dst.Type()); err != nil { - return err - } - - // Parse into the field. - fieldSet[name] = true - if err := p.readAny(dst, props); err != nil { - if _, ok := err.(*RequiredNotSetError); !ok { - return err - } - reqFieldErr = err - } - if props.Required { - reqCount-- - } - - if err := p.consumeOptionalSeparator(); err != nil { - return err - } - - } - - if reqCount > 0 { - return p.missingRequiredFieldError(sv) - } - return reqFieldErr -} - -// consumeExtName consumes extension name or expanded Any type URL and the -// following ']'. It returns the name or URL consumed. -func (p *textParser) consumeExtName() (string, error) { - tok := p.next() - if tok.err != nil { - return "", tok.err - } - - // If extension name or type url is quoted, it's a single token. - if len(tok.value) > 2 && isQuote(tok.value[0]) && tok.value[len(tok.value)-1] == tok.value[0] { - name, err := unquoteC(tok.value[1:len(tok.value)-1], rune(tok.value[0])) - if err != nil { - return "", err - } - return name, p.consumeToken("]") - } - - // Consume everything up to "]" - var parts []string - for tok.value != "]" { - parts = append(parts, tok.value) - tok = p.next() - if tok.err != nil { - return "", p.errorf("unrecognized type_url or extension name: %s", tok.err) - } - } - return strings.Join(parts, ""), nil -} - -// consumeOptionalSeparator consumes an optional semicolon or comma. -// It is used in readStruct to provide backward compatibility. -func (p *textParser) consumeOptionalSeparator() error { - tok := p.next() - if tok.err != nil { - return tok.err - } - if tok.value != ";" && tok.value != "," { - p.back() - } - return nil -} - -func (p *textParser) readAny(v reflect.Value, props *Properties) error { - tok := p.next() - if tok.err != nil { - return tok.err - } - if tok.value == "" { - return p.errorf("unexpected EOF") - } - - switch fv := v; fv.Kind() { - case reflect.Slice: - at := v.Type() - if at.Elem().Kind() == reflect.Uint8 { - // Special case for []byte - if tok.value[0] != '"' && tok.value[0] != '\'' { - // Deliberately written out here, as the error after - // this switch statement would write "invalid []byte: ...", - // which is not as user-friendly. - return p.errorf("invalid string: %v", tok.value) - } - bytes := []byte(tok.unquoted) - fv.Set(reflect.ValueOf(bytes)) - return nil - } - // Repeated field. - if tok.value == "[" { - // Repeated field with list notation, like [1,2,3]. - for { - fv.Set(reflect.Append(fv, reflect.New(at.Elem()).Elem())) - err := p.readAny(fv.Index(fv.Len()-1), props) - if err != nil { - return err - } - tok := p.next() - if tok.err != nil { - return tok.err - } - if tok.value == "]" { - break - } - if tok.value != "," { - return p.errorf("Expected ']' or ',' found %q", tok.value) - } - } - return nil - } - // One value of the repeated field. - p.back() - fv.Set(reflect.Append(fv, reflect.New(at.Elem()).Elem())) - return p.readAny(fv.Index(fv.Len()-1), props) - case reflect.Bool: - // true/1/t/True or false/f/0/False. - switch tok.value { - case "true", "1", "t", "True": - fv.SetBool(true) - return nil - case "false", "0", "f", "False": - fv.SetBool(false) - return nil - } - case reflect.Float32, reflect.Float64: - v := tok.value - // Ignore 'f' for compatibility with output generated by C++, but don't - // remove 'f' when the value is "-inf" or "inf". - if strings.HasSuffix(v, "f") && tok.value != "-inf" && tok.value != "inf" { - v = v[:len(v)-1] - } - if f, err := strconv.ParseFloat(v, fv.Type().Bits()); err == nil { - fv.SetFloat(f) - return nil - } - case reflect.Int32: - if x, err := strconv.ParseInt(tok.value, 0, 32); err == nil { - fv.SetInt(x) - return nil - } - - if len(props.Enum) == 0 { - break - } - m, ok := enumValueMaps[props.Enum] - if !ok { - break - } - x, ok := m[tok.value] - if !ok { - break - } - fv.SetInt(int64(x)) - return nil - case reflect.Int64: - if x, err := strconv.ParseInt(tok.value, 0, 64); err == nil { - fv.SetInt(x) - return nil - } - - case reflect.Ptr: - // A basic field (indirected through pointer), or a repeated message/group - p.back() - fv.Set(reflect.New(fv.Type().Elem())) - return p.readAny(fv.Elem(), props) - case reflect.String: - if tok.value[0] == '"' || tok.value[0] == '\'' { - fv.SetString(tok.unquoted) - return nil - } - case reflect.Struct: - var terminator string - switch tok.value { - case "{": - terminator = "}" - case "<": - terminator = ">" - default: - return p.errorf("expected '{' or '<', found %q", tok.value) - } - // TODO: Handle nested messages which implement encoding.TextUnmarshaler. - return p.readStruct(fv, terminator) - case reflect.Uint32: - if x, err := strconv.ParseUint(tok.value, 0, 32); err == nil { - fv.SetUint(x) - return nil - } - case reflect.Uint64: - if x, err := strconv.ParseUint(tok.value, 0, 64); err == nil { - fv.SetUint(x) - return nil - } - } - return p.errorf("invalid %v: %v", v.Type(), tok.value) -} - -// UnmarshalText reads a protocol buffer in Text format. UnmarshalText resets pb -// before starting to unmarshal, so any existing data in pb is always removed. -// If a required field is not set and no other error occurs, -// UnmarshalText returns *RequiredNotSetError. -func UnmarshalText(s string, pb Message) error { - if um, ok := pb.(encoding.TextUnmarshaler); ok { - err := um.UnmarshalText([]byte(s)) - return err - } - pb.Reset() - v := reflect.ValueOf(pb) - if pe := newTextParser(s).readStruct(v.Elem(), ""); pe != nil { - return pe - } - return nil -} diff --git a/vendor/github.com/golang/protobuf/ptypes/any/LICENSE b/vendor/github.com/golang/protobuf/ptypes/any/LICENSE deleted file mode 100644 index 1b1b1921efa..00000000000 --- a/vendor/github.com/golang/protobuf/ptypes/any/LICENSE +++ /dev/null @@ -1,31 +0,0 @@ -Go support for Protocol Buffers - Google's data interchange format - -Copyright 2010 The Go Authors. All rights reserved. -https://github.com/golang/protobuf - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - diff --git a/vendor/github.com/golang/protobuf/ptypes/any/any.pb.go b/vendor/github.com/golang/protobuf/ptypes/any/any.pb.go deleted file mode 100644 index 1fbaa44caa2..00000000000 --- a/vendor/github.com/golang/protobuf/ptypes/any/any.pb.go +++ /dev/null @@ -1,168 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// source: github.com/golang/protobuf/ptypes/any/any.proto - -/* -Package any is a generated protocol buffer package. - -It is generated from these files: - github.com/golang/protobuf/ptypes/any/any.proto - -It has these top-level messages: - Any -*/ -package any - -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package - -// `Any` contains an arbitrary serialized protocol buffer message along with a -// URL that describes the type of the serialized message. -// -// Protobuf library provides support to pack/unpack Any values in the form -// of utility functions or additional generated methods of the Any type. -// -// Example 1: Pack and unpack a message in C++. -// -// Foo foo = ...; -// Any any; -// any.PackFrom(foo); -// ... -// if (any.UnpackTo(&foo)) { -// ... -// } -// -// Example 2: Pack and unpack a message in Java. -// -// Foo foo = ...; -// Any any = Any.pack(foo); -// ... -// if (any.is(Foo.class)) { -// foo = any.unpack(Foo.class); -// } -// -// Example 3: Pack and unpack a message in Python. -// -// foo = Foo(...) -// any = Any() -// any.Pack(foo) -// ... -// if any.Is(Foo.DESCRIPTOR): -// any.Unpack(foo) -// ... -// -// The pack methods provided by protobuf library will by default use -// 'type.googleapis.com/full.type.name' as the type URL and the unpack -// methods only use the fully qualified type name after the last '/' -// in the type URL, for example "foo.bar.com/x/y.z" will yield type -// name "y.z". -// -// -// JSON -// ==== -// The JSON representation of an `Any` value uses the regular -// representation of the deserialized, embedded message, with an -// additional field `@type` which contains the type URL. Example: -// -// package google.profile; -// message Person { -// string first_name = 1; -// string last_name = 2; -// } -// -// { -// "@type": "type.googleapis.com/google.profile.Person", -// "firstName": , -// "lastName": -// } -// -// If the embedded message type is well-known and has a custom JSON -// representation, that representation will be embedded adding a field -// `value` which holds the custom JSON in addition to the `@type` -// field. Example (for message [google.protobuf.Duration][]): -// -// { -// "@type": "type.googleapis.com/google.protobuf.Duration", -// "value": "1.212s" -// } -// -type Any struct { - // A URL/resource name whose content describes the type of the - // serialized protocol buffer message. - // - // For URLs which use the scheme `http`, `https`, or no scheme, the - // following restrictions and interpretations apply: - // - // * If no scheme is provided, `https` is assumed. - // * The last segment of the URL's path must represent the fully - // qualified name of the type (as in `path/google.protobuf.Duration`). - // The name should be in a canonical form (e.g., leading "." is - // not accepted). - // * An HTTP GET on the URL must yield a [google.protobuf.Type][] - // value in binary format, or produce an error. - // * Applications are allowed to cache lookup results based on the - // URL, or have them precompiled into a binary to avoid any - // lookup. Therefore, binary compatibility needs to be preserved - // on changes to types. (Use versioned type names to manage - // breaking changes.) - // - // Schemes other than `http`, `https` (or the empty scheme) might be - // used with implementation specific semantics. - // - TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl" json:"type_url,omitempty"` - // Must be a valid serialized protocol buffer of the above specified type. - Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` -} - -func (m *Any) Reset() { *m = Any{} } -func (m *Any) String() string { return proto.CompactTextString(m) } -func (*Any) ProtoMessage() {} -func (*Any) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } -func (*Any) XXX_WellKnownType() string { return "Any" } - -func (m *Any) GetTypeUrl() string { - if m != nil { - return m.TypeUrl - } - return "" -} - -func (m *Any) GetValue() []byte { - if m != nil { - return m.Value - } - return nil -} - -func init() { - proto.RegisterType((*Any)(nil), "google.protobuf.Any") -} - -func init() { proto.RegisterFile("github.com/golang/protobuf/ptypes/any/any.proto", fileDescriptor0) } - -var fileDescriptor0 = []byte{ - // 184 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x4f, 0xcf, 0x2c, 0xc9, - 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xcf, 0xcf, 0x49, 0xcc, 0x4b, 0xd7, 0x2f, 0x28, - 0xca, 0x2f, 0xc9, 0x4f, 0x2a, 0x4d, 0xd3, 0x2f, 0x28, 0xa9, 0x2c, 0x48, 0x2d, 0xd6, 0x4f, 0xcc, - 0xab, 0x04, 0x61, 0x3d, 0xb0, 0xb8, 0x10, 0x7f, 0x7a, 0x7e, 0x7e, 0x7a, 0x4e, 0xaa, 0x1e, 0x4c, - 0x95, 0x92, 0x19, 0x17, 0xb3, 0x63, 0x5e, 0xa5, 0x90, 0x24, 0x17, 0x07, 0x48, 0x79, 0x7c, 0x69, - 0x51, 0x8e, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x67, 0x10, 0x3b, 0x88, 0x1f, 0x5a, 0x94, 0x23, 0x24, - 0xc2, 0xc5, 0x5a, 0x96, 0x98, 0x53, 0x9a, 0x2a, 0xc1, 0xa4, 0xc0, 0xa8, 0xc1, 0x13, 0x04, 0xe1, - 0x38, 0xe5, 0x73, 0x09, 0x27, 0xe7, 0xe7, 0xea, 0xa1, 0x19, 0xe7, 0xc4, 0xe1, 0x98, 0x57, 0x19, - 0x00, 0xe2, 0x04, 0x30, 0x46, 0xa9, 0x12, 0xe5, 0xb8, 0x45, 0x4c, 0xcc, 0xee, 0x01, 0x4e, 0xab, - 0x98, 0xe4, 0xdc, 0x21, 0x46, 0x05, 0x40, 0x95, 0xe8, 0x85, 0xa7, 0xe6, 0xe4, 0x78, 0xe7, 0xe5, - 0x97, 0xe7, 0x85, 0x80, 0x94, 0x26, 0xb1, 0x81, 0xf5, 0x1a, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, - 0x45, 0x1f, 0x1a, 0xf2, 0xf3, 0x00, 0x00, 0x00, -} diff --git a/vendor/github.com/gorilla/websocket/LICENSE b/vendor/github.com/gorilla/websocket/LICENSE deleted file mode 100644 index 9171c972252..00000000000 --- a/vendor/github.com/gorilla/websocket/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - - Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/gorilla/websocket/client.go b/vendor/github.com/gorilla/websocket/client.go deleted file mode 100644 index 43a87c753bf..00000000000 --- a/vendor/github.com/gorilla/websocket/client.go +++ /dev/null @@ -1,392 +0,0 @@ -// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package websocket - -import ( - "bufio" - "bytes" - "crypto/tls" - "encoding/base64" - "errors" - "io" - "io/ioutil" - "net" - "net/http" - "net/url" - "strings" - "time" -) - -// ErrBadHandshake is returned when the server response to opening handshake is -// invalid. -var ErrBadHandshake = errors.New("websocket: bad handshake") - -var errInvalidCompression = errors.New("websocket: invalid compression negotiation") - -// NewClient creates a new client connection using the given net connection. -// The URL u specifies the host and request URI. Use requestHeader to specify -// the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies -// (Cookie). Use the response.Header to get the selected subprotocol -// (Sec-WebSocket-Protocol) and cookies (Set-Cookie). -// -// If the WebSocket handshake fails, ErrBadHandshake is returned along with a -// non-nil *http.Response so that callers can handle redirects, authentication, -// etc. -// -// Deprecated: Use Dialer instead. -func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) { - d := Dialer{ - ReadBufferSize: readBufSize, - WriteBufferSize: writeBufSize, - NetDial: func(net, addr string) (net.Conn, error) { - return netConn, nil - }, - } - return d.Dial(u.String(), requestHeader) -} - -// A Dialer contains options for connecting to WebSocket server. -type Dialer struct { - // NetDial specifies the dial function for creating TCP connections. If - // NetDial is nil, net.Dial is used. - NetDial func(network, addr string) (net.Conn, error) - - // Proxy specifies a function to return a proxy for a given - // Request. If the function returns a non-nil error, the - // request is aborted with the provided error. - // If Proxy is nil or returns a nil *URL, no proxy is used. - Proxy func(*http.Request) (*url.URL, error) - - // TLSClientConfig specifies the TLS configuration to use with tls.Client. - // If nil, the default configuration is used. - TLSClientConfig *tls.Config - - // HandshakeTimeout specifies the duration for the handshake to complete. - HandshakeTimeout time.Duration - - // ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer - // size is zero, then a useful default size is used. The I/O buffer sizes - // do not limit the size of the messages that can be sent or received. - ReadBufferSize, WriteBufferSize int - - // Subprotocols specifies the client's requested subprotocols. - Subprotocols []string - - // EnableCompression specifies if the client should attempt to negotiate - // per message compression (RFC 7692). Setting this value to true does not - // guarantee that compression will be supported. Currently only "no context - // takeover" modes are supported. - EnableCompression bool - - // Jar specifies the cookie jar. - // If Jar is nil, cookies are not sent in requests and ignored - // in responses. - Jar http.CookieJar -} - -var errMalformedURL = errors.New("malformed ws or wss URL") - -// parseURL parses the URL. -// -// This function is a replacement for the standard library url.Parse function. -// In Go 1.4 and earlier, url.Parse loses information from the path. -func parseURL(s string) (*url.URL, error) { - // From the RFC: - // - // ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ] - // wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ] - var u url.URL - switch { - case strings.HasPrefix(s, "ws://"): - u.Scheme = "ws" - s = s[len("ws://"):] - case strings.HasPrefix(s, "wss://"): - u.Scheme = "wss" - s = s[len("wss://"):] - default: - return nil, errMalformedURL - } - - if i := strings.Index(s, "?"); i >= 0 { - u.RawQuery = s[i+1:] - s = s[:i] - } - - if i := strings.Index(s, "/"); i >= 0 { - u.Opaque = s[i:] - s = s[:i] - } else { - u.Opaque = "/" - } - - u.Host = s - - if strings.Contains(u.Host, "@") { - // Don't bother parsing user information because user information is - // not allowed in websocket URIs. - return nil, errMalformedURL - } - - return &u, nil -} - -func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) { - hostPort = u.Host - hostNoPort = u.Host - if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") { - hostNoPort = hostNoPort[:i] - } else { - switch u.Scheme { - case "wss": - hostPort += ":443" - case "https": - hostPort += ":443" - default: - hostPort += ":80" - } - } - return hostPort, hostNoPort -} - -// DefaultDialer is a dialer with all fields set to the default zero values. -var DefaultDialer = &Dialer{ - Proxy: http.ProxyFromEnvironment, -} - -// Dial creates a new client connection. Use requestHeader to specify the -// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie). -// Use the response.Header to get the selected subprotocol -// (Sec-WebSocket-Protocol) and cookies (Set-Cookie). -// -// If the WebSocket handshake fails, ErrBadHandshake is returned along with a -// non-nil *http.Response so that callers can handle redirects, authentication, -// etcetera. The response body may not contain the entire response and does not -// need to be closed by the application. -func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) { - - if d == nil { - d = &Dialer{ - Proxy: http.ProxyFromEnvironment, - } - } - - challengeKey, err := generateChallengeKey() - if err != nil { - return nil, nil, err - } - - u, err := parseURL(urlStr) - if err != nil { - return nil, nil, err - } - - switch u.Scheme { - case "ws": - u.Scheme = "http" - case "wss": - u.Scheme = "https" - default: - return nil, nil, errMalformedURL - } - - if u.User != nil { - // User name and password are not allowed in websocket URIs. - return nil, nil, errMalformedURL - } - - req := &http.Request{ - Method: "GET", - URL: u, - Proto: "HTTP/1.1", - ProtoMajor: 1, - ProtoMinor: 1, - Header: make(http.Header), - Host: u.Host, - } - - // Set the cookies present in the cookie jar of the dialer - if d.Jar != nil { - for _, cookie := range d.Jar.Cookies(u) { - req.AddCookie(cookie) - } - } - - // Set the request headers using the capitalization for names and values in - // RFC examples. Although the capitalization shouldn't matter, there are - // servers that depend on it. The Header.Set method is not used because the - // method canonicalizes the header names. - req.Header["Upgrade"] = []string{"websocket"} - req.Header["Connection"] = []string{"Upgrade"} - req.Header["Sec-WebSocket-Key"] = []string{challengeKey} - req.Header["Sec-WebSocket-Version"] = []string{"13"} - if len(d.Subprotocols) > 0 { - req.Header["Sec-WebSocket-Protocol"] = []string{strings.Join(d.Subprotocols, ", ")} - } - for k, vs := range requestHeader { - switch { - case k == "Host": - if len(vs) > 0 { - req.Host = vs[0] - } - case k == "Upgrade" || - k == "Connection" || - k == "Sec-Websocket-Key" || - k == "Sec-Websocket-Version" || - k == "Sec-Websocket-Extensions" || - (k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0): - return nil, nil, errors.New("websocket: duplicate header not allowed: " + k) - default: - req.Header[k] = vs - } - } - - if d.EnableCompression { - req.Header.Set("Sec-Websocket-Extensions", "permessage-deflate; server_no_context_takeover; client_no_context_takeover") - } - - hostPort, hostNoPort := hostPortNoPort(u) - - var proxyURL *url.URL - // Check wether the proxy method has been configured - if d.Proxy != nil { - proxyURL, err = d.Proxy(req) - } - if err != nil { - return nil, nil, err - } - - var targetHostPort string - if proxyURL != nil { - targetHostPort, _ = hostPortNoPort(proxyURL) - } else { - targetHostPort = hostPort - } - - var deadline time.Time - if d.HandshakeTimeout != 0 { - deadline = time.Now().Add(d.HandshakeTimeout) - } - - netDial := d.NetDial - if netDial == nil { - netDialer := &net.Dialer{Deadline: deadline} - netDial = netDialer.Dial - } - - netConn, err := netDial("tcp", targetHostPort) - if err != nil { - return nil, nil, err - } - - defer func() { - if netConn != nil { - netConn.Close() - } - }() - - if err := netConn.SetDeadline(deadline); err != nil { - return nil, nil, err - } - - if proxyURL != nil { - connectHeader := make(http.Header) - if user := proxyURL.User; user != nil { - proxyUser := user.Username() - if proxyPassword, passwordSet := user.Password(); passwordSet { - credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword)) - connectHeader.Set("Proxy-Authorization", "Basic "+credential) - } - } - connectReq := &http.Request{ - Method: "CONNECT", - URL: &url.URL{Opaque: hostPort}, - Host: hostPort, - Header: connectHeader, - } - - connectReq.Write(netConn) - - // Read response. - // Okay to use and discard buffered reader here, because - // TLS server will not speak until spoken to. - br := bufio.NewReader(netConn) - resp, err := http.ReadResponse(br, connectReq) - if err != nil { - return nil, nil, err - } - if resp.StatusCode != 200 { - f := strings.SplitN(resp.Status, " ", 2) - return nil, nil, errors.New(f[1]) - } - } - - if u.Scheme == "https" { - cfg := cloneTLSConfig(d.TLSClientConfig) - if cfg.ServerName == "" { - cfg.ServerName = hostNoPort - } - tlsConn := tls.Client(netConn, cfg) - netConn = tlsConn - if err := tlsConn.Handshake(); err != nil { - return nil, nil, err - } - if !cfg.InsecureSkipVerify { - if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil { - return nil, nil, err - } - } - } - - conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize) - - if err := req.Write(netConn); err != nil { - return nil, nil, err - } - - resp, err := http.ReadResponse(conn.br, req) - if err != nil { - return nil, nil, err - } - - if d.Jar != nil { - if rc := resp.Cookies(); len(rc) > 0 { - d.Jar.SetCookies(u, rc) - } - } - - if resp.StatusCode != 101 || - !strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") || - !strings.EqualFold(resp.Header.Get("Connection"), "upgrade") || - resp.Header.Get("Sec-Websocket-Accept") != computeAcceptKey(challengeKey) { - // Before closing the network connection on return from this - // function, slurp up some of the response to aid application - // debugging. - buf := make([]byte, 1024) - n, _ := io.ReadFull(resp.Body, buf) - resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n])) - return nil, resp, ErrBadHandshake - } - - for _, ext := range parseExtensions(resp.Header) { - if ext[""] != "permessage-deflate" { - continue - } - _, snct := ext["server_no_context_takeover"] - _, cnct := ext["client_no_context_takeover"] - if !snct || !cnct { - return nil, resp, errInvalidCompression - } - conn.newCompressionWriter = compressNoContextTakeover - conn.newDecompressionReader = decompressNoContextTakeover - break - } - - resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{})) - conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol") - - netConn.SetDeadline(time.Time{}) - netConn = nil // to avoid close in defer. - return conn, resp, nil -} diff --git a/vendor/github.com/gorilla/websocket/client_clone.go b/vendor/github.com/gorilla/websocket/client_clone.go deleted file mode 100644 index 4f0d943723a..00000000000 --- a/vendor/github.com/gorilla/websocket/client_clone.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build go1.8 - -package websocket - -import "crypto/tls" - -func cloneTLSConfig(cfg *tls.Config) *tls.Config { - if cfg == nil { - return &tls.Config{} - } - return cfg.Clone() -} diff --git a/vendor/github.com/gorilla/websocket/client_clone_legacy.go b/vendor/github.com/gorilla/websocket/client_clone_legacy.go deleted file mode 100644 index babb007fb41..00000000000 --- a/vendor/github.com/gorilla/websocket/client_clone_legacy.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !go1.8 - -package websocket - -import "crypto/tls" - -// cloneTLSConfig clones all public fields except the fields -// SessionTicketsDisabled and SessionTicketKey. This avoids copying the -// sync.Mutex in the sync.Once and makes it safe to call cloneTLSConfig on a -// config in active use. -func cloneTLSConfig(cfg *tls.Config) *tls.Config { - if cfg == nil { - return &tls.Config{} - } - return &tls.Config{ - Rand: cfg.Rand, - Time: cfg.Time, - Certificates: cfg.Certificates, - NameToCertificate: cfg.NameToCertificate, - GetCertificate: cfg.GetCertificate, - RootCAs: cfg.RootCAs, - NextProtos: cfg.NextProtos, - ServerName: cfg.ServerName, - ClientAuth: cfg.ClientAuth, - ClientCAs: cfg.ClientCAs, - InsecureSkipVerify: cfg.InsecureSkipVerify, - CipherSuites: cfg.CipherSuites, - PreferServerCipherSuites: cfg.PreferServerCipherSuites, - ClientSessionCache: cfg.ClientSessionCache, - MinVersion: cfg.MinVersion, - MaxVersion: cfg.MaxVersion, - CurvePreferences: cfg.CurvePreferences, - } -} diff --git a/vendor/github.com/gorilla/websocket/compression.go b/vendor/github.com/gorilla/websocket/compression.go deleted file mode 100644 index 813ffb1e843..00000000000 --- a/vendor/github.com/gorilla/websocket/compression.go +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package websocket - -import ( - "compress/flate" - "errors" - "io" - "strings" - "sync" -) - -const ( - minCompressionLevel = -2 // flate.HuffmanOnly not defined in Go < 1.6 - maxCompressionLevel = flate.BestCompression - defaultCompressionLevel = 1 -) - -var ( - flateWriterPools [maxCompressionLevel - minCompressionLevel + 1]sync.Pool - flateReaderPool = sync.Pool{New: func() interface{} { - return flate.NewReader(nil) - }} -) - -func decompressNoContextTakeover(r io.Reader) io.ReadCloser { - const tail = - // Add four bytes as specified in RFC - "\x00\x00\xff\xff" + - // Add final block to squelch unexpected EOF error from flate reader. - "\x01\x00\x00\xff\xff" - - fr, _ := flateReaderPool.Get().(io.ReadCloser) - fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil) - return &flateReadWrapper{fr} -} - -func isValidCompressionLevel(level int) bool { - return minCompressionLevel <= level && level <= maxCompressionLevel -} - -func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteCloser { - p := &flateWriterPools[level-minCompressionLevel] - tw := &truncWriter{w: w} - fw, _ := p.Get().(*flate.Writer) - if fw == nil { - fw, _ = flate.NewWriter(tw, level) - } else { - fw.Reset(tw) - } - return &flateWriteWrapper{fw: fw, tw: tw, p: p} -} - -// truncWriter is an io.Writer that writes all but the last four bytes of the -// stream to another io.Writer. -type truncWriter struct { - w io.WriteCloser - n int - p [4]byte -} - -func (w *truncWriter) Write(p []byte) (int, error) { - n := 0 - - // fill buffer first for simplicity. - if w.n < len(w.p) { - n = copy(w.p[w.n:], p) - p = p[n:] - w.n += n - if len(p) == 0 { - return n, nil - } - } - - m := len(p) - if m > len(w.p) { - m = len(w.p) - } - - if nn, err := w.w.Write(w.p[:m]); err != nil { - return n + nn, err - } - - copy(w.p[:], w.p[m:]) - copy(w.p[len(w.p)-m:], p[len(p)-m:]) - nn, err := w.w.Write(p[:len(p)-m]) - return n + nn, err -} - -type flateWriteWrapper struct { - fw *flate.Writer - tw *truncWriter - p *sync.Pool -} - -func (w *flateWriteWrapper) Write(p []byte) (int, error) { - if w.fw == nil { - return 0, errWriteClosed - } - return w.fw.Write(p) -} - -func (w *flateWriteWrapper) Close() error { - if w.fw == nil { - return errWriteClosed - } - err1 := w.fw.Flush() - w.p.Put(w.fw) - w.fw = nil - if w.tw.p != [4]byte{0, 0, 0xff, 0xff} { - return errors.New("websocket: internal error, unexpected bytes at end of flate stream") - } - err2 := w.tw.w.Close() - if err1 != nil { - return err1 - } - return err2 -} - -type flateReadWrapper struct { - fr io.ReadCloser -} - -func (r *flateReadWrapper) Read(p []byte) (int, error) { - if r.fr == nil { - return 0, io.ErrClosedPipe - } - n, err := r.fr.Read(p) - if err == io.EOF { - // Preemptively place the reader back in the pool. This helps with - // scenarios where the application does not call NextReader() soon after - // this final read. - r.Close() - } - return n, err -} - -func (r *flateReadWrapper) Close() error { - if r.fr == nil { - return io.ErrClosedPipe - } - err := r.fr.Close() - flateReaderPool.Put(r.fr) - r.fr = nil - return err -} diff --git a/vendor/github.com/gorilla/websocket/conn.go b/vendor/github.com/gorilla/websocket/conn.go deleted file mode 100644 index 97e1dbacb12..00000000000 --- a/vendor/github.com/gorilla/websocket/conn.go +++ /dev/null @@ -1,1149 +0,0 @@ -// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package websocket - -import ( - "bufio" - "encoding/binary" - "errors" - "io" - "io/ioutil" - "math/rand" - "net" - "strconv" - "sync" - "time" - "unicode/utf8" -) - -const ( - // Frame header byte 0 bits from Section 5.2 of RFC 6455 - finalBit = 1 << 7 - rsv1Bit = 1 << 6 - rsv2Bit = 1 << 5 - rsv3Bit = 1 << 4 - - // Frame header byte 1 bits from Section 5.2 of RFC 6455 - maskBit = 1 << 7 - - maxFrameHeaderSize = 2 + 8 + 4 // Fixed header + length + mask - maxControlFramePayloadSize = 125 - - writeWait = time.Second - - defaultReadBufferSize = 4096 - defaultWriteBufferSize = 4096 - - continuationFrame = 0 - noFrame = -1 -) - -// Close codes defined in RFC 6455, section 11.7. -const ( - CloseNormalClosure = 1000 - CloseGoingAway = 1001 - CloseProtocolError = 1002 - CloseUnsupportedData = 1003 - CloseNoStatusReceived = 1005 - CloseAbnormalClosure = 1006 - CloseInvalidFramePayloadData = 1007 - ClosePolicyViolation = 1008 - CloseMessageTooBig = 1009 - CloseMandatoryExtension = 1010 - CloseInternalServerErr = 1011 - CloseServiceRestart = 1012 - CloseTryAgainLater = 1013 - CloseTLSHandshake = 1015 -) - -// The message types are defined in RFC 6455, section 11.8. -const ( - // TextMessage denotes a text data message. The text message payload is - // interpreted as UTF-8 encoded text data. - TextMessage = 1 - - // BinaryMessage denotes a binary data message. - BinaryMessage = 2 - - // CloseMessage denotes a close control message. The optional message - // payload contains a numeric code and text. Use the FormatCloseMessage - // function to format a close message payload. - CloseMessage = 8 - - // PingMessage denotes a ping control message. The optional message payload - // is UTF-8 encoded text. - PingMessage = 9 - - // PongMessage denotes a ping control message. The optional message payload - // is UTF-8 encoded text. - PongMessage = 10 -) - -// ErrCloseSent is returned when the application writes a message to the -// connection after sending a close message. -var ErrCloseSent = errors.New("websocket: close sent") - -// ErrReadLimit is returned when reading a message that is larger than the -// read limit set for the connection. -var ErrReadLimit = errors.New("websocket: read limit exceeded") - -// netError satisfies the net Error interface. -type netError struct { - msg string - temporary bool - timeout bool -} - -func (e *netError) Error() string { return e.msg } -func (e *netError) Temporary() bool { return e.temporary } -func (e *netError) Timeout() bool { return e.timeout } - -// CloseError represents close frame. -type CloseError struct { - - // Code is defined in RFC 6455, section 11.7. - Code int - - // Text is the optional text payload. - Text string -} - -func (e *CloseError) Error() string { - s := []byte("websocket: close ") - s = strconv.AppendInt(s, int64(e.Code), 10) - switch e.Code { - case CloseNormalClosure: - s = append(s, " (normal)"...) - case CloseGoingAway: - s = append(s, " (going away)"...) - case CloseProtocolError: - s = append(s, " (protocol error)"...) - case CloseUnsupportedData: - s = append(s, " (unsupported data)"...) - case CloseNoStatusReceived: - s = append(s, " (no status)"...) - case CloseAbnormalClosure: - s = append(s, " (abnormal closure)"...) - case CloseInvalidFramePayloadData: - s = append(s, " (invalid payload data)"...) - case ClosePolicyViolation: - s = append(s, " (policy violation)"...) - case CloseMessageTooBig: - s = append(s, " (message too big)"...) - case CloseMandatoryExtension: - s = append(s, " (mandatory extension missing)"...) - case CloseInternalServerErr: - s = append(s, " (internal server error)"...) - case CloseTLSHandshake: - s = append(s, " (TLS handshake error)"...) - } - if e.Text != "" { - s = append(s, ": "...) - s = append(s, e.Text...) - } - return string(s) -} - -// IsCloseError returns boolean indicating whether the error is a *CloseError -// with one of the specified codes. -func IsCloseError(err error, codes ...int) bool { - if e, ok := err.(*CloseError); ok { - for _, code := range codes { - if e.Code == code { - return true - } - } - } - return false -} - -// IsUnexpectedCloseError returns boolean indicating whether the error is a -// *CloseError with a code not in the list of expected codes. -func IsUnexpectedCloseError(err error, expectedCodes ...int) bool { - if e, ok := err.(*CloseError); ok { - for _, code := range expectedCodes { - if e.Code == code { - return false - } - } - return true - } - return false -} - -var ( - errWriteTimeout = &netError{msg: "websocket: write timeout", timeout: true, temporary: true} - errUnexpectedEOF = &CloseError{Code: CloseAbnormalClosure, Text: io.ErrUnexpectedEOF.Error()} - errBadWriteOpCode = errors.New("websocket: bad write message type") - errWriteClosed = errors.New("websocket: write closed") - errInvalidControlFrame = errors.New("websocket: invalid control frame") -) - -func newMaskKey() [4]byte { - n := rand.Uint32() - return [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)} -} - -func hideTempErr(err error) error { - if e, ok := err.(net.Error); ok && e.Temporary() { - err = &netError{msg: e.Error(), timeout: e.Timeout()} - } - return err -} - -func isControl(frameType int) bool { - return frameType == CloseMessage || frameType == PingMessage || frameType == PongMessage -} - -func isData(frameType int) bool { - return frameType == TextMessage || frameType == BinaryMessage -} - -var validReceivedCloseCodes = map[int]bool{ - // see http://www.iana.org/assignments/websocket/websocket.xhtml#close-code-number - - CloseNormalClosure: true, - CloseGoingAway: true, - CloseProtocolError: true, - CloseUnsupportedData: true, - CloseNoStatusReceived: false, - CloseAbnormalClosure: false, - CloseInvalidFramePayloadData: true, - ClosePolicyViolation: true, - CloseMessageTooBig: true, - CloseMandatoryExtension: true, - CloseInternalServerErr: true, - CloseServiceRestart: true, - CloseTryAgainLater: true, - CloseTLSHandshake: false, -} - -func isValidReceivedCloseCode(code int) bool { - return validReceivedCloseCodes[code] || (code >= 3000 && code <= 4999) -} - -// The Conn type represents a WebSocket connection. -type Conn struct { - conn net.Conn - isServer bool - subprotocol string - - // Write fields - mu chan bool // used as mutex to protect write to conn - writeBuf []byte // frame is constructed in this buffer. - writeDeadline time.Time - writer io.WriteCloser // the current writer returned to the application - isWriting bool // for best-effort concurrent write detection - - writeErrMu sync.Mutex - writeErr error - - enableWriteCompression bool - compressionLevel int - newCompressionWriter func(io.WriteCloser, int) io.WriteCloser - - // Read fields - reader io.ReadCloser // the current reader returned to the application - readErr error - br *bufio.Reader - readRemaining int64 // bytes remaining in current frame. - readFinal bool // true the current message has more frames. - readLength int64 // Message size. - readLimit int64 // Maximum message size. - readMaskPos int - readMaskKey [4]byte - handlePong func(string) error - handlePing func(string) error - handleClose func(int, string) error - readErrCount int - messageReader *messageReader // the current low-level reader - - readDecompress bool // whether last read frame had RSV1 set - newDecompressionReader func(io.Reader) io.ReadCloser -} - -func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int) *Conn { - return newConnBRW(conn, isServer, readBufferSize, writeBufferSize, nil) -} - -type writeHook struct { - p []byte -} - -func (wh *writeHook) Write(p []byte) (int, error) { - wh.p = p - return len(p), nil -} - -func newConnBRW(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int, brw *bufio.ReadWriter) *Conn { - mu := make(chan bool, 1) - mu <- true - - var br *bufio.Reader - if readBufferSize == 0 && brw != nil && brw.Reader != nil { - // Reuse the supplied bufio.Reader if the buffer has a useful size. - // This code assumes that peek on a reader returns - // bufio.Reader.buf[:0]. - brw.Reader.Reset(conn) - if p, err := brw.Reader.Peek(0); err == nil && cap(p) >= 256 { - br = brw.Reader - } - } - if br == nil { - if readBufferSize == 0 { - readBufferSize = defaultReadBufferSize - } - if readBufferSize < maxControlFramePayloadSize { - readBufferSize = maxControlFramePayloadSize - } - br = bufio.NewReaderSize(conn, readBufferSize) - } - - var writeBuf []byte - if writeBufferSize == 0 && brw != nil && brw.Writer != nil { - // Use the bufio.Writer's buffer if the buffer has a useful size. This - // code assumes that bufio.Writer.buf[:1] is passed to the - // bufio.Writer's underlying writer. - var wh writeHook - brw.Writer.Reset(&wh) - brw.Writer.WriteByte(0) - brw.Flush() - if cap(wh.p) >= maxFrameHeaderSize+256 { - writeBuf = wh.p[:cap(wh.p)] - } - } - - if writeBuf == nil { - if writeBufferSize == 0 { - writeBufferSize = defaultWriteBufferSize - } - writeBuf = make([]byte, writeBufferSize+maxFrameHeaderSize) - } - - c := &Conn{ - isServer: isServer, - br: br, - conn: conn, - mu: mu, - readFinal: true, - writeBuf: writeBuf, - enableWriteCompression: true, - compressionLevel: defaultCompressionLevel, - } - c.SetCloseHandler(nil) - c.SetPingHandler(nil) - c.SetPongHandler(nil) - return c -} - -// Subprotocol returns the negotiated protocol for the connection. -func (c *Conn) Subprotocol() string { - return c.subprotocol -} - -// Close closes the underlying network connection without sending or waiting for a close frame. -func (c *Conn) Close() error { - return c.conn.Close() -} - -// LocalAddr returns the local network address. -func (c *Conn) LocalAddr() net.Addr { - return c.conn.LocalAddr() -} - -// RemoteAddr returns the remote network address. -func (c *Conn) RemoteAddr() net.Addr { - return c.conn.RemoteAddr() -} - -// Write methods - -func (c *Conn) writeFatal(err error) error { - err = hideTempErr(err) - c.writeErrMu.Lock() - if c.writeErr == nil { - c.writeErr = err - } - c.writeErrMu.Unlock() - return err -} - -func (c *Conn) write(frameType int, deadline time.Time, bufs ...[]byte) error { - <-c.mu - defer func() { c.mu <- true }() - - c.writeErrMu.Lock() - err := c.writeErr - c.writeErrMu.Unlock() - if err != nil { - return err - } - - c.conn.SetWriteDeadline(deadline) - for _, buf := range bufs { - if len(buf) > 0 { - _, err := c.conn.Write(buf) - if err != nil { - return c.writeFatal(err) - } - } - } - - if frameType == CloseMessage { - c.writeFatal(ErrCloseSent) - } - return nil -} - -// WriteControl writes a control message with the given deadline. The allowed -// message types are CloseMessage, PingMessage and PongMessage. -func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) error { - if !isControl(messageType) { - return errBadWriteOpCode - } - if len(data) > maxControlFramePayloadSize { - return errInvalidControlFrame - } - - b0 := byte(messageType) | finalBit - b1 := byte(len(data)) - if !c.isServer { - b1 |= maskBit - } - - buf := make([]byte, 0, maxFrameHeaderSize+maxControlFramePayloadSize) - buf = append(buf, b0, b1) - - if c.isServer { - buf = append(buf, data...) - } else { - key := newMaskKey() - buf = append(buf, key[:]...) - buf = append(buf, data...) - maskBytes(key, 0, buf[6:]) - } - - d := time.Hour * 1000 - if !deadline.IsZero() { - d = deadline.Sub(time.Now()) - if d < 0 { - return errWriteTimeout - } - } - - timer := time.NewTimer(d) - select { - case <-c.mu: - timer.Stop() - case <-timer.C: - return errWriteTimeout - } - defer func() { c.mu <- true }() - - c.writeErrMu.Lock() - err := c.writeErr - c.writeErrMu.Unlock() - if err != nil { - return err - } - - c.conn.SetWriteDeadline(deadline) - _, err = c.conn.Write(buf) - if err != nil { - return c.writeFatal(err) - } - if messageType == CloseMessage { - c.writeFatal(ErrCloseSent) - } - return err -} - -func (c *Conn) prepWrite(messageType int) error { - // Close previous writer if not already closed by the application. It's - // probably better to return an error in this situation, but we cannot - // change this without breaking existing applications. - if c.writer != nil { - c.writer.Close() - c.writer = nil - } - - if !isControl(messageType) && !isData(messageType) { - return errBadWriteOpCode - } - - c.writeErrMu.Lock() - err := c.writeErr - c.writeErrMu.Unlock() - return err -} - -// NextWriter returns a writer for the next message to send. The writer's Close -// method flushes the complete message to the network. -// -// There can be at most one open writer on a connection. NextWriter closes the -// previous writer if the application has not already done so. -func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) { - if err := c.prepWrite(messageType); err != nil { - return nil, err - } - - mw := &messageWriter{ - c: c, - frameType: messageType, - pos: maxFrameHeaderSize, - } - c.writer = mw - if c.newCompressionWriter != nil && c.enableWriteCompression && isData(messageType) { - w := c.newCompressionWriter(c.writer, c.compressionLevel) - mw.compress = true - c.writer = w - } - return c.writer, nil -} - -type messageWriter struct { - c *Conn - compress bool // whether next call to flushFrame should set RSV1 - pos int // end of data in writeBuf. - frameType int // type of the current frame. - err error -} - -func (w *messageWriter) fatal(err error) error { - if w.err != nil { - w.err = err - w.c.writer = nil - } - return err -} - -// flushFrame writes buffered data and extra as a frame to the network. The -// final argument indicates that this is the last frame in the message. -func (w *messageWriter) flushFrame(final bool, extra []byte) error { - c := w.c - length := w.pos - maxFrameHeaderSize + len(extra) - - // Check for invalid control frames. - if isControl(w.frameType) && - (!final || length > maxControlFramePayloadSize) { - return w.fatal(errInvalidControlFrame) - } - - b0 := byte(w.frameType) - if final { - b0 |= finalBit - } - if w.compress { - b0 |= rsv1Bit - } - w.compress = false - - b1 := byte(0) - if !c.isServer { - b1 |= maskBit - } - - // Assume that the frame starts at beginning of c.writeBuf. - framePos := 0 - if c.isServer { - // Adjust up if mask not included in the header. - framePos = 4 - } - - switch { - case length >= 65536: - c.writeBuf[framePos] = b0 - c.writeBuf[framePos+1] = b1 | 127 - binary.BigEndian.PutUint64(c.writeBuf[framePos+2:], uint64(length)) - case length > 125: - framePos += 6 - c.writeBuf[framePos] = b0 - c.writeBuf[framePos+1] = b1 | 126 - binary.BigEndian.PutUint16(c.writeBuf[framePos+2:], uint16(length)) - default: - framePos += 8 - c.writeBuf[framePos] = b0 - c.writeBuf[framePos+1] = b1 | byte(length) - } - - if !c.isServer { - key := newMaskKey() - copy(c.writeBuf[maxFrameHeaderSize-4:], key[:]) - maskBytes(key, 0, c.writeBuf[maxFrameHeaderSize:w.pos]) - if len(extra) > 0 { - return c.writeFatal(errors.New("websocket: internal error, extra used in client mode")) - } - } - - // Write the buffers to the connection with best-effort detection of - // concurrent writes. See the concurrency section in the package - // documentation for more info. - - if c.isWriting { - panic("concurrent write to websocket connection") - } - c.isWriting = true - - err := c.write(w.frameType, c.writeDeadline, c.writeBuf[framePos:w.pos], extra) - - if !c.isWriting { - panic("concurrent write to websocket connection") - } - c.isWriting = false - - if err != nil { - return w.fatal(err) - } - - if final { - c.writer = nil - return nil - } - - // Setup for next frame. - w.pos = maxFrameHeaderSize - w.frameType = continuationFrame - return nil -} - -func (w *messageWriter) ncopy(max int) (int, error) { - n := len(w.c.writeBuf) - w.pos - if n <= 0 { - if err := w.flushFrame(false, nil); err != nil { - return 0, err - } - n = len(w.c.writeBuf) - w.pos - } - if n > max { - n = max - } - return n, nil -} - -func (w *messageWriter) Write(p []byte) (int, error) { - if w.err != nil { - return 0, w.err - } - - if len(p) > 2*len(w.c.writeBuf) && w.c.isServer { - // Don't buffer large messages. - err := w.flushFrame(false, p) - if err != nil { - return 0, err - } - return len(p), nil - } - - nn := len(p) - for len(p) > 0 { - n, err := w.ncopy(len(p)) - if err != nil { - return 0, err - } - copy(w.c.writeBuf[w.pos:], p[:n]) - w.pos += n - p = p[n:] - } - return nn, nil -} - -func (w *messageWriter) WriteString(p string) (int, error) { - if w.err != nil { - return 0, w.err - } - - nn := len(p) - for len(p) > 0 { - n, err := w.ncopy(len(p)) - if err != nil { - return 0, err - } - copy(w.c.writeBuf[w.pos:], p[:n]) - w.pos += n - p = p[n:] - } - return nn, nil -} - -func (w *messageWriter) ReadFrom(r io.Reader) (nn int64, err error) { - if w.err != nil { - return 0, w.err - } - for { - if w.pos == len(w.c.writeBuf) { - err = w.flushFrame(false, nil) - if err != nil { - break - } - } - var n int - n, err = r.Read(w.c.writeBuf[w.pos:]) - w.pos += n - nn += int64(n) - if err != nil { - if err == io.EOF { - err = nil - } - break - } - } - return nn, err -} - -func (w *messageWriter) Close() error { - if w.err != nil { - return w.err - } - if err := w.flushFrame(true, nil); err != nil { - return err - } - w.err = errWriteClosed - return nil -} - -// WritePreparedMessage writes prepared message into connection. -func (c *Conn) WritePreparedMessage(pm *PreparedMessage) error { - frameType, frameData, err := pm.frame(prepareKey{ - isServer: c.isServer, - compress: c.newCompressionWriter != nil && c.enableWriteCompression && isData(pm.messageType), - compressionLevel: c.compressionLevel, - }) - if err != nil { - return err - } - if c.isWriting { - panic("concurrent write to websocket connection") - } - c.isWriting = true - err = c.write(frameType, c.writeDeadline, frameData, nil) - if !c.isWriting { - panic("concurrent write to websocket connection") - } - c.isWriting = false - return err -} - -// WriteMessage is a helper method for getting a writer using NextWriter, -// writing the message and closing the writer. -func (c *Conn) WriteMessage(messageType int, data []byte) error { - - if c.isServer && (c.newCompressionWriter == nil || !c.enableWriteCompression) { - // Fast path with no allocations and single frame. - - if err := c.prepWrite(messageType); err != nil { - return err - } - mw := messageWriter{c: c, frameType: messageType, pos: maxFrameHeaderSize} - n := copy(c.writeBuf[mw.pos:], data) - mw.pos += n - data = data[n:] - return mw.flushFrame(true, data) - } - - w, err := c.NextWriter(messageType) - if err != nil { - return err - } - if _, err = w.Write(data); err != nil { - return err - } - return w.Close() -} - -// SetWriteDeadline sets the write deadline on the underlying network -// connection. After a write has timed out, the websocket state is corrupt and -// all future writes will return an error. A zero value for t means writes will -// not time out. -func (c *Conn) SetWriteDeadline(t time.Time) error { - c.writeDeadline = t - return nil -} - -// Read methods - -func (c *Conn) advanceFrame() (int, error) { - - // 1. Skip remainder of previous frame. - - if c.readRemaining > 0 { - if _, err := io.CopyN(ioutil.Discard, c.br, c.readRemaining); err != nil { - return noFrame, err - } - } - - // 2. Read and parse first two bytes of frame header. - - p, err := c.read(2) - if err != nil { - return noFrame, err - } - - final := p[0]&finalBit != 0 - frameType := int(p[0] & 0xf) - mask := p[1]&maskBit != 0 - c.readRemaining = int64(p[1] & 0x7f) - - c.readDecompress = false - if c.newDecompressionReader != nil && (p[0]&rsv1Bit) != 0 { - c.readDecompress = true - p[0] &^= rsv1Bit - } - - if rsv := p[0] & (rsv1Bit | rsv2Bit | rsv3Bit); rsv != 0 { - return noFrame, c.handleProtocolError("unexpected reserved bits 0x" + strconv.FormatInt(int64(rsv), 16)) - } - - switch frameType { - case CloseMessage, PingMessage, PongMessage: - if c.readRemaining > maxControlFramePayloadSize { - return noFrame, c.handleProtocolError("control frame length > 125") - } - if !final { - return noFrame, c.handleProtocolError("control frame not final") - } - case TextMessage, BinaryMessage: - if !c.readFinal { - return noFrame, c.handleProtocolError("message start before final message frame") - } - c.readFinal = final - case continuationFrame: - if c.readFinal { - return noFrame, c.handleProtocolError("continuation after final message frame") - } - c.readFinal = final - default: - return noFrame, c.handleProtocolError("unknown opcode " + strconv.Itoa(frameType)) - } - - // 3. Read and parse frame length. - - switch c.readRemaining { - case 126: - p, err := c.read(2) - if err != nil { - return noFrame, err - } - c.readRemaining = int64(binary.BigEndian.Uint16(p)) - case 127: - p, err := c.read(8) - if err != nil { - return noFrame, err - } - c.readRemaining = int64(binary.BigEndian.Uint64(p)) - } - - // 4. Handle frame masking. - - if mask != c.isServer { - return noFrame, c.handleProtocolError("incorrect mask flag") - } - - if mask { - c.readMaskPos = 0 - p, err := c.read(len(c.readMaskKey)) - if err != nil { - return noFrame, err - } - copy(c.readMaskKey[:], p) - } - - // 5. For text and binary messages, enforce read limit and return. - - if frameType == continuationFrame || frameType == TextMessage || frameType == BinaryMessage { - - c.readLength += c.readRemaining - if c.readLimit > 0 && c.readLength > c.readLimit { - c.WriteControl(CloseMessage, FormatCloseMessage(CloseMessageTooBig, ""), time.Now().Add(writeWait)) - return noFrame, ErrReadLimit - } - - return frameType, nil - } - - // 6. Read control frame payload. - - var payload []byte - if c.readRemaining > 0 { - payload, err = c.read(int(c.readRemaining)) - c.readRemaining = 0 - if err != nil { - return noFrame, err - } - if c.isServer { - maskBytes(c.readMaskKey, 0, payload) - } - } - - // 7. Process control frame payload. - - switch frameType { - case PongMessage: - if err := c.handlePong(string(payload)); err != nil { - return noFrame, err - } - case PingMessage: - if err := c.handlePing(string(payload)); err != nil { - return noFrame, err - } - case CloseMessage: - closeCode := CloseNoStatusReceived - closeText := "" - if len(payload) >= 2 { - closeCode = int(binary.BigEndian.Uint16(payload)) - if !isValidReceivedCloseCode(closeCode) { - return noFrame, c.handleProtocolError("invalid close code") - } - closeText = string(payload[2:]) - if !utf8.ValidString(closeText) { - return noFrame, c.handleProtocolError("invalid utf8 payload in close frame") - } - } - if err := c.handleClose(closeCode, closeText); err != nil { - return noFrame, err - } - return noFrame, &CloseError{Code: closeCode, Text: closeText} - } - - return frameType, nil -} - -func (c *Conn) handleProtocolError(message string) error { - c.WriteControl(CloseMessage, FormatCloseMessage(CloseProtocolError, message), time.Now().Add(writeWait)) - return errors.New("websocket: " + message) -} - -// NextReader returns the next data message received from the peer. The -// returned messageType is either TextMessage or BinaryMessage. -// -// There can be at most one open reader on a connection. NextReader discards -// the previous message if the application has not already consumed it. -// -// Applications must break out of the application's read loop when this method -// returns a non-nil error value. Errors returned from this method are -// permanent. Once this method returns a non-nil error, all subsequent calls to -// this method return the same error. -func (c *Conn) NextReader() (messageType int, r io.Reader, err error) { - // Close previous reader, only relevant for decompression. - if c.reader != nil { - c.reader.Close() - c.reader = nil - } - - c.messageReader = nil - c.readLength = 0 - - for c.readErr == nil { - frameType, err := c.advanceFrame() - if err != nil { - c.readErr = hideTempErr(err) - break - } - if frameType == TextMessage || frameType == BinaryMessage { - c.messageReader = &messageReader{c} - c.reader = c.messageReader - if c.readDecompress { - c.reader = c.newDecompressionReader(c.reader) - } - return frameType, c.reader, nil - } - } - - // Applications that do handle the error returned from this method spin in - // tight loop on connection failure. To help application developers detect - // this error, panic on repeated reads to the failed connection. - c.readErrCount++ - if c.readErrCount >= 1000 { - panic("repeated read on failed websocket connection") - } - - return noFrame, nil, c.readErr -} - -type messageReader struct{ c *Conn } - -func (r *messageReader) Read(b []byte) (int, error) { - c := r.c - if c.messageReader != r { - return 0, io.EOF - } - - for c.readErr == nil { - - if c.readRemaining > 0 { - if int64(len(b)) > c.readRemaining { - b = b[:c.readRemaining] - } - n, err := c.br.Read(b) - c.readErr = hideTempErr(err) - if c.isServer { - c.readMaskPos = maskBytes(c.readMaskKey, c.readMaskPos, b[:n]) - } - c.readRemaining -= int64(n) - if c.readRemaining > 0 && c.readErr == io.EOF { - c.readErr = errUnexpectedEOF - } - return n, c.readErr - } - - if c.readFinal { - c.messageReader = nil - return 0, io.EOF - } - - frameType, err := c.advanceFrame() - switch { - case err != nil: - c.readErr = hideTempErr(err) - case frameType == TextMessage || frameType == BinaryMessage: - c.readErr = errors.New("websocket: internal error, unexpected text or binary in Reader") - } - } - - err := c.readErr - if err == io.EOF && c.messageReader == r { - err = errUnexpectedEOF - } - return 0, err -} - -func (r *messageReader) Close() error { - return nil -} - -// ReadMessage is a helper method for getting a reader using NextReader and -// reading from that reader to a buffer. -func (c *Conn) ReadMessage() (messageType int, p []byte, err error) { - var r io.Reader - messageType, r, err = c.NextReader() - if err != nil { - return messageType, nil, err - } - p, err = ioutil.ReadAll(r) - return messageType, p, err -} - -// SetReadDeadline sets the read deadline on the underlying network connection. -// After a read has timed out, the websocket connection state is corrupt and -// all future reads will return an error. A zero value for t means reads will -// not time out. -func (c *Conn) SetReadDeadline(t time.Time) error { - return c.conn.SetReadDeadline(t) -} - -// SetReadLimit sets the maximum size for a message read from the peer. If a -// message exceeds the limit, the connection sends a close frame to the peer -// and returns ErrReadLimit to the application. -func (c *Conn) SetReadLimit(limit int64) { - c.readLimit = limit -} - -// CloseHandler returns the current close handler -func (c *Conn) CloseHandler() func(code int, text string) error { - return c.handleClose -} - -// SetCloseHandler sets the handler for close messages received from the peer. -// The code argument to h is the received close code or CloseNoStatusReceived -// if the close message is empty. The default close handler sends a close frame -// back to the peer. -// -// The application must read the connection to process close messages as -// described in the section on Control Frames above. -// -// The connection read methods return a CloseError when a close frame is -// received. Most applications should handle close messages as part of their -// normal error handling. Applications should only set a close handler when the -// application must perform some action before sending a close frame back to -// the peer. -func (c *Conn) SetCloseHandler(h func(code int, text string) error) { - if h == nil { - h = func(code int, text string) error { - message := []byte{} - if code != CloseNoStatusReceived { - message = FormatCloseMessage(code, "") - } - c.WriteControl(CloseMessage, message, time.Now().Add(writeWait)) - return nil - } - } - c.handleClose = h -} - -// PingHandler returns the current ping handler -func (c *Conn) PingHandler() func(appData string) error { - return c.handlePing -} - -// SetPingHandler sets the handler for ping messages received from the peer. -// The appData argument to h is the PING frame application data. The default -// ping handler sends a pong to the peer. -// -// The application must read the connection to process ping messages as -// described in the section on Control Frames above. -func (c *Conn) SetPingHandler(h func(appData string) error) { - if h == nil { - h = func(message string) error { - err := c.WriteControl(PongMessage, []byte(message), time.Now().Add(writeWait)) - if err == ErrCloseSent { - return nil - } else if e, ok := err.(net.Error); ok && e.Temporary() { - return nil - } - return err - } - } - c.handlePing = h -} - -// PongHandler returns the current pong handler -func (c *Conn) PongHandler() func(appData string) error { - return c.handlePong -} - -// SetPongHandler sets the handler for pong messages received from the peer. -// The appData argument to h is the PONG frame application data. The default -// pong handler does nothing. -// -// The application must read the connection to process ping messages as -// described in the section on Control Frames above. -func (c *Conn) SetPongHandler(h func(appData string) error) { - if h == nil { - h = func(string) error { return nil } - } - c.handlePong = h -} - -// UnderlyingConn returns the internal net.Conn. This can be used to further -// modifications to connection specific flags. -func (c *Conn) UnderlyingConn() net.Conn { - return c.conn -} - -// EnableWriteCompression enables and disables write compression of -// subsequent text and binary messages. This function is a noop if -// compression was not negotiated with the peer. -func (c *Conn) EnableWriteCompression(enable bool) { - c.enableWriteCompression = enable -} - -// SetCompressionLevel sets the flate compression level for subsequent text and -// binary messages. This function is a noop if compression was not negotiated -// with the peer. See the compress/flate package for a description of -// compression levels. -func (c *Conn) SetCompressionLevel(level int) error { - if !isValidCompressionLevel(level) { - return errors.New("websocket: invalid compression level") - } - c.compressionLevel = level - return nil -} - -// FormatCloseMessage formats closeCode and text as a WebSocket close message. -func FormatCloseMessage(closeCode int, text string) []byte { - buf := make([]byte, 2+len(text)) - binary.BigEndian.PutUint16(buf, uint16(closeCode)) - copy(buf[2:], text) - return buf -} diff --git a/vendor/github.com/gorilla/websocket/conn_read.go b/vendor/github.com/gorilla/websocket/conn_read.go deleted file mode 100644 index 1ea15059ee1..00000000000 --- a/vendor/github.com/gorilla/websocket/conn_read.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build go1.5 - -package websocket - -import "io" - -func (c *Conn) read(n int) ([]byte, error) { - p, err := c.br.Peek(n) - if err == io.EOF { - err = errUnexpectedEOF - } - c.br.Discard(len(p)) - return p, err -} diff --git a/vendor/github.com/gorilla/websocket/conn_read_legacy.go b/vendor/github.com/gorilla/websocket/conn_read_legacy.go deleted file mode 100644 index 018541cf6cb..00000000000 --- a/vendor/github.com/gorilla/websocket/conn_read_legacy.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !go1.5 - -package websocket - -import "io" - -func (c *Conn) read(n int) ([]byte, error) { - p, err := c.br.Peek(n) - if err == io.EOF { - err = errUnexpectedEOF - } - if len(p) > 0 { - // advance over the bytes just read - io.ReadFull(c.br, p) - } - return p, err -} diff --git a/vendor/github.com/gorilla/websocket/doc.go b/vendor/github.com/gorilla/websocket/doc.go deleted file mode 100644 index f5ff0823da7..00000000000 --- a/vendor/github.com/gorilla/websocket/doc.go +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package websocket implements the WebSocket protocol defined in RFC 6455. -// -// Overview -// -// The Conn type represents a WebSocket connection. A server application calls -// the Upgrader.Upgrade method from an HTTP request handler to get a *Conn: -// -// var upgrader = websocket.Upgrader{ -// ReadBufferSize: 1024, -// WriteBufferSize: 1024, -// } -// -// func handler(w http.ResponseWriter, r *http.Request) { -// conn, err := upgrader.Upgrade(w, r, nil) -// if err != nil { -// log.Println(err) -// return -// } -// ... Use conn to send and receive messages. -// } -// -// Call the connection's WriteMessage and ReadMessage methods to send and -// receive messages as a slice of bytes. This snippet of code shows how to echo -// messages using these methods: -// -// for { -// messageType, p, err := conn.ReadMessage() -// if err != nil { -// return -// } -// if err := conn.WriteMessage(messageType, p); err != nil { -// return err -// } -// } -// -// In above snippet of code, p is a []byte and messageType is an int with value -// websocket.BinaryMessage or websocket.TextMessage. -// -// An application can also send and receive messages using the io.WriteCloser -// and io.Reader interfaces. To send a message, call the connection NextWriter -// method to get an io.WriteCloser, write the message to the writer and close -// the writer when done. To receive a message, call the connection NextReader -// method to get an io.Reader and read until io.EOF is returned. This snippet -// shows how to echo messages using the NextWriter and NextReader methods: -// -// for { -// messageType, r, err := conn.NextReader() -// if err != nil { -// return -// } -// w, err := conn.NextWriter(messageType) -// if err != nil { -// return err -// } -// if _, err := io.Copy(w, r); err != nil { -// return err -// } -// if err := w.Close(); err != nil { -// return err -// } -// } -// -// Data Messages -// -// The WebSocket protocol distinguishes between text and binary data messages. -// Text messages are interpreted as UTF-8 encoded text. The interpretation of -// binary messages is left to the application. -// -// This package uses the TextMessage and BinaryMessage integer constants to -// identify the two data message types. The ReadMessage and NextReader methods -// return the type of the received message. The messageType argument to the -// WriteMessage and NextWriter methods specifies the type of a sent message. -// -// It is the application's responsibility to ensure that text messages are -// valid UTF-8 encoded text. -// -// Control Messages -// -// The WebSocket protocol defines three types of control messages: close, ping -// and pong. Call the connection WriteControl, WriteMessage or NextWriter -// methods to send a control message to the peer. -// -// Connections handle received close messages by sending a close message to the -// peer and returning a *CloseError from the the NextReader, ReadMessage or the -// message Read method. -// -// Connections handle received ping and pong messages by invoking callback -// functions set with SetPingHandler and SetPongHandler methods. The callback -// functions are called from the NextReader, ReadMessage and the message Read -// methods. -// -// The default ping handler sends a pong to the peer. The application's reading -// goroutine can block for a short time while the handler writes the pong data -// to the connection. -// -// The application must read the connection to process ping, pong and close -// messages sent from the peer. If the application is not otherwise interested -// in messages from the peer, then the application should start a goroutine to -// read and discard messages from the peer. A simple example is: -// -// func readLoop(c *websocket.Conn) { -// for { -// if _, _, err := c.NextReader(); err != nil { -// c.Close() -// break -// } -// } -// } -// -// Concurrency -// -// Connections support one concurrent reader and one concurrent writer. -// -// Applications are responsible for ensuring that no more than one goroutine -// calls the write methods (NextWriter, SetWriteDeadline, WriteMessage, -// WriteJSON, EnableWriteCompression, SetCompressionLevel) concurrently and -// that no more than one goroutine calls the read methods (NextReader, -// SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler) -// concurrently. -// -// The Close and WriteControl methods can be called concurrently with all other -// methods. -// -// Origin Considerations -// -// Web browsers allow Javascript applications to open a WebSocket connection to -// any host. It's up to the server to enforce an origin policy using the Origin -// request header sent by the browser. -// -// The Upgrader calls the function specified in the CheckOrigin field to check -// the origin. If the CheckOrigin function returns false, then the Upgrade -// method fails the WebSocket handshake with HTTP status 403. -// -// If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail -// the handshake if the Origin request header is present and not equal to the -// Host request header. -// -// An application can allow connections from any origin by specifying a -// function that always returns true: -// -// var upgrader = websocket.Upgrader{ -// CheckOrigin: func(r *http.Request) bool { return true }, -// } -// -// The deprecated package-level Upgrade function does not perform origin -// checking. The application is responsible for checking the Origin header -// before calling the Upgrade function. -// -// Compression EXPERIMENTAL -// -// Per message compression extensions (RFC 7692) are experimentally supported -// by this package in a limited capacity. Setting the EnableCompression option -// to true in Dialer or Upgrader will attempt to negotiate per message deflate -// support. -// -// var upgrader = websocket.Upgrader{ -// EnableCompression: true, -// } -// -// If compression was successfully negotiated with the connection's peer, any -// message received in compressed form will be automatically decompressed. -// All Read methods will return uncompressed bytes. -// -// Per message compression of messages written to a connection can be enabled -// or disabled by calling the corresponding Conn method: -// -// conn.EnableWriteCompression(false) -// -// Currently this package does not support compression with "context takeover". -// This means that messages must be compressed and decompressed in isolation, -// without retaining sliding window or dictionary state across messages. For -// more details refer to RFC 7692. -// -// Use of compression is experimental and may result in decreased performance. -package websocket diff --git a/vendor/github.com/gorilla/websocket/examples/autobahn/server.go b/vendor/github.com/gorilla/websocket/examples/autobahn/server.go deleted file mode 100644 index 3db880f9017..00000000000 --- a/vendor/github.com/gorilla/websocket/examples/autobahn/server.go +++ /dev/null @@ -1,265 +0,0 @@ -// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Command server is a test server for the Autobahn WebSockets Test Suite. -package main - -import ( - "errors" - "flag" - "io" - "log" - "net/http" - "time" - "unicode/utf8" - - "github.com/gorilla/websocket" -) - -var upgrader = websocket.Upgrader{ - ReadBufferSize: 4096, - WriteBufferSize: 4096, - EnableCompression: true, - CheckOrigin: func(r *http.Request) bool { - return true - }, -} - -// echoCopy echoes messages from the client using io.Copy. -func echoCopy(w http.ResponseWriter, r *http.Request, writerOnly bool) { - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - log.Println("Upgrade:", err) - return - } - defer conn.Close() - for { - mt, r, err := conn.NextReader() - if err != nil { - if err != io.EOF { - log.Println("NextReader:", err) - } - return - } - if mt == websocket.TextMessage { - r = &validator{r: r} - } - w, err := conn.NextWriter(mt) - if err != nil { - log.Println("NextWriter:", err) - return - } - if mt == websocket.TextMessage { - r = &validator{r: r} - } - if writerOnly { - _, err = io.Copy(struct{ io.Writer }{w}, r) - } else { - _, err = io.Copy(w, r) - } - if err != nil { - if err == errInvalidUTF8 { - conn.WriteControl(websocket.CloseMessage, - websocket.FormatCloseMessage(websocket.CloseInvalidFramePayloadData, ""), - time.Time{}) - } - log.Println("Copy:", err) - return - } - err = w.Close() - if err != nil { - log.Println("Close:", err) - return - } - } -} - -func echoCopyWriterOnly(w http.ResponseWriter, r *http.Request) { - echoCopy(w, r, true) -} - -func echoCopyFull(w http.ResponseWriter, r *http.Request) { - echoCopy(w, r, false) -} - -// echoReadAll echoes messages from the client by reading the entire message -// with ioutil.ReadAll. -func echoReadAll(w http.ResponseWriter, r *http.Request, writeMessage, writePrepared bool) { - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - log.Println("Upgrade:", err) - return - } - defer conn.Close() - for { - mt, b, err := conn.ReadMessage() - if err != nil { - if err != io.EOF { - log.Println("NextReader:", err) - } - return - } - if mt == websocket.TextMessage { - if !utf8.Valid(b) { - conn.WriteControl(websocket.CloseMessage, - websocket.FormatCloseMessage(websocket.CloseInvalidFramePayloadData, ""), - time.Time{}) - log.Println("ReadAll: invalid utf8") - } - } - if writeMessage { - if !writePrepared { - err = conn.WriteMessage(mt, b) - if err != nil { - log.Println("WriteMessage:", err) - } - } else { - pm, err := websocket.NewPreparedMessage(mt, b) - if err != nil { - log.Println("NewPreparedMessage:", err) - return - } - err = conn.WritePreparedMessage(pm) - if err != nil { - log.Println("WritePreparedMessage:", err) - } - } - } else { - w, err := conn.NextWriter(mt) - if err != nil { - log.Println("NextWriter:", err) - return - } - if _, err := w.Write(b); err != nil { - log.Println("Writer:", err) - return - } - if err := w.Close(); err != nil { - log.Println("Close:", err) - return - } - } - } -} - -func echoReadAllWriter(w http.ResponseWriter, r *http.Request) { - echoReadAll(w, r, false, false) -} - -func echoReadAllWriteMessage(w http.ResponseWriter, r *http.Request) { - echoReadAll(w, r, true, false) -} - -func echoReadAllWritePreparedMessage(w http.ResponseWriter, r *http.Request) { - echoReadAll(w, r, true, true) -} - -func serveHome(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/" { - http.Error(w, "Not found.", 404) - return - } - if r.Method != "GET" { - http.Error(w, "Method not allowed", 405) - return - } - w.Header().Set("Content-Type", "text/html; charset=utf-8") - io.WriteString(w, "Echo Server") -} - -var addr = flag.String("addr", ":9000", "http service address") - -func main() { - flag.Parse() - http.HandleFunc("/", serveHome) - http.HandleFunc("/c", echoCopyWriterOnly) - http.HandleFunc("/f", echoCopyFull) - http.HandleFunc("/r", echoReadAllWriter) - http.HandleFunc("/m", echoReadAllWriteMessage) - http.HandleFunc("/p", echoReadAllWritePreparedMessage) - err := http.ListenAndServe(*addr, nil) - if err != nil { - log.Fatal("ListenAndServe: ", err) - } -} - -type validator struct { - state int - x rune - r io.Reader -} - -var errInvalidUTF8 = errors.New("invalid utf8") - -func (r *validator) Read(p []byte) (int, error) { - n, err := r.r.Read(p) - state := r.state - x := r.x - for _, b := range p[:n] { - state, x = decode(state, x, b) - if state == utf8Reject { - break - } - } - r.state = state - r.x = x - if state == utf8Reject || (err == io.EOF && state != utf8Accept) { - return n, errInvalidUTF8 - } - return n, err -} - -// UTF-8 decoder from http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ -// -// Copyright (c) 2008-2009 Bjoern Hoehrmann -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -var utf8d = [...]byte{ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1f - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3f - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5f - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7f - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9f - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // a0..bf - 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // c0..df - 0xa, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // e0..ef - 0xb, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // f0..ff - 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 - 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 - 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 - 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s7..s8 -} - -const ( - utf8Accept = 0 - utf8Reject = 1 -) - -func decode(state int, x rune, b byte) (int, rune) { - t := utf8d[b] - if state != utf8Accept { - x = rune(b&0x3f) | (x << 6) - } else { - x = rune((0xff >> t) & b) - } - state = int(utf8d[256+state*16+int(t)]) - return state, x -} diff --git a/vendor/github.com/gorilla/websocket/examples/chat/client.go b/vendor/github.com/gorilla/websocket/examples/chat/client.go deleted file mode 100644 index ecfd9a7aabf..00000000000 --- a/vendor/github.com/gorilla/websocket/examples/chat/client.go +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package main - -import ( - "bytes" - "log" - "net/http" - "time" - - "github.com/gorilla/websocket" -) - -const ( - // Time allowed to write a message to the peer. - writeWait = 10 * time.Second - - // Time allowed to read the next pong message from the peer. - pongWait = 60 * time.Second - - // Send pings to peer with this period. Must be less than pongWait. - pingPeriod = (pongWait * 9) / 10 - - // Maximum message size allowed from peer. - maxMessageSize = 512 -) - -var ( - newline = []byte{'\n'} - space = []byte{' '} -) - -var upgrader = websocket.Upgrader{ - ReadBufferSize: 1024, - WriteBufferSize: 1024, -} - -// Client is a middleman between the websocket connection and the hub. -type Client struct { - hub *Hub - - // The websocket connection. - conn *websocket.Conn - - // Buffered channel of outbound messages. - send chan []byte -} - -// readPump pumps messages from the websocket connection to the hub. -// -// The application runs readPump in a per-connection goroutine. The application -// ensures that there is at most one reader on a connection by executing all -// reads from this goroutine. -func (c *Client) readPump() { - defer func() { - c.hub.unregister <- c - c.conn.Close() - }() - c.conn.SetReadLimit(maxMessageSize) - c.conn.SetReadDeadline(time.Now().Add(pongWait)) - c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil }) - for { - _, message, err := c.conn.ReadMessage() - if err != nil { - if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) { - log.Printf("error: %v", err) - } - break - } - message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1)) - c.hub.broadcast <- message - } -} - -// writePump pumps messages from the hub to the websocket connection. -// -// A goroutine running writePump is started for each connection. The -// application ensures that there is at most one writer to a connection by -// executing all writes from this goroutine. -func (c *Client) writePump() { - ticker := time.NewTicker(pingPeriod) - defer func() { - ticker.Stop() - c.conn.Close() - }() - for { - select { - case message, ok := <-c.send: - c.conn.SetWriteDeadline(time.Now().Add(writeWait)) - if !ok { - // The hub closed the channel. - c.conn.WriteMessage(websocket.CloseMessage, []byte{}) - return - } - - w, err := c.conn.NextWriter(websocket.TextMessage) - if err != nil { - return - } - w.Write(message) - - // Add queued chat messages to the current websocket message. - n := len(c.send) - for i := 0; i < n; i++ { - w.Write(newline) - w.Write(<-c.send) - } - - if err := w.Close(); err != nil { - return - } - case <-ticker.C: - c.conn.SetWriteDeadline(time.Now().Add(writeWait)) - if err := c.conn.WriteMessage(websocket.PingMessage, []byte{}); err != nil { - return - } - } - } -} - -// serveWs handles websocket requests from the peer. -func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - log.Println(err) - return - } - client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)} - client.hub.register <- client - - // Allow collection of memory referenced by the caller by doing all work in - // new goroutines. - go client.writePump() - go client.readPump() -} diff --git a/vendor/github.com/gorilla/websocket/examples/chat/hub.go b/vendor/github.com/gorilla/websocket/examples/chat/hub.go deleted file mode 100644 index 7f07ea0790e..00000000000 --- a/vendor/github.com/gorilla/websocket/examples/chat/hub.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package main - -// hub maintains the set of active clients and broadcasts messages to the -// clients. -type Hub struct { - // Registered clients. - clients map[*Client]bool - - // Inbound messages from the clients. - broadcast chan []byte - - // Register requests from the clients. - register chan *Client - - // Unregister requests from clients. - unregister chan *Client -} - -func newHub() *Hub { - return &Hub{ - broadcast: make(chan []byte), - register: make(chan *Client), - unregister: make(chan *Client), - clients: make(map[*Client]bool), - } -} - -func (h *Hub) run() { - for { - select { - case client := <-h.register: - h.clients[client] = true - case client := <-h.unregister: - if _, ok := h.clients[client]; ok { - delete(h.clients, client) - close(client.send) - } - case message := <-h.broadcast: - for client := range h.clients { - select { - case client.send <- message: - default: - close(client.send) - delete(h.clients, client) - } - } - } - } -} diff --git a/vendor/github.com/gorilla/websocket/examples/chat/main.go b/vendor/github.com/gorilla/websocket/examples/chat/main.go deleted file mode 100644 index 74615d59c8f..00000000000 --- a/vendor/github.com/gorilla/websocket/examples/chat/main.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package main - -import ( - "flag" - "log" - "net/http" -) - -var addr = flag.String("addr", ":8080", "http service address") - -func serveHome(w http.ResponseWriter, r *http.Request) { - log.Println(r.URL) - if r.URL.Path != "/" { - http.Error(w, "Not found", 404) - return - } - if r.Method != "GET" { - http.Error(w, "Method not allowed", 405) - return - } - http.ServeFile(w, r, "home.html") -} - -func main() { - flag.Parse() - hub := newHub() - go hub.run() - http.HandleFunc("/", serveHome) - http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { - serveWs(hub, w, r) - }) - err := http.ListenAndServe(*addr, nil) - if err != nil { - log.Fatal("ListenAndServe: ", err) - } -} diff --git a/vendor/github.com/gorilla/websocket/examples/command/main.go b/vendor/github.com/gorilla/websocket/examples/command/main.go deleted file mode 100644 index 239c5c85cf1..00000000000 --- a/vendor/github.com/gorilla/websocket/examples/command/main.go +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package main - -import ( - "bufio" - "flag" - "io" - "log" - "net/http" - "os" - "os/exec" - "time" - - "github.com/gorilla/websocket" -) - -var ( - addr = flag.String("addr", "127.0.0.1:8080", "http service address") - cmdPath string -) - -const ( - // Time allowed to write a message to the peer. - writeWait = 10 * time.Second - - // Maximum message size allowed from peer. - maxMessageSize = 8192 - - // Time allowed to read the next pong message from the peer. - pongWait = 60 * time.Second - - // Send pings to peer with this period. Must be less than pongWait. - pingPeriod = (pongWait * 9) / 10 - - // Time to wait before force close on connection. - closeGracePeriod = 10 * time.Second -) - -func pumpStdin(ws *websocket.Conn, w io.Writer) { - defer ws.Close() - ws.SetReadLimit(maxMessageSize) - ws.SetReadDeadline(time.Now().Add(pongWait)) - ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil }) - for { - _, message, err := ws.ReadMessage() - if err != nil { - break - } - message = append(message, '\n') - if _, err := w.Write(message); err != nil { - break - } - } -} - -func pumpStdout(ws *websocket.Conn, r io.Reader, done chan struct{}) { - defer func() { - }() - s := bufio.NewScanner(r) - for s.Scan() { - ws.SetWriteDeadline(time.Now().Add(writeWait)) - if err := ws.WriteMessage(websocket.TextMessage, s.Bytes()); err != nil { - ws.Close() - break - } - } - if s.Err() != nil { - log.Println("scan:", s.Err()) - } - close(done) - - ws.SetWriteDeadline(time.Now().Add(writeWait)) - ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) - time.Sleep(closeGracePeriod) - ws.Close() -} - -func ping(ws *websocket.Conn, done chan struct{}) { - ticker := time.NewTicker(pingPeriod) - defer ticker.Stop() - for { - select { - case <-ticker.C: - if err := ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait)); err != nil { - log.Println("ping:", err) - } - case <-done: - return - } - } -} - -func internalError(ws *websocket.Conn, msg string, err error) { - log.Println(msg, err) - ws.WriteMessage(websocket.TextMessage, []byte("Internal server error.")) -} - -var upgrader = websocket.Upgrader{} - -func serveWs(w http.ResponseWriter, r *http.Request) { - ws, err := upgrader.Upgrade(w, r, nil) - if err != nil { - log.Println("upgrade:", err) - return - } - - defer ws.Close() - - outr, outw, err := os.Pipe() - if err != nil { - internalError(ws, "stdout:", err) - return - } - defer outr.Close() - defer outw.Close() - - inr, inw, err := os.Pipe() - if err != nil { - internalError(ws, "stdin:", err) - return - } - defer inr.Close() - defer inw.Close() - - proc, err := os.StartProcess(cmdPath, flag.Args(), &os.ProcAttr{ - Files: []*os.File{inr, outw, outw}, - }) - if err != nil { - internalError(ws, "start:", err) - return - } - - inr.Close() - outw.Close() - - stdoutDone := make(chan struct{}) - go pumpStdout(ws, outr, stdoutDone) - go ping(ws, stdoutDone) - - pumpStdin(ws, inw) - - // Some commands will exit when stdin is closed. - inw.Close() - - // Other commands need a bonk on the head. - if err := proc.Signal(os.Interrupt); err != nil { - log.Println("inter:", err) - } - - select { - case <-stdoutDone: - case <-time.After(time.Second): - // A bigger bonk on the head. - if err := proc.Signal(os.Kill); err != nil { - log.Println("term:", err) - } - <-stdoutDone - } - - if _, err := proc.Wait(); err != nil { - log.Println("wait:", err) - } -} - -func serveHome(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/" { - http.Error(w, "Not found", 404) - return - } - if r.Method != "GET" { - http.Error(w, "Method not allowed", 405) - return - } - http.ServeFile(w, r, "home.html") -} - -func main() { - flag.Parse() - if len(flag.Args()) < 1 { - log.Fatal("must specify at least one argument") - } - var err error - cmdPath, err = exec.LookPath(flag.Args()[0]) - if err != nil { - log.Fatal(err) - } - http.HandleFunc("/", serveHome) - http.HandleFunc("/ws", serveWs) - log.Fatal(http.ListenAndServe(*addr, nil)) -} diff --git a/vendor/github.com/gorilla/websocket/examples/echo/client.go b/vendor/github.com/gorilla/websocket/examples/echo/client.go deleted file mode 100644 index 6578094e773..00000000000 --- a/vendor/github.com/gorilla/websocket/examples/echo/client.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build ignore - -package main - -import ( - "flag" - "log" - "net/url" - "os" - "os/signal" - "time" - - "github.com/gorilla/websocket" -) - -var addr = flag.String("addr", "localhost:8080", "http service address") - -func main() { - flag.Parse() - log.SetFlags(0) - - interrupt := make(chan os.Signal, 1) - signal.Notify(interrupt, os.Interrupt) - - u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo"} - log.Printf("connecting to %s", u.String()) - - c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) - if err != nil { - log.Fatal("dial:", err) - } - defer c.Close() - - done := make(chan struct{}) - - go func() { - defer c.Close() - defer close(done) - for { - _, message, err := c.ReadMessage() - if err != nil { - log.Println("read:", err) - return - } - log.Printf("recv: %s", message) - } - }() - - ticker := time.NewTicker(time.Second) - defer ticker.Stop() - - for { - select { - case t := <-ticker.C: - err := c.WriteMessage(websocket.TextMessage, []byte(t.String())) - if err != nil { - log.Println("write:", err) - return - } - case <-interrupt: - log.Println("interrupt") - // To cleanly close a connection, a client should send a close - // frame and wait for the server to close the connection. - err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) - if err != nil { - log.Println("write close:", err) - return - } - select { - case <-done: - case <-time.After(time.Second): - } - c.Close() - return - } - } -} diff --git a/vendor/github.com/gorilla/websocket/examples/echo/server.go b/vendor/github.com/gorilla/websocket/examples/echo/server.go deleted file mode 100644 index a685b0974ad..00000000000 --- a/vendor/github.com/gorilla/websocket/examples/echo/server.go +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build ignore - -package main - -import ( - "flag" - "html/template" - "log" - "net/http" - - "github.com/gorilla/websocket" -) - -var addr = flag.String("addr", "localhost:8080", "http service address") - -var upgrader = websocket.Upgrader{} // use default options - -func echo(w http.ResponseWriter, r *http.Request) { - c, err := upgrader.Upgrade(w, r, nil) - if err != nil { - log.Print("upgrade:", err) - return - } - defer c.Close() - for { - mt, message, err := c.ReadMessage() - if err != nil { - log.Println("read:", err) - break - } - log.Printf("recv: %s", message) - err = c.WriteMessage(mt, message) - if err != nil { - log.Println("write:", err) - break - } - } -} - -func home(w http.ResponseWriter, r *http.Request) { - homeTemplate.Execute(w, "ws://"+r.Host+"/echo") -} - -func main() { - flag.Parse() - log.SetFlags(0) - http.HandleFunc("/echo", echo) - http.HandleFunc("/", home) - log.Fatal(http.ListenAndServe(*addr, nil)) -} - -var homeTemplate = template.Must(template.New("").Parse(` - - - - - - - -
    -

    Click "Open" to create a connection to the server, -"Send" to send a message to the server and "Close" to close the connection. -You can change the message and send multiple times. -

    -

    - - -

    - -

    -
    -
    -
    - - -`)) diff --git a/vendor/github.com/gorilla/websocket/examples/filewatch/main.go b/vendor/github.com/gorilla/websocket/examples/filewatch/main.go deleted file mode 100644 index f5f9da5c388..00000000000 --- a/vendor/github.com/gorilla/websocket/examples/filewatch/main.go +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package main - -import ( - "flag" - "html/template" - "io/ioutil" - "log" - "net/http" - "os" - "strconv" - "time" - - "github.com/gorilla/websocket" -) - -const ( - // Time allowed to write the file to the client. - writeWait = 10 * time.Second - - // Time allowed to read the next pong message from the client. - pongWait = 60 * time.Second - - // Send pings to client with this period. Must be less than pongWait. - pingPeriod = (pongWait * 9) / 10 - - // Poll file for changes with this period. - filePeriod = 10 * time.Second -) - -var ( - addr = flag.String("addr", ":8080", "http service address") - homeTempl = template.Must(template.New("").Parse(homeHTML)) - filename string - upgrader = websocket.Upgrader{ - ReadBufferSize: 1024, - WriteBufferSize: 1024, - } -) - -func readFileIfModified(lastMod time.Time) ([]byte, time.Time, error) { - fi, err := os.Stat(filename) - if err != nil { - return nil, lastMod, err - } - if !fi.ModTime().After(lastMod) { - return nil, lastMod, nil - } - p, err := ioutil.ReadFile(filename) - if err != nil { - return nil, fi.ModTime(), err - } - return p, fi.ModTime(), nil -} - -func reader(ws *websocket.Conn) { - defer ws.Close() - ws.SetReadLimit(512) - ws.SetReadDeadline(time.Now().Add(pongWait)) - ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil }) - for { - _, _, err := ws.ReadMessage() - if err != nil { - break - } - } -} - -func writer(ws *websocket.Conn, lastMod time.Time) { - lastError := "" - pingTicker := time.NewTicker(pingPeriod) - fileTicker := time.NewTicker(filePeriod) - defer func() { - pingTicker.Stop() - fileTicker.Stop() - ws.Close() - }() - for { - select { - case <-fileTicker.C: - var p []byte - var err error - - p, lastMod, err = readFileIfModified(lastMod) - - if err != nil { - if s := err.Error(); s != lastError { - lastError = s - p = []byte(lastError) - } - } else { - lastError = "" - } - - if p != nil { - ws.SetWriteDeadline(time.Now().Add(writeWait)) - if err := ws.WriteMessage(websocket.TextMessage, p); err != nil { - return - } - } - case <-pingTicker.C: - ws.SetWriteDeadline(time.Now().Add(writeWait)) - if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil { - return - } - } - } -} - -func serveWs(w http.ResponseWriter, r *http.Request) { - ws, err := upgrader.Upgrade(w, r, nil) - if err != nil { - if _, ok := err.(websocket.HandshakeError); !ok { - log.Println(err) - } - return - } - - var lastMod time.Time - if n, err := strconv.ParseInt(r.FormValue("lastMod"), 16, 64); err == nil { - lastMod = time.Unix(0, n) - } - - go writer(ws, lastMod) - reader(ws) -} - -func serveHome(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/" { - http.Error(w, "Not found", 404) - return - } - if r.Method != "GET" { - http.Error(w, "Method not allowed", 405) - return - } - w.Header().Set("Content-Type", "text/html; charset=utf-8") - p, lastMod, err := readFileIfModified(time.Time{}) - if err != nil { - p = []byte(err.Error()) - lastMod = time.Unix(0, 0) - } - var v = struct { - Host string - Data string - LastMod string - }{ - r.Host, - string(p), - strconv.FormatInt(lastMod.UnixNano(), 16), - } - homeTempl.Execute(w, &v) -} - -func main() { - flag.Parse() - if flag.NArg() != 1 { - log.Fatal("filename not specified") - } - filename = flag.Args()[0] - http.HandleFunc("/", serveHome) - http.HandleFunc("/ws", serveWs) - if err := http.ListenAndServe(*addr, nil); err != nil { - log.Fatal(err) - } -} - -const homeHTML = ` - - - WebSocket Example - - -
    {{.Data}}
    - - - -` diff --git a/vendor/github.com/gorilla/websocket/json.go b/vendor/github.com/gorilla/websocket/json.go deleted file mode 100644 index dc2c1f6415f..00000000000 --- a/vendor/github.com/gorilla/websocket/json.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package websocket - -import ( - "encoding/json" - "io" -) - -// WriteJSON writes the JSON encoding of v as a message. -// -// Deprecated: Use c.WriteJSON instead. -func WriteJSON(c *Conn, v interface{}) error { - return c.WriteJSON(v) -} - -// WriteJSON writes the JSON encoding of v as a message. -// -// See the documentation for encoding/json Marshal for details about the -// conversion of Go values to JSON. -func (c *Conn) WriteJSON(v interface{}) error { - w, err := c.NextWriter(TextMessage) - if err != nil { - return err - } - err1 := json.NewEncoder(w).Encode(v) - err2 := w.Close() - if err1 != nil { - return err1 - } - return err2 -} - -// ReadJSON reads the next JSON-encoded message from the connection and stores -// it in the value pointed to by v. -// -// Deprecated: Use c.ReadJSON instead. -func ReadJSON(c *Conn, v interface{}) error { - return c.ReadJSON(v) -} - -// ReadJSON reads the next JSON-encoded message from the connection and stores -// it in the value pointed to by v. -// -// See the documentation for the encoding/json Unmarshal function for details -// about the conversion of JSON to a Go value. -func (c *Conn) ReadJSON(v interface{}) error { - _, r, err := c.NextReader() - if err != nil { - return err - } - err = json.NewDecoder(r).Decode(v) - if err == io.EOF { - // One value is expected in the message. - err = io.ErrUnexpectedEOF - } - return err -} diff --git a/vendor/github.com/gorilla/websocket/mask.go b/vendor/github.com/gorilla/websocket/mask.go deleted file mode 100644 index 6a88bbc7434..00000000000 --- a/vendor/github.com/gorilla/websocket/mask.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of -// this source code is governed by a BSD-style license that can be found in the -// LICENSE file. - -// +build !appengine - -package websocket - -import "unsafe" - -const wordSize = int(unsafe.Sizeof(uintptr(0))) - -func maskBytes(key [4]byte, pos int, b []byte) int { - - // Mask one byte at a time for small buffers. - if len(b) < 2*wordSize { - for i := range b { - b[i] ^= key[pos&3] - pos++ - } - return pos & 3 - } - - // Mask one byte at a time to word boundary. - if n := int(uintptr(unsafe.Pointer(&b[0]))) % wordSize; n != 0 { - n = wordSize - n - for i := range b[:n] { - b[i] ^= key[pos&3] - pos++ - } - b = b[n:] - } - - // Create aligned word size key. - var k [wordSize]byte - for i := range k { - k[i] = key[(pos+i)&3] - } - kw := *(*uintptr)(unsafe.Pointer(&k)) - - // Mask one word at a time. - n := (len(b) / wordSize) * wordSize - for i := 0; i < n; i += wordSize { - *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(i))) ^= kw - } - - // Mask one byte at a time for remaining bytes. - b = b[n:] - for i := range b { - b[i] ^= key[pos&3] - pos++ - } - - return pos & 3 -} diff --git a/vendor/github.com/gorilla/websocket/mask_safe.go b/vendor/github.com/gorilla/websocket/mask_safe.go deleted file mode 100644 index 2aac060e52e..00000000000 --- a/vendor/github.com/gorilla/websocket/mask_safe.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of -// this source code is governed by a BSD-style license that can be found in the -// LICENSE file. - -// +build appengine - -package websocket - -func maskBytes(key [4]byte, pos int, b []byte) int { - for i := range b { - b[i] ^= key[pos&3] - pos++ - } - return pos & 3 -} diff --git a/vendor/github.com/gorilla/websocket/prepared.go b/vendor/github.com/gorilla/websocket/prepared.go deleted file mode 100644 index 1efffbd1ebe..00000000000 --- a/vendor/github.com/gorilla/websocket/prepared.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package websocket - -import ( - "bytes" - "net" - "sync" - "time" -) - -// PreparedMessage caches on the wire representations of a message payload. -// Use PreparedMessage to efficiently send a message payload to multiple -// connections. PreparedMessage is especially useful when compression is used -// because the CPU and memory expensive compression operation can be executed -// once for a given set of compression options. -type PreparedMessage struct { - messageType int - data []byte - err error - mu sync.Mutex - frames map[prepareKey]*preparedFrame -} - -// prepareKey defines a unique set of options to cache prepared frames in PreparedMessage. -type prepareKey struct { - isServer bool - compress bool - compressionLevel int -} - -// preparedFrame contains data in wire representation. -type preparedFrame struct { - once sync.Once - data []byte -} - -// NewPreparedMessage returns an initialized PreparedMessage. You can then send -// it to connection using WritePreparedMessage method. Valid wire -// representation will be calculated lazily only once for a set of current -// connection options. -func NewPreparedMessage(messageType int, data []byte) (*PreparedMessage, error) { - pm := &PreparedMessage{ - messageType: messageType, - frames: make(map[prepareKey]*preparedFrame), - data: data, - } - - // Prepare a plain server frame. - _, frameData, err := pm.frame(prepareKey{isServer: true, compress: false}) - if err != nil { - return nil, err - } - - // To protect against caller modifying the data argument, remember the data - // copied to the plain server frame. - pm.data = frameData[len(frameData)-len(data):] - return pm, nil -} - -func (pm *PreparedMessage) frame(key prepareKey) (int, []byte, error) { - pm.mu.Lock() - frame, ok := pm.frames[key] - if !ok { - frame = &preparedFrame{} - pm.frames[key] = frame - } - pm.mu.Unlock() - - var err error - frame.once.Do(func() { - // Prepare a frame using a 'fake' connection. - // TODO: Refactor code in conn.go to allow more direct construction of - // the frame. - mu := make(chan bool, 1) - mu <- true - var nc prepareConn - c := &Conn{ - conn: &nc, - mu: mu, - isServer: key.isServer, - compressionLevel: key.compressionLevel, - enableWriteCompression: true, - writeBuf: make([]byte, defaultWriteBufferSize+maxFrameHeaderSize), - } - if key.compress { - c.newCompressionWriter = compressNoContextTakeover - } - err = c.WriteMessage(pm.messageType, pm.data) - frame.data = nc.buf.Bytes() - }) - return pm.messageType, frame.data, err -} - -type prepareConn struct { - buf bytes.Buffer - net.Conn -} - -func (pc *prepareConn) Write(p []byte) (int, error) { return pc.buf.Write(p) } -func (pc *prepareConn) SetWriteDeadline(t time.Time) error { return nil } diff --git a/vendor/github.com/gorilla/websocket/server.go b/vendor/github.com/gorilla/websocket/server.go deleted file mode 100644 index 6ae97c54fec..00000000000 --- a/vendor/github.com/gorilla/websocket/server.go +++ /dev/null @@ -1,292 +0,0 @@ -// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package websocket - -import ( - "bufio" - "errors" - "net" - "net/http" - "net/url" - "strings" - "time" -) - -// HandshakeError describes an error with the handshake from the peer. -type HandshakeError struct { - message string -} - -func (e HandshakeError) Error() string { return e.message } - -// Upgrader specifies parameters for upgrading an HTTP connection to a -// WebSocket connection. -type Upgrader struct { - // HandshakeTimeout specifies the duration for the handshake to complete. - HandshakeTimeout time.Duration - - // ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer - // size is zero, then buffers allocated by the HTTP server are used. The - // I/O buffer sizes do not limit the size of the messages that can be sent - // or received. - ReadBufferSize, WriteBufferSize int - - // Subprotocols specifies the server's supported protocols in order of - // preference. If this field is set, then the Upgrade method negotiates a - // subprotocol by selecting the first match in this list with a protocol - // requested by the client. - Subprotocols []string - - // Error specifies the function for generating HTTP error responses. If Error - // is nil, then http.Error is used to generate the HTTP response. - Error func(w http.ResponseWriter, r *http.Request, status int, reason error) - - // CheckOrigin returns true if the request Origin header is acceptable. If - // CheckOrigin is nil, the host in the Origin header must not be set or - // must match the host of the request. - CheckOrigin func(r *http.Request) bool - - // EnableCompression specify if the server should attempt to negotiate per - // message compression (RFC 7692). Setting this value to true does not - // guarantee that compression will be supported. Currently only "no context - // takeover" modes are supported. - EnableCompression bool -} - -func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request, status int, reason string) (*Conn, error) { - err := HandshakeError{reason} - if u.Error != nil { - u.Error(w, r, status, err) - } else { - w.Header().Set("Sec-Websocket-Version", "13") - http.Error(w, http.StatusText(status), status) - } - return nil, err -} - -// checkSameOrigin returns true if the origin is not set or is equal to the request host. -func checkSameOrigin(r *http.Request) bool { - origin := r.Header["Origin"] - if len(origin) == 0 { - return true - } - u, err := url.Parse(origin[0]) - if err != nil { - return false - } - return u.Host == r.Host -} - -func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string { - if u.Subprotocols != nil { - clientProtocols := Subprotocols(r) - for _, serverProtocol := range u.Subprotocols { - for _, clientProtocol := range clientProtocols { - if clientProtocol == serverProtocol { - return clientProtocol - } - } - } - } else if responseHeader != nil { - return responseHeader.Get("Sec-Websocket-Protocol") - } - return "" -} - -// Upgrade upgrades the HTTP server connection to the WebSocket protocol. -// -// The responseHeader is included in the response to the client's upgrade -// request. Use the responseHeader to specify cookies (Set-Cookie) and the -// application negotiated subprotocol (Sec-Websocket-Protocol). -// -// If the upgrade fails, then Upgrade replies to the client with an HTTP error -// response. -func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) { - if r.Method != "GET" { - return u.returnError(w, r, http.StatusMethodNotAllowed, "websocket: not a websocket handshake: request method is not GET") - } - - if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok { - return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-Websocket-Extensions' headers are unsupported") - } - - if !tokenListContainsValue(r.Header, "Connection", "upgrade") { - return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'upgrade' token not found in 'Connection' header") - } - - if !tokenListContainsValue(r.Header, "Upgrade", "websocket") { - return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'websocket' token not found in 'Upgrade' header") - } - - if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") { - return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header") - } - - checkOrigin := u.CheckOrigin - if checkOrigin == nil { - checkOrigin = checkSameOrigin - } - if !checkOrigin(r) { - return u.returnError(w, r, http.StatusForbidden, "websocket: 'Origin' header value not allowed") - } - - challengeKey := r.Header.Get("Sec-Websocket-Key") - if challengeKey == "" { - return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-Websocket-Key' header is missing or blank") - } - - subprotocol := u.selectSubprotocol(r, responseHeader) - - // Negotiate PMCE - var compress bool - if u.EnableCompression { - for _, ext := range parseExtensions(r.Header) { - if ext[""] != "permessage-deflate" { - continue - } - compress = true - break - } - } - - var ( - netConn net.Conn - err error - ) - - h, ok := w.(http.Hijacker) - if !ok { - return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker") - } - var brw *bufio.ReadWriter - netConn, brw, err = h.Hijack() - if err != nil { - return u.returnError(w, r, http.StatusInternalServerError, err.Error()) - } - - if brw.Reader.Buffered() > 0 { - netConn.Close() - return nil, errors.New("websocket: client sent data before handshake is complete") - } - - c := newConnBRW(netConn, true, u.ReadBufferSize, u.WriteBufferSize, brw) - c.subprotocol = subprotocol - - if compress { - c.newCompressionWriter = compressNoContextTakeover - c.newDecompressionReader = decompressNoContextTakeover - } - - p := c.writeBuf[:0] - p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...) - p = append(p, computeAcceptKey(challengeKey)...) - p = append(p, "\r\n"...) - if c.subprotocol != "" { - p = append(p, "Sec-Websocket-Protocol: "...) - p = append(p, c.subprotocol...) - p = append(p, "\r\n"...) - } - if compress { - p = append(p, "Sec-Websocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...) - } - for k, vs := range responseHeader { - if k == "Sec-Websocket-Protocol" { - continue - } - for _, v := range vs { - p = append(p, k...) - p = append(p, ": "...) - for i := 0; i < len(v); i++ { - b := v[i] - if b <= 31 { - // prevent response splitting. - b = ' ' - } - p = append(p, b) - } - p = append(p, "\r\n"...) - } - } - p = append(p, "\r\n"...) - - // Clear deadlines set by HTTP server. - netConn.SetDeadline(time.Time{}) - - if u.HandshakeTimeout > 0 { - netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout)) - } - if _, err = netConn.Write(p); err != nil { - netConn.Close() - return nil, err - } - if u.HandshakeTimeout > 0 { - netConn.SetWriteDeadline(time.Time{}) - } - - return c, nil -} - -// Upgrade upgrades the HTTP server connection to the WebSocket protocol. -// -// Deprecated: Use websocket.Upgrader instead. -// -// Upgrade does not perform origin checking. The application is responsible for -// checking the Origin header before calling Upgrade. An example implementation -// of the same origin policy check is: -// -// if req.Header.Get("Origin") != "http://"+req.Host { -// http.Error(w, "Origin not allowed", 403) -// return -// } -// -// If the endpoint supports subprotocols, then the application is responsible -// for negotiating the protocol used on the connection. Use the Subprotocols() -// function to get the subprotocols requested by the client. Use the -// Sec-Websocket-Protocol response header to specify the subprotocol selected -// by the application. -// -// The responseHeader is included in the response to the client's upgrade -// request. Use the responseHeader to specify cookies (Set-Cookie) and the -// negotiated subprotocol (Sec-Websocket-Protocol). -// -// The connection buffers IO to the underlying network connection. The -// readBufSize and writeBufSize parameters specify the size of the buffers to -// use. Messages can be larger than the buffers. -// -// If the request is not a valid WebSocket handshake, then Upgrade returns an -// error of type HandshakeError. Applications should handle this error by -// replying to the client with an HTTP error response. -func Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header, readBufSize, writeBufSize int) (*Conn, error) { - u := Upgrader{ReadBufferSize: readBufSize, WriteBufferSize: writeBufSize} - u.Error = func(w http.ResponseWriter, r *http.Request, status int, reason error) { - // don't return errors to maintain backwards compatibility - } - u.CheckOrigin = func(r *http.Request) bool { - // allow all connections by default - return true - } - return u.Upgrade(w, r, responseHeader) -} - -// Subprotocols returns the subprotocols requested by the client in the -// Sec-Websocket-Protocol header. -func Subprotocols(r *http.Request) []string { - h := strings.TrimSpace(r.Header.Get("Sec-Websocket-Protocol")) - if h == "" { - return nil - } - protocols := strings.Split(h, ",") - for i := range protocols { - protocols[i] = strings.TrimSpace(protocols[i]) - } - return protocols -} - -// IsWebSocketUpgrade returns true if the client requested upgrade to the -// WebSocket protocol. -func IsWebSocketUpgrade(r *http.Request) bool { - return tokenListContainsValue(r.Header, "Connection", "upgrade") && - tokenListContainsValue(r.Header, "Upgrade", "websocket") -} diff --git a/vendor/github.com/gorilla/websocket/util.go b/vendor/github.com/gorilla/websocket/util.go deleted file mode 100644 index 262e647bce2..00000000000 --- a/vendor/github.com/gorilla/websocket/util.go +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package websocket - -import ( - "crypto/rand" - "crypto/sha1" - "encoding/base64" - "io" - "net/http" - "strings" -) - -var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11") - -func computeAcceptKey(challengeKey string) string { - h := sha1.New() - h.Write([]byte(challengeKey)) - h.Write(keyGUID) - return base64.StdEncoding.EncodeToString(h.Sum(nil)) -} - -func generateChallengeKey() (string, error) { - p := make([]byte, 16) - if _, err := io.ReadFull(rand.Reader, p); err != nil { - return "", err - } - return base64.StdEncoding.EncodeToString(p), nil -} - -// Octet types from RFC 2616. -var octetTypes [256]byte - -const ( - isTokenOctet = 1 << iota - isSpaceOctet -) - -func init() { - // From RFC 2616 - // - // OCTET = - // CHAR = - // CTL = - // CR = - // LF = - // SP = - // HT = - // <"> = - // CRLF = CR LF - // LWS = [CRLF] 1*( SP | HT ) - // TEXT = - // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> - // | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT - // token = 1* - // qdtext = > - - for c := 0; c < 256; c++ { - var t byte - isCtl := c <= 31 || c == 127 - isChar := 0 <= c && c <= 127 - isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0 - if strings.IndexRune(" \t\r\n", rune(c)) >= 0 { - t |= isSpaceOctet - } - if isChar && !isCtl && !isSeparator { - t |= isTokenOctet - } - octetTypes[c] = t - } -} - -func skipSpace(s string) (rest string) { - i := 0 - for ; i < len(s); i++ { - if octetTypes[s[i]]&isSpaceOctet == 0 { - break - } - } - return s[i:] -} - -func nextToken(s string) (token, rest string) { - i := 0 - for ; i < len(s); i++ { - if octetTypes[s[i]]&isTokenOctet == 0 { - break - } - } - return s[:i], s[i:] -} - -func nextTokenOrQuoted(s string) (value string, rest string) { - if !strings.HasPrefix(s, "\"") { - return nextToken(s) - } - s = s[1:] - for i := 0; i < len(s); i++ { - switch s[i] { - case '"': - return s[:i], s[i+1:] - case '\\': - p := make([]byte, len(s)-1) - j := copy(p, s[:i]) - escape := true - for i = i + 1; i < len(s); i++ { - b := s[i] - switch { - case escape: - escape = false - p[j] = b - j++ - case b == '\\': - escape = true - case b == '"': - return string(p[:j]), s[i+1:] - default: - p[j] = b - j++ - } - } - return "", "" - } - } - return "", "" -} - -// tokenListContainsValue returns true if the 1#token header with the given -// name contains token. -func tokenListContainsValue(header http.Header, name string, value string) bool { -headers: - for _, s := range header[name] { - for { - var t string - t, s = nextToken(skipSpace(s)) - if t == "" { - continue headers - } - s = skipSpace(s) - if s != "" && s[0] != ',' { - continue headers - } - if strings.EqualFold(t, value) { - return true - } - if s == "" { - continue headers - } - s = s[1:] - } - } - return false -} - -// parseExtensiosn parses WebSocket extensions from a header. -func parseExtensions(header http.Header) []map[string]string { - - // From RFC 6455: - // - // Sec-WebSocket-Extensions = extension-list - // extension-list = 1#extension - // extension = extension-token *( ";" extension-param ) - // extension-token = registered-token - // registered-token = token - // extension-param = token [ "=" (token | quoted-string) ] - // ;When using the quoted-string syntax variant, the value - // ;after quoted-string unescaping MUST conform to the - // ;'token' ABNF. - - var result []map[string]string -headers: - for _, s := range header["Sec-Websocket-Extensions"] { - for { - var t string - t, s = nextToken(skipSpace(s)) - if t == "" { - continue headers - } - ext := map[string]string{"": t} - for { - s = skipSpace(s) - if !strings.HasPrefix(s, ";") { - break - } - var k string - k, s = nextToken(skipSpace(s[1:])) - if k == "" { - continue headers - } - s = skipSpace(s) - var v string - if strings.HasPrefix(s, "=") { - v, s = nextTokenOrQuoted(skipSpace(s[1:])) - s = skipSpace(s) - } - if s != "" && s[0] != ',' && s[0] != ';' { - continue headers - } - ext[k] = v - } - if s != "" && s[0] != ',' { - continue headers - } - result = append(result, ext) - if s == "" { - continue headers - } - s = s[1:] - } - } - return result -} diff --git a/vendor/github.com/hashicorp/go-syslog/LICENSE b/vendor/github.com/hashicorp/go-syslog/LICENSE deleted file mode 100644 index a5df10e675d..00000000000 --- a/vendor/github.com/hashicorp/go-syslog/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Armon Dadgar - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/hashicorp/go-syslog/builtin.go b/vendor/github.com/hashicorp/go-syslog/builtin.go deleted file mode 100644 index 72bdd61c933..00000000000 --- a/vendor/github.com/hashicorp/go-syslog/builtin.go +++ /dev/null @@ -1,214 +0,0 @@ -// This file is taken from the log/syslog in the standard lib. -// However, there is a bug with overwhelming syslog that causes writes -// to block indefinitely. This is fixed by adding a write deadline. -// -// +build !windows,!nacl,!plan9 - -package gsyslog - -import ( - "errors" - "fmt" - "log/syslog" - "net" - "os" - "strings" - "sync" - "time" -) - -const severityMask = 0x07 -const facilityMask = 0xf8 -const localDeadline = 20 * time.Millisecond -const remoteDeadline = 50 * time.Millisecond - -// A builtinWriter is a connection to a syslog server. -type builtinWriter struct { - priority syslog.Priority - tag string - hostname string - network string - raddr string - - mu sync.Mutex // guards conn - conn serverConn -} - -// This interface and the separate syslog_unix.go file exist for -// Solaris support as implemented by gccgo. On Solaris you can not -// simply open a TCP connection to the syslog daemon. The gccgo -// sources have a syslog_solaris.go file that implements unixSyslog to -// return a type that satisfies this interface and simply calls the C -// library syslog function. -type serverConn interface { - writeString(p syslog.Priority, hostname, tag, s, nl string) error - close() error -} - -type netConn struct { - local bool - conn net.Conn -} - -// New establishes a new connection to the system log daemon. Each -// write to the returned writer sends a log message with the given -// priority and prefix. -func newBuiltin(priority syslog.Priority, tag string) (w *builtinWriter, err error) { - return dialBuiltin("", "", priority, tag) -} - -// Dial establishes a connection to a log daemon by connecting to -// address raddr on the specified network. Each write to the returned -// writer sends a log message with the given facility, severity and -// tag. -// If network is empty, Dial will connect to the local syslog server. -func dialBuiltin(network, raddr string, priority syslog.Priority, tag string) (*builtinWriter, error) { - if priority < 0 || priority > syslog.LOG_LOCAL7|syslog.LOG_DEBUG { - return nil, errors.New("log/syslog: invalid priority") - } - - if tag == "" { - tag = os.Args[0] - } - hostname, _ := os.Hostname() - - w := &builtinWriter{ - priority: priority, - tag: tag, - hostname: hostname, - network: network, - raddr: raddr, - } - - w.mu.Lock() - defer w.mu.Unlock() - - err := w.connect() - if err != nil { - return nil, err - } - return w, err -} - -// connect makes a connection to the syslog server. -// It must be called with w.mu held. -func (w *builtinWriter) connect() (err error) { - if w.conn != nil { - // ignore err from close, it makes sense to continue anyway - w.conn.close() - w.conn = nil - } - - if w.network == "" { - w.conn, err = unixSyslog() - if w.hostname == "" { - w.hostname = "localhost" - } - } else { - var c net.Conn - c, err = net.DialTimeout(w.network, w.raddr, remoteDeadline) - if err == nil { - w.conn = &netConn{conn: c} - if w.hostname == "" { - w.hostname = c.LocalAddr().String() - } - } - } - return -} - -// Write sends a log message to the syslog daemon. -func (w *builtinWriter) Write(b []byte) (int, error) { - return w.writeAndRetry(w.priority, string(b)) -} - -// Close closes a connection to the syslog daemon. -func (w *builtinWriter) Close() error { - w.mu.Lock() - defer w.mu.Unlock() - - if w.conn != nil { - err := w.conn.close() - w.conn = nil - return err - } - return nil -} - -func (w *builtinWriter) writeAndRetry(p syslog.Priority, s string) (int, error) { - pr := (w.priority & facilityMask) | (p & severityMask) - - w.mu.Lock() - defer w.mu.Unlock() - - if w.conn != nil { - if n, err := w.write(pr, s); err == nil { - return n, err - } - } - if err := w.connect(); err != nil { - return 0, err - } - return w.write(pr, s) -} - -// write generates and writes a syslog formatted string. The -// format is as follows: TIMESTAMP HOSTNAME TAG[PID]: MSG -func (w *builtinWriter) write(p syslog.Priority, msg string) (int, error) { - // ensure it ends in a \n - nl := "" - if !strings.HasSuffix(msg, "\n") { - nl = "\n" - } - - err := w.conn.writeString(p, w.hostname, w.tag, msg, nl) - if err != nil { - return 0, err - } - // Note: return the length of the input, not the number of - // bytes printed by Fprintf, because this must behave like - // an io.Writer. - return len(msg), nil -} - -func (n *netConn) writeString(p syslog.Priority, hostname, tag, msg, nl string) error { - if n.local { - // Compared to the network form below, the changes are: - // 1. Use time.Stamp instead of time.RFC3339. - // 2. Drop the hostname field from the Fprintf. - timestamp := time.Now().Format(time.Stamp) - n.conn.SetWriteDeadline(time.Now().Add(localDeadline)) - _, err := fmt.Fprintf(n.conn, "<%d>%s %s[%d]: %s%s", - p, timestamp, - tag, os.Getpid(), msg, nl) - return err - } - timestamp := time.Now().Format(time.RFC3339) - n.conn.SetWriteDeadline(time.Now().Add(remoteDeadline)) - _, err := fmt.Fprintf(n.conn, "<%d>%s %s %s[%d]: %s%s", - p, timestamp, hostname, - tag, os.Getpid(), msg, nl) - return err -} - -func (n *netConn) close() error { - return n.conn.Close() -} - -// unixSyslog opens a connection to the syslog daemon running on the -// local machine using a Unix domain socket. -func unixSyslog() (conn serverConn, err error) { - logTypes := []string{"unixgram", "unix"} - logPaths := []string{"/dev/log", "/var/run/syslog", "/var/run/log"} - for _, network := range logTypes { - for _, path := range logPaths { - conn, err := net.DialTimeout(network, path, localDeadline) - if err != nil { - continue - } else { - return &netConn{conn: conn, local: true}, nil - } - } - } - return nil, errors.New("Unix syslog delivery error") -} diff --git a/vendor/github.com/hashicorp/go-syslog/syslog.go b/vendor/github.com/hashicorp/go-syslog/syslog.go deleted file mode 100644 index 3f5a6f3fb48..00000000000 --- a/vendor/github.com/hashicorp/go-syslog/syslog.go +++ /dev/null @@ -1,27 +0,0 @@ -package gsyslog - -// Priority maps to the syslog priority levels -type Priority int - -const ( - LOG_EMERG Priority = iota - LOG_ALERT - LOG_CRIT - LOG_ERR - LOG_WARNING - LOG_NOTICE - LOG_INFO - LOG_DEBUG -) - -// Syslogger interface is used to write log messages to syslog -type Syslogger interface { - // WriteLevel is used to write a message at a given level - WriteLevel(Priority, []byte) error - - // Write is used to write a message at the default level - Write([]byte) (int, error) - - // Close is used to close the connection to the logger - Close() error -} diff --git a/vendor/github.com/hashicorp/go-syslog/unix.go b/vendor/github.com/hashicorp/go-syslog/unix.go deleted file mode 100644 index 70b71802eac..00000000000 --- a/vendor/github.com/hashicorp/go-syslog/unix.go +++ /dev/null @@ -1,123 +0,0 @@ -// +build linux darwin dragonfly freebsd netbsd openbsd solaris - -package gsyslog - -import ( - "fmt" - "log/syslog" - "strings" -) - -// builtinLogger wraps the Golang implementation of a -// syslog.Writer to provide the Syslogger interface -type builtinLogger struct { - *builtinWriter -} - -// NewLogger is used to construct a new Syslogger -func NewLogger(p Priority, facility, tag string) (Syslogger, error) { - fPriority, err := facilityPriority(facility) - if err != nil { - return nil, err - } - priority := syslog.Priority(p) | fPriority - l, err := newBuiltin(priority, tag) - if err != nil { - return nil, err - } - return &builtinLogger{l}, nil -} - -// DialLogger is used to construct a new Syslogger that establishes connection to remote syslog server -func DialLogger(network, raddr string, p Priority, facility, tag string) (Syslogger, error) { - fPriority, err := facilityPriority(facility) - if err != nil { - return nil, err - } - - priority := syslog.Priority(p) | fPriority - - l, err := dialBuiltin(network, raddr, priority, tag) - if err != nil { - return nil, err - } - - return &builtinLogger{l}, nil -} - -// WriteLevel writes out a message at the given priority -func (b *builtinLogger) WriteLevel(p Priority, buf []byte) error { - var err error - m := string(buf) - switch p { - case LOG_EMERG: - _, err = b.writeAndRetry(syslog.LOG_EMERG, m) - case LOG_ALERT: - _, err = b.writeAndRetry(syslog.LOG_ALERT, m) - case LOG_CRIT: - _, err = b.writeAndRetry(syslog.LOG_CRIT, m) - case LOG_ERR: - _, err = b.writeAndRetry(syslog.LOG_ERR, m) - case LOG_WARNING: - _, err = b.writeAndRetry(syslog.LOG_WARNING, m) - case LOG_NOTICE: - _, err = b.writeAndRetry(syslog.LOG_NOTICE, m) - case LOG_INFO: - _, err = b.writeAndRetry(syslog.LOG_INFO, m) - case LOG_DEBUG: - _, err = b.writeAndRetry(syslog.LOG_DEBUG, m) - default: - err = fmt.Errorf("Unknown priority: %v", p) - } - return err -} - -// facilityPriority converts a facility string into -// an appropriate priority level or returns an error -func facilityPriority(facility string) (syslog.Priority, error) { - facility = strings.ToUpper(facility) - switch facility { - case "KERN": - return syslog.LOG_KERN, nil - case "USER": - return syslog.LOG_USER, nil - case "MAIL": - return syslog.LOG_MAIL, nil - case "DAEMON": - return syslog.LOG_DAEMON, nil - case "AUTH": - return syslog.LOG_AUTH, nil - case "SYSLOG": - return syslog.LOG_SYSLOG, nil - case "LPR": - return syslog.LOG_LPR, nil - case "NEWS": - return syslog.LOG_NEWS, nil - case "UUCP": - return syslog.LOG_UUCP, nil - case "CRON": - return syslog.LOG_CRON, nil - case "AUTHPRIV": - return syslog.LOG_AUTHPRIV, nil - case "FTP": - return syslog.LOG_FTP, nil - case "LOCAL0": - return syslog.LOG_LOCAL0, nil - case "LOCAL1": - return syslog.LOG_LOCAL1, nil - case "LOCAL2": - return syslog.LOG_LOCAL2, nil - case "LOCAL3": - return syslog.LOG_LOCAL3, nil - case "LOCAL4": - return syslog.LOG_LOCAL4, nil - case "LOCAL5": - return syslog.LOG_LOCAL5, nil - case "LOCAL6": - return syslog.LOG_LOCAL6, nil - case "LOCAL7": - return syslog.LOG_LOCAL7, nil - default: - return 0, fmt.Errorf("invalid syslog facility: %s", facility) - } -} diff --git a/vendor/github.com/hashicorp/go-syslog/unsupported.go b/vendor/github.com/hashicorp/go-syslog/unsupported.go deleted file mode 100644 index d9fe87b0a15..00000000000 --- a/vendor/github.com/hashicorp/go-syslog/unsupported.go +++ /dev/null @@ -1,17 +0,0 @@ -// +build windows plan9 - -package gsyslog - -import ( - "fmt" -) - -// NewLogger is used to construct a new Syslogger -func NewLogger(p Priority, facility, tag string) (Syslogger, error) { - return nil, fmt.Errorf("Platform does not support syslog") -} - -// DialLogger is used to construct a new Syslogger that establishes connection to remote syslog server -func DialLogger(network, raddr string, p Priority, facility, tag string) (Syslogger, error) { - return nil, fmt.Errorf("Platform does not support syslog") -} diff --git a/vendor/github.com/hashicorp/golang-lru/2q.go b/vendor/github.com/hashicorp/golang-lru/2q.go deleted file mode 100644 index 337d963296c..00000000000 --- a/vendor/github.com/hashicorp/golang-lru/2q.go +++ /dev/null @@ -1,212 +0,0 @@ -package lru - -import ( - "fmt" - "sync" - - "github.com/hashicorp/golang-lru/simplelru" -) - -const ( - // Default2QRecentRatio is the ratio of the 2Q cache dedicated - // to recently added entries that have only been accessed once. - Default2QRecentRatio = 0.25 - - // Default2QGhostEntries is the default ratio of ghost - // entries kept to track entries recently evicted - Default2QGhostEntries = 0.50 -) - -// TwoQueueCache is a thread-safe fixed size 2Q cache. -// 2Q is an enhancement over the standard LRU cache -// in that it tracks both frequently and recently used -// entries separately. This avoids a burst in access to new -// entries from evicting frequently used entries. It adds some -// additional tracking overhead to the standard LRU cache, and is -// computationally about 2x the cost, and adds some metadata over -// head. The ARCCache is similar, but does not require setting any -// parameters. -type TwoQueueCache struct { - size int - recentSize int - - recent *simplelru.LRU - frequent *simplelru.LRU - recentEvict *simplelru.LRU - lock sync.RWMutex -} - -// New2Q creates a new TwoQueueCache using the default -// values for the parameters. -func New2Q(size int) (*TwoQueueCache, error) { - return New2QParams(size, Default2QRecentRatio, Default2QGhostEntries) -} - -// New2QParams creates a new TwoQueueCache using the provided -// parameter values. -func New2QParams(size int, recentRatio float64, ghostRatio float64) (*TwoQueueCache, error) { - if size <= 0 { - return nil, fmt.Errorf("invalid size") - } - if recentRatio < 0.0 || recentRatio > 1.0 { - return nil, fmt.Errorf("invalid recent ratio") - } - if ghostRatio < 0.0 || ghostRatio > 1.0 { - return nil, fmt.Errorf("invalid ghost ratio") - } - - // Determine the sub-sizes - recentSize := int(float64(size) * recentRatio) - evictSize := int(float64(size) * ghostRatio) - - // Allocate the LRUs - recent, err := simplelru.NewLRU(size, nil) - if err != nil { - return nil, err - } - frequent, err := simplelru.NewLRU(size, nil) - if err != nil { - return nil, err - } - recentEvict, err := simplelru.NewLRU(evictSize, nil) - if err != nil { - return nil, err - } - - // Initialize the cache - c := &TwoQueueCache{ - size: size, - recentSize: recentSize, - recent: recent, - frequent: frequent, - recentEvict: recentEvict, - } - return c, nil -} - -func (c *TwoQueueCache) Get(key interface{}) (interface{}, bool) { - c.lock.Lock() - defer c.lock.Unlock() - - // Check if this is a frequent value - if val, ok := c.frequent.Get(key); ok { - return val, ok - } - - // If the value is contained in recent, then we - // promote it to frequent - if val, ok := c.recent.Peek(key); ok { - c.recent.Remove(key) - c.frequent.Add(key, val) - return val, ok - } - - // No hit - return nil, false -} - -func (c *TwoQueueCache) Add(key, value interface{}) { - c.lock.Lock() - defer c.lock.Unlock() - - // Check if the value is frequently used already, - // and just update the value - if c.frequent.Contains(key) { - c.frequent.Add(key, value) - return - } - - // Check if the value is recently used, and promote - // the value into the frequent list - if c.recent.Contains(key) { - c.recent.Remove(key) - c.frequent.Add(key, value) - return - } - - // If the value was recently evicted, add it to the - // frequently used list - if c.recentEvict.Contains(key) { - c.ensureSpace(true) - c.recentEvict.Remove(key) - c.frequent.Add(key, value) - return - } - - // Add to the recently seen list - c.ensureSpace(false) - c.recent.Add(key, value) - return -} - -// ensureSpace is used to ensure we have space in the cache -func (c *TwoQueueCache) ensureSpace(recentEvict bool) { - // If we have space, nothing to do - recentLen := c.recent.Len() - freqLen := c.frequent.Len() - if recentLen+freqLen < c.size { - return - } - - // If the recent buffer is larger than - // the target, evict from there - if recentLen > 0 && (recentLen > c.recentSize || (recentLen == c.recentSize && !recentEvict)) { - k, _, _ := c.recent.RemoveOldest() - c.recentEvict.Add(k, nil) - return - } - - // Remove from the frequent list otherwise - c.frequent.RemoveOldest() -} - -func (c *TwoQueueCache) Len() int { - c.lock.RLock() - defer c.lock.RUnlock() - return c.recent.Len() + c.frequent.Len() -} - -func (c *TwoQueueCache) Keys() []interface{} { - c.lock.RLock() - defer c.lock.RUnlock() - k1 := c.frequent.Keys() - k2 := c.recent.Keys() - return append(k1, k2...) -} - -func (c *TwoQueueCache) Remove(key interface{}) { - c.lock.Lock() - defer c.lock.Unlock() - if c.frequent.Remove(key) { - return - } - if c.recent.Remove(key) { - return - } - if c.recentEvict.Remove(key) { - return - } -} - -func (c *TwoQueueCache) Purge() { - c.lock.Lock() - defer c.lock.Unlock() - c.recent.Purge() - c.frequent.Purge() - c.recentEvict.Purge() -} - -func (c *TwoQueueCache) Contains(key interface{}) bool { - c.lock.RLock() - defer c.lock.RUnlock() - return c.frequent.Contains(key) || c.recent.Contains(key) -} - -func (c *TwoQueueCache) Peek(key interface{}) (interface{}, bool) { - c.lock.RLock() - defer c.lock.RUnlock() - if val, ok := c.frequent.Peek(key); ok { - return val, ok - } - return c.recent.Peek(key) -} diff --git a/vendor/github.com/hashicorp/golang-lru/LICENSE b/vendor/github.com/hashicorp/golang-lru/LICENSE deleted file mode 100644 index be2cc4dfb60..00000000000 --- a/vendor/github.com/hashicorp/golang-lru/LICENSE +++ /dev/null @@ -1,362 +0,0 @@ -Mozilla Public License, version 2.0 - -1. Definitions - -1.1. "Contributor" - - means each individual or legal entity that creates, contributes to the - creation of, or owns Covered Software. - -1.2. "Contributor Version" - - means the combination of the Contributions of others (if any) used by a - Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - - means Source Code Form to which the initial Contributor has attached the - notice in Exhibit A, the Executable Form of such Source Code Form, and - Modifications of such Source Code Form, in each case including portions - thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - a. that the initial Contributor has attached the notice described in - Exhibit B to the Covered Software; or - - b. that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the terms of - a Secondary License. - -1.6. "Executable Form" - - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - - means a work that combines Covered Software with other material, in a - separate file or files, that is not Covered Software. - -1.8. "License" - - means this document. - -1.9. "Licensable" - - means having the right to grant, to the maximum extent possible, whether - at the time of the initial grant or subsequently, any and all of the - rights conveyed by this License. - -1.10. "Modifications" - - means any of the following: - - a. any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered Software; or - - b. any new file in Source Code Form that contains any Covered Software. - -1.11. "Patent Claims" of a Contributor - - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the License, - by the making, using, selling, offering for sale, having made, import, - or transfer of either its Contributions or its Contributor Version. - -1.12. "Secondary License" - - means either the GNU General Public License, Version 2.0, the GNU Lesser - General Public License, Version 2.1, the GNU Affero General Public - License, Version 3.0, or any later versions of those licenses. - -1.13. "Source Code Form" - - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that controls, is - controlled by, or is under common control with You. For purposes of this - definition, "control" means (a) the power, direct or indirect, to cause - the direction or management of such entity, whether by contract or - otherwise, or (b) ownership of more than fifty percent (50%) of the - outstanding shares or beneficial ownership of such entity. - - -2. License Grants and Conditions - -2.1. Grants - - Each Contributor hereby grants You a world-wide, royalty-free, - non-exclusive license: - - a. under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - - b. under Patent Claims of such Contributor to make, use, sell, offer for - sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - - The licenses granted in Section 2.1 with respect to any Contribution - become effective for each Contribution on the date the Contributor first - distributes such Contribution. - -2.3. Limitations on Grant Scope - - The licenses granted in this Section 2 are the only rights granted under - this License. No additional rights or licenses will be implied from the - distribution or licensing of Covered Software under this License. - Notwithstanding Section 2.1(b) above, no patent license is granted by a - Contributor: - - a. for any code that a Contributor has removed from Covered Software; or - - b. for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - - c. under Patent Claims infringed by Covered Software in the absence of - its Contributions. - - This License does not grant any rights in the trademarks, service marks, - or logos of any Contributor (except as may be necessary to comply with - the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - - No Contributor makes additional grants as a result of Your choice to - distribute the Covered Software under a subsequent version of this - License (see Section 10.2) or under the terms of a Secondary License (if - permitted under the terms of Section 3.3). - -2.5. Representation - - Each Contributor represents that the Contributor believes its - Contributions are its original creation(s) or it has sufficient rights to - grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - - This License is not intended to limit any rights You have under - applicable copyright doctrines of fair use, fair dealing, or other - equivalents. - -2.7. Conditions - - Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in - Section 2.1. - - -3. Responsibilities - -3.1. Distribution of Source Form - - All distribution of Covered Software in Source Code Form, including any - Modifications that You create or to which You contribute, must be under - the terms of this License. You must inform recipients that the Source - Code Form of the Covered Software is governed by the terms of this - License, and how they can obtain a copy of this License. You may not - attempt to alter or restrict the recipients' rights in the Source Code - Form. - -3.2. Distribution of Executable Form - - If You distribute Covered Software in Executable Form then: - - a. such Covered Software must also be made available in Source Code Form, - as described in Section 3.1, and You must inform recipients of the - Executable Form how they can obtain a copy of such Source Code Form by - reasonable means in a timely manner, at a charge no more than the cost - of distribution to the recipient; and - - b. You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter the - recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - - You may create and distribute a Larger Work under terms of Your choice, - provided that You also comply with the requirements of this License for - the Covered Software. If the Larger Work is a combination of Covered - Software with a work governed by one or more Secondary Licenses, and the - Covered Software is not Incompatible With Secondary Licenses, this - License permits You to additionally distribute such Covered Software - under the terms of such Secondary License(s), so that the recipient of - the Larger Work may, at their option, further distribute the Covered - Software under the terms of either this License or such Secondary - License(s). - -3.4. Notices - - You may not remove or alter the substance of any license notices - (including copyright notices, patent notices, disclaimers of warranty, or - limitations of liability) contained within the Source Code Form of the - Covered Software, except that You may alter any license notices to the - extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - - You may choose to offer, and to charge a fee for, warranty, support, - indemnity or liability obligations to one or more recipients of Covered - Software. However, You may do so only on Your own behalf, and not on - behalf of any Contributor. You must make it absolutely clear that any - such warranty, support, indemnity, or liability obligation is offered by - You alone, and You hereby agree to indemnify every Contributor for any - liability incurred by such Contributor as a result of warranty, support, - indemnity or liability terms You offer. You may include additional - disclaimers of warranty and limitations of liability specific to any - jurisdiction. - -4. Inability to Comply Due to Statute or Regulation - - If it is impossible for You to comply with any of the terms of this License - with respect to some or all of the Covered Software due to statute, - judicial order, or regulation then You must: (a) comply with the terms of - this License to the maximum extent possible; and (b) describe the - limitations and the code they affect. Such description must be placed in a - text file included with all distributions of the Covered Software under - this License. Except to the extent prohibited by statute or regulation, - such description must be sufficiently detailed for a recipient of ordinary - skill to be able to understand it. - -5. Termination - -5.1. The rights granted under this License will terminate automatically if You - fail to comply with any of its terms. However, if You become compliant, - then the rights granted under this License from a particular Contributor - are reinstated (a) provisionally, unless and until such Contributor - explicitly and finally terminates Your grants, and (b) on an ongoing - basis, if such Contributor fails to notify You of the non-compliance by - some reasonable means prior to 60 days after You have come back into - compliance. Moreover, Your grants from a particular Contributor are - reinstated on an ongoing basis if such Contributor notifies You of the - non-compliance by some reasonable means, this is the first time You have - received notice of non-compliance with this License from such - Contributor, and You become compliant prior to 30 days after Your receipt - of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent - infringement claim (excluding declaratory judgment actions, - counter-claims, and cross-claims) alleging that a Contributor Version - directly or indirectly infringes any patent, then the rights granted to - You by any and all Contributors for the Covered Software under Section - 2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user - license agreements (excluding distributors and resellers) which have been - validly granted by You or Your distributors under this License prior to - termination shall survive termination. - -6. Disclaimer of Warranty - - Covered Software is provided under this License on an "as is" basis, - without warranty of any kind, either expressed, implied, or statutory, - including, without limitation, warranties that the Covered Software is free - of defects, merchantable, fit for a particular purpose or non-infringing. - The entire risk as to the quality and performance of the Covered Software - is with You. Should any Covered Software prove defective in any respect, - You (not any Contributor) assume the cost of any necessary servicing, - repair, or correction. This disclaimer of warranty constitutes an essential - part of this License. No use of any Covered Software is authorized under - this License except under this disclaimer. - -7. Limitation of Liability - - Under no circumstances and under no legal theory, whether tort (including - negligence), contract, or otherwise, shall any Contributor, or anyone who - distributes Covered Software as permitted above, be liable to You for any - direct, indirect, special, incidental, or consequential damages of any - character including, without limitation, damages for lost profits, loss of - goodwill, work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses, even if such party shall have been - informed of the possibility of such damages. This limitation of liability - shall not apply to liability for death or personal injury resulting from - such party's negligence to the extent applicable law prohibits such - limitation. Some jurisdictions do not allow the exclusion or limitation of - incidental or consequential damages, so this exclusion and limitation may - not apply to You. - -8. Litigation - - Any litigation relating to this License may be brought only in the courts - of a jurisdiction where the defendant maintains its principal place of - business and such litigation shall be governed by laws of that - jurisdiction, without reference to its conflict-of-law provisions. Nothing - in this Section shall prevent a party's ability to bring cross-claims or - counter-claims. - -9. Miscellaneous - - This License represents the complete agreement concerning the subject - matter hereof. If any provision of this License is held to be - unenforceable, such provision shall be reformed only to the extent - necessary to make it enforceable. Any law or regulation which provides that - the language of a contract shall be construed against the drafter shall not - be used to construe this License against a Contributor. - - -10. Versions of the License - -10.1. New Versions - - Mozilla Foundation is the license steward. Except as provided in Section - 10.3, no one other than the license steward has the right to modify or - publish new versions of this License. Each version will be given a - distinguishing version number. - -10.2. Effect of New Versions - - You may distribute the Covered Software under the terms of the version - of the License under which You originally received the Covered Software, - or under the terms of any subsequent version published by the license - steward. - -10.3. Modified Versions - - If you create software not governed by this License, and you want to - create a new license for such software, you may create and use a - modified version of this License if you rename the license and remove - any references to the name of the license steward (except to note that - such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary - Licenses If You choose to distribute Source Code Form that is - Incompatible With Secondary Licenses under the terms of this version of - the License, the notice described in Exhibit B of this License must be - attached. - -Exhibit A - Source Code Form License Notice - - This Source Code Form is subject to the - terms of the Mozilla Public License, v. - 2.0. If a copy of the MPL was not - distributed with this file, You can - obtain one at - http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular file, -then You may include the notice in a location (such as a LICENSE file in a -relevant directory) where a recipient would be likely to look for such a -notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice - - This Source Code Form is "Incompatible - With Secondary Licenses", as defined by - the Mozilla Public License, v. 2.0. diff --git a/vendor/github.com/hashicorp/golang-lru/arc.go b/vendor/github.com/hashicorp/golang-lru/arc.go deleted file mode 100644 index a2a25281733..00000000000 --- a/vendor/github.com/hashicorp/golang-lru/arc.go +++ /dev/null @@ -1,257 +0,0 @@ -package lru - -import ( - "sync" - - "github.com/hashicorp/golang-lru/simplelru" -) - -// ARCCache is a thread-safe fixed size Adaptive Replacement Cache (ARC). -// ARC is an enhancement over the standard LRU cache in that tracks both -// frequency and recency of use. This avoids a burst in access to new -// entries from evicting the frequently used older entries. It adds some -// additional tracking overhead to a standard LRU cache, computationally -// it is roughly 2x the cost, and the extra memory overhead is linear -// with the size of the cache. ARC has been patented by IBM, but is -// similar to the TwoQueueCache (2Q) which requires setting parameters. -type ARCCache struct { - size int // Size is the total capacity of the cache - p int // P is the dynamic preference towards T1 or T2 - - t1 *simplelru.LRU // T1 is the LRU for recently accessed items - b1 *simplelru.LRU // B1 is the LRU for evictions from t1 - - t2 *simplelru.LRU // T2 is the LRU for frequently accessed items - b2 *simplelru.LRU // B2 is the LRU for evictions from t2 - - lock sync.RWMutex -} - -// NewARC creates an ARC of the given size -func NewARC(size int) (*ARCCache, error) { - // Create the sub LRUs - b1, err := simplelru.NewLRU(size, nil) - if err != nil { - return nil, err - } - b2, err := simplelru.NewLRU(size, nil) - if err != nil { - return nil, err - } - t1, err := simplelru.NewLRU(size, nil) - if err != nil { - return nil, err - } - t2, err := simplelru.NewLRU(size, nil) - if err != nil { - return nil, err - } - - // Initialize the ARC - c := &ARCCache{ - size: size, - p: 0, - t1: t1, - b1: b1, - t2: t2, - b2: b2, - } - return c, nil -} - -// Get looks up a key's value from the cache. -func (c *ARCCache) Get(key interface{}) (interface{}, bool) { - c.lock.Lock() - defer c.lock.Unlock() - - // Ff the value is contained in T1 (recent), then - // promote it to T2 (frequent) - if val, ok := c.t1.Peek(key); ok { - c.t1.Remove(key) - c.t2.Add(key, val) - return val, ok - } - - // Check if the value is contained in T2 (frequent) - if val, ok := c.t2.Get(key); ok { - return val, ok - } - - // No hit - return nil, false -} - -// Add adds a value to the cache. -func (c *ARCCache) Add(key, value interface{}) { - c.lock.Lock() - defer c.lock.Unlock() - - // Check if the value is contained in T1 (recent), and potentially - // promote it to frequent T2 - if c.t1.Contains(key) { - c.t1.Remove(key) - c.t2.Add(key, value) - return - } - - // Check if the value is already in T2 (frequent) and update it - if c.t2.Contains(key) { - c.t2.Add(key, value) - return - } - - // Check if this value was recently evicted as part of the - // recently used list - if c.b1.Contains(key) { - // T1 set is too small, increase P appropriately - delta := 1 - b1Len := c.b1.Len() - b2Len := c.b2.Len() - if b2Len > b1Len { - delta = b2Len / b1Len - } - if c.p+delta >= c.size { - c.p = c.size - } else { - c.p += delta - } - - // Potentially need to make room in the cache - if c.t1.Len()+c.t2.Len() >= c.size { - c.replace(false) - } - - // Remove from B1 - c.b1.Remove(key) - - // Add the key to the frequently used list - c.t2.Add(key, value) - return - } - - // Check if this value was recently evicted as part of the - // frequently used list - if c.b2.Contains(key) { - // T2 set is too small, decrease P appropriately - delta := 1 - b1Len := c.b1.Len() - b2Len := c.b2.Len() - if b1Len > b2Len { - delta = b1Len / b2Len - } - if delta >= c.p { - c.p = 0 - } else { - c.p -= delta - } - - // Potentially need to make room in the cache - if c.t1.Len()+c.t2.Len() >= c.size { - c.replace(true) - } - - // Remove from B2 - c.b2.Remove(key) - - // Add the key to the frequntly used list - c.t2.Add(key, value) - return - } - - // Potentially need to make room in the cache - if c.t1.Len()+c.t2.Len() >= c.size { - c.replace(false) - } - - // Keep the size of the ghost buffers trim - if c.b1.Len() > c.size-c.p { - c.b1.RemoveOldest() - } - if c.b2.Len() > c.p { - c.b2.RemoveOldest() - } - - // Add to the recently seen list - c.t1.Add(key, value) - return -} - -// replace is used to adaptively evict from either T1 or T2 -// based on the current learned value of P -func (c *ARCCache) replace(b2ContainsKey bool) { - t1Len := c.t1.Len() - if t1Len > 0 && (t1Len > c.p || (t1Len == c.p && b2ContainsKey)) { - k, _, ok := c.t1.RemoveOldest() - if ok { - c.b1.Add(k, nil) - } - } else { - k, _, ok := c.t2.RemoveOldest() - if ok { - c.b2.Add(k, nil) - } - } -} - -// Len returns the number of cached entries -func (c *ARCCache) Len() int { - c.lock.RLock() - defer c.lock.RUnlock() - return c.t1.Len() + c.t2.Len() -} - -// Keys returns all the cached keys -func (c *ARCCache) Keys() []interface{} { - c.lock.RLock() - defer c.lock.RUnlock() - k1 := c.t1.Keys() - k2 := c.t2.Keys() - return append(k1, k2...) -} - -// Remove is used to purge a key from the cache -func (c *ARCCache) Remove(key interface{}) { - c.lock.Lock() - defer c.lock.Unlock() - if c.t1.Remove(key) { - return - } - if c.t2.Remove(key) { - return - } - if c.b1.Remove(key) { - return - } - if c.b2.Remove(key) { - return - } -} - -// Purge is used to clear the cache -func (c *ARCCache) Purge() { - c.lock.Lock() - defer c.lock.Unlock() - c.t1.Purge() - c.t2.Purge() - c.b1.Purge() - c.b2.Purge() -} - -// Contains is used to check if the cache contains a key -// without updating recency or frequency. -func (c *ARCCache) Contains(key interface{}) bool { - c.lock.RLock() - defer c.lock.RUnlock() - return c.t1.Contains(key) || c.t2.Contains(key) -} - -// Peek is used to inspect the cache value of a key -// without updating recency or frequency. -func (c *ARCCache) Peek(key interface{}) (interface{}, bool) { - c.lock.RLock() - defer c.lock.RUnlock() - if val, ok := c.t1.Peek(key); ok { - return val, ok - } - return c.t2.Peek(key) -} diff --git a/vendor/github.com/hashicorp/golang-lru/lru.go b/vendor/github.com/hashicorp/golang-lru/lru.go deleted file mode 100644 index a6285f989e0..00000000000 --- a/vendor/github.com/hashicorp/golang-lru/lru.go +++ /dev/null @@ -1,114 +0,0 @@ -// This package provides a simple LRU cache. It is based on the -// LRU implementation in groupcache: -// https://github.com/golang/groupcache/tree/master/lru -package lru - -import ( - "sync" - - "github.com/hashicorp/golang-lru/simplelru" -) - -// Cache is a thread-safe fixed size LRU cache. -type Cache struct { - lru *simplelru.LRU - lock sync.RWMutex -} - -// New creates an LRU of the given size -func New(size int) (*Cache, error) { - return NewWithEvict(size, nil) -} - -// NewWithEvict constructs a fixed size cache with the given eviction -// callback. -func NewWithEvict(size int, onEvicted func(key interface{}, value interface{})) (*Cache, error) { - lru, err := simplelru.NewLRU(size, simplelru.EvictCallback(onEvicted)) - if err != nil { - return nil, err - } - c := &Cache{ - lru: lru, - } - return c, nil -} - -// Purge is used to completely clear the cache -func (c *Cache) Purge() { - c.lock.Lock() - c.lru.Purge() - c.lock.Unlock() -} - -// Add adds a value to the cache. Returns true if an eviction occurred. -func (c *Cache) Add(key, value interface{}) bool { - c.lock.Lock() - defer c.lock.Unlock() - return c.lru.Add(key, value) -} - -// Get looks up a key's value from the cache. -func (c *Cache) Get(key interface{}) (interface{}, bool) { - c.lock.Lock() - defer c.lock.Unlock() - return c.lru.Get(key) -} - -// Check if a key is in the cache, without updating the recent-ness -// or deleting it for being stale. -func (c *Cache) Contains(key interface{}) bool { - c.lock.RLock() - defer c.lock.RUnlock() - return c.lru.Contains(key) -} - -// Returns the key value (or undefined if not found) without updating -// the "recently used"-ness of the key. -func (c *Cache) Peek(key interface{}) (interface{}, bool) { - c.lock.RLock() - defer c.lock.RUnlock() - return c.lru.Peek(key) -} - -// ContainsOrAdd checks if a key is in the cache without updating the -// recent-ness or deleting it for being stale, and if not, adds the value. -// Returns whether found and whether an eviction occurred. -func (c *Cache) ContainsOrAdd(key, value interface{}) (ok, evict bool) { - c.lock.Lock() - defer c.lock.Unlock() - - if c.lru.Contains(key) { - return true, false - } else { - evict := c.lru.Add(key, value) - return false, evict - } -} - -// Remove removes the provided key from the cache. -func (c *Cache) Remove(key interface{}) { - c.lock.Lock() - c.lru.Remove(key) - c.lock.Unlock() -} - -// RemoveOldest removes the oldest item from the cache. -func (c *Cache) RemoveOldest() { - c.lock.Lock() - c.lru.RemoveOldest() - c.lock.Unlock() -} - -// Keys returns a slice of the keys in the cache, from oldest to newest. -func (c *Cache) Keys() []interface{} { - c.lock.RLock() - defer c.lock.RUnlock() - return c.lru.Keys() -} - -// Len returns the number of items in the cache. -func (c *Cache) Len() int { - c.lock.RLock() - defer c.lock.RUnlock() - return c.lru.Len() -} diff --git a/vendor/github.com/hashicorp/golang-lru/simplelru/lru.go b/vendor/github.com/hashicorp/golang-lru/simplelru/lru.go deleted file mode 100644 index cb416b394f2..00000000000 --- a/vendor/github.com/hashicorp/golang-lru/simplelru/lru.go +++ /dev/null @@ -1,160 +0,0 @@ -package simplelru - -import ( - "container/list" - "errors" -) - -// EvictCallback is used to get a callback when a cache entry is evicted -type EvictCallback func(key interface{}, value interface{}) - -// LRU implements a non-thread safe fixed size LRU cache -type LRU struct { - size int - evictList *list.List - items map[interface{}]*list.Element - onEvict EvictCallback -} - -// entry is used to hold a value in the evictList -type entry struct { - key interface{} - value interface{} -} - -// NewLRU constructs an LRU of the given size -func NewLRU(size int, onEvict EvictCallback) (*LRU, error) { - if size <= 0 { - return nil, errors.New("Must provide a positive size") - } - c := &LRU{ - size: size, - evictList: list.New(), - items: make(map[interface{}]*list.Element), - onEvict: onEvict, - } - return c, nil -} - -// Purge is used to completely clear the cache -func (c *LRU) Purge() { - for k, v := range c.items { - if c.onEvict != nil { - c.onEvict(k, v.Value.(*entry).value) - } - delete(c.items, k) - } - c.evictList.Init() -} - -// Add adds a value to the cache. Returns true if an eviction occurred. -func (c *LRU) Add(key, value interface{}) bool { - // Check for existing item - if ent, ok := c.items[key]; ok { - c.evictList.MoveToFront(ent) - ent.Value.(*entry).value = value - return false - } - - // Add new item - ent := &entry{key, value} - entry := c.evictList.PushFront(ent) - c.items[key] = entry - - evict := c.evictList.Len() > c.size - // Verify size not exceeded - if evict { - c.removeOldest() - } - return evict -} - -// Get looks up a key's value from the cache. -func (c *LRU) Get(key interface{}) (value interface{}, ok bool) { - if ent, ok := c.items[key]; ok { - c.evictList.MoveToFront(ent) - return ent.Value.(*entry).value, true - } - return -} - -// Check if a key is in the cache, without updating the recent-ness -// or deleting it for being stale. -func (c *LRU) Contains(key interface{}) (ok bool) { - _, ok = c.items[key] - return ok -} - -// Returns the key value (or undefined if not found) without updating -// the "recently used"-ness of the key. -func (c *LRU) Peek(key interface{}) (value interface{}, ok bool) { - if ent, ok := c.items[key]; ok { - return ent.Value.(*entry).value, true - } - return nil, ok -} - -// Remove removes the provided key from the cache, returning if the -// key was contained. -func (c *LRU) Remove(key interface{}) bool { - if ent, ok := c.items[key]; ok { - c.removeElement(ent) - return true - } - return false -} - -// RemoveOldest removes the oldest item from the cache. -func (c *LRU) RemoveOldest() (interface{}, interface{}, bool) { - ent := c.evictList.Back() - if ent != nil { - c.removeElement(ent) - kv := ent.Value.(*entry) - return kv.key, kv.value, true - } - return nil, nil, false -} - -// GetOldest returns the oldest entry -func (c *LRU) GetOldest() (interface{}, interface{}, bool) { - ent := c.evictList.Back() - if ent != nil { - kv := ent.Value.(*entry) - return kv.key, kv.value, true - } - return nil, nil, false -} - -// Keys returns a slice of the keys in the cache, from oldest to newest. -func (c *LRU) Keys() []interface{} { - keys := make([]interface{}, len(c.items)) - i := 0 - for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() { - keys[i] = ent.Value.(*entry).key - i++ - } - return keys -} - -// Len returns the number of items in the cache. -func (c *LRU) Len() int { - return c.evictList.Len() -} - -// removeOldest removes the oldest item from the cache. -func (c *LRU) removeOldest() { - ent := c.evictList.Back() - if ent != nil { - c.removeElement(ent) - } -} - -// removeElement is used to remove a given list element from the cache -func (c *LRU) removeElement(e *list.Element) { - c.evictList.Remove(e) - kv := e.Value.(*entry) - delete(c.items, kv.key) - if c.onEvict != nil { - c.onEvict(kv.key, kv.value) - } -} diff --git a/vendor/github.com/jimstudt/http-authentication/basic/LICENSE b/vendor/github.com/jimstudt/http-authentication/basic/LICENSE deleted file mode 100644 index 70def10f987..00000000000 --- a/vendor/github.com/jimstudt/http-authentication/basic/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Jim Studt - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/jimstudt/http-authentication/basic/bcrypt.go b/vendor/github.com/jimstudt/http-authentication/basic/bcrypt.go deleted file mode 100644 index 4fc32b4b549..00000000000 --- a/vendor/github.com/jimstudt/http-authentication/basic/bcrypt.go +++ /dev/null @@ -1,15 +0,0 @@ -package basic - -import ( - "fmt" - "strings" -) - -// Reject any password encoded using bcrypt. -func RejectBcrypt(src string) (EncodedPasswd, error) { - if strings.HasPrefix(src, "$2y$") { - return nil, fmt.Errorf("bcrypt passwords are not accepted: %s", src) - } - - return nil, nil -} diff --git a/vendor/github.com/jimstudt/http-authentication/basic/htpasswd.go b/vendor/github.com/jimstudt/http-authentication/basic/htpasswd.go deleted file mode 100644 index bc4bf8e8b44..00000000000 --- a/vendor/github.com/jimstudt/http-authentication/basic/htpasswd.go +++ /dev/null @@ -1,208 +0,0 @@ -// Package htpasswd provides HTTP Basic Authentication using Apache-style htpasswd files -// for the user and password data. -// -// It supports most common hashing systems used over the decades and can be easily extended -// by the programmer to support others. (See the sha.go source file as a guide.) -// -// You will want to use something like... -// myauth := htpasswd.New("My Realm", "./my-htpasswd-file", htpasswd.DefaultSystems, nil) -// m.Use(myauth.Handler) -// ...to configure your authentication and then use the myauth.Handler as a middleware handler in your Martini stack. -// You should read about that nil, as well as Reread() too. -package basic - -import ( - "bufio" - "encoding/base64" - "fmt" - "net/http" - "os" - "os/signal" - "strings" - "sync" -) - -// An EncodedPasswd is created from the encoded password in a password file by a PasswdParser. -// -// The password files consist of lines like "user:passwd-encoding". The user part is stripped off and -// the passwd-encoding part is captured in an EncodedPasswd. -type EncodedPasswd interface { - // Return true if the string matches the password. - // This may cache the result in the case of expensive comparison functions. - MatchesPassword(pw string) bool -} - -// Examine an encoded password, and if it is formatted correctly and sane, return an -// EncodedPasswd which will recognize it. -// -// If the format is not understood, then return nil -// so that another parser may have a chance. If the format is understood but not sane, -// return an error to prevent other formats from possibly claiming it -// -// You may write and supply one of these functions to support a format (e.g. bcrypt) not -// already included in this package. Use sha.c as a template, it is simple but not too simple. -type PasswdParser func(pw string) (EncodedPasswd, error) - -type passwdTable map[string]EncodedPasswd - -// A BadLineHandler is used to notice bad lines in a password file. If not nil, it will be -// called for each bad line with a descriptive error. Think about what you do with these, they -// will sometimes contain hashed passwords. -type BadLineHandler func(err error) - -// An HtpasswdFile encompasses an Apache-style htpasswd file for HTTP Basic authentication -type HtpasswdFile struct { - realm string - filePath string - mutex sync.Mutex - passwds passwdTable - parsers []PasswdParser -} - -// An array of PasswdParser including all builtin parsers. Notice that Plain is last, since it accepts anything -var DefaultSystems []PasswdParser = []PasswdParser{AcceptMd5, AcceptSha, RejectBcrypt, AcceptPlain} - -// New creates an HtpasswdFile from an Apache-style htpasswd file for HTTP Basic Authentication. -// -// The realm is presented to the user in the login dialog. -// -// The filename must exist and be accessible to the process, as well as being a valid htpasswd file. -// -// parsers is a list of functions to handle various hashing systems. In practice you will probably -// just pass htpasswd.DefaultSystems, but you could make your own to explicitly reject some formats or -// implement your own. -// -// bad is a function, which if not nil will be called for each malformed or rejected entry in -// the password file. -func New(realm string, filename string, parsers []PasswdParser, bad BadLineHandler) (*HtpasswdFile, error) { - bf := HtpasswdFile{ - realm: realm, - filePath: filename, - parsers: parsers, - } - - if err := bf.Reload(bad); err != nil { - return nil, err - } - - return &bf, nil -} - -// A Martini middleware handler to enforce HTTP Basic Auth using the policy read from the htpasswd file. -func (bf *HtpasswdFile) ServeHTTP(res http.ResponseWriter, req *http.Request) { - // if everything works, we return, otherwise we get to the - // end where we do an http.Error to stop the request - auth := req.Header.Get("Authorization") - - if auth != "" { - userPassword, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(auth, "Basic ")) - if err == nil { - parts := strings.SplitN(string(userPassword), ":", 2) - if len(parts) == 2 { - user := parts[0] - pw := parts[1] - - bf.mutex.Lock() - matcher, ok := bf.passwds[user] - bf.mutex.Unlock() - - if ok && matcher.MatchesPassword(pw) { - // we are good - return - } - } - } - } - - res.Header().Set("WWW-Authenticate", "Basic realm=\""+bf.realm+"\"") - http.Error(res, "Not Authorized", http.StatusUnauthorized) -} - -// Reread the password file for this HtpasswdFile. -// You will need to call this to notice any changes to the password file. -// This function is thread safe. Someone versed in fsnotify might make it -// happen automatically. Likewise you might also connect a SIGHUP handler to -// this function. -func (bf *HtpasswdFile) Reload(bad BadLineHandler) error { - // with the file... - f, err := os.Open(bf.filePath) - if err != nil { - return err - } - defer f.Close() - - // ... and a new map ... - newPasswdMap := passwdTable{} - - // ... for each line ... - scanner := bufio.NewScanner(f) - for scanner.Scan() { - line := scanner.Text() - - // ... add it to the map, noting errors along the way - if perr := bf.addHtpasswdUser(&newPasswdMap, line); perr != nil && bad != nil { - bad(perr) - } - } - if err := scanner.Err(); err != nil { - return fmt.Errorf("Error scanning htpasswd file: %s", err.Error()) - } - - // .. finally, safely swap in the new map - bf.mutex.Lock() - bf.passwds = newPasswdMap - bf.mutex.Unlock() - - return nil -} - -// Reload the htpasswd file on a signal. If there is an error, the old data will be kept instead. -// Typically you would use syscall.SIGHUP for the value of "when" -func (bf *HtpasswdFile) ReloadOn(when os.Signal, onbad BadLineHandler) { - // this is rather common with code in digest, but I don't have a common area... - c := make(chan os.Signal, 1) - signal.Notify(c, when) - - go func() { - for { - _ = <-c - bf.Reload(onbad) - } - }() -} - -// Process a line from an htpasswd file and add it to the user/password map. We may -// encounter some malformed lines, this will not be an error, but we will log them if -// the caller has given us a logger. -func (bf *HtpasswdFile) addHtpasswdUser(pwmap *passwdTable, rawLine string) error { - // ignore white space lines - line := strings.TrimSpace(rawLine) - if line == "" { - return nil - } - - // split "user:encoding" at colon - parts := strings.SplitN(line, ":", 2) - if len(parts) != 2 { - return fmt.Errorf("malformed line, no colon: %s", line) - } - - user := parts[0] - encoding := parts[1] - - // give each parser a shot. The first one to produce a matcher wins. - // If one produces an error then stop (to prevent Plain from catching it) - for _, p := range bf.parsers { - matcher, err := p(encoding) - if err != nil { - return err - } - if matcher != nil { - (*pwmap)[user] = matcher - return nil // we are done, we took to first match - } - } - - // No one liked this line - return fmt.Errorf("unable to recognize password for %s in %s", user, encoding) -} diff --git a/vendor/github.com/jimstudt/http-authentication/basic/md5.go b/vendor/github.com/jimstudt/http-authentication/basic/md5.go deleted file mode 100644 index 0ac793a978b..00000000000 --- a/vendor/github.com/jimstudt/http-authentication/basic/md5.go +++ /dev/null @@ -1,143 +0,0 @@ -package basic - -import ( - "bytes" - "crypto/md5" - "fmt" - "strings" -) - -type md5Password struct { - salt string - hashed string -} - -// Accept valid MD5 encoded passwords -func AcceptMd5(src string) (EncodedPasswd, error) { - if !strings.HasPrefix(src, "$apr1$") { - return nil, nil - } - - rest := strings.TrimPrefix(src, "$apr1$") - mparts := strings.SplitN(rest, "$", 2) - if len(mparts) != 2 { - return nil, fmt.Errorf("malformed md5 password: %s", src) - } - - salt, hashed := mparts[0], mparts[1] - return &md5Password{salt, hashed}, nil -} - -// Reject any MD5 encoded password -func RejectMd5(src string) (EncodedPasswd, error) { - if !strings.HasPrefix(src, "$apr1$") { - return nil, nil - } - return nil, fmt.Errorf("md5 password rejected: %s", src) -} - -// This is the MD5 hashing function out of Apache's htpasswd program. The algorithm -// is insane, but we have to match it. Mercifully I found a PHP variant of it at -// http://stackoverflow.com/questions/2994637/how-to-edit-htpasswd-using-php -// in an answer. That reads better than the original C, and is easy to instrument. -// We will eventually go back to the original apr_md5.c for inspiration when the -// PHP gets too weird. -// The algorithm makes more sense if you imagine the original authors in a pub, -// drinking beer and rolling dice as the fundamental design process. -func apr1Md5(password string, salt string) string { - - // start with a hash of password and salt - initBin := md5.Sum([]byte(password + salt + password)) - - // begin an initial string with hash and salt - initText := bytes.NewBufferString(password + "$apr1$" + salt) - - // add crap to the string willy-nilly - for i := len(password); i > 0; i -= 16 { - lim := i - if lim > 16 { - lim = 16 - } - initText.Write(initBin[0:lim]) - } - - // add more crap to the string willy-nilly - for i := len(password); i > 0; i >>= 1 { - if (i & 1) == 1 { - initText.WriteByte(byte(0)) - } else { - initText.WriteByte(password[0]) - } - } - - // Begin our hashing in earnest using our initial string - bin := md5.Sum(initText.Bytes()) - - n := bytes.NewBuffer([]byte{}) - - for i := 0; i < 1000; i++ { - // prepare to make a new muddle - n.Reset() - - // alternate password+crap+bin with bin+crap+password - if (i & 1) == 1 { - n.WriteString(password) - } else { - n.Write(bin[:]) - } - - // usually add the salt, but not always - if i%3 != 0 { - n.WriteString(salt) - } - - // usually add the password but not always - if i%7 != 0 { - n.WriteString(password) - } - - // the back half of that alternation - if (i & 1) == 1 { - n.Write(bin[:]) - } else { - n.WriteString(password) - } - - // replace bin with the md5 of this muddle - bin = md5.Sum(n.Bytes()) - } - - // At this point we stop transliterating the PHP code and flip back to - // reading the Apache source. The PHP uses their base64 library, but that - // uses the wrong character set so needs to be repaired afterwards and reversed - // and it is just really weird to read. - - result := bytes.NewBuffer([]byte{}) - - // This is our own little similar-to-base64-but-not-quite filler - fill := func(a byte, b byte, c byte) { - v := (uint(a) << 16) + (uint(b) << 8) + uint(c) // take our 24 input bits - - for i := 0; i < 4; i++ { // and pump out a character for each 6 bits - result.WriteByte("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"[v&0x3f]) - v >>= 6 - } - } - - // The order of these indices is strange, be careful - fill(bin[0], bin[6], bin[12]) - fill(bin[1], bin[7], bin[13]) - fill(bin[2], bin[8], bin[14]) - fill(bin[3], bin[9], bin[15]) - fill(bin[4], bin[10], bin[5]) // 5? Yes. - fill(0, 0, bin[11]) - - resultString := string(result.Bytes()[0:22]) // we wrote two extras since we only need 22. - - return resultString -} - -func (m *md5Password) MatchesPassword(pw string) bool { - hashed := apr1Md5(pw, m.salt) - return constantTimeEquals(hashed, m.hashed) -} diff --git a/vendor/github.com/jimstudt/http-authentication/basic/plain.go b/vendor/github.com/jimstudt/http-authentication/basic/plain.go deleted file mode 100644 index 5435e954b47..00000000000 --- a/vendor/github.com/jimstudt/http-authentication/basic/plain.go +++ /dev/null @@ -1,28 +0,0 @@ -package basic - -import ( - "fmt" -) - -type plainPassword struct { - password string -} - -// Accept any password in the plain text encoding. -// Be careful: This matches any line, so it *must* be the last parser in you list. -func AcceptPlain(pw string) (EncodedPasswd, error) { - return &plainPassword{pw}, nil -} - -// Reject any plain text encoded passoword. -// Be careful: This matches any line, so it *must* be the last parser in you list. -func RejectPlain(pw string) (EncodedPasswd, error) { - return nil, fmt.Errorf("plain password rejected: %s", pw) -} - -func (p *plainPassword) MatchesPassword(pw string) bool { - // Notice: nginx prefixes plain passwords with {PLAIN}, so we see if that would - // let us match too. I'd split {PLAIN} off, but someone probably uses that - // in their password. It's a big planet. - return constantTimeEquals(pw, p.password) || constantTimeEquals("{PLAIN}"+pw, p.password) -} diff --git a/vendor/github.com/jimstudt/http-authentication/basic/sha.go b/vendor/github.com/jimstudt/http-authentication/basic/sha.go deleted file mode 100644 index dbe9f9a5a34..00000000000 --- a/vendor/github.com/jimstudt/http-authentication/basic/sha.go +++ /dev/null @@ -1,43 +0,0 @@ -package basic - -import ( - "crypto/sha1" - "crypto/subtle" - "encoding/base64" - "fmt" - "strings" -) - -type shaPassword struct { - hashed []byte -} - -// Accept valid SHA encoded passwords. -func AcceptSha(src string) (EncodedPasswd, error) { - if !strings.HasPrefix(src, "{SHA}") { - return nil, nil - } - - b64 := strings.TrimPrefix(src, "{SHA}") - hashed, err := base64.StdEncoding.DecodeString(b64) - if err != nil { - return nil, fmt.Errorf("Malformed sha1(%s): %s", src, err.Error()) - } - if len(hashed) != sha1.Size { - return nil, fmt.Errorf("Malformed sha1(%s): wrong length", src) - } - return &shaPassword{hashed}, nil -} - -// Reject any password encoded as SHA. -func RejectSha(src string) (EncodedPasswd, error) { - if !strings.HasPrefix(src, "{SHA}") { - return nil, nil - } - return nil, fmt.Errorf("sha password rejected: %s", src) -} - -func (s *shaPassword) MatchesPassword(pw string) bool { - h := sha1.Sum([]byte(pw)) - return subtle.ConstantTimeCompare(h[:], s.hashed) == 1 -} diff --git a/vendor/github.com/jimstudt/http-authentication/basic/util.go b/vendor/github.com/jimstudt/http-authentication/basic/util.go deleted file mode 100644 index daeb30cf383..00000000000 --- a/vendor/github.com/jimstudt/http-authentication/basic/util.go +++ /dev/null @@ -1,18 +0,0 @@ -package basic - -import ( - "crypto/sha1" - "crypto/subtle" -) - -func constantTimeEquals(a string, b string) bool { - // compare SHA-1 as a gatekeeper in constant time - // then check that we didn't get by because of a collision - aSha := sha1.Sum([]byte(a)) - bSha := sha1.Sum([]byte(b)) - if subtle.ConstantTimeCompare(aSha[:], bSha[:]) == 1 { - // yes, this bit isn't constant, but you had to make a Sha1 collision to get here - return a == b - } - return false -} diff --git a/vendor/github.com/lucas-clemente/aes12/LICENSE b/vendor/github.com/lucas-clemente/aes12/LICENSE deleted file mode 100644 index 2c08ae24541..00000000000 --- a/vendor/github.com/lucas-clemente/aes12/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2016 Lucas Clemente - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/lucas-clemente/aes12/aes_gcm.go b/vendor/github.com/lucas-clemente/aes12/aes_gcm.go deleted file mode 100644 index 21f2b23318b..00000000000 --- a/vendor/github.com/lucas-clemente/aes12/aes_gcm.go +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build amd64 - -package aes12 - -import "crypto/subtle" - -// The following functions are defined in gcm_amd64.s. -func hasGCMAsm() bool - -//go:noescape -func aesEncBlock(dst, src *[16]byte, ks []uint32) - -//go:noescape -func gcmAesInit(productTable *[256]byte, ks []uint32) - -//go:noescape -func gcmAesData(productTable *[256]byte, data []byte, T *[16]byte) - -//go:noescape -func gcmAesEnc(productTable *[256]byte, dst, src []byte, ctr, T *[16]byte, ks []uint32) - -//go:noescape -func gcmAesDec(productTable *[256]byte, dst, src []byte, ctr, T *[16]byte, ks []uint32) - -//go:noescape -func gcmAesFinish(productTable *[256]byte, tagMask, T *[16]byte, pLen, dLen uint64) - -// aesCipherGCM implements crypto/cipher.gcmAble so that crypto/cipher.NewGCM -// will use the optimised implementation in this file when possible. Instances -// of this type only exist when hasGCMAsm returns true. -type aesCipherGCM struct { - aesCipherAsm -} - -// Assert that aesCipherGCM implements the gcmAble interface. -var _ gcmAble = (*aesCipherGCM)(nil) - -// NewGCM returns the AES cipher wrapped in Galois Counter Mode. This is only -// called by crypto/cipher.NewGCM via the gcmAble interface. -func (c *aesCipherGCM) NewGCM(nonceSize int) (AEAD, error) { - g := &gcmAsm{ks: c.enc, nonceSize: nonceSize} - gcmAesInit(&g.productTable, g.ks) - return g, nil -} - -type gcmAsm struct { - // ks is the key schedule, the length of which depends on the size of - // the AES key. - ks []uint32 - // productTable contains pre-computed multiples of the binary-field - // element used in GHASH. - productTable [256]byte - // nonceSize contains the expected size of the nonce, in bytes. - nonceSize int -} - -func (g *gcmAsm) NonceSize() int { - return g.nonceSize -} - -func (*gcmAsm) Overhead() int { - return gcmTagSize -} - -// Seal encrypts and authenticates plaintext. See the AEAD interface for -// details. -func (g *gcmAsm) Seal(dst, nonce, plaintext, data []byte) []byte { - if len(nonce) != g.nonceSize { - panic("cipher: incorrect nonce length given to GCM") - } - - var counter, tagMask [gcmBlockSize]byte - - if len(nonce) == gcmStandardNonceSize { - // Init counter to nonce||1 - copy(counter[:], nonce) - counter[gcmBlockSize-1] = 1 - } else { - // Otherwise counter = GHASH(nonce) - gcmAesData(&g.productTable, nonce, &counter) - gcmAesFinish(&g.productTable, &tagMask, &counter, uint64(len(nonce)), uint64(0)) - } - - aesEncBlock(&tagMask, &counter, g.ks) - - var tagOut [16]byte - gcmAesData(&g.productTable, data, &tagOut) - - ret, out := sliceForAppend(dst, len(plaintext)+gcmTagSize) - if len(plaintext) > 0 { - gcmAesEnc(&g.productTable, out, plaintext, &counter, &tagOut, g.ks) - } - gcmAesFinish(&g.productTable, &tagMask, &tagOut, uint64(len(plaintext)), uint64(len(data))) - copy(out[len(plaintext):], tagOut[:gcmTagSize]) - - return ret -} - -// Open authenticates and decrypts ciphertext. See the AEAD interface -// for details. -func (g *gcmAsm) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { - if len(nonce) != g.nonceSize { - panic("cipher: incorrect nonce length given to GCM") - } - - if len(ciphertext) < gcmTagSize { - return nil, errOpen - } - tag := ciphertext[len(ciphertext)-gcmTagSize:] - ciphertext = ciphertext[:len(ciphertext)-gcmTagSize] - - // See GCM spec, section 7.1. - var counter, tagMask [gcmBlockSize]byte - - if len(nonce) == gcmStandardNonceSize { - // Init counter to nonce||1 - copy(counter[:], nonce) - counter[gcmBlockSize-1] = 1 - } else { - // Otherwise counter = GHASH(nonce) - gcmAesData(&g.productTable, nonce, &counter) - gcmAesFinish(&g.productTable, &tagMask, &counter, uint64(len(nonce)), uint64(0)) - } - - aesEncBlock(&tagMask, &counter, g.ks) - - var expectedTag [16]byte - gcmAesData(&g.productTable, data, &expectedTag) - - ret, out := sliceForAppend(dst, len(ciphertext)) - if len(ciphertext) > 0 { - gcmAesDec(&g.productTable, out, ciphertext, &counter, &expectedTag, g.ks) - } - gcmAesFinish(&g.productTable, &tagMask, &expectedTag, uint64(len(ciphertext)), uint64(len(data))) - - if subtle.ConstantTimeCompare(expectedTag[:12], tag) != 1 { - for i := range out { - out[i] = 0 - } - return nil, errOpen - } - - return ret, nil -} diff --git a/vendor/github.com/lucas-clemente/aes12/asm_amd64.s b/vendor/github.com/lucas-clemente/aes12/asm_amd64.s deleted file mode 100644 index b2579987d8d..00000000000 --- a/vendor/github.com/lucas-clemente/aes12/asm_amd64.s +++ /dev/null @@ -1,285 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -#include "textflag.h" - -// func hasAsm() bool -// returns whether AES-NI is supported -TEXT ·hasAsm(SB),NOSPLIT,$0 - XORQ AX, AX - INCL AX - CPUID - SHRQ $25, CX - ANDQ $1, CX - MOVB CX, ret+0(FP) - RET - -// func encryptBlockAsm(nr int, xk *uint32, dst, src *byte) -TEXT ·encryptBlockAsm(SB),NOSPLIT,$0 - MOVQ nr+0(FP), CX - MOVQ xk+8(FP), AX - MOVQ dst+16(FP), DX - MOVQ src+24(FP), BX - MOVUPS 0(AX), X1 - MOVUPS 0(BX), X0 - ADDQ $16, AX - PXOR X1, X0 - SUBQ $12, CX - JE Lenc196 - JB Lenc128 -Lenc256: - MOVUPS 0(AX), X1 - AESENC X1, X0 - MOVUPS 16(AX), X1 - AESENC X1, X0 - ADDQ $32, AX -Lenc196: - MOVUPS 0(AX), X1 - AESENC X1, X0 - MOVUPS 16(AX), X1 - AESENC X1, X0 - ADDQ $32, AX -Lenc128: - MOVUPS 0(AX), X1 - AESENC X1, X0 - MOVUPS 16(AX), X1 - AESENC X1, X0 - MOVUPS 32(AX), X1 - AESENC X1, X0 - MOVUPS 48(AX), X1 - AESENC X1, X0 - MOVUPS 64(AX), X1 - AESENC X1, X0 - MOVUPS 80(AX), X1 - AESENC X1, X0 - MOVUPS 96(AX), X1 - AESENC X1, X0 - MOVUPS 112(AX), X1 - AESENC X1, X0 - MOVUPS 128(AX), X1 - AESENC X1, X0 - MOVUPS 144(AX), X1 - AESENCLAST X1, X0 - MOVUPS X0, 0(DX) - RET - -// func decryptBlockAsm(nr int, xk *uint32, dst, src *byte) -TEXT ·decryptBlockAsm(SB),NOSPLIT,$0 - MOVQ nr+0(FP), CX - MOVQ xk+8(FP), AX - MOVQ dst+16(FP), DX - MOVQ src+24(FP), BX - MOVUPS 0(AX), X1 - MOVUPS 0(BX), X0 - ADDQ $16, AX - PXOR X1, X0 - SUBQ $12, CX - JE Ldec196 - JB Ldec128 -Ldec256: - MOVUPS 0(AX), X1 - AESDEC X1, X0 - MOVUPS 16(AX), X1 - AESDEC X1, X0 - ADDQ $32, AX -Ldec196: - MOVUPS 0(AX), X1 - AESDEC X1, X0 - MOVUPS 16(AX), X1 - AESDEC X1, X0 - ADDQ $32, AX -Ldec128: - MOVUPS 0(AX), X1 - AESDEC X1, X0 - MOVUPS 16(AX), X1 - AESDEC X1, X0 - MOVUPS 32(AX), X1 - AESDEC X1, X0 - MOVUPS 48(AX), X1 - AESDEC X1, X0 - MOVUPS 64(AX), X1 - AESDEC X1, X0 - MOVUPS 80(AX), X1 - AESDEC X1, X0 - MOVUPS 96(AX), X1 - AESDEC X1, X0 - MOVUPS 112(AX), X1 - AESDEC X1, X0 - MOVUPS 128(AX), X1 - AESDEC X1, X0 - MOVUPS 144(AX), X1 - AESDECLAST X1, X0 - MOVUPS X0, 0(DX) - RET - -// func expandKeyAsm(nr int, key *byte, enc, dec *uint32) { -// Note that round keys are stored in uint128 format, not uint32 -TEXT ·expandKeyAsm(SB),NOSPLIT,$0 - MOVQ nr+0(FP), CX - MOVQ key+8(FP), AX - MOVQ enc+16(FP), BX - MOVQ dec+24(FP), DX - MOVUPS (AX), X0 - // enc - MOVUPS X0, (BX) - ADDQ $16, BX - PXOR X4, X4 // _expand_key_* expect X4 to be zero - CMPL CX, $12 - JE Lexp_enc196 - JB Lexp_enc128 -Lexp_enc256: - MOVUPS 16(AX), X2 - MOVUPS X2, (BX) - ADDQ $16, BX - AESKEYGENASSIST $0x01, X2, X1 - CALL _expand_key_256a<>(SB) - AESKEYGENASSIST $0x01, X0, X1 - CALL _expand_key_256b<>(SB) - AESKEYGENASSIST $0x02, X2, X1 - CALL _expand_key_256a<>(SB) - AESKEYGENASSIST $0x02, X0, X1 - CALL _expand_key_256b<>(SB) - AESKEYGENASSIST $0x04, X2, X1 - CALL _expand_key_256a<>(SB) - AESKEYGENASSIST $0x04, X0, X1 - CALL _expand_key_256b<>(SB) - AESKEYGENASSIST $0x08, X2, X1 - CALL _expand_key_256a<>(SB) - AESKEYGENASSIST $0x08, X0, X1 - CALL _expand_key_256b<>(SB) - AESKEYGENASSIST $0x10, X2, X1 - CALL _expand_key_256a<>(SB) - AESKEYGENASSIST $0x10, X0, X1 - CALL _expand_key_256b<>(SB) - AESKEYGENASSIST $0x20, X2, X1 - CALL _expand_key_256a<>(SB) - AESKEYGENASSIST $0x20, X0, X1 - CALL _expand_key_256b<>(SB) - AESKEYGENASSIST $0x40, X2, X1 - CALL _expand_key_256a<>(SB) - JMP Lexp_dec -Lexp_enc196: - MOVQ 16(AX), X2 - AESKEYGENASSIST $0x01, X2, X1 - CALL _expand_key_192a<>(SB) - AESKEYGENASSIST $0x02, X2, X1 - CALL _expand_key_192b<>(SB) - AESKEYGENASSIST $0x04, X2, X1 - CALL _expand_key_192a<>(SB) - AESKEYGENASSIST $0x08, X2, X1 - CALL _expand_key_192b<>(SB) - AESKEYGENASSIST $0x10, X2, X1 - CALL _expand_key_192a<>(SB) - AESKEYGENASSIST $0x20, X2, X1 - CALL _expand_key_192b<>(SB) - AESKEYGENASSIST $0x40, X2, X1 - CALL _expand_key_192a<>(SB) - AESKEYGENASSIST $0x80, X2, X1 - CALL _expand_key_192b<>(SB) - JMP Lexp_dec -Lexp_enc128: - AESKEYGENASSIST $0x01, X0, X1 - CALL _expand_key_128<>(SB) - AESKEYGENASSIST $0x02, X0, X1 - CALL _expand_key_128<>(SB) - AESKEYGENASSIST $0x04, X0, X1 - CALL _expand_key_128<>(SB) - AESKEYGENASSIST $0x08, X0, X1 - CALL _expand_key_128<>(SB) - AESKEYGENASSIST $0x10, X0, X1 - CALL _expand_key_128<>(SB) - AESKEYGENASSIST $0x20, X0, X1 - CALL _expand_key_128<>(SB) - AESKEYGENASSIST $0x40, X0, X1 - CALL _expand_key_128<>(SB) - AESKEYGENASSIST $0x80, X0, X1 - CALL _expand_key_128<>(SB) - AESKEYGENASSIST $0x1b, X0, X1 - CALL _expand_key_128<>(SB) - AESKEYGENASSIST $0x36, X0, X1 - CALL _expand_key_128<>(SB) -Lexp_dec: - // dec - SUBQ $16, BX - MOVUPS (BX), X1 - MOVUPS X1, (DX) - DECQ CX -Lexp_dec_loop: - MOVUPS -16(BX), X1 - AESIMC X1, X0 - MOVUPS X0, 16(DX) - SUBQ $16, BX - ADDQ $16, DX - DECQ CX - JNZ Lexp_dec_loop - MOVUPS -16(BX), X0 - MOVUPS X0, 16(DX) - RET - -TEXT _expand_key_128<>(SB),NOSPLIT,$0 - PSHUFD $0xff, X1, X1 - SHUFPS $0x10, X0, X4 - PXOR X4, X0 - SHUFPS $0x8c, X0, X4 - PXOR X4, X0 - PXOR X1, X0 - MOVUPS X0, (BX) - ADDQ $16, BX - RET - -TEXT _expand_key_192a<>(SB),NOSPLIT,$0 - PSHUFD $0x55, X1, X1 - SHUFPS $0x10, X0, X4 - PXOR X4, X0 - SHUFPS $0x8c, X0, X4 - PXOR X4, X0 - PXOR X1, X0 - - MOVAPS X2, X5 - MOVAPS X2, X6 - PSLLDQ $0x4, X5 - PSHUFD $0xff, X0, X3 - PXOR X3, X2 - PXOR X5, X2 - - MOVAPS X0, X1 - SHUFPS $0x44, X0, X6 - MOVUPS X6, (BX) - SHUFPS $0x4e, X2, X1 - MOVUPS X1, 16(BX) - ADDQ $32, BX - RET - -TEXT _expand_key_192b<>(SB),NOSPLIT,$0 - PSHUFD $0x55, X1, X1 - SHUFPS $0x10, X0, X4 - PXOR X4, X0 - SHUFPS $0x8c, X0, X4 - PXOR X4, X0 - PXOR X1, X0 - - MOVAPS X2, X5 - PSLLDQ $0x4, X5 - PSHUFD $0xff, X0, X3 - PXOR X3, X2 - PXOR X5, X2 - - MOVUPS X0, (BX) - ADDQ $16, BX - RET - -TEXT _expand_key_256a<>(SB),NOSPLIT,$0 - JMP _expand_key_128<>(SB) - -TEXT _expand_key_256b<>(SB),NOSPLIT,$0 - PSHUFD $0xaa, X1, X1 - SHUFPS $0x10, X2, X4 - PXOR X4, X2 - SHUFPS $0x8c, X2, X4 - PXOR X4, X2 - PXOR X1, X2 - - MOVUPS X2, (BX) - ADDQ $16, BX - RET diff --git a/vendor/github.com/lucas-clemente/aes12/block.go b/vendor/github.com/lucas-clemente/aes12/block.go deleted file mode 100644 index 1f29ddaa675..00000000000 --- a/vendor/github.com/lucas-clemente/aes12/block.go +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This Go implementation is derived in part from the reference -// ANSI C implementation, which carries the following notice: -// -// rijndael-alg-fst.c -// -// @version 3.0 (December 2000) -// -// Optimised ANSI C code for the Rijndael cipher (now AES) -// -// @author Vincent Rijmen -// @author Antoon Bosselaers -// @author Paulo Barreto -// -// This code is hereby placed in the public domain. -// -// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS -// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR -// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// See FIPS 197 for specification, and see Daemen and Rijmen's Rijndael submission -// for implementation details. -// http://www.csrc.nist.gov/publications/fips/fips197/fips-197.pdf -// http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf - -package aes12 - -// Encrypt one block from src into dst, using the expanded key xk. -func encryptBlockGo(xk []uint32, dst, src []byte) { - var s0, s1, s2, s3, t0, t1, t2, t3 uint32 - - s0 = uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3]) - s1 = uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7]) - s2 = uint32(src[8])<<24 | uint32(src[9])<<16 | uint32(src[10])<<8 | uint32(src[11]) - s3 = uint32(src[12])<<24 | uint32(src[13])<<16 | uint32(src[14])<<8 | uint32(src[15]) - - // First round just XORs input with key. - s0 ^= xk[0] - s1 ^= xk[1] - s2 ^= xk[2] - s3 ^= xk[3] - - // Middle rounds shuffle using tables. - // Number of rounds is set by length of expanded key. - nr := len(xk)/4 - 2 // - 2: one above, one more below - k := 4 - for r := 0; r < nr; r++ { - t0 = xk[k+0] ^ te0[uint8(s0>>24)] ^ te1[uint8(s1>>16)] ^ te2[uint8(s2>>8)] ^ te3[uint8(s3)] - t1 = xk[k+1] ^ te0[uint8(s1>>24)] ^ te1[uint8(s2>>16)] ^ te2[uint8(s3>>8)] ^ te3[uint8(s0)] - t2 = xk[k+2] ^ te0[uint8(s2>>24)] ^ te1[uint8(s3>>16)] ^ te2[uint8(s0>>8)] ^ te3[uint8(s1)] - t3 = xk[k+3] ^ te0[uint8(s3>>24)] ^ te1[uint8(s0>>16)] ^ te2[uint8(s1>>8)] ^ te3[uint8(s2)] - k += 4 - s0, s1, s2, s3 = t0, t1, t2, t3 - } - - // Last round uses s-box directly and XORs to produce output. - s0 = uint32(sbox0[t0>>24])<<24 | uint32(sbox0[t1>>16&0xff])<<16 | uint32(sbox0[t2>>8&0xff])<<8 | uint32(sbox0[t3&0xff]) - s1 = uint32(sbox0[t1>>24])<<24 | uint32(sbox0[t2>>16&0xff])<<16 | uint32(sbox0[t3>>8&0xff])<<8 | uint32(sbox0[t0&0xff]) - s2 = uint32(sbox0[t2>>24])<<24 | uint32(sbox0[t3>>16&0xff])<<16 | uint32(sbox0[t0>>8&0xff])<<8 | uint32(sbox0[t1&0xff]) - s3 = uint32(sbox0[t3>>24])<<24 | uint32(sbox0[t0>>16&0xff])<<16 | uint32(sbox0[t1>>8&0xff])<<8 | uint32(sbox0[t2&0xff]) - - s0 ^= xk[k+0] - s1 ^= xk[k+1] - s2 ^= xk[k+2] - s3 ^= xk[k+3] - - dst[0], dst[1], dst[2], dst[3] = byte(s0>>24), byte(s0>>16), byte(s0>>8), byte(s0) - dst[4], dst[5], dst[6], dst[7] = byte(s1>>24), byte(s1>>16), byte(s1>>8), byte(s1) - dst[8], dst[9], dst[10], dst[11] = byte(s2>>24), byte(s2>>16), byte(s2>>8), byte(s2) - dst[12], dst[13], dst[14], dst[15] = byte(s3>>24), byte(s3>>16), byte(s3>>8), byte(s3) -} - -// Decrypt one block from src into dst, using the expanded key xk. -func decryptBlockGo(xk []uint32, dst, src []byte) { - var s0, s1, s2, s3, t0, t1, t2, t3 uint32 - - s0 = uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3]) - s1 = uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7]) - s2 = uint32(src[8])<<24 | uint32(src[9])<<16 | uint32(src[10])<<8 | uint32(src[11]) - s3 = uint32(src[12])<<24 | uint32(src[13])<<16 | uint32(src[14])<<8 | uint32(src[15]) - - // First round just XORs input with key. - s0 ^= xk[0] - s1 ^= xk[1] - s2 ^= xk[2] - s3 ^= xk[3] - - // Middle rounds shuffle using tables. - // Number of rounds is set by length of expanded key. - nr := len(xk)/4 - 2 // - 2: one above, one more below - k := 4 - for r := 0; r < nr; r++ { - t0 = xk[k+0] ^ td0[uint8(s0>>24)] ^ td1[uint8(s3>>16)] ^ td2[uint8(s2>>8)] ^ td3[uint8(s1)] - t1 = xk[k+1] ^ td0[uint8(s1>>24)] ^ td1[uint8(s0>>16)] ^ td2[uint8(s3>>8)] ^ td3[uint8(s2)] - t2 = xk[k+2] ^ td0[uint8(s2>>24)] ^ td1[uint8(s1>>16)] ^ td2[uint8(s0>>8)] ^ td3[uint8(s3)] - t3 = xk[k+3] ^ td0[uint8(s3>>24)] ^ td1[uint8(s2>>16)] ^ td2[uint8(s1>>8)] ^ td3[uint8(s0)] - k += 4 - s0, s1, s2, s3 = t0, t1, t2, t3 - } - - // Last round uses s-box directly and XORs to produce output. - s0 = uint32(sbox1[t0>>24])<<24 | uint32(sbox1[t3>>16&0xff])<<16 | uint32(sbox1[t2>>8&0xff])<<8 | uint32(sbox1[t1&0xff]) - s1 = uint32(sbox1[t1>>24])<<24 | uint32(sbox1[t0>>16&0xff])<<16 | uint32(sbox1[t3>>8&0xff])<<8 | uint32(sbox1[t2&0xff]) - s2 = uint32(sbox1[t2>>24])<<24 | uint32(sbox1[t1>>16&0xff])<<16 | uint32(sbox1[t0>>8&0xff])<<8 | uint32(sbox1[t3&0xff]) - s3 = uint32(sbox1[t3>>24])<<24 | uint32(sbox1[t2>>16&0xff])<<16 | uint32(sbox1[t1>>8&0xff])<<8 | uint32(sbox1[t0&0xff]) - - s0 ^= xk[k+0] - s1 ^= xk[k+1] - s2 ^= xk[k+2] - s3 ^= xk[k+3] - - dst[0], dst[1], dst[2], dst[3] = byte(s0>>24), byte(s0>>16), byte(s0>>8), byte(s0) - dst[4], dst[5], dst[6], dst[7] = byte(s1>>24), byte(s1>>16), byte(s1>>8), byte(s1) - dst[8], dst[9], dst[10], dst[11] = byte(s2>>24), byte(s2>>16), byte(s2>>8), byte(s2) - dst[12], dst[13], dst[14], dst[15] = byte(s3>>24), byte(s3>>16), byte(s3>>8), byte(s3) -} - -// Apply sbox0 to each byte in w. -func subw(w uint32) uint32 { - return uint32(sbox0[w>>24])<<24 | - uint32(sbox0[w>>16&0xff])<<16 | - uint32(sbox0[w>>8&0xff])<<8 | - uint32(sbox0[w&0xff]) -} - -// Rotate -func rotw(w uint32) uint32 { return w<<8 | w>>24 } - -// Key expansion algorithm. See FIPS-197, Figure 11. -// Their rcon[i] is our powx[i-1] << 24. -func expandKeyGo(key []byte, enc, dec []uint32) { - // Encryption key setup. - var i int - nk := len(key) / 4 - for i = 0; i < nk; i++ { - enc[i] = uint32(key[4*i])<<24 | uint32(key[4*i+1])<<16 | uint32(key[4*i+2])<<8 | uint32(key[4*i+3]) - } - for ; i < len(enc); i++ { - t := enc[i-1] - if i%nk == 0 { - t = subw(rotw(t)) ^ (uint32(powx[i/nk-1]) << 24) - } else if nk > 6 && i%nk == 4 { - t = subw(t) - } - enc[i] = enc[i-nk] ^ t - } - - // Derive decryption key from encryption key. - // Reverse the 4-word round key sets from enc to produce dec. - // All sets but the first and last get the MixColumn transform applied. - if dec == nil { - return - } - n := len(enc) - for i := 0; i < n; i += 4 { - ei := n - i - 4 - for j := 0; j < 4; j++ { - x := enc[ei+j] - if i > 0 && i+4 < n { - x = td0[sbox0[x>>24]] ^ td1[sbox0[x>>16&0xff]] ^ td2[sbox0[x>>8&0xff]] ^ td3[sbox0[x&0xff]] - } - dec[i+j] = x - } - } -} diff --git a/vendor/github.com/lucas-clemente/aes12/cipher.go b/vendor/github.com/lucas-clemente/aes12/cipher.go deleted file mode 100644 index a9b8e547fc2..00000000000 --- a/vendor/github.com/lucas-clemente/aes12/cipher.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package aes12 - -import "strconv" - -// The AES block size in bytes. -const BlockSize = 16 - -// A cipher is an instance of AES encryption using a particular key. -type aesCipher struct { - enc []uint32 - dec []uint32 -} - -type KeySizeError int - -func (k KeySizeError) Error() string { - return "crypto/aes: invalid key size " + strconv.Itoa(int(k)) -} - -// NewCipher creates and returns a new Block. -// The key argument should be the AES key, -// either 16, 24, or 32 bytes to select -// AES-128, AES-192, or AES-256. -func NewCipher(key []byte) (Block, error) { - k := len(key) - switch k { - default: - return nil, KeySizeError(k) - case 16, 24, 32: - break - } - return newCipher(key) -} - -// newCipherGeneric creates and returns a new Block -// implemented in pure Go. -func newCipherGeneric(key []byte) (Block, error) { - n := len(key) + 28 - c := aesCipher{make([]uint32, n), make([]uint32, n)} - expandKeyGo(key, c.enc, c.dec) - return &c, nil -} - -func (c *aesCipher) BlockSize() int { return BlockSize } - -func (c *aesCipher) Encrypt(dst, src []byte) { - if len(src) < BlockSize { - panic("crypto/aes: input not full block") - } - if len(dst) < BlockSize { - panic("crypto/aes: output not full block") - } - encryptBlockGo(c.enc, dst, src) -} - -func (c *aesCipher) Decrypt(dst, src []byte) { - if len(src) < BlockSize { - panic("crypto/aes: input not full block") - } - if len(dst) < BlockSize { - panic("crypto/aes: output not full block") - } - decryptBlockGo(c.dec, dst, src) -} diff --git a/vendor/github.com/lucas-clemente/aes12/cipher_2.go b/vendor/github.com/lucas-clemente/aes12/cipher_2.go deleted file mode 100644 index ae2f520bdf1..00000000000 --- a/vendor/github.com/lucas-clemente/aes12/cipher_2.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// package aes12 implements standard block cipher modes that can be wrapped -// around low-level block cipher implementations. -// See http://csrc.nist.gov/groups/ST/toolkit/BCM/current_modes.html -// and NIST Special Publication 800-38A. -package aes12 - -// A Block represents an implementation of block cipher -// using a given key. It provides the capability to encrypt -// or decrypt individual blocks. The mode implementations -// extend that capability to streams of blocks. -type Block interface { - // BlockSize returns the cipher's block size. - BlockSize() int - - // Encrypt encrypts the first block in src into dst. - // Dst and src may point at the same memory. - Encrypt(dst, src []byte) - - // Decrypt decrypts the first block in src into dst. - // Dst and src may point at the same memory. - Decrypt(dst, src []byte) -} - -// A Stream represents a stream cipher. -type Stream interface { - // XORKeyStream XORs each byte in the given slice with a byte from the - // cipher's key stream. Dst and src may point to the same memory. - // If len(dst) < len(src), XORKeyStream should panic. It is acceptable - // to pass a dst bigger than src, and in that case, XORKeyStream will - // only update dst[:len(src)] and will not touch the rest of dst. - XORKeyStream(dst, src []byte) -} - -// A BlockMode represents a block cipher running in a block-based mode (CBC, -// ECB etc). -type BlockMode interface { - // BlockSize returns the mode's block size. - BlockSize() int - - // CryptBlocks encrypts or decrypts a number of blocks. The length of - // src must be a multiple of the block size. Dst and src may point to - // the same memory. - CryptBlocks(dst, src []byte) -} - -// Utility routines - -func dup(p []byte) []byte { - q := make([]byte, len(p)) - copy(q, p) - return q -} diff --git a/vendor/github.com/lucas-clemente/aes12/cipher_amd64.go b/vendor/github.com/lucas-clemente/aes12/cipher_amd64.go deleted file mode 100644 index cd0544f386d..00000000000 --- a/vendor/github.com/lucas-clemente/aes12/cipher_amd64.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package aes12 - -// defined in asm_amd64.s -func hasAsm() bool -func encryptBlockAsm(nr int, xk *uint32, dst, src *byte) -func decryptBlockAsm(nr int, xk *uint32, dst, src *byte) -func expandKeyAsm(nr int, key *byte, enc *uint32, dec *uint32) - -type aesCipherAsm struct { - aesCipher -} - -var useAsm = hasAsm() - -func newCipher(key []byte) (Block, error) { - if !useAsm { - return newCipherGeneric(key) - } - n := len(key) + 28 - c := aesCipherAsm{aesCipher{make([]uint32, n), make([]uint32, n)}} - rounds := 10 - switch len(key) { - case 128 / 8: - rounds = 10 - case 192 / 8: - rounds = 12 - case 256 / 8: - rounds = 14 - } - expandKeyAsm(rounds, &key[0], &c.enc[0], &c.dec[0]) - if hasGCMAsm() { - return &aesCipherGCM{c}, nil - } - - return &c, nil -} - -func (c *aesCipherAsm) BlockSize() int { return BlockSize } - -func (c *aesCipherAsm) Encrypt(dst, src []byte) { - if len(src) < BlockSize { - panic("crypto/aes: input not full block") - } - if len(dst) < BlockSize { - panic("crypto/aes: output not full block") - } - encryptBlockAsm(len(c.enc)/4-1, &c.enc[0], &dst[0], &src[0]) -} - -func (c *aesCipherAsm) Decrypt(dst, src []byte) { - if len(src) < BlockSize { - panic("crypto/aes: input not full block") - } - if len(dst) < BlockSize { - panic("crypto/aes: output not full block") - } - decryptBlockAsm(len(c.dec)/4-1, &c.dec[0], &dst[0], &src[0]) -} - -// expandKey is used by BenchmarkExpand to ensure that the asm implementation -// of key expansion is used for the benchmark when it is available. -func expandKey(key []byte, enc, dec []uint32) { - if useAsm { - rounds := 10 // rounds needed for AES128 - switch len(key) { - case 192 / 8: - rounds = 12 - case 256 / 8: - rounds = 14 - } - expandKeyAsm(rounds, &key[0], &enc[0], &dec[0]) - } else { - expandKeyGo(key, enc, dec) - } -} diff --git a/vendor/github.com/lucas-clemente/aes12/cipher_generic.go b/vendor/github.com/lucas-clemente/aes12/cipher_generic.go deleted file mode 100644 index a9a6abd55a4..00000000000 --- a/vendor/github.com/lucas-clemente/aes12/cipher_generic.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !amd64,!s390x - -package aes12 - -// newCipher calls the newCipherGeneric function -// directly. Platforms with hardware accelerated -// implementations of AES should implement their -// own version of newCipher (which may then call -// newCipherGeneric if needed). -func newCipher(key []byte) (Block, error) { - return newCipherGeneric(key) -} - -// expandKey is used by BenchmarkExpand and should -// call an assembly implementation if one is available. -func expandKey(key []byte, enc, dec []uint32) { - expandKeyGo(key, enc, dec) -} diff --git a/vendor/github.com/lucas-clemente/aes12/const.go b/vendor/github.com/lucas-clemente/aes12/const.go deleted file mode 100644 index 40296fa7210..00000000000 --- a/vendor/github.com/lucas-clemente/aes12/const.go +++ /dev/null @@ -1,358 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// package aes12 implements AES encryption (formerly Rijndael), as defined in -// U.S. Federal Information Processing Standards Publication 197. -package aes12 - -// This file contains AES constants - 8720 bytes of initialized data. - -// http://www.csrc.nist.gov/publications/fips/fips197/fips-197.pdf - -// AES is based on the mathematical behavior of binary polynomials -// (polynomials over GF(2)) modulo the irreducible polynomial x⁸ + x⁴ + x³ + x + 1. -// Addition of these binary polynomials corresponds to binary xor. -// Reducing mod poly corresponds to binary xor with poly every -// time a 0x100 bit appears. -const poly = 1<<8 | 1<<4 | 1<<3 | 1<<1 | 1<<0 // x⁸ + x⁴ + x³ + x + 1 - -// Powers of x mod poly in GF(2). -var powx = [16]byte{ - 0x01, - 0x02, - 0x04, - 0x08, - 0x10, - 0x20, - 0x40, - 0x80, - 0x1b, - 0x36, - 0x6c, - 0xd8, - 0xab, - 0x4d, - 0x9a, - 0x2f, -} - -// FIPS-197 Figure 7. S-box substitution values in hexadecimal format. -var sbox0 = [256]byte{ - 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, - 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, - 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, - 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, - 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, - 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, - 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, - 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, - 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, - 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, - 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, - 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, - 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, - 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, - 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, - 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16, -} - -// FIPS-197 Figure 14. Inverse S-box substitution values in hexadecimal format. -var sbox1 = [256]byte{ - 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, - 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, - 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, - 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, - 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, - 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, - 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, - 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, - 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, - 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, - 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, - 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, - 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, - 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, - 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, - 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d, -} - -// Lookup tables for encryption. -// These can be recomputed by adapting the tests in aes_test.go. - -var te0 = [256]uint32{ - 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, - 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, - 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, - 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, - 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, - 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, - 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, - 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, - 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, - 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, - 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, - 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, - 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, - 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, - 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, - 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, - 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, - 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, - 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, - 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, - 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, - 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, - 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, - 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, - 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, - 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, - 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, - 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, - 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, - 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, - 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, - 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a, -} -var te1 = [256]uint32{ - 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5, - 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b, 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676, - 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d, 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0, - 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0, - 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc, - 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x08f9f1f1, 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, - 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3, 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a, - 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, - 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a, 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, - 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484, - 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded, 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b, - 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939, 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, - 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585, - 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f, 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, - 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f, 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5, - 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121, 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2, - 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, - 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d, 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373, - 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888, - 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb, - 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a, 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c, - 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979, - 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d, 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, - 0xb4d86c6c, 0xfaac5656, 0x07f3f4f4, 0x25cfeaea, 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, - 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e, 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6, - 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, - 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666, 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e, - 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e, - 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494, - 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9, 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, - 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868, - 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616, -} -var te2 = [256]uint32{ - 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5, - 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b, 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76, - 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d, 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, - 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0, - 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, - 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1, 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, - 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3, 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a, - 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, - 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a, 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, - 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384, - 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed, 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b, - 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239, 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, - 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185, - 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f, 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, - 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f, 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, - 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221, 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2, - 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec, 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, - 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d, 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673, - 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88, - 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb, - 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a, 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c, - 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279, - 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d, 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, - 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea, 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008, - 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e, 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6, - 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, - 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66, 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e, - 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e, - 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394, - 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9, 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, - 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d, 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068, - 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f, 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16, -} -var te3 = [256]uint32{ - 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, - 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56, 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, - 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa, 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, - 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45, 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b, - 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, - 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9, 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a, - 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, 0x18182830, 0x9696a137, 0x05050f0a, 0x9a9ab52f, - 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea, - 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34, 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, - 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713, - 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1, 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, - 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72, 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, - 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed, 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411, - 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe, 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, - 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05, 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1, - 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf, - 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3, 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e, - 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a, 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, - 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b, - 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28, 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad, - 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0x0a0a1e14, 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8, - 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4, 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2, - 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, - 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf, 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810, - 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197, - 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f, - 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc, 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c, - 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927, - 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322, 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, - 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9, 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, - 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a, 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0, - 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e, 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c, -} - -// Lookup tables for decryption. -// These can be recomputed by adapting the tests in aes_test.go. - -var td0 = [256]uint32{ - 0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393, - 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f, - 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, - 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844, - 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, - 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45, 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94, - 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, - 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c, - 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a, - 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051, - 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff, - 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb, - 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, - 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927, 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a, - 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e, 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, - 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8, - 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, - 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120, - 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0, - 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef, - 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, - 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5, - 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, - 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8, 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6, - 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, - 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f, - 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, - 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713, - 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, - 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86, - 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, - 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742, -} -var td1 = [256]uint32{ - 0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e, 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303, - 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c, 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3, - 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0, 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9, - 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259, 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8, - 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971, 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a, - 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f, 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b, - 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8, 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab, - 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708, 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682, - 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2, 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe, - 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb, 0x390b83ec, 0xaa4060ef, 0x065e719f, 0x51bd6e10, - 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd, 0xb591548d, 0x0571c45d, 0x6f0406d4, 0xff605015, - 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e, 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee, - 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000, 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72, - 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39, 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e, - 0xb10c0a67, 0x0f9357e7, 0xd2b4ee96, 0x9e1b9b91, 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a, - 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17, 0x0b0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9, - 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60, 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e, - 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1, 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611, - 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1, 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3, - 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964, 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390, - 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b, 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf, - 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46, 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af, - 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512, 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb, - 0x09cd2678, 0xf46e5918, 0x01ec9ab7, 0xa8834f9a, 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8, - 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c, 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266, - 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8, 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6, - 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604, 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551, - 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41, 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647, - 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c, 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1, - 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737, 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db, - 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340, 0x72161dc3, 0x0cbce225, 0x8b283c49, 0x41ff0d95, - 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1, 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857, -} -var td2 = [256]uint32{ - 0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27, 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x03934be3, - 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502, 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562, - 0x5a49deb1, 0x1b6725ba, 0x0e9845ea, 0xc0e15dfe, 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3, - 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552, 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9, - 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9, 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce, - 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253, 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908, - 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b, 0xd323ab73, 0x02e2724b, 0x8f57e31f, 0xab2a6655, - 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337, 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16, - 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69, 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6, - 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6, 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e, - 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6, 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050, - 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9, 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8, - 0x0a47a17c, 0x0fe97c42, 0x1ec9f884, 0x00000000, 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a, - 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d, 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436, - 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b, 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12, - 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b, 0x0d0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e, - 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f, 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb, - 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4, 0xdccad731, 0x85104263, 0x22401397, 0x112084c6, - 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729, 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1, - 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9, 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233, - 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0x0b3698d4, 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad, - 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e, 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3, - 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25, 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b, - 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f, 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15, - 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0, 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2, - 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7, 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791, - 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x04dfe496, 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665, - 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b, 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6, - 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13, 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47, - 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7, 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844, - 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3, 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d, - 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456, 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8, -} -var td3 = [256]uint32{ - 0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a, 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b, - 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5, 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5, - 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d, 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b, - 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95, 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e, - 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27, 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d, - 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562, 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9, - 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752, 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66, - 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3, 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced, - 0xcf1c2b8a, 0x79b492a7, 0x07f2f0f3, 0x69e2a14e, 0xdaf4cd65, 0x05bed506, 0x34621fd1, 0xa6fe8ac4, - 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4, 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd, - 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d, 0x548db591, 0xc45d0571, 0x06d46f04, 0x5015ff60, - 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767, 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79, - 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000, 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c, - 0x0efffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736, 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24, - 0x0a67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b, 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c, - 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12, 0x090d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814, - 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3, 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b, - 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8, 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084, - 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7, 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077, - 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247, 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22, - 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698, 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f, - 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254, 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582, - 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf, 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb, - 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883, 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef, - 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629, 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035, - 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533, 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17, - 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4, 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46, - 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb, 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d, - 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb, 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a, - 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73, 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678, - 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2, 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0x0d9541ff, - 0xa8017139, 0x0cb3de08, 0xb4e49cd8, 0x56c19064, 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0, -} diff --git a/vendor/github.com/lucas-clemente/aes12/gcm.go b/vendor/github.com/lucas-clemente/aes12/gcm.go deleted file mode 100644 index ed7dc918acf..00000000000 --- a/vendor/github.com/lucas-clemente/aes12/gcm.go +++ /dev/null @@ -1,401 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package aes12 - -import ( - "crypto/subtle" - "errors" -) - -// AEAD is a cipher mode providing authenticated encryption with associated -// data. For a description of the methodology, see -// https://en.wikipedia.org/wiki/Authenticated_encryption -type AEAD interface { - // NonceSize returns the size of the nonce that must be passed to Seal - // and Open. - NonceSize() int - - // Overhead returns the maximum difference between the lengths of a - // plaintext and its ciphertext. - Overhead() int - - // Seal encrypts and authenticates plaintext, authenticates the - // additional data and appends the result to dst, returning the updated - // slice. The nonce must be NonceSize() bytes long and unique for all - // time, for a given key. - // - // The plaintext and dst may alias exactly or not at all. To reuse - // plaintext's storage for the encrypted output, use plaintext[:0] as dst. - Seal(dst, nonce, plaintext, additionalData []byte) []byte - - // Open decrypts and authenticates ciphertext, authenticates the - // additional data and, if successful, appends the resulting plaintext - // to dst, returning the updated slice. The nonce must be NonceSize() - // bytes long and both it and the additional data must match the - // value passed to Seal. - // - // The ciphertext and dst may alias exactly or not at all. To reuse - // ciphertext's storage for the decrypted output, use ciphertext[:0] as dst. - // - // Even if the function fails, the contents of dst, up to its capacity, - // may be overwritten. - Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) -} - -// gcmAble is an interface implemented by ciphers that have a specific optimized -// implementation of GCM, like crypto/aes. NewGCM will check for this interface -// and return the specific AEAD if found. -type gcmAble interface { - NewGCM(int) (AEAD, error) -} - -// gcmFieldElement represents a value in GF(2¹²⁸). In order to reflect the GCM -// standard and make getUint64 suitable for marshaling these values, the bits -// are stored backwards. For example: -// the coefficient of x⁰ can be obtained by v.low >> 63. -// the coefficient of x⁶³ can be obtained by v.low & 1. -// the coefficient of x⁶⁴ can be obtained by v.high >> 63. -// the coefficient of x¹²⁷ can be obtained by v.high & 1. -type gcmFieldElement struct { - low, high uint64 -} - -// gcm represents a Galois Counter Mode with a specific key. See -// http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf -type gcm struct { - cipher Block - nonceSize int - // productTable contains the first sixteen powers of the key, H. - // However, they are in bit reversed order. See NewGCMWithNonceSize. - productTable [16]gcmFieldElement -} - -// NewGCM returns the given 128-bit, block cipher wrapped in Galois Counter Mode -// with the standard nonce length. -func NewGCM(cipher Block) (AEAD, error) { - return NewGCMWithNonceSize(cipher, gcmStandardNonceSize) -} - -// NewGCMWithNonceSize returns the given 128-bit, block cipher wrapped in Galois -// Counter Mode, which accepts nonces of the given length. -// -// Only use this function if you require compatibility with an existing -// cryptosystem that uses non-standard nonce lengths. All other users should use -// NewGCM, which is faster and more resistant to misuse. -func NewGCMWithNonceSize(cipher Block, size int) (AEAD, error) { - if cipher, ok := cipher.(gcmAble); ok { - return cipher.NewGCM(size) - } - - if cipher.BlockSize() != gcmBlockSize { - return nil, errors.New("cipher: NewGCM requires 128-bit block cipher") - } - - var key [gcmBlockSize]byte - cipher.Encrypt(key[:], key[:]) - - g := &gcm{cipher: cipher, nonceSize: size} - - // We precompute 16 multiples of |key|. However, when we do lookups - // into this table we'll be using bits from a field element and - // therefore the bits will be in the reverse order. So normally one - // would expect, say, 4*key to be in index 4 of the table but due to - // this bit ordering it will actually be in index 0010 (base 2) = 2. - x := gcmFieldElement{ - getUint64(key[:8]), - getUint64(key[8:]), - } - g.productTable[reverseBits(1)] = x - - for i := 2; i < 16; i += 2 { - g.productTable[reverseBits(i)] = gcmDouble(&g.productTable[reverseBits(i/2)]) - g.productTable[reverseBits(i+1)] = gcmAdd(&g.productTable[reverseBits(i)], &x) - } - - return g, nil -} - -const ( - gcmBlockSize = 16 - gcmTagSize = 12 - gcmStandardNonceSize = 12 -) - -func (g *gcm) NonceSize() int { - return g.nonceSize -} - -func (*gcm) Overhead() int { - return gcmTagSize -} - -func (g *gcm) Seal(dst, nonce, plaintext, data []byte) []byte { - if len(nonce) != g.nonceSize { - panic("cipher: incorrect nonce length given to GCM") - } - ret, out := sliceForAppend(dst, len(plaintext)+gcmTagSize) - - var counter, tagMask [gcmBlockSize]byte - g.deriveCounter(&counter, nonce) - - g.cipher.Encrypt(tagMask[:], counter[:]) - gcmInc32(&counter) - - g.counterCrypt(out, plaintext, &counter) - - tag := make([]byte, 16) - g.auth(tag, out[:len(plaintext)], data, &tagMask) - copy(ret[len(ret)-12:], tag) - - return ret -} - -var errOpen = errors.New("cipher: message authentication failed") - -func (g *gcm) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { - if len(nonce) != g.nonceSize { - panic("cipher: incorrect nonce length given to GCM") - } - - if len(ciphertext) < gcmTagSize { - return nil, errOpen - } - tag := ciphertext[len(ciphertext)-gcmTagSize:] - ciphertext = ciphertext[:len(ciphertext)-gcmTagSize] - - var counter, tagMask [gcmBlockSize]byte - g.deriveCounter(&counter, nonce) - - g.cipher.Encrypt(tagMask[:], counter[:]) - gcmInc32(&counter) - - var expectedTag [gcmBlockSize]byte - g.auth(expectedTag[:], ciphertext, data, &tagMask) - - ret, out := sliceForAppend(dst, len(ciphertext)) - - if subtle.ConstantTimeCompare(expectedTag[:gcmTagSize], tag) != 1 { - // The AESNI code decrypts and authenticates concurrently, and - // so overwrites dst in the event of a tag mismatch. That - // behaviour is mimicked here in order to be consistent across - // platforms. - for i := range out { - out[i] = 0 - } - return nil, errOpen - } - - g.counterCrypt(out, ciphertext, &counter) - - return ret, nil -} - -// reverseBits reverses the order of the bits of 4-bit number in i. -func reverseBits(i int) int { - i = ((i << 2) & 0xc) | ((i >> 2) & 0x3) - i = ((i << 1) & 0xa) | ((i >> 1) & 0x5) - return i -} - -// gcmAdd adds two elements of GF(2¹²⁸) and returns the sum. -func gcmAdd(x, y *gcmFieldElement) gcmFieldElement { - // Addition in a characteristic 2 field is just XOR. - return gcmFieldElement{x.low ^ y.low, x.high ^ y.high} -} - -// gcmDouble returns the result of doubling an element of GF(2¹²⁸). -func gcmDouble(x *gcmFieldElement) (double gcmFieldElement) { - msbSet := x.high&1 == 1 - - // Because of the bit-ordering, doubling is actually a right shift. - double.high = x.high >> 1 - double.high |= x.low << 63 - double.low = x.low >> 1 - - // If the most-significant bit was set before shifting then it, - // conceptually, becomes a term of x^128. This is greater than the - // irreducible polynomial so the result has to be reduced. The - // irreducible polynomial is 1+x+x^2+x^7+x^128. We can subtract that to - // eliminate the term at x^128 which also means subtracting the other - // four terms. In characteristic 2 fields, subtraction == addition == - // XOR. - if msbSet { - double.low ^= 0xe100000000000000 - } - - return -} - -var gcmReductionTable = []uint16{ - 0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0, - 0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0, -} - -// mul sets y to y*H, where H is the GCM key, fixed during NewGCMWithNonceSize. -func (g *gcm) mul(y *gcmFieldElement) { - var z gcmFieldElement - - for i := 0; i < 2; i++ { - word := y.high - if i == 1 { - word = y.low - } - - // Multiplication works by multiplying z by 16 and adding in - // one of the precomputed multiples of H. - for j := 0; j < 64; j += 4 { - msw := z.high & 0xf - z.high >>= 4 - z.high |= z.low << 60 - z.low >>= 4 - z.low ^= uint64(gcmReductionTable[msw]) << 48 - - // the values in |table| are ordered for - // little-endian bit positions. See the comment - // in NewGCMWithNonceSize. - t := &g.productTable[word&0xf] - - z.low ^= t.low - z.high ^= t.high - word >>= 4 - } - } - - *y = z -} - -// updateBlocks extends y with more polynomial terms from blocks, based on -// Horner's rule. There must be a multiple of gcmBlockSize bytes in blocks. -func (g *gcm) updateBlocks(y *gcmFieldElement, blocks []byte) { - for len(blocks) > 0 { - y.low ^= getUint64(blocks) - y.high ^= getUint64(blocks[8:]) - g.mul(y) - blocks = blocks[gcmBlockSize:] - } -} - -// update extends y with more polynomial terms from data. If data is not a -// multiple of gcmBlockSize bytes long then the remainder is zero padded. -func (g *gcm) update(y *gcmFieldElement, data []byte) { - fullBlocks := (len(data) >> 4) << 4 - g.updateBlocks(y, data[:fullBlocks]) - - if len(data) != fullBlocks { - var partialBlock [gcmBlockSize]byte - copy(partialBlock[:], data[fullBlocks:]) - g.updateBlocks(y, partialBlock[:]) - } -} - -// gcmInc32 treats the final four bytes of counterBlock as a big-endian value -// and increments it. -func gcmInc32(counterBlock *[16]byte) { - for i := gcmBlockSize - 1; i >= gcmBlockSize-4; i-- { - counterBlock[i]++ - if counterBlock[i] != 0 { - break - } - } -} - -// sliceForAppend takes a slice and a requested number of bytes. It returns a -// slice with the contents of the given slice followed by that many bytes and a -// second slice that aliases into it and contains only the extra bytes. If the -// original slice has sufficient capacity then no allocation is performed. -func sliceForAppend(in []byte, n int) (head, tail []byte) { - if total := len(in) + n; cap(in) >= total { - head = in[:total] - } else { - head = make([]byte, total) - copy(head, in) - } - tail = head[len(in):] - return -} - -// counterCrypt crypts in to out using g.cipher in counter mode. -func (g *gcm) counterCrypt(out, in []byte, counter *[gcmBlockSize]byte) { - var mask [gcmBlockSize]byte - - for len(in) >= gcmBlockSize { - g.cipher.Encrypt(mask[:], counter[:]) - gcmInc32(counter) - - xorWords(out, in, mask[:]) - out = out[gcmBlockSize:] - in = in[gcmBlockSize:] - } - - if len(in) > 0 { - g.cipher.Encrypt(mask[:], counter[:]) - gcmInc32(counter) - xorBytes(out, in, mask[:]) - } -} - -// deriveCounter computes the initial GCM counter state from the given nonce. -// See NIST SP 800-38D, section 7.1. This assumes that counter is filled with -// zeros on entry. -func (g *gcm) deriveCounter(counter *[gcmBlockSize]byte, nonce []byte) { - // GCM has two modes of operation with respect to the initial counter - // state: a "fast path" for 96-bit (12-byte) nonces, and a "slow path" - // for nonces of other lengths. For a 96-bit nonce, the nonce, along - // with a four-byte big-endian counter starting at one, is used - // directly as the starting counter. For other nonce sizes, the counter - // is computed by passing it through the GHASH function. - if len(nonce) == gcmStandardNonceSize { - copy(counter[:], nonce) - counter[gcmBlockSize-1] = 1 - } else { - var y gcmFieldElement - g.update(&y, nonce) - y.high ^= uint64(len(nonce)) * 8 - g.mul(&y) - putUint64(counter[:8], y.low) - putUint64(counter[8:], y.high) - } -} - -// auth calculates GHASH(ciphertext, additionalData), masks the result with -// tagMask and writes the result to out. -func (g *gcm) auth(out, ciphertext, additionalData []byte, tagMask *[gcmBlockSize]byte) { - var y gcmFieldElement - g.update(&y, additionalData) - g.update(&y, ciphertext) - - y.low ^= uint64(len(additionalData)) * 8 - y.high ^= uint64(len(ciphertext)) * 8 - - g.mul(&y) - - putUint64(out, y.low) - putUint64(out[8:], y.high) - - xorWords(out, out, tagMask[:]) -} - -func getUint64(data []byte) uint64 { - r := uint64(data[0])<<56 | - uint64(data[1])<<48 | - uint64(data[2])<<40 | - uint64(data[3])<<32 | - uint64(data[4])<<24 | - uint64(data[5])<<16 | - uint64(data[6])<<8 | - uint64(data[7]) - return r -} - -func putUint64(out []byte, v uint64) { - out[0] = byte(v >> 56) - out[1] = byte(v >> 48) - out[2] = byte(v >> 40) - out[3] = byte(v >> 32) - out[4] = byte(v >> 24) - out[5] = byte(v >> 16) - out[6] = byte(v >> 8) - out[7] = byte(v) -} diff --git a/vendor/github.com/lucas-clemente/aes12/gcm_amd64.s b/vendor/github.com/lucas-clemente/aes12/gcm_amd64.s deleted file mode 100644 index c25badd5583..00000000000 --- a/vendor/github.com/lucas-clemente/aes12/gcm_amd64.s +++ /dev/null @@ -1,1277 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This is an optimized implementation of AES-GCM using AES-NI and CLMUL-NI -// The implementation uses some optimization as described in: -// [1] Gueron, S., Kounavis, M.E.: Intel® Carry-Less Multiplication -// Instruction and its Usage for Computing the GCM Mode rev. 2.02 -// [2] Gueron, S., Krasnov, V.: Speeding up Counter Mode in Software and -// Hardware - -#include "textflag.h" - -#define B0 X0 -#define B1 X1 -#define B2 X2 -#define B3 X3 -#define B4 X4 -#define B5 X5 -#define B6 X6 -#define B7 X7 - -#define ACC0 X8 -#define ACC1 X9 -#define ACCM X10 - -#define T0 X11 -#define T1 X12 -#define T2 X13 -#define POLY X14 -#define BSWAP X15 - -DATA bswapMask<>+0x00(SB)/8, $0x08090a0b0c0d0e0f -DATA bswapMask<>+0x08(SB)/8, $0x0001020304050607 - -DATA gcmPoly<>+0x00(SB)/8, $0x0000000000000001 -DATA gcmPoly<>+0x08(SB)/8, $0xc200000000000000 - -DATA andMask<>+0x00(SB)/8, $0x00000000000000ff -DATA andMask<>+0x08(SB)/8, $0x0000000000000000 -DATA andMask<>+0x10(SB)/8, $0x000000000000ffff -DATA andMask<>+0x18(SB)/8, $0x0000000000000000 -DATA andMask<>+0x20(SB)/8, $0x0000000000ffffff -DATA andMask<>+0x28(SB)/8, $0x0000000000000000 -DATA andMask<>+0x30(SB)/8, $0x00000000ffffffff -DATA andMask<>+0x38(SB)/8, $0x0000000000000000 -DATA andMask<>+0x40(SB)/8, $0x000000ffffffffff -DATA andMask<>+0x48(SB)/8, $0x0000000000000000 -DATA andMask<>+0x50(SB)/8, $0x0000ffffffffffff -DATA andMask<>+0x58(SB)/8, $0x0000000000000000 -DATA andMask<>+0x60(SB)/8, $0x00ffffffffffffff -DATA andMask<>+0x68(SB)/8, $0x0000000000000000 -DATA andMask<>+0x70(SB)/8, $0xffffffffffffffff -DATA andMask<>+0x78(SB)/8, $0x0000000000000000 -DATA andMask<>+0x80(SB)/8, $0xffffffffffffffff -DATA andMask<>+0x88(SB)/8, $0x00000000000000ff -DATA andMask<>+0x90(SB)/8, $0xffffffffffffffff -DATA andMask<>+0x98(SB)/8, $0x000000000000ffff -DATA andMask<>+0xa0(SB)/8, $0xffffffffffffffff -DATA andMask<>+0xa8(SB)/8, $0x0000000000ffffff -DATA andMask<>+0xb0(SB)/8, $0xffffffffffffffff -DATA andMask<>+0xb8(SB)/8, $0x00000000ffffffff -DATA andMask<>+0xc0(SB)/8, $0xffffffffffffffff -DATA andMask<>+0xc8(SB)/8, $0x000000ffffffffff -DATA andMask<>+0xd0(SB)/8, $0xffffffffffffffff -DATA andMask<>+0xd8(SB)/8, $0x0000ffffffffffff -DATA andMask<>+0xe0(SB)/8, $0xffffffffffffffff -DATA andMask<>+0xe8(SB)/8, $0x00ffffffffffffff - -GLOBL bswapMask<>(SB), (NOPTR+RODATA), $16 -GLOBL gcmPoly<>(SB), (NOPTR+RODATA), $16 -GLOBL andMask<>(SB), (NOPTR+RODATA), $240 - -// func hasGCMAsm() bool -// returns whether AES-NI AND CLMUL-NI are supported -TEXT ·hasGCMAsm(SB),NOSPLIT,$0 - XORQ AX, AX - INCL AX - CPUID - MOVQ CX, DX - SHRQ $25, CX - SHRQ $1, DX - ANDQ DX, CX - ANDQ $1, CX - MOVB CX, ret+0(FP) - RET - -// func aesEncBlock(dst, src *[16]byte, ks []uint32) -TEXT ·aesEncBlock(SB),NOSPLIT,$0 - MOVQ dst+0(FP), DI - MOVQ src+8(FP), SI - MOVQ ks_base+16(FP), DX - MOVQ ks_len+24(FP), CX - - SHRQ $2, CX - DECQ CX - - MOVOU (SI), X0 - MOVOU (16*0)(DX), X1 - PXOR X1, X0 - MOVOU (16*1)(DX), X1 - AESENC X1, X0 - MOVOU (16*2)(DX), X1 - AESENC X1, X0 - MOVOU (16*3)(DX), X1 - AESENC X1, X0 - MOVOU (16*4)(DX), X1 - AESENC X1, X0 - MOVOU (16*5)(DX), X1 - AESENC X1, X0 - MOVOU (16*6)(DX), X1 - AESENC X1, X0 - MOVOU (16*7)(DX), X1 - AESENC X1, X0 - MOVOU (16*8)(DX), X1 - AESENC X1, X0 - MOVOU (16*9)(DX), X1 - AESENC X1, X0 - MOVOU (16*10)(DX), X1 - CMPQ CX, $12 - JB encLast - AESENC X1, X0 - MOVOU (16*11)(DX), X1 - AESENC X1, X0 - MOVOU (16*12)(DX), X1 - JE encLast - AESENC X1, X0 - MOVOU (16*13)(DX), X1 - AESENC X1, X0 - MOVOU (16*14)(DX), X1 - -encLast: - AESENCLAST X1, X0 - MOVOU X0, (DI) - - RET - -// func gcmAesFinish(productTable *[256]byte, tagMask, T *[16]byte, pLen, dLen uint64) -TEXT ·gcmAesFinish(SB),NOSPLIT,$0 -#define pTbl DI -#define tMsk SI -#define tPtr DX -#define plen AX -#define dlen CX - - MOVQ productTable+0(FP), pTbl - MOVQ tagMask+8(FP), tMsk - MOVQ T+16(FP), tPtr - MOVQ pLen+24(FP), plen - MOVQ dLen+32(FP), dlen - - MOVOU (tPtr), ACC0 - MOVOU (tMsk), T2 - - MOVOU bswapMask<>(SB), BSWAP - MOVOU gcmPoly<>(SB), POLY - - SHLQ $3, plen - SHLQ $3, dlen - - MOVQ plen, B0 - PINSRQ $1, dlen, B0 - - PXOR ACC0, B0 - - MOVOU (16*14)(pTbl), ACC0 - MOVOU (16*15)(pTbl), ACCM - MOVOU ACC0, ACC1 - - PCLMULQDQ $0x00, B0, ACC0 - PCLMULQDQ $0x11, B0, ACC1 - PSHUFD $78, B0, T0 - PXOR B0, T0 - PCLMULQDQ $0x00, T0, ACCM - - PXOR ACC0, ACCM - PXOR ACC1, ACCM - MOVOU ACCM, T0 - PSRLDQ $8, ACCM - PSLLDQ $8, T0 - PXOR ACCM, ACC1 - PXOR T0, ACC0 - - MOVOU POLY, T0 - PCLMULQDQ $0x01, ACC0, T0 - PSHUFD $78, ACC0, ACC0 - PXOR T0, ACC0 - - MOVOU POLY, T0 - PCLMULQDQ $0x01, ACC0, T0 - PSHUFD $78, ACC0, ACC0 - PXOR T0, ACC0 - - PXOR ACC1, ACC0 - - PSHUFB BSWAP, ACC0 - PXOR T2, ACC0 - MOVOU ACC0, (tPtr) - - RET -#undef pTbl -#undef tMsk -#undef tPtr -#undef plen -#undef dlen - -// func gcmAesInit(productTable *[256]byte, ks []uint32) -TEXT ·gcmAesInit(SB),NOSPLIT,$0 -#define dst DI -#define KS SI -#define NR DX - - MOVQ productTable+0(FP), dst - MOVQ ks_base+8(FP), KS - MOVQ ks_len+16(FP), NR - - SHRQ $2, NR - DECQ NR - - MOVOU bswapMask<>(SB), BSWAP - MOVOU gcmPoly<>(SB), POLY - - // Encrypt block 0, with the AES key to generate the hash key H - MOVOU (16*0)(KS), B0 - MOVOU (16*1)(KS), T0 - AESENC T0, B0 - MOVOU (16*2)(KS), T0 - AESENC T0, B0 - MOVOU (16*3)(KS), T0 - AESENC T0, B0 - MOVOU (16*4)(KS), T0 - AESENC T0, B0 - MOVOU (16*5)(KS), T0 - AESENC T0, B0 - MOVOU (16*6)(KS), T0 - AESENC T0, B0 - MOVOU (16*7)(KS), T0 - AESENC T0, B0 - MOVOU (16*8)(KS), T0 - AESENC T0, B0 - MOVOU (16*9)(KS), T0 - AESENC T0, B0 - MOVOU (16*10)(KS), T0 - CMPQ NR, $12 - JB initEncLast - AESENC T0, B0 - MOVOU (16*11)(KS), T0 - AESENC T0, B0 - MOVOU (16*12)(KS), T0 - JE initEncLast - AESENC T0, B0 - MOVOU (16*13)(KS), T0 - AESENC T0, B0 - MOVOU (16*14)(KS), T0 -initEncLast: - AESENCLAST T0, B0 - - PSHUFB BSWAP, B0 - // H * 2 - PSHUFD $0xff, B0, T0 - MOVOU B0, T1 - PSRAL $31, T0 - PAND POLY, T0 - PSRLL $31, T1 - PSLLDQ $4, T1 - PSLLL $1, B0 - PXOR T0, B0 - PXOR T1, B0 - // Karatsuba pre-computations - MOVOU B0, (16*14)(dst) - PSHUFD $78, B0, B1 - PXOR B0, B1 - MOVOU B1, (16*15)(dst) - - MOVOU B0, B2 - MOVOU B1, B3 - // Now prepare powers of H and pre-computations for them - MOVQ $7, AX - -initLoop: - MOVOU B2, T0 - MOVOU B2, T1 - MOVOU B3, T2 - PCLMULQDQ $0x00, B0, T0 - PCLMULQDQ $0x11, B0, T1 - PCLMULQDQ $0x00, B1, T2 - - PXOR T0, T2 - PXOR T1, T2 - MOVOU T2, B4 - PSLLDQ $8, B4 - PSRLDQ $8, T2 - PXOR B4, T0 - PXOR T2, T1 - - MOVOU POLY, B2 - PCLMULQDQ $0x01, T0, B2 - PSHUFD $78, T0, T0 - PXOR B2, T0 - MOVOU POLY, B2 - PCLMULQDQ $0x01, T0, B2 - PSHUFD $78, T0, T0 - PXOR T0, B2 - PXOR T1, B2 - - MOVOU B2, (16*12)(dst) - PSHUFD $78, B2, B3 - PXOR B2, B3 - MOVOU B3, (16*13)(dst) - - DECQ AX - LEAQ (-16*2)(dst), dst - JNE initLoop - - RET -#undef NR -#undef KS -#undef dst - -// func gcmAesData(productTable *[256]byte, data []byte, T *[16]byte) -TEXT ·gcmAesData(SB),NOSPLIT,$0 -#define pTbl DI -#define aut SI -#define tPtr CX -#define autLen DX - - MOVQ productTable+0(FP), pTbl - MOVQ data_base+8(FP), aut - MOVQ data_len+16(FP), autLen - MOVQ T+32(FP), tPtr - - PXOR ACC0, ACC0 - MOVOU bswapMask<>(SB), BSWAP - MOVOU gcmPoly<>(SB), POLY - - MOVOU (16*14)(pTbl), T1 - MOVOU (16*15)(pTbl), T2 - - TESTQ autLen, autLen - JEQ dataBail - - CMPQ autLen, $13 // optimize the TLS case - JNE dataSinglesLoop - - PXOR B0, B0 - MOVQ (aut), B0 - PINSRD $2, 8(aut), B0 - PINSRB $12, 12(aut), B0 - XORQ autLen, autLen - JMP dataMul - -dataSinglesLoop: - - CMPQ autLen, $16 - JB dataEnd - SUBQ $16, autLen - - MOVOU (aut), B0 -dataMul: - PSHUFB BSWAP, B0 - PXOR ACC0, B0 - - MOVOU T1, ACC0 - MOVOU T2, ACCM - MOVOU T1, ACC1 - - PSHUFD $78, B0, T0 - PXOR B0, T0 - PCLMULQDQ $0x00, B0, ACC0 - PCLMULQDQ $0x11, B0, ACC1 - PCLMULQDQ $0x00, T0, ACCM - - PXOR ACC0, ACCM - PXOR ACC1, ACCM - MOVOU ACCM, T0 - PSRLDQ $8, ACCM - PSLLDQ $8, T0 - PXOR ACCM, ACC1 - PXOR T0, ACC0 - - MOVOU POLY, T0 - PCLMULQDQ $0x01, ACC0, T0 - PSHUFD $78, ACC0, ACC0 - PXOR T0, ACC0 - - MOVOU POLY, T0 - PCLMULQDQ $0x01, ACC0, T0 - PSHUFD $78, ACC0, ACC0 - PXOR T0, ACC0 - PXOR ACC1, ACC0 - - LEAQ 16(aut), aut - - JMP dataSinglesLoop - -dataEnd: - - TESTQ autLen, autLen - JEQ dataBail - - PXOR B0, B0 - LEAQ -1(aut)(autLen*1), aut - -dataLoadLoop: - - PSLLDQ $1, B0 - PINSRB $0, (aut), B0 - - LEAQ -1(aut), aut - DECQ autLen - JNE dataLoadLoop - - JMP dataMul - -dataBail: - MOVOU ACC0, (tPtr) - RET -#undef pTbl -#undef aut -#undef tPtr -#undef autLen - -// func gcmAesEnc(productTable *[256]byte, dst, src []byte, ctr, T *[16]byte, ks []uint32) -TEXT ·gcmAesEnc(SB),0,$256-96 -#define pTbl DI -#define ctx DX -#define ctrPtr CX -#define ptx SI -#define ks AX -#define tPtr R8 -#define ptxLen R9 -#define aluCTR R10 -#define aluTMP R11 -#define aluK R12 -#define NR R13 - -#define increment(i) ADDL $1, aluCTR; MOVL aluCTR, aluTMP; XORL aluK, aluTMP; BSWAPL aluTMP; MOVL aluTMP, (3*4 + 8*16 + i*16)(SP) -#define aesRnd(k) AESENC k, B0; AESENC k, B1; AESENC k, B2; AESENC k, B3; AESENC k, B4; AESENC k, B5; AESENC k, B6; AESENC k, B7 -#define aesRound(i) MOVOU (16*i)(ks), T0;AESENC T0, B0; AESENC T0, B1; AESENC T0, B2; AESENC T0, B3; AESENC T0, B4; AESENC T0, B5; AESENC T0, B6; AESENC T0, B7 -#define aesRndLast(k) AESENCLAST k, B0; AESENCLAST k, B1; AESENCLAST k, B2; AESENCLAST k, B3; AESENCLAST k, B4; AESENCLAST k, B5; AESENCLAST k, B6; AESENCLAST k, B7 -#define reduceRound(a) MOVOU POLY, T0; PCLMULQDQ $0x01, a, T0; PSHUFD $78, a, a; PXOR T0, a -#define combinedRound(i) \ - MOVOU (16*i)(ks), T0;\ - AESENC T0, B0;\ - AESENC T0, B1;\ - AESENC T0, B2;\ - AESENC T0, B3;\ - MOVOU (16*(i*2))(pTbl), T1;\ - MOVOU T1, T2;\ - AESENC T0, B4;\ - AESENC T0, B5;\ - AESENC T0, B6;\ - AESENC T0, B7;\ - MOVOU (16*i)(SP), T0;\ - PCLMULQDQ $0x00, T0, T1;\ - PXOR T1, ACC0;\ - PSHUFD $78, T0, T1;\ - PCLMULQDQ $0x11, T0, T2;\ - PXOR T1, T0;\ - PXOR T2, ACC1;\ - MOVOU (16*(i*2+1))(pTbl), T2;\ - PCLMULQDQ $0x00, T2, T0;\ - PXOR T0, ACCM -#define mulRound(i) \ - MOVOU (16*i)(SP), T0;\ - MOVOU (16*(i*2))(pTbl), T1;\ - MOVOU T1, T2;\ - PCLMULQDQ $0x00, T0, T1;\ - PXOR T1, ACC0;\ - PCLMULQDQ $0x11, T0, T2;\ - PXOR T2, ACC1;\ - PSHUFD $78, T0, T1;\ - PXOR T1, T0;\ - MOVOU (16*(i*2+1))(pTbl), T1;\ - PCLMULQDQ $0x00, T0, T1;\ - PXOR T1, ACCM - - MOVQ productTable+0(FP), pTbl - MOVQ dst+8(FP), ctx - MOVQ src_base+32(FP), ptx - MOVQ src_len+40(FP), ptxLen - MOVQ ctr+56(FP), ctrPtr - MOVQ T+64(FP), tPtr - MOVQ ks_base+72(FP), ks - MOVQ ks_len+80(FP), NR - - SHRQ $2, NR - DECQ NR - - MOVOU bswapMask<>(SB), BSWAP - MOVOU gcmPoly<>(SB), POLY - - MOVOU (tPtr), ACC0 - PXOR ACC1, ACC1 - PXOR ACCM, ACCM - MOVOU (ctrPtr), B0 - MOVL (3*4)(ctrPtr), aluCTR - MOVOU (ks), T0 - MOVL (3*4)(ks), aluK - BSWAPL aluCTR - BSWAPL aluK - - PXOR B0, T0 - MOVOU T0, (8*16 + 0*16)(SP) - increment(0) - - CMPQ ptxLen, $128 - JB gcmAesEncSingles - SUBQ $128, ptxLen - - // We have at least 8 blocks to encrypt, prepare the rest of the counters - MOVOU T0, (8*16 + 1*16)(SP) - increment(1) - MOVOU T0, (8*16 + 2*16)(SP) - increment(2) - MOVOU T0, (8*16 + 3*16)(SP) - increment(3) - MOVOU T0, (8*16 + 4*16)(SP) - increment(4) - MOVOU T0, (8*16 + 5*16)(SP) - increment(5) - MOVOU T0, (8*16 + 6*16)(SP) - increment(6) - MOVOU T0, (8*16 + 7*16)(SP) - increment(7) - - MOVOU (8*16 + 0*16)(SP), B0 - MOVOU (8*16 + 1*16)(SP), B1 - MOVOU (8*16 + 2*16)(SP), B2 - MOVOU (8*16 + 3*16)(SP), B3 - MOVOU (8*16 + 4*16)(SP), B4 - MOVOU (8*16 + 5*16)(SP), B5 - MOVOU (8*16 + 6*16)(SP), B6 - MOVOU (8*16 + 7*16)(SP), B7 - - aesRound(1) - increment(0) - aesRound(2) - increment(1) - aesRound(3) - increment(2) - aesRound(4) - increment(3) - aesRound(5) - increment(4) - aesRound(6) - increment(5) - aesRound(7) - increment(6) - aesRound(8) - increment(7) - aesRound(9) - MOVOU (16*10)(ks), T0 - CMPQ NR, $12 - JB encLast1 - aesRnd(T0) - aesRound(11) - MOVOU (16*12)(ks), T0 - JE encLast1 - aesRnd(T0) - aesRound(13) - MOVOU (16*14)(ks), T0 -encLast1: - aesRndLast(T0) - - MOVOU (16*0)(ptx), T0 - PXOR T0, B0 - MOVOU (16*1)(ptx), T0 - PXOR T0, B1 - MOVOU (16*2)(ptx), T0 - PXOR T0, B2 - MOVOU (16*3)(ptx), T0 - PXOR T0, B3 - MOVOU (16*4)(ptx), T0 - PXOR T0, B4 - MOVOU (16*5)(ptx), T0 - PXOR T0, B5 - MOVOU (16*6)(ptx), T0 - PXOR T0, B6 - MOVOU (16*7)(ptx), T0 - PXOR T0, B7 - - MOVOU B0, (16*0)(ctx) - PSHUFB BSWAP, B0 - PXOR ACC0, B0 - MOVOU B1, (16*1)(ctx) - PSHUFB BSWAP, B1 - MOVOU B2, (16*2)(ctx) - PSHUFB BSWAP, B2 - MOVOU B3, (16*3)(ctx) - PSHUFB BSWAP, B3 - MOVOU B4, (16*4)(ctx) - PSHUFB BSWAP, B4 - MOVOU B5, (16*5)(ctx) - PSHUFB BSWAP, B5 - MOVOU B6, (16*6)(ctx) - PSHUFB BSWAP, B6 - MOVOU B7, (16*7)(ctx) - PSHUFB BSWAP, B7 - - MOVOU B0, (16*0)(SP) - MOVOU B1, (16*1)(SP) - MOVOU B2, (16*2)(SP) - MOVOU B3, (16*3)(SP) - MOVOU B4, (16*4)(SP) - MOVOU B5, (16*5)(SP) - MOVOU B6, (16*6)(SP) - MOVOU B7, (16*7)(SP) - - LEAQ 128(ptx), ptx - LEAQ 128(ctx), ctx - -gcmAesEncOctetsLoop: - - CMPQ ptxLen, $128 - JB gcmAesEncOctetsEnd - SUBQ $128, ptxLen - - MOVOU (8*16 + 0*16)(SP), B0 - MOVOU (8*16 + 1*16)(SP), B1 - MOVOU (8*16 + 2*16)(SP), B2 - MOVOU (8*16 + 3*16)(SP), B3 - MOVOU (8*16 + 4*16)(SP), B4 - MOVOU (8*16 + 5*16)(SP), B5 - MOVOU (8*16 + 6*16)(SP), B6 - MOVOU (8*16 + 7*16)(SP), B7 - - MOVOU (16*0)(SP), T0 - PSHUFD $78, T0, T1 - PXOR T0, T1 - - MOVOU (16*0)(pTbl), ACC0 - MOVOU (16*1)(pTbl), ACCM - MOVOU ACC0, ACC1 - - PCLMULQDQ $0x00, T1, ACCM - PCLMULQDQ $0x00, T0, ACC0 - PCLMULQDQ $0x11, T0, ACC1 - - combinedRound(1) - increment(0) - combinedRound(2) - increment(1) - combinedRound(3) - increment(2) - combinedRound(4) - increment(3) - combinedRound(5) - increment(4) - combinedRound(6) - increment(5) - combinedRound(7) - increment(6) - - aesRound(8) - increment(7) - - PXOR ACC0, ACCM - PXOR ACC1, ACCM - MOVOU ACCM, T0 - PSRLDQ $8, ACCM - PSLLDQ $8, T0 - PXOR ACCM, ACC1 - PXOR T0, ACC0 - - reduceRound(ACC0) - aesRound(9) - - reduceRound(ACC0) - PXOR ACC1, ACC0 - - MOVOU (16*10)(ks), T0 - CMPQ NR, $12 - JB encLast2 - aesRnd(T0) - aesRound(11) - MOVOU (16*12)(ks), T0 - JE encLast2 - aesRnd(T0) - aesRound(13) - MOVOU (16*14)(ks), T0 -encLast2: - aesRndLast(T0) - - MOVOU (16*0)(ptx), T0 - PXOR T0, B0 - MOVOU (16*1)(ptx), T0 - PXOR T0, B1 - MOVOU (16*2)(ptx), T0 - PXOR T0, B2 - MOVOU (16*3)(ptx), T0 - PXOR T0, B3 - MOVOU (16*4)(ptx), T0 - PXOR T0, B4 - MOVOU (16*5)(ptx), T0 - PXOR T0, B5 - MOVOU (16*6)(ptx), T0 - PXOR T0, B6 - MOVOU (16*7)(ptx), T0 - PXOR T0, B7 - - MOVOU B0, (16*0)(ctx) - PSHUFB BSWAP, B0 - PXOR ACC0, B0 - MOVOU B1, (16*1)(ctx) - PSHUFB BSWAP, B1 - MOVOU B2, (16*2)(ctx) - PSHUFB BSWAP, B2 - MOVOU B3, (16*3)(ctx) - PSHUFB BSWAP, B3 - MOVOU B4, (16*4)(ctx) - PSHUFB BSWAP, B4 - MOVOU B5, (16*5)(ctx) - PSHUFB BSWAP, B5 - MOVOU B6, (16*6)(ctx) - PSHUFB BSWAP, B6 - MOVOU B7, (16*7)(ctx) - PSHUFB BSWAP, B7 - - MOVOU B0, (16*0)(SP) - MOVOU B1, (16*1)(SP) - MOVOU B2, (16*2)(SP) - MOVOU B3, (16*3)(SP) - MOVOU B4, (16*4)(SP) - MOVOU B5, (16*5)(SP) - MOVOU B6, (16*6)(SP) - MOVOU B7, (16*7)(SP) - - LEAQ 128(ptx), ptx - LEAQ 128(ctx), ctx - - JMP gcmAesEncOctetsLoop - -gcmAesEncOctetsEnd: - - MOVOU (16*0)(SP), T0 - MOVOU (16*0)(pTbl), ACC0 - MOVOU (16*1)(pTbl), ACCM - MOVOU ACC0, ACC1 - PSHUFD $78, T0, T1 - PXOR T0, T1 - PCLMULQDQ $0x00, T0, ACC0 - PCLMULQDQ $0x11, T0, ACC1 - PCLMULQDQ $0x00, T1, ACCM - - mulRound(1) - mulRound(2) - mulRound(3) - mulRound(4) - mulRound(5) - mulRound(6) - mulRound(7) - - PXOR ACC0, ACCM - PXOR ACC1, ACCM - MOVOU ACCM, T0 - PSRLDQ $8, ACCM - PSLLDQ $8, T0 - PXOR ACCM, ACC1 - PXOR T0, ACC0 - - reduceRound(ACC0) - reduceRound(ACC0) - PXOR ACC1, ACC0 - - TESTQ ptxLen, ptxLen - JE gcmAesEncDone - - SUBQ $7, aluCTR - -gcmAesEncSingles: - - MOVOU (16*1)(ks), B1 - MOVOU (16*2)(ks), B2 - MOVOU (16*3)(ks), B3 - MOVOU (16*4)(ks), B4 - MOVOU (16*5)(ks), B5 - MOVOU (16*6)(ks), B6 - MOVOU (16*7)(ks), B7 - - MOVOU (16*14)(pTbl), T2 - -gcmAesEncSinglesLoop: - - CMPQ ptxLen, $16 - JB gcmAesEncTail - SUBQ $16, ptxLen - - MOVOU (8*16 + 0*16)(SP), B0 - increment(0) - - AESENC B1, B0 - AESENC B2, B0 - AESENC B3, B0 - AESENC B4, B0 - AESENC B5, B0 - AESENC B6, B0 - AESENC B7, B0 - MOVOU (16*8)(ks), T0 - AESENC T0, B0 - MOVOU (16*9)(ks), T0 - AESENC T0, B0 - MOVOU (16*10)(ks), T0 - CMPQ NR, $12 - JB encLast3 - AESENC T0, B0 - MOVOU (16*11)(ks), T0 - AESENC T0, B0 - MOVOU (16*12)(ks), T0 - JE encLast3 - AESENC T0, B0 - MOVOU (16*13)(ks), T0 - AESENC T0, B0 - MOVOU (16*14)(ks), T0 -encLast3: - AESENCLAST T0, B0 - - MOVOU (ptx), T0 - PXOR T0, B0 - MOVOU B0, (ctx) - - PSHUFB BSWAP, B0 - PXOR ACC0, B0 - - MOVOU T2, ACC0 - MOVOU T2, ACC1 - MOVOU (16*15)(pTbl), ACCM - - PSHUFD $78, B0, T0 - PXOR B0, T0 - PCLMULQDQ $0x00, B0, ACC0 - PCLMULQDQ $0x11, B0, ACC1 - PCLMULQDQ $0x00, T0, ACCM - - PXOR ACC0, ACCM - PXOR ACC1, ACCM - MOVOU ACCM, T0 - PSRLDQ $8, ACCM - PSLLDQ $8, T0 - PXOR ACCM, ACC1 - PXOR T0, ACC0 - - reduceRound(ACC0) - reduceRound(ACC0) - PXOR ACC1, ACC0 - - LEAQ (16*1)(ptx), ptx - LEAQ (16*1)(ctx), ctx - - JMP gcmAesEncSinglesLoop - -gcmAesEncTail: - TESTQ ptxLen, ptxLen - JE gcmAesEncDone - - MOVOU (8*16 + 0*16)(SP), B0 - AESENC B1, B0 - AESENC B2, B0 - AESENC B3, B0 - AESENC B4, B0 - AESENC B5, B0 - AESENC B6, B0 - AESENC B7, B0 - MOVOU (16*8)(ks), T0 - AESENC T0, B0 - MOVOU (16*9)(ks), T0 - AESENC T0, B0 - MOVOU (16*10)(ks), T0 - CMPQ NR, $12 - JB encLast4 - AESENC T0, B0 - MOVOU (16*11)(ks), T0 - AESENC T0, B0 - MOVOU (16*12)(ks), T0 - JE encLast4 - AESENC T0, B0 - MOVOU (16*13)(ks), T0 - AESENC T0, B0 - MOVOU (16*14)(ks), T0 -encLast4: - AESENCLAST T0, B0 - MOVOU B0, T0 - - LEAQ -1(ptx)(ptxLen*1), ptx - - MOVQ ptxLen, aluTMP - SHLQ $4, aluTMP - - LEAQ andMask<>(SB), aluCTR - MOVOU -16(aluCTR)(aluTMP*1), T1 - - PXOR B0, B0 -ptxLoadLoop: - PSLLDQ $1, B0 - PINSRB $0, (ptx), B0 - LEAQ -1(ptx), ptx - DECQ ptxLen - JNE ptxLoadLoop - - PXOR T0, B0 - PAND T1, B0 - MOVOU B0, (ctx) // I assume there is always space, due to TAG in the end of the CT - - PSHUFB BSWAP, B0 - PXOR ACC0, B0 - - MOVOU T2, ACC0 - MOVOU T2, ACC1 - MOVOU (16*15)(pTbl), ACCM - - PSHUFD $78, B0, T0 - PXOR B0, T0 - PCLMULQDQ $0x00, B0, ACC0 - PCLMULQDQ $0x11, B0, ACC1 - PCLMULQDQ $0x00, T0, ACCM - - PXOR ACC0, ACCM - PXOR ACC1, ACCM - MOVOU ACCM, T0 - PSRLDQ $8, ACCM - PSLLDQ $8, T0 - PXOR ACCM, ACC1 - PXOR T0, ACC0 - - reduceRound(ACC0) - reduceRound(ACC0) - PXOR ACC1, ACC0 - -gcmAesEncDone: - MOVOU ACC0, (tPtr) - RET -#undef increment - -// func gcmAesDec(productTable *[256]byte, dst, src []byte, ctr, T *[16]byte, ks []uint32) -TEXT ·gcmAesDec(SB),0,$128-96 -#define increment(i) ADDL $1, aluCTR; MOVL aluCTR, aluTMP; XORL aluK, aluTMP; BSWAPL aluTMP; MOVL aluTMP, (3*4 + i*16)(SP) -#define combinedDecRound(i) \ - MOVOU (16*i)(ks), T0;\ - AESENC T0, B0;\ - AESENC T0, B1;\ - AESENC T0, B2;\ - AESENC T0, B3;\ - MOVOU (16*(i*2))(pTbl), T1;\ - MOVOU T1, T2;\ - AESENC T0, B4;\ - AESENC T0, B5;\ - AESENC T0, B6;\ - AESENC T0, B7;\ - MOVOU (16*i)(ctx), T0;\ - PSHUFB BSWAP, T0;\ - PCLMULQDQ $0x00, T0, T1;\ - PXOR T1, ACC0;\ - PSHUFD $78, T0, T1;\ - PCLMULQDQ $0x11, T0, T2;\ - PXOR T1, T0;\ - PXOR T2, ACC1;\ - MOVOU (16*(i*2+1))(pTbl), T2;\ - PCLMULQDQ $0x00, T2, T0;\ - PXOR T0, ACCM - - MOVQ productTable+0(FP), pTbl - MOVQ dst+8(FP), ptx - MOVQ src_base+32(FP), ctx - MOVQ src_len+40(FP), ptxLen - MOVQ ctr+56(FP), ctrPtr - MOVQ T+64(FP), tPtr - MOVQ ks_base+72(FP), ks - MOVQ ks_len+80(FP), NR - - SHRQ $2, NR - DECQ NR - - MOVOU bswapMask<>(SB), BSWAP - MOVOU gcmPoly<>(SB), POLY - - MOVOU (tPtr), ACC0 - PXOR ACC1, ACC1 - PXOR ACCM, ACCM - MOVOU (ctrPtr), B0 - MOVL (3*4)(ctrPtr), aluCTR - MOVOU (ks), T0 - MOVL (3*4)(ks), aluK - BSWAPL aluCTR - BSWAPL aluK - - PXOR B0, T0 - MOVOU T0, (0*16)(SP) - increment(0) - - CMPQ ptxLen, $128 - JB gcmAesDecSingles - - MOVOU T0, (1*16)(SP) - increment(1) - MOVOU T0, (2*16)(SP) - increment(2) - MOVOU T0, (3*16)(SP) - increment(3) - MOVOU T0, (4*16)(SP) - increment(4) - MOVOU T0, (5*16)(SP) - increment(5) - MOVOU T0, (6*16)(SP) - increment(6) - MOVOU T0, (7*16)(SP) - increment(7) - -gcmAesDecOctetsLoop: - - CMPQ ptxLen, $128 - JB gcmAesDecEndOctets - SUBQ $128, ptxLen - - MOVOU (0*16)(SP), B0 - MOVOU (1*16)(SP), B1 - MOVOU (2*16)(SP), B2 - MOVOU (3*16)(SP), B3 - MOVOU (4*16)(SP), B4 - MOVOU (5*16)(SP), B5 - MOVOU (6*16)(SP), B6 - MOVOU (7*16)(SP), B7 - - MOVOU (16*0)(ctx), T0 - PSHUFB BSWAP, T0 - PXOR ACC0, T0 - PSHUFD $78, T0, T1 - PXOR T0, T1 - - MOVOU (16*0)(pTbl), ACC0 - MOVOU (16*1)(pTbl), ACCM - MOVOU ACC0, ACC1 - - PCLMULQDQ $0x00, T1, ACCM - PCLMULQDQ $0x00, T0, ACC0 - PCLMULQDQ $0x11, T0, ACC1 - - combinedDecRound(1) - increment(0) - combinedDecRound(2) - increment(1) - combinedDecRound(3) - increment(2) - combinedDecRound(4) - increment(3) - combinedDecRound(5) - increment(4) - combinedDecRound(6) - increment(5) - combinedDecRound(7) - increment(6) - - aesRound(8) - increment(7) - - PXOR ACC0, ACCM - PXOR ACC1, ACCM - MOVOU ACCM, T0 - PSRLDQ $8, ACCM - PSLLDQ $8, T0 - PXOR ACCM, ACC1 - PXOR T0, ACC0 - - reduceRound(ACC0) - aesRound(9) - - reduceRound(ACC0) - PXOR ACC1, ACC0 - - MOVOU (16*10)(ks), T0 - CMPQ NR, $12 - JB decLast1 - aesRnd(T0) - aesRound(11) - MOVOU (16*12)(ks), T0 - JE decLast1 - aesRnd(T0) - aesRound(13) - MOVOU (16*14)(ks), T0 -decLast1: - aesRndLast(T0) - - MOVOU (16*0)(ctx), T0 - PXOR T0, B0 - MOVOU (16*1)(ctx), T0 - PXOR T0, B1 - MOVOU (16*2)(ctx), T0 - PXOR T0, B2 - MOVOU (16*3)(ctx), T0 - PXOR T0, B3 - MOVOU (16*4)(ctx), T0 - PXOR T0, B4 - MOVOU (16*5)(ctx), T0 - PXOR T0, B5 - MOVOU (16*6)(ctx), T0 - PXOR T0, B6 - MOVOU (16*7)(ctx), T0 - PXOR T0, B7 - - MOVOU B0, (16*0)(ptx) - MOVOU B1, (16*1)(ptx) - MOVOU B2, (16*2)(ptx) - MOVOU B3, (16*3)(ptx) - MOVOU B4, (16*4)(ptx) - MOVOU B5, (16*5)(ptx) - MOVOU B6, (16*6)(ptx) - MOVOU B7, (16*7)(ptx) - - LEAQ 128(ptx), ptx - LEAQ 128(ctx), ctx - - JMP gcmAesDecOctetsLoop - -gcmAesDecEndOctets: - - SUBQ $7, aluCTR - -gcmAesDecSingles: - - MOVOU (16*1)(ks), B1 - MOVOU (16*2)(ks), B2 - MOVOU (16*3)(ks), B3 - MOVOU (16*4)(ks), B4 - MOVOU (16*5)(ks), B5 - MOVOU (16*6)(ks), B6 - MOVOU (16*7)(ks), B7 - - MOVOU (16*14)(pTbl), T2 - -gcmAesDecSinglesLoop: - - CMPQ ptxLen, $16 - JB gcmAesDecTail - SUBQ $16, ptxLen - - MOVOU (ctx), B0 - MOVOU B0, T1 - PSHUFB BSWAP, B0 - PXOR ACC0, B0 - - MOVOU T2, ACC0 - MOVOU T2, ACC1 - MOVOU (16*15)(pTbl), ACCM - - PCLMULQDQ $0x00, B0, ACC0 - PCLMULQDQ $0x11, B0, ACC1 - PSHUFD $78, B0, T0 - PXOR B0, T0 - PCLMULQDQ $0x00, T0, ACCM - - PXOR ACC0, ACCM - PXOR ACC1, ACCM - MOVOU ACCM, T0 - PSRLDQ $8, ACCM - PSLLDQ $8, T0 - PXOR ACCM, ACC1 - PXOR T0, ACC0 - - reduceRound(ACC0) - reduceRound(ACC0) - PXOR ACC1, ACC0 - - MOVOU (0*16)(SP), B0 - increment(0) - AESENC B1, B0 - AESENC B2, B0 - AESENC B3, B0 - AESENC B4, B0 - AESENC B5, B0 - AESENC B6, B0 - AESENC B7, B0 - MOVOU (16*8)(ks), T0 - AESENC T0, B0 - MOVOU (16*9)(ks), T0 - AESENC T0, B0 - MOVOU (16*10)(ks), T0 - CMPQ NR, $12 - JB decLast2 - AESENC T0, B0 - MOVOU (16*11)(ks), T0 - AESENC T0, B0 - MOVOU (16*12)(ks), T0 - JE decLast2 - AESENC T0, B0 - MOVOU (16*13)(ks), T0 - AESENC T0, B0 - MOVOU (16*14)(ks), T0 -decLast2: - AESENCLAST T0, B0 - - PXOR T1, B0 - MOVOU B0, (ptx) - - LEAQ (16*1)(ptx), ptx - LEAQ (16*1)(ctx), ctx - - JMP gcmAesDecSinglesLoop - -gcmAesDecTail: - - TESTQ ptxLen, ptxLen - JE gcmAesDecDone - - MOVQ ptxLen, aluTMP - SHLQ $4, aluTMP - LEAQ andMask<>(SB), aluCTR - MOVOU -16(aluCTR)(aluTMP*1), T1 - - MOVOU (ctx), B0 // I assume there is TAG attached to the ctx, and there is no read overflow - PAND T1, B0 - - MOVOU B0, T1 - PSHUFB BSWAP, B0 - PXOR ACC0, B0 - - MOVOU (16*14)(pTbl), ACC0 - MOVOU (16*15)(pTbl), ACCM - MOVOU ACC0, ACC1 - - PCLMULQDQ $0x00, B0, ACC0 - PCLMULQDQ $0x11, B0, ACC1 - PSHUFD $78, B0, T0 - PXOR B0, T0 - PCLMULQDQ $0x00, T0, ACCM - - PXOR ACC0, ACCM - PXOR ACC1, ACCM - MOVOU ACCM, T0 - PSRLDQ $8, ACCM - PSLLDQ $8, T0 - PXOR ACCM, ACC1 - PXOR T0, ACC0 - - reduceRound(ACC0) - reduceRound(ACC0) - PXOR ACC1, ACC0 - - MOVOU (0*16)(SP), B0 - increment(0) - AESENC B1, B0 - AESENC B2, B0 - AESENC B3, B0 - AESENC B4, B0 - AESENC B5, B0 - AESENC B6, B0 - AESENC B7, B0 - MOVOU (16*8)(ks), T0 - AESENC T0, B0 - MOVOU (16*9)(ks), T0 - AESENC T0, B0 - MOVOU (16*10)(ks), T0 - CMPQ NR, $12 - JB decLast3 - AESENC T0, B0 - MOVOU (16*11)(ks), T0 - AESENC T0, B0 - MOVOU (16*12)(ks), T0 - JE decLast3 - AESENC T0, B0 - MOVOU (16*13)(ks), T0 - AESENC T0, B0 - MOVOU (16*14)(ks), T0 -decLast3: - AESENCLAST T0, B0 - PXOR T1, B0 - -ptxStoreLoop: - PEXTRB $0, B0, (ptx) - PSRLDQ $1, B0 - LEAQ 1(ptx), ptx - DECQ ptxLen - - JNE ptxStoreLoop - -gcmAesDecDone: - - MOVOU ACC0, (tPtr) - RET diff --git a/vendor/github.com/lucas-clemente/aes12/xor.go b/vendor/github.com/lucas-clemente/aes12/xor.go deleted file mode 100644 index 668c13fdd53..00000000000 --- a/vendor/github.com/lucas-clemente/aes12/xor.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package aes12 - -import ( - "runtime" - "unsafe" -) - -const wordSize = int(unsafe.Sizeof(uintptr(0))) -const supportsUnaligned = runtime.GOARCH == "386" || runtime.GOARCH == "amd64" || runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "s390x" - -// fastXORBytes xors in bulk. It only works on architectures that -// support unaligned read/writes. -func fastXORBytes(dst, a, b []byte) int { - n := len(a) - if len(b) < n { - n = len(b) - } - - w := n / wordSize - if w > 0 { - dw := *(*[]uintptr)(unsafe.Pointer(&dst)) - aw := *(*[]uintptr)(unsafe.Pointer(&a)) - bw := *(*[]uintptr)(unsafe.Pointer(&b)) - for i := 0; i < w; i++ { - dw[i] = aw[i] ^ bw[i] - } - } - - for i := (n - n%wordSize); i < n; i++ { - dst[i] = a[i] ^ b[i] - } - - return n -} - -func safeXORBytes(dst, a, b []byte) int { - n := len(a) - if len(b) < n { - n = len(b) - } - for i := 0; i < n; i++ { - dst[i] = a[i] ^ b[i] - } - return n -} - -// xorBytes xors the bytes in a and b. The destination is assumed to have enough -// space. Returns the number of bytes xor'd. -func xorBytes(dst, a, b []byte) int { - if supportsUnaligned { - return fastXORBytes(dst, a, b) - } else { - // TODO(hanwen): if (dst, a, b) have common alignment - // we could still try fastXORBytes. It is not clear - // how often this happens, and it's only worth it if - // the block encryption itself is hardware - // accelerated. - return safeXORBytes(dst, a, b) - } -} - -// fastXORWords XORs multiples of 4 or 8 bytes (depending on architecture.) -// The arguments are assumed to be of equal length. -func fastXORWords(dst, a, b []byte) { - dw := *(*[]uintptr)(unsafe.Pointer(&dst)) - aw := *(*[]uintptr)(unsafe.Pointer(&a)) - bw := *(*[]uintptr)(unsafe.Pointer(&b)) - n := len(b) / wordSize - for i := 0; i < n; i++ { - dw[i] = aw[i] ^ bw[i] - } -} - -func xorWords(dst, a, b []byte) { - if supportsUnaligned { - fastXORWords(dst, a, b) - } else { - safeXORBytes(dst, a, b) - } -} diff --git a/vendor/github.com/lucas-clemente/fnv128a/LICENSE b/vendor/github.com/lucas-clemente/fnv128a/LICENSE deleted file mode 100644 index 06dc795d859..00000000000 --- a/vendor/github.com/lucas-clemente/fnv128a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Lucas Clemente - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/lucas-clemente/fnv128a/fnv128a.go b/vendor/github.com/lucas-clemente/fnv128a/fnv128a.go deleted file mode 100644 index 592123967b5..00000000000 --- a/vendor/github.com/lucas-clemente/fnv128a/fnv128a.go +++ /dev/null @@ -1,87 +0,0 @@ -// Package fnv128a implements FNV-1 and FNV-1a, non-cryptographic hash functions -// created by Glenn Fowler, Landon Curt Noll, and Phong Vo. -// See https://en.wikipedia.org/wiki/Fowler-Noll-Vo_hash_function. -// -// Write() algorithm taken and modified from github.com/romain-jacotin/quic -package fnv128a - -import "hash" - -// Hash128 is the common interface implemented by all 128-bit hash functions. -type Hash128 interface { - hash.Hash - Sum128() (uint64, uint64) -} - -type sum128a struct { - v0, v1, v2, v3 uint64 -} - -var _ Hash128 = &sum128a{} - -// New1 returns a new 128-bit FNV-1a hash.Hash. -func New() Hash128 { - s := &sum128a{} - s.Reset() - return s -} - -func (s *sum128a) Reset() { - s.v0 = 0x6295C58D - s.v1 = 0x62B82175 - s.v2 = 0x07BB0142 - s.v3 = 0x6C62272E -} - -func (s *sum128a) Sum128() (uint64, uint64) { - return s.v3<<32 | s.v2, s.v1<<32 | s.v0 -} - -func (s *sum128a) Write(data []byte) (int, error) { - var t0, t1, t2, t3 uint64 - const fnv128PrimeLow = 0x0000013B - const fnv128PrimeShift = 24 - - for _, v := range data { - // xor the bottom with the current octet - s.v0 ^= uint64(v) - - // multiply by the 128 bit FNV magic prime mod 2^128 - // fnv_prime = 309485009821345068724781371 (decimal) - // = 0x0000000001000000000000000000013B (hexadecimal) - // = 0x00000000 0x01000000 0x00000000 0x0000013B (in 4*32 words) - // = 0x0 1<> 32) - t2 += (t1 >> 32) - t3 += (t2 >> 32) - - s.v0 = t0 & 0xffffffff - s.v1 = t1 & 0xffffffff - s.v2 = t2 & 0xffffffff - s.v3 = t3 // & 0xffffffff - // Doing a s.v3 &= 0xffffffff is not really needed since it simply - // removes multiples of 2^128. We can discard these excess bits - // outside of the loop when writing the hash in Little Endian. - } - - return len(data), nil -} - -func (s *sum128a) Size() int { return 16 } - -func (s *sum128a) BlockSize() int { return 1 } - -func (s *sum128a) Sum(in []byte) []byte { - panic("FNV: not supported") -} diff --git a/vendor/github.com/lucas-clemente/quic-go-certificates/LICENSE b/vendor/github.com/lucas-clemente/quic-go-certificates/LICENSE deleted file mode 100644 index 2c08ae24541..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go-certificates/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2016 Lucas Clemente - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/lucas-clemente/quic-go-certificates/cert_set_2.go b/vendor/github.com/lucas-clemente/quic-go-certificates/cert_set_2.go deleted file mode 100644 index f2f2250f312..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go-certificates/cert_set_2.go +++ /dev/null @@ -1,5824 +0,0 @@ -package certsets - -var CertSet2 = [][]byte{ - certSet2Cert0, - certSet2Cert1, - certSet2Cert2, - certSet2Cert3, - certSet2Cert4, - certSet2Cert5, - certSet2Cert6, - certSet2Cert7, - certSet2Cert8, - certSet2Cert9, - certSet2Cert10, - certSet2Cert11, - certSet2Cert12, - certSet2Cert13, - certSet2Cert14, - certSet2Cert15, - certSet2Cert16, - certSet2Cert17, - certSet2Cert18, - certSet2Cert19, - certSet2Cert20, - certSet2Cert21, - certSet2Cert22, - certSet2Cert23, - certSet2Cert24, - certSet2Cert25, - certSet2Cert26, - certSet2Cert27, - certSet2Cert28, - certSet2Cert29, - certSet2Cert30, - certSet2Cert31, - certSet2Cert32, - certSet2Cert33, - certSet2Cert34, - certSet2Cert35, - certSet2Cert36, - certSet2Cert37, - certSet2Cert38, - certSet2Cert39, - certSet2Cert40, - certSet2Cert41, - certSet2Cert42, - certSet2Cert43, - certSet2Cert44, - certSet2Cert45, - certSet2Cert46, - certSet2Cert47, - certSet2Cert48, - certSet2Cert49, - certSet2Cert50, - certSet2Cert51, - certSet2Cert52, - certSet2Cert53, -} - -const CertSet2Hash uint64 = (0xe81a92926081e801) - -var certSet2Cert0 = []byte{ - 0x30, 0x82, 0x03, 0x7d, 0x30, 0x82, 0x02, 0xe6, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x03, 0x12, 0xbb, 0xe6, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x4e, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x07, 0x45, - 0x71, 0x75, 0x69, 0x66, 0x61, 0x78, 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03, - 0x55, 0x04, 0x0b, 0x13, 0x24, 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, 0x78, - 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x32, 0x30, - 0x35, 0x32, 0x31, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, - 0x31, 0x38, 0x30, 0x38, 0x32, 0x31, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, - 0x5a, 0x30, 0x42, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, - 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, - 0x03, 0x13, 0x12, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, - 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, - 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xda, 0xcc, 0x18, 0x63, 0x30, 0xfd, - 0xf4, 0x17, 0x23, 0x1a, 0x56, 0x7e, 0x5b, 0xdf, 0x3c, 0x6c, 0x38, 0xe4, - 0x71, 0xb7, 0x78, 0x91, 0xd4, 0xbc, 0xa1, 0xd8, 0x4c, 0xf8, 0xa8, 0x43, - 0xb6, 0x03, 0xe9, 0x4d, 0x21, 0x07, 0x08, 0x88, 0xda, 0x58, 0x2f, 0x66, - 0x39, 0x29, 0xbd, 0x05, 0x78, 0x8b, 0x9d, 0x38, 0xe8, 0x05, 0xb7, 0x6a, - 0x7e, 0x71, 0xa4, 0xe6, 0xc4, 0x60, 0xa6, 0xb0, 0xef, 0x80, 0xe4, 0x89, - 0x28, 0x0f, 0x9e, 0x25, 0xd6, 0xed, 0x83, 0xf3, 0xad, 0xa6, 0x91, 0xc7, - 0x98, 0xc9, 0x42, 0x18, 0x35, 0x14, 0x9d, 0xad, 0x98, 0x46, 0x92, 0x2e, - 0x4f, 0xca, 0xf1, 0x87, 0x43, 0xc1, 0x16, 0x95, 0x57, 0x2d, 0x50, 0xef, - 0x89, 0x2d, 0x80, 0x7a, 0x57, 0xad, 0xf2, 0xee, 0x5f, 0x6b, 0xd2, 0x00, - 0x8d, 0xb9, 0x14, 0xf8, 0x14, 0x15, 0x35, 0xd9, 0xc0, 0x46, 0xa3, 0x7b, - 0x72, 0xc8, 0x91, 0xbf, 0xc9, 0x55, 0x2b, 0xcd, 0xd0, 0x97, 0x3e, 0x9c, - 0x26, 0x64, 0xcc, 0xdf, 0xce, 0x83, 0x19, 0x71, 0xca, 0x4e, 0xe6, 0xd4, - 0xd5, 0x7b, 0xa9, 0x19, 0xcd, 0x55, 0xde, 0xc8, 0xec, 0xd2, 0x5e, 0x38, - 0x53, 0xe5, 0x5c, 0x4f, 0x8c, 0x2d, 0xfe, 0x50, 0x23, 0x36, 0xfc, 0x66, - 0xe6, 0xcb, 0x8e, 0xa4, 0x39, 0x19, 0x00, 0xb7, 0x95, 0x02, 0x39, 0x91, - 0x0b, 0x0e, 0xfe, 0x38, 0x2e, 0xd1, 0x1d, 0x05, 0x9a, 0xf6, 0x4d, 0x3e, - 0x6f, 0x0f, 0x07, 0x1d, 0xaf, 0x2c, 0x1e, 0x8f, 0x60, 0x39, 0xe2, 0xfa, - 0x36, 0x53, 0x13, 0x39, 0xd4, 0x5e, 0x26, 0x2b, 0xdb, 0x3d, 0xa8, 0x14, - 0xbd, 0x32, 0xeb, 0x18, 0x03, 0x28, 0x52, 0x04, 0x71, 0xe5, 0xab, 0x33, - 0x3d, 0xe1, 0x38, 0xbb, 0x07, 0x36, 0x84, 0x62, 0x9c, 0x79, 0xea, 0x16, - 0x30, 0xf4, 0x5f, 0xc0, 0x2b, 0xe8, 0x71, 0x6b, 0xe4, 0xf9, 0x02, 0x03, - 0x01, 0x00, 0x01, 0xa3, 0x81, 0xf0, 0x30, 0x81, 0xed, 0x30, 0x1f, 0x06, - 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x48, 0xe6, - 0x68, 0xf9, 0x2b, 0xd2, 0xb2, 0x95, 0xd7, 0x47, 0xd8, 0x23, 0x20, 0x10, - 0x4f, 0x33, 0x98, 0x90, 0x9f, 0xd4, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, - 0x0e, 0x04, 0x16, 0x04, 0x14, 0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb, - 0xab, 0x05, 0x64, 0x0c, 0x11, 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc, - 0x4e, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, - 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, - 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x3a, - 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x33, 0x30, 0x31, 0x30, 0x2f, 0xa0, - 0x2d, 0xa0, 0x2b, 0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x63, 0x72, 0x6c, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x73, 0x65, - 0x63, 0x75, 0x72, 0x65, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x4e, - 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x47, 0x30, 0x45, 0x30, 0x43, 0x06, - 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x3b, 0x30, 0x39, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x2d, 0x68, 0x74, 0x74, - 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f, - 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x72, 0x65, 0x70, 0x6f, - 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, - 0x00, 0x76, 0xe1, 0x12, 0x6e, 0x4e, 0x4b, 0x16, 0x12, 0x86, 0x30, 0x06, - 0xb2, 0x81, 0x08, 0xcf, 0xf0, 0x08, 0xc7, 0xc7, 0x71, 0x7e, 0x66, 0xee, - 0xc2, 0xed, 0xd4, 0x3b, 0x1f, 0xff, 0xf0, 0xf0, 0xc8, 0x4e, 0xd6, 0x43, - 0x38, 0xb0, 0xb9, 0x30, 0x7d, 0x18, 0xd0, 0x55, 0x83, 0xa2, 0x6a, 0xcb, - 0x36, 0x11, 0x9c, 0xe8, 0x48, 0x66, 0xa3, 0x6d, 0x7f, 0xb8, 0x13, 0xd4, - 0x47, 0xfe, 0x8b, 0x5a, 0x5c, 0x73, 0xfc, 0xae, 0xd9, 0x1b, 0x32, 0x19, - 0x38, 0xab, 0x97, 0x34, 0x14, 0xaa, 0x96, 0xd2, 0xeb, 0xa3, 0x1c, 0x14, - 0x08, 0x49, 0xb6, 0xbb, 0xe5, 0x91, 0xef, 0x83, 0x36, 0xeb, 0x1d, 0x56, - 0x6f, 0xca, 0xda, 0xbc, 0x73, 0x63, 0x90, 0xe4, 0x7f, 0x7b, 0x3e, 0x22, - 0xcb, 0x3d, 0x07, 0xed, 0x5f, 0x38, 0x74, 0x9c, 0xe3, 0x03, 0x50, 0x4e, - 0xa1, 0xaf, 0x98, 0xee, 0x61, 0xf2, 0x84, 0x3f, 0x12, -} - -var certSet2Cert1 = []byte{ - 0x30, 0x82, 0x03, 0x8b, 0x30, 0x82, 0x02, 0xf4, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x03, 0x0d, 0x6e, 0x62, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x4e, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x07, 0x45, - 0x71, 0x75, 0x69, 0x66, 0x61, 0x78, 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03, - 0x55, 0x04, 0x0b, 0x13, 0x24, 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, 0x78, - 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, - 0x31, 0x32, 0x37, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, - 0x31, 0x38, 0x30, 0x38, 0x32, 0x31, 0x31, 0x36, 0x31, 0x35, 0x30, 0x30, - 0x5a, 0x30, 0x58, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, - 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, - 0x03, 0x13, 0x28, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x82, 0x01, 0x22, 0x30, - 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, - 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, - 0x82, 0x01, 0x01, 0x00, 0xbe, 0xb8, 0x15, 0x7b, 0xff, 0xd4, 0x7c, 0x7d, - 0x67, 0xad, 0x83, 0x64, 0x7b, 0xc8, 0x42, 0x53, 0x2d, 0xdf, 0xf6, 0x84, - 0x08, 0x20, 0x61, 0xd6, 0x01, 0x59, 0x6a, 0x9c, 0x44, 0x11, 0xaf, 0xef, - 0x76, 0xfd, 0x95, 0x7e, 0xce, 0x61, 0x30, 0xbb, 0x7a, 0x83, 0x5f, 0x02, - 0xbd, 0x01, 0x66, 0xca, 0xee, 0x15, 0x8d, 0x6f, 0xa1, 0x30, 0x9c, 0xbd, - 0xa1, 0x85, 0x9e, 0x94, 0x3a, 0xf3, 0x56, 0x88, 0x00, 0x31, 0xcf, 0xd8, - 0xee, 0x6a, 0x96, 0x02, 0xd9, 0xed, 0x03, 0x8c, 0xfb, 0x75, 0x6d, 0xe7, - 0xea, 0xb8, 0x55, 0x16, 0x05, 0x16, 0x9a, 0xf4, 0xe0, 0x5e, 0xb1, 0x88, - 0xc0, 0x64, 0x85, 0x5c, 0x15, 0x4d, 0x88, 0xc7, 0xb7, 0xba, 0xe0, 0x75, - 0xe9, 0xad, 0x05, 0x3d, 0x9d, 0xc7, 0x89, 0x48, 0xe0, 0xbb, 0x28, 0xc8, - 0x03, 0xe1, 0x30, 0x93, 0x64, 0x5e, 0x52, 0xc0, 0x59, 0x70, 0x22, 0x35, - 0x57, 0x88, 0x8a, 0xf1, 0x95, 0x0a, 0x83, 0xd7, 0xbc, 0x31, 0x73, 0x01, - 0x34, 0xed, 0xef, 0x46, 0x71, 0xe0, 0x6b, 0x02, 0xa8, 0x35, 0x72, 0x6b, - 0x97, 0x9b, 0x66, 0xe0, 0xcb, 0x1c, 0x79, 0x5f, 0xd8, 0x1a, 0x04, 0x68, - 0x1e, 0x47, 0x02, 0xe6, 0x9d, 0x60, 0xe2, 0x36, 0x97, 0x01, 0xdf, 0xce, - 0x35, 0x92, 0xdf, 0xbe, 0x67, 0xc7, 0x6d, 0x77, 0x59, 0x3b, 0x8f, 0x9d, - 0xd6, 0x90, 0x15, 0x94, 0xbc, 0x42, 0x34, 0x10, 0xc1, 0x39, 0xf9, 0xb1, - 0x27, 0x3e, 0x7e, 0xd6, 0x8a, 0x75, 0xc5, 0xb2, 0xaf, 0x96, 0xd3, 0xa2, - 0xde, 0x9b, 0xe4, 0x98, 0xbe, 0x7d, 0xe1, 0xe9, 0x81, 0xad, 0xb6, 0x6f, - 0xfc, 0xd7, 0x0e, 0xda, 0xe0, 0x34, 0xb0, 0x0d, 0x1a, 0x77, 0xe7, 0xe3, - 0x08, 0x98, 0xef, 0x58, 0xfa, 0x9c, 0x84, 0xb7, 0x36, 0xaf, 0xc2, 0xdf, - 0xac, 0xd2, 0xf4, 0x10, 0x06, 0x70, 0x71, 0x35, 0x02, 0x03, 0x01, 0x00, - 0x01, 0xa3, 0x81, 0xe8, 0x30, 0x81, 0xe5, 0x30, 0x0e, 0x06, 0x03, 0x55, - 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, - 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x2c, 0xd5, - 0x50, 0x41, 0x97, 0x15, 0x8b, 0xf0, 0x8f, 0x36, 0x61, 0x5b, 0x4a, 0xfb, - 0x6b, 0xd9, 0x99, 0xc9, 0x33, 0x92, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, - 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x48, 0xe6, 0x68, 0xf9, 0x2b, - 0xd2, 0xb2, 0x95, 0xd7, 0x47, 0xd8, 0x23, 0x20, 0x10, 0x4f, 0x33, 0x98, - 0x90, 0x9f, 0xd4, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, - 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x3a, 0x06, 0x03, - 0x55, 0x1d, 0x1f, 0x04, 0x33, 0x30, 0x31, 0x30, 0x2f, 0xa0, 0x2d, 0xa0, - 0x2b, 0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, - 0x6c, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x73, 0x65, 0x63, 0x75, - 0x72, 0x65, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x46, 0x06, 0x03, - 0x55, 0x1d, 0x20, 0x04, 0x3f, 0x30, 0x3d, 0x30, 0x3b, 0x06, 0x04, 0x55, - 0x1d, 0x20, 0x00, 0x30, 0x33, 0x30, 0x31, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, - 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, - 0x81, 0x81, 0x00, 0xaf, 0xf3, 0x0e, 0xd6, 0x72, 0xab, 0xc7, 0xa9, 0x97, - 0xca, 0x2a, 0x6b, 0x84, 0x39, 0xde, 0x79, 0xa9, 0xf0, 0x81, 0xe5, 0x08, - 0x67, 0xab, 0xd7, 0x2f, 0x20, 0x02, 0x01, 0x71, 0x0c, 0x04, 0x22, 0xc9, - 0x1e, 0x88, 0x95, 0x03, 0xc9, 0x49, 0x3a, 0xaf, 0x67, 0x08, 0x49, 0xb0, - 0xd5, 0x08, 0xf5, 0x20, 0x3d, 0x80, 0x91, 0xa0, 0xc5, 0x87, 0xa3, 0xfb, - 0xc9, 0xa3, 0x17, 0x91, 0xf9, 0xa8, 0x2f, 0xae, 0xe9, 0x0f, 0xdf, 0x96, - 0x72, 0x0f, 0x75, 0x17, 0x80, 0x5d, 0x78, 0x01, 0x4d, 0x9f, 0x1f, 0x6d, - 0x7b, 0xd8, 0xf5, 0x42, 0x38, 0x23, 0x1a, 0x99, 0x93, 0xf4, 0x83, 0xbe, - 0x3b, 0x35, 0x74, 0xe7, 0x37, 0x13, 0x35, 0x7a, 0xac, 0xb4, 0xb6, 0x90, - 0x82, 0x6c, 0x27, 0xa4, 0xe0, 0xec, 0x9e, 0x35, 0xbd, 0xbf, 0xe5, 0x29, - 0xa1, 0x47, 0x9f, 0x5b, 0x32, 0xfc, 0xe9, 0x99, 0x7d, 0x2b, 0x39, -} - -var certSet2Cert2 = []byte{ - 0x30, 0x82, 0x03, 0xd5, 0x30, 0x82, 0x02, 0xbd, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x03, 0x02, 0x36, 0xd1, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x42, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, - 0x32, 0x31, 0x39, 0x32, 0x32, 0x34, 0x35, 0x30, 0x35, 0x5a, 0x17, 0x0d, - 0x32, 0x30, 0x30, 0x32, 0x31, 0x38, 0x32, 0x32, 0x34, 0x35, 0x30, 0x35, - 0x5a, 0x30, 0x3c, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, - 0x13, 0x02, 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x13, 0x0e, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x2c, - 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x0b, 0x52, 0x61, 0x70, 0x69, 0x64, 0x53, 0x53, 0x4c, - 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, - 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, - 0xc7, 0x71, 0xf8, 0x56, 0xc7, 0x1e, 0xd9, 0xcc, 0xb5, 0xad, 0xf6, 0xb4, - 0x97, 0xa3, 0xfb, 0xa1, 0xe6, 0x0b, 0x50, 0x5f, 0x50, 0xaa, 0x3a, 0xda, - 0x0f, 0xfc, 0x3d, 0x29, 0x24, 0x43, 0xc6, 0x10, 0x29, 0xc1, 0xfc, 0x55, - 0x40, 0x72, 0xee, 0xbd, 0xea, 0xdf, 0x9f, 0xb6, 0x41, 0xf4, 0x48, 0x4b, - 0xc8, 0x6e, 0xfe, 0x4f, 0x57, 0x12, 0x8b, 0x5b, 0xfa, 0x92, 0xdd, 0x5e, - 0xe8, 0xad, 0xf3, 0xf0, 0x1b, 0xb1, 0x7b, 0x4d, 0xfb, 0xcf, 0xfd, 0xd1, - 0xe5, 0xf8, 0xe3, 0xdc, 0xe7, 0xf5, 0x73, 0x7f, 0xdf, 0x01, 0x49, 0xcf, - 0x8c, 0x56, 0xc1, 0xbd, 0x37, 0xe3, 0x5b, 0xbe, 0xb5, 0x4f, 0x8b, 0x8b, - 0xf0, 0xda, 0x4f, 0xc7, 0xe3, 0xdd, 0x55, 0x47, 0x69, 0xdf, 0xf2, 0x5b, - 0x7b, 0x07, 0x4f, 0x3d, 0xe5, 0xac, 0x21, 0xc1, 0xc8, 0x1d, 0x7a, 0xe8, - 0xe7, 0xf6, 0x0f, 0xa1, 0xaa, 0xf5, 0x6f, 0xde, 0xa8, 0x65, 0x4f, 0x10, - 0x89, 0x9c, 0x03, 0xf3, 0x89, 0x7a, 0xa5, 0x5e, 0x01, 0x72, 0x33, 0xed, - 0xa9, 0xe9, 0x5a, 0x1e, 0x79, 0xf3, 0x87, 0xc8, 0xdf, 0xc8, 0xc5, 0xfc, - 0x37, 0xc8, 0x9a, 0x9a, 0xd7, 0xb8, 0x76, 0xcc, 0xb0, 0x3e, 0xe7, 0xfd, - 0xe6, 0x54, 0xea, 0xdf, 0x5f, 0x52, 0x41, 0x78, 0x59, 0x57, 0xad, 0xf1, - 0x12, 0xd6, 0x7f, 0xbc, 0xd5, 0x9f, 0x70, 0xd3, 0x05, 0x6c, 0xfa, 0xa3, - 0x7d, 0x67, 0x58, 0xdd, 0x26, 0x62, 0x1d, 0x31, 0x92, 0x0c, 0x79, 0x79, - 0x1c, 0x8e, 0xcf, 0xca, 0x7b, 0xc1, 0x66, 0xaf, 0xa8, 0x74, 0x48, 0xfb, - 0x8e, 0x82, 0xc2, 0x9e, 0x2c, 0x99, 0x5c, 0x7b, 0x2d, 0x5d, 0x9b, 0xbc, - 0x5b, 0x57, 0x9e, 0x7c, 0x3a, 0x7a, 0x13, 0xad, 0xf2, 0xa3, 0x18, 0x5b, - 0x2b, 0x59, 0x0f, 0xcd, 0x5c, 0x3a, 0xeb, 0x68, 0x33, 0xc6, 0x28, 0x1d, - 0x82, 0xd1, 0x50, 0x8b, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xd9, - 0x30, 0x81, 0xd6, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, - 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, - 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x6b, 0x69, 0x3d, 0x6a, 0x18, 0x42, - 0x4a, 0xdd, 0x8f, 0x02, 0x65, 0x39, 0xfd, 0x35, 0x24, 0x86, 0x78, 0x91, - 0x16, 0x30, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, - 0x16, 0x80, 0x14, 0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb, 0xab, 0x05, - 0x64, 0x0c, 0x11, 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc, 0x4e, 0x30, - 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, - 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x3a, 0x06, 0x03, 0x55, - 0x1d, 0x1f, 0x04, 0x33, 0x30, 0x31, 0x30, 0x2f, 0xa0, 0x2d, 0xa0, 0x2b, - 0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, - 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x67, 0x74, 0x67, 0x6c, 0x6f, - 0x62, 0x61, 0x6c, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x34, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x28, 0x30, 0x26, 0x30, - 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, - 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, - 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, - 0x6d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0xab, 0xbc, 0xbc, - 0x0a, 0x5d, 0x18, 0x94, 0xe3, 0xc1, 0xb1, 0xc3, 0xa8, 0x4c, 0x55, 0xd6, - 0xbe, 0xb4, 0x98, 0xf1, 0xee, 0x3c, 0x1c, 0xcd, 0xcf, 0xf3, 0x24, 0x24, - 0x5c, 0x96, 0x03, 0x27, 0x58, 0xfc, 0x36, 0xae, 0xa2, 0x2f, 0x8f, 0xf1, - 0xfe, 0xda, 0x2b, 0x02, 0xc3, 0x33, 0xbd, 0xc8, 0xdd, 0x48, 0x22, 0x2b, - 0x60, 0x0f, 0xa5, 0x03, 0x10, 0xfd, 0x77, 0xf8, 0xd0, 0xed, 0x96, 0x67, - 0x4f, 0xfd, 0xea, 0x47, 0x20, 0x70, 0x54, 0xdc, 0xa9, 0x0c, 0x55, 0x7e, - 0xe1, 0x96, 0x25, 0x8a, 0xd9, 0xb5, 0xda, 0x57, 0x4a, 0xbe, 0x8d, 0x8e, - 0x49, 0x43, 0x63, 0xa5, 0x6c, 0x4e, 0x27, 0x87, 0x25, 0xeb, 0x5b, 0x6d, - 0xfe, 0xa2, 0x7f, 0x38, 0x28, 0xe0, 0x36, 0xab, 0xad, 0x39, 0xa5, 0xa5, - 0x62, 0xc4, 0xb7, 0x5c, 0x58, 0x2c, 0xaa, 0x5d, 0x01, 0x60, 0xa6, 0x62, - 0x67, 0xa3, 0xc0, 0xc7, 0x62, 0x23, 0xf4, 0xe7, 0x6c, 0x46, 0xee, 0xb5, - 0xd3, 0x80, 0x6a, 0x22, 0x13, 0xd2, 0x2d, 0x3f, 0x74, 0x4f, 0xea, 0xaf, - 0x8c, 0x5f, 0xb4, 0x38, 0x9c, 0xdb, 0xae, 0xce, 0xaf, 0x84, 0x1e, 0xa6, - 0xf6, 0x34, 0x51, 0x59, 0x79, 0xd3, 0xe3, 0x75, 0xdc, 0xbc, 0xd7, 0xf3, - 0x73, 0xdf, 0x92, 0xec, 0xd2, 0x20, 0x59, 0x6f, 0x9c, 0xfb, 0x95, 0xf8, - 0x92, 0x76, 0x18, 0x0a, 0x7c, 0x0f, 0x2c, 0xa6, 0xca, 0xde, 0x8a, 0x62, - 0x7b, 0xd8, 0xf3, 0xce, 0x5f, 0x68, 0xbd, 0x8f, 0x3e, 0xc1, 0x74, 0xbb, - 0x15, 0x72, 0x3a, 0x16, 0x83, 0xa9, 0x0b, 0xe6, 0x4d, 0x99, 0x9c, 0xd8, - 0x57, 0xec, 0xa8, 0x01, 0x51, 0xc7, 0x6f, 0x57, 0x34, 0x5e, 0xab, 0x4a, - 0x2c, 0x42, 0xf6, 0x4f, 0x1c, 0x89, 0x78, 0xde, 0x26, 0x4e, 0xf5, 0x6f, - 0x93, 0x4c, 0x15, 0x6b, 0x27, 0x56, 0x4d, 0x00, 0x54, 0x6c, 0x7a, 0xb7, - 0xb7, -} - -var certSet2Cert3 = []byte{ - 0x30, 0x82, 0x03, 0xf0, 0x30, 0x82, 0x02, 0xd8, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x03, 0x02, 0x3a, 0x83, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x42, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x33, 0x30, - 0x34, 0x30, 0x35, 0x31, 0x35, 0x31, 0x35, 0x35, 0x36, 0x5a, 0x17, 0x0d, - 0x31, 0x36, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, - 0x5a, 0x30, 0x49, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, - 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x13, 0x0a, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x49, 0x6e, - 0x63, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1c, - 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, - 0x6e, 0x65, 0x74, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, - 0x79, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, - 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, - 0x00, 0x9c, 0x2a, 0x04, 0x77, 0x5c, 0xd8, 0x50, 0x91, 0x3a, 0x06, 0xa3, - 0x82, 0xe0, 0xd8, 0x50, 0x48, 0xbc, 0x89, 0x3f, 0xf1, 0x19, 0x70, 0x1a, - 0x88, 0x46, 0x7e, 0xe0, 0x8f, 0xc5, 0xf1, 0x89, 0xce, 0x21, 0xee, 0x5a, - 0xfe, 0x61, 0x0d, 0xb7, 0x32, 0x44, 0x89, 0xa0, 0x74, 0x0b, 0x53, 0x4f, - 0x55, 0xa4, 0xce, 0x82, 0x62, 0x95, 0xee, 0xeb, 0x59, 0x5f, 0xc6, 0xe1, - 0x05, 0x80, 0x12, 0xc4, 0x5e, 0x94, 0x3f, 0xbc, 0x5b, 0x48, 0x38, 0xf4, - 0x53, 0xf7, 0x24, 0xe6, 0xfb, 0x91, 0xe9, 0x15, 0xc4, 0xcf, 0xf4, 0x53, - 0x0d, 0xf4, 0x4a, 0xfc, 0x9f, 0x54, 0xde, 0x7d, 0xbe, 0xa0, 0x6b, 0x6f, - 0x87, 0xc0, 0xd0, 0x50, 0x1f, 0x28, 0x30, 0x03, 0x40, 0xda, 0x08, 0x73, - 0x51, 0x6c, 0x7f, 0xff, 0x3a, 0x3c, 0xa7, 0x37, 0x06, 0x8e, 0xbd, 0x4b, - 0x11, 0x04, 0xeb, 0x7d, 0x24, 0xde, 0xe6, 0xf9, 0xfc, 0x31, 0x71, 0xfb, - 0x94, 0xd5, 0x60, 0xf3, 0x2e, 0x4a, 0xaf, 0x42, 0xd2, 0xcb, 0xea, 0xc4, - 0x6a, 0x1a, 0xb2, 0xcc, 0x53, 0xdd, 0x15, 0x4b, 0x8b, 0x1f, 0xc8, 0x19, - 0x61, 0x1f, 0xcd, 0x9d, 0xa8, 0x3e, 0x63, 0x2b, 0x84, 0x35, 0x69, 0x65, - 0x84, 0xc8, 0x19, 0xc5, 0x46, 0x22, 0xf8, 0x53, 0x95, 0xbe, 0xe3, 0x80, - 0x4a, 0x10, 0xc6, 0x2a, 0xec, 0xba, 0x97, 0x20, 0x11, 0xc7, 0x39, 0x99, - 0x10, 0x04, 0xa0, 0xf0, 0x61, 0x7a, 0x95, 0x25, 0x8c, 0x4e, 0x52, 0x75, - 0xe2, 0xb6, 0xed, 0x08, 0xca, 0x14, 0xfc, 0xce, 0x22, 0x6a, 0xb3, 0x4e, - 0xcf, 0x46, 0x03, 0x97, 0x97, 0x03, 0x7e, 0xc0, 0xb1, 0xde, 0x7b, 0xaf, - 0x45, 0x33, 0xcf, 0xba, 0x3e, 0x71, 0xb7, 0xde, 0xf4, 0x25, 0x25, 0xc2, - 0x0d, 0x35, 0x89, 0x9d, 0x9d, 0xfb, 0x0e, 0x11, 0x79, 0x89, 0x1e, 0x37, - 0xc5, 0xaf, 0x8e, 0x72, 0x69, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, - 0xe7, 0x30, 0x81, 0xe4, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, - 0x18, 0x30, 0x16, 0x80, 0x14, 0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb, - 0xab, 0x05, 0x64, 0x0c, 0x11, 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc, - 0x4e, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, - 0x4a, 0xdd, 0x06, 0x16, 0x1b, 0xbc, 0xf6, 0x68, 0xb5, 0x76, 0xf5, 0x81, - 0xb6, 0xbb, 0x62, 0x1a, 0xba, 0x5a, 0x81, 0x2f, 0x30, 0x0e, 0x06, 0x03, - 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, - 0x30, 0x2e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, - 0x04, 0x22, 0x30, 0x20, 0x30, 0x1e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x30, 0x01, 0x86, 0x12, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, - 0x2f, 0x67, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x64, 0x2e, 0x63, 0x6f, 0x6d, - 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, - 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x35, 0x06, 0x03, - 0x55, 0x1d, 0x1f, 0x04, 0x2e, 0x30, 0x2c, 0x30, 0x2a, 0xa0, 0x28, 0xa0, - 0x26, 0x86, 0x24, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x67, 0x2e, - 0x73, 0x79, 0x6d, 0x63, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x72, - 0x6c, 0x73, 0x2f, 0x67, 0x74, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x2e, - 0x63, 0x72, 0x6c, 0x30, 0x17, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x10, - 0x30, 0x0e, 0x30, 0x0c, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, - 0x79, 0x02, 0x05, 0x01, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, - 0xaa, 0xfa, 0xa9, 0x20, 0xcd, 0x6a, 0x67, 0x83, 0xed, 0x5e, 0xd4, 0x7e, - 0xde, 0x1d, 0xc4, 0x7f, 0xe0, 0x25, 0x06, 0x00, 0xc5, 0x24, 0xfb, 0xa9, - 0xc8, 0x2d, 0x6d, 0x7e, 0xde, 0x9d, 0x82, 0x65, 0x2c, 0x81, 0x63, 0x34, - 0x66, 0x3e, 0xe9, 0x52, 0xc2, 0x08, 0xb4, 0xcb, 0x2f, 0xf7, 0x5f, 0x99, - 0x3a, 0x6a, 0x9c, 0x50, 0x7a, 0x85, 0x05, 0x8c, 0x7d, 0xd1, 0x2a, 0x48, - 0x84, 0xd3, 0x09, 0x6c, 0x7c, 0xc2, 0xcd, 0x35, 0x9f, 0xf3, 0x82, 0xee, - 0x52, 0xde, 0x68, 0x5f, 0xe4, 0x00, 0x8a, 0x17, 0x20, 0x96, 0xf7, 0x29, - 0x8d, 0x9a, 0x4d, 0xcb, 0xa8, 0xde, 0x86, 0xc8, 0x0d, 0x6f, 0x56, 0x87, - 0x03, 0x7d, 0x03, 0x3f, 0xdc, 0xfa, 0x79, 0x7d, 0x21, 0x19, 0xf9, 0xc8, - 0x3a, 0x2f, 0x51, 0x76, 0x8c, 0xc7, 0x41, 0x92, 0x71, 0x8f, 0x25, 0xce, - 0x37, 0xf8, 0x4a, 0x4c, 0x00, 0x23, 0xef, 0xc4, 0x35, 0x10, 0xae, 0xe0, - 0x23, 0x80, 0x73, 0x7c, 0x4d, 0x34, 0x2e, 0xc8, 0x6e, 0x90, 0xd6, 0x10, - 0x1e, 0x99, 0x84, 0x73, 0x1a, 0x70, 0xf2, 0xed, 0x55, 0x0e, 0xee, 0x17, - 0x06, 0xea, 0x67, 0xee, 0x32, 0xeb, 0x2c, 0xdd, 0x67, 0x07, 0x3f, 0xf6, - 0x8b, 0xc2, 0x70, 0xde, 0x5b, 0x00, 0xe6, 0xbb, 0x1b, 0xd3, 0x36, 0x1a, - 0x22, 0x6c, 0x6c, 0xb0, 0x35, 0x42, 0x6c, 0x90, 0x09, 0x3d, 0x93, 0xe9, - 0x64, 0x09, 0x22, 0x0e, 0x85, 0x06, 0x9f, 0xc2, 0x73, 0x21, 0xd3, 0xe6, - 0x5f, 0x80, 0xe4, 0x8d, 0x85, 0x22, 0x3a, 0x73, 0x03, 0xb1, 0x60, 0x8e, - 0xae, 0x68, 0xe2, 0xf4, 0x3e, 0x97, 0xe7, 0x60, 0x12, 0x09, 0x68, 0x36, - 0xde, 0x3a, 0xd6, 0xe2, 0x43, 0x95, 0x5b, 0x37, 0x81, 0x92, 0x81, 0x1f, - 0xbb, 0x8d, 0xd7, 0xad, 0x52, 0x64, 0x16, 0x57, 0x96, 0xd9, 0x5e, 0x34, - 0x7e, 0xc8, 0x35, 0xd8, -} - -var certSet2Cert4 = []byte{ - 0x30, 0x82, 0x04, 0x15, 0x30, 0x82, 0x03, 0x7e, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x04, 0x07, 0x27, 0x8e, 0xed, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x75, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f, - 0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b, - 0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, - 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21, - 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43, - 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, - 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, - 0x0d, 0x31, 0x32, 0x30, 0x34, 0x31, 0x38, 0x31, 0x36, 0x33, 0x36, 0x31, - 0x38, 0x5a, 0x17, 0x0d, 0x31, 0x38, 0x30, 0x38, 0x31, 0x33, 0x31, 0x36, - 0x33, 0x35, 0x31, 0x37, 0x5a, 0x30, 0x5a, 0x31, 0x0b, 0x30, 0x09, 0x06, - 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, 0x45, 0x31, 0x12, 0x30, 0x10, - 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09, 0x42, 0x61, 0x6c, 0x74, 0x69, - 0x6d, 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, - 0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, - 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x19, - 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x43, 0x79, - 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x52, 0x6f, 0x6f, - 0x74, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, - 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, - 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa3, 0x04, - 0xbb, 0x22, 0xab, 0x98, 0x3d, 0x57, 0xe8, 0x26, 0x72, 0x9a, 0xb5, 0x79, - 0xd4, 0x29, 0xe2, 0xe1, 0xe8, 0x95, 0x80, 0xb1, 0xb0, 0xe3, 0x5b, 0x8e, - 0x2b, 0x29, 0x9a, 0x64, 0xdf, 0xa1, 0x5d, 0xed, 0xb0, 0x09, 0x05, 0x6d, - 0xdb, 0x28, 0x2e, 0xce, 0x62, 0xa2, 0x62, 0xfe, 0xb4, 0x88, 0xda, 0x12, - 0xeb, 0x38, 0xeb, 0x21, 0x9d, 0xc0, 0x41, 0x2b, 0x01, 0x52, 0x7b, 0x88, - 0x77, 0xd3, 0x1c, 0x8f, 0xc7, 0xba, 0xb9, 0x88, 0xb5, 0x6a, 0x09, 0xe7, - 0x73, 0xe8, 0x11, 0x40, 0xa7, 0xd1, 0xcc, 0xca, 0x62, 0x8d, 0x2d, 0xe5, - 0x8f, 0x0b, 0xa6, 0x50, 0xd2, 0xa8, 0x50, 0xc3, 0x28, 0xea, 0xf5, 0xab, - 0x25, 0x87, 0x8a, 0x9a, 0x96, 0x1c, 0xa9, 0x67, 0xb8, 0x3f, 0x0c, 0xd5, - 0xf7, 0xf9, 0x52, 0x13, 0x2f, 0xc2, 0x1b, 0xd5, 0x70, 0x70, 0xf0, 0x8f, - 0xc0, 0x12, 0xca, 0x06, 0xcb, 0x9a, 0xe1, 0xd9, 0xca, 0x33, 0x7a, 0x77, - 0xd6, 0xf8, 0xec, 0xb9, 0xf1, 0x68, 0x44, 0x42, 0x48, 0x13, 0xd2, 0xc0, - 0xc2, 0xa4, 0xae, 0x5e, 0x60, 0xfe, 0xb6, 0xa6, 0x05, 0xfc, 0xb4, 0xdd, - 0x07, 0x59, 0x02, 0xd4, 0x59, 0x18, 0x98, 0x63, 0xf5, 0xa5, 0x63, 0xe0, - 0x90, 0x0c, 0x7d, 0x5d, 0xb2, 0x06, 0x7a, 0xf3, 0x85, 0xea, 0xeb, 0xd4, - 0x03, 0xae, 0x5e, 0x84, 0x3e, 0x5f, 0xff, 0x15, 0xed, 0x69, 0xbc, 0xf9, - 0x39, 0x36, 0x72, 0x75, 0xcf, 0x77, 0x52, 0x4d, 0xf3, 0xc9, 0x90, 0x2c, - 0xb9, 0x3d, 0xe5, 0xc9, 0x23, 0x53, 0x3f, 0x1f, 0x24, 0x98, 0x21, 0x5c, - 0x07, 0x99, 0x29, 0xbd, 0xc6, 0x3a, 0xec, 0xe7, 0x6e, 0x86, 0x3a, 0x6b, - 0x97, 0x74, 0x63, 0x33, 0xbd, 0x68, 0x18, 0x31, 0xf0, 0x78, 0x8d, 0x76, - 0xbf, 0xfc, 0x9e, 0x8e, 0x5d, 0x2a, 0x86, 0xa7, 0x4d, 0x90, 0xdc, 0x27, - 0x1a, 0x39, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x47, 0x30, - 0x82, 0x01, 0x43, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, - 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x03, 0x30, - 0x4a, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x43, 0x30, 0x41, 0x30, 0x3f, - 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x37, 0x30, 0x35, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x29, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x2e, 0x6f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, - 0x6f, 0x72, 0x79, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, - 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x81, 0x89, 0x06, 0x03, - 0x55, 0x1d, 0x23, 0x04, 0x81, 0x81, 0x30, 0x7f, 0xa1, 0x79, 0xa4, 0x77, - 0x30, 0x75, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, - 0x02, 0x55, 0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, - 0x13, 0x0f, 0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, - 0x04, 0x0b, 0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, - 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, - 0x30, 0x21, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, - 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x82, - 0x02, 0x01, 0xa5, 0x30, 0x45, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3e, - 0x30, 0x3c, 0x30, 0x3a, 0xa0, 0x38, 0xa0, 0x36, 0x86, 0x34, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x70, 0x75, 0x62, - 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x63, 0x67, 0x69, 0x2d, 0x62, 0x69, 0x6e, 0x2f, 0x43, 0x52, - 0x4c, 0x2f, 0x32, 0x30, 0x31, 0x38, 0x2f, 0x63, 0x64, 0x70, 0x2e, 0x63, - 0x72, 0x6c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, - 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x93, 0x1d, 0xfe, - 0x8b, 0xae, 0x46, 0xec, 0xcb, 0xa9, 0x0f, 0xab, 0xe5, 0xef, 0xca, 0xb2, - 0x68, 0x16, 0x68, 0xd8, 0x8f, 0xfa, 0x13, 0xa9, 0xaf, 0xb3, 0xcb, 0x2d, - 0xe7, 0x4b, 0x6e, 0x8e, 0x69, 0x2a, 0xc2, 0x2b, 0x10, 0x0a, 0x8d, 0xf6, - 0xae, 0x73, 0xb6, 0xb9, 0xfb, 0x14, 0xfd, 0x5f, 0x6d, 0xb8, 0x50, 0xb6, - 0xc4, 0x8a, 0xd6, 0x40, 0x7e, 0xd7, 0xc3, 0xcb, 0x73, 0xdc, 0xc9, 0x5d, - 0x5b, 0xaf, 0xb0, 0x41, 0xb5, 0x37, 0xeb, 0xea, 0xdc, 0x20, 0x91, 0xc4, - 0x34, 0x6a, 0xf4, 0xa1, 0xf3, 0x96, 0x9d, 0x37, 0x86, 0x97, 0xe1, 0x71, - 0xa4, 0xdd, 0x7d, 0xfa, 0x44, 0x84, 0x94, 0xae, 0xd7, 0x09, 0x04, 0x22, - 0x76, 0x0f, 0x64, 0x51, 0x35, 0xa9, 0x24, 0x0f, 0xf9, 0x0b, 0xdb, 0x32, - 0xda, 0xc2, 0xfe, 0xc1, 0xb9, 0x2a, 0x5c, 0x7a, 0x27, 0x13, 0xca, 0xb1, - 0x48, 0x3a, 0x71, 0xd0, 0x43, -} - -var certSet2Cert5 = []byte{ - 0x30, 0x82, 0x04, 0x22, 0x30, 0x82, 0x03, 0x0a, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x03, 0x02, 0x3a, 0x79, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x42, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x34, 0x30, - 0x39, 0x30, 0x38, 0x32, 0x30, 0x34, 0x31, 0x31, 0x30, 0x5a, 0x17, 0x0d, - 0x32, 0x32, 0x30, 0x35, 0x32, 0x30, 0x32, 0x30, 0x34, 0x31, 0x31, 0x30, - 0x5a, 0x30, 0x44, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, - 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x1d, 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, - 0x03, 0x13, 0x14, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, 0x34, 0x30, - 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, - 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, - 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0x9a, 0x7d, 0x98, 0x68, - 0x11, 0x40, 0xc1, 0x5f, 0x72, 0xec, 0x55, 0xb3, 0xb1, 0x63, 0xf3, 0x32, - 0x22, 0x72, 0x91, 0xc6, 0x16, 0x05, 0xbb, 0x08, 0x82, 0x31, 0xb4, 0xf6, - 0xee, 0xd4, 0x18, 0x39, 0x11, 0x2f, 0x2e, 0xda, 0x47, 0xfe, 0x51, 0x31, - 0x6e, 0x5b, 0xf2, 0xa9, 0x0a, 0xeb, 0x2f, 0xbb, 0xf5, 0x61, 0x59, 0x65, - 0x57, 0x02, 0xcd, 0x80, 0xff, 0xc7, 0x70, 0x32, 0x54, 0x89, 0xfd, 0xdb, - 0xae, 0x99, 0x72, 0xd4, 0x4f, 0x0c, 0x26, 0xb9, 0x2e, 0x63, 0x30, 0x7d, - 0xde, 0x14, 0x5b, 0x6a, 0xd7, 0x52, 0x78, 0x21, 0xf9, 0xbf, 0xbc, 0x50, - 0xd5, 0x54, 0x12, 0x59, 0xd8, 0xb5, 0x36, 0xd9, 0x21, 0x47, 0xb8, 0x3f, - 0x6a, 0x58, 0x1d, 0x8c, 0x72, 0xe1, 0x97, 0x95, 0xd3, 0xe1, 0x45, 0xa8, - 0xf1, 0x5a, 0xe5, 0xbe, 0xfe, 0xe3, 0x53, 0x7c, 0xa5, 0xf0, 0x52, 0xe0, - 0xcf, 0x39, 0x94, 0x0c, 0x19, 0x71, 0xf2, 0xc0, 0x25, 0x07, 0x48, 0x7d, - 0x1c, 0xe6, 0xf1, 0x39, 0x25, 0x2f, 0x98, 0x79, 0x43, 0xe8, 0x18, 0x72, - 0xf4, 0x65, 0x86, 0x98, 0x5a, 0x00, 0x04, 0x47, 0xda, 0x4b, 0x58, 0x1e, - 0x7c, 0x86, 0xb1, 0x4b, 0x35, 0xa6, 0x20, 0x00, 0x1c, 0xcd, 0x1b, 0x3b, - 0x22, 0x5d, 0xd1, 0x93, 0x28, 0x33, 0x12, 0x23, 0x94, 0x08, 0xaa, 0xc3, - 0x3a, 0xf5, 0xd1, 0xc6, 0x8c, 0x7e, 0x99, 0xd3, 0x18, 0xa0, 0xad, 0x9d, - 0x18, 0xcf, 0x49, 0xad, 0x10, 0x03, 0xf7, 0x99, 0x33, 0x26, 0x86, 0x46, - 0x9a, 0x2f, 0xa0, 0xba, 0x6c, 0x6e, 0xc8, 0x88, 0x02, 0xb7, 0x6e, 0xfa, - 0x7a, 0x9e, 0x98, 0x4a, 0xee, 0x9a, 0x31, 0x7d, 0x19, 0x14, 0x60, 0x0c, - 0xec, 0x8f, 0x20, 0x23, 0x3c, 0xda, 0x97, 0x26, 0xb6, 0xea, 0x80, 0x6c, - 0x8a, 0x57, 0x9e, 0x20, 0xee, 0x6f, 0x17, 0x25, 0x4a, 0x32, 0xad, 0x35, - 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x1d, 0x30, 0x82, 0x01, - 0x19, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, - 0x80, 0x14, 0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb, 0xab, 0x05, 0x64, - 0x0c, 0x11, 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc, 0x4e, 0x30, 0x1d, - 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xac, 0x32, 0xed, - 0x5a, 0xc9, 0xe0, 0xde, 0x30, 0x9c, 0x90, 0x58, 0x55, 0x26, 0x63, 0xf6, - 0x72, 0xa6, 0x54, 0x5f, 0xe3, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, - 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, - 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, - 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x35, 0x06, 0x03, 0x55, 0x1d, 0x1f, - 0x04, 0x2e, 0x30, 0x2c, 0x30, 0x2a, 0xa0, 0x28, 0xa0, 0x26, 0x86, 0x24, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x67, 0x2e, 0x73, 0x79, 0x6d, - 0x63, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, - 0x67, 0x74, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x2e, 0x63, 0x72, 0x6c, - 0x30, 0x2e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, - 0x04, 0x22, 0x30, 0x20, 0x30, 0x1e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x30, 0x01, 0x86, 0x12, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, - 0x2f, 0x67, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x64, 0x2e, 0x63, 0x6f, 0x6d, - 0x30, 0x4c, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x45, 0x30, 0x43, 0x30, - 0x41, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x07, - 0x36, 0x30, 0x33, 0x30, 0x31, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x02, 0x01, 0x16, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x73, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, - 0x01, 0x00, 0x61, 0x40, 0xad, 0x21, 0x0f, 0x03, 0xbb, 0x95, 0xdc, 0x89, - 0xfc, 0xa3, 0xcb, 0x05, 0x71, 0xe9, 0x1c, 0x59, 0x97, 0x35, 0xc2, 0xfa, - 0x6b, 0x05, 0xa4, 0x16, 0xc6, 0x56, 0x46, 0x37, 0x74, 0x1b, 0x1b, 0xf1, - 0x3e, 0x2c, 0xe8, 0x37, 0x19, 0xb7, 0x94, 0xd2, 0x0f, 0x0e, 0xc5, 0xbf, - 0x14, 0x07, 0x2b, 0x34, 0xcd, 0x5b, 0xb4, 0x8d, 0xc7, 0x56, 0x9d, 0x19, - 0xfc, 0x02, 0xb4, 0x9e, 0x90, 0x31, 0xfa, 0xa4, 0x44, 0xc6, 0x75, 0xdd, - 0xdd, 0x1f, 0x25, 0x54, 0xa3, 0x30, 0x4c, 0xac, 0xdb, 0xfe, 0xc4, 0x88, - 0xf7, 0x31, 0x26, 0x18, 0x47, 0xae, 0x4c, 0x20, 0x19, 0x1a, 0xc7, 0xae, - 0x3e, 0x98, 0x0a, 0x16, 0x3d, 0xd2, 0xc2, 0xa6, 0x5d, 0x0d, 0x2e, 0x29, - 0x7d, 0xb2, 0x9d, 0xc7, 0x41, 0x32, 0x17, 0xca, 0x9d, 0xae, 0x39, 0xbf, - 0x91, 0x98, 0xde, 0xe7, 0x44, 0xe2, 0x95, 0x9c, 0x94, 0x5c, 0x6c, 0x42, - 0x1b, 0x59, 0xc9, 0x7b, 0x68, 0x13, 0xa8, 0x96, 0x09, 0x74, 0xee, 0x40, - 0x14, 0xa4, 0xd5, 0xd7, 0xc9, 0x7b, 0x33, 0xa3, 0x0f, 0x5a, 0x69, 0x9c, - 0x1a, 0xfa, 0x6f, 0x12, 0x47, 0x1c, 0xdf, 0x1e, 0x4c, 0x70, 0x4e, 0x6d, - 0xdd, 0xfe, 0x1c, 0x87, 0xb5, 0x9d, 0xe1, 0x54, 0x07, 0x09, 0x8a, 0xcd, - 0xbe, 0xaa, 0xa8, 0x46, 0x78, 0x6e, 0x16, 0xf2, 0xe7, 0x91, 0x0e, 0xc3, - 0xaf, 0xda, 0x76, 0x00, 0xd1, 0xd8, 0xa2, 0x46, 0x24, 0x03, 0xa5, 0x1a, - 0x85, 0x81, 0x56, 0x83, 0x63, 0x27, 0xba, 0x90, 0x8e, 0xf9, 0x62, 0x11, - 0xba, 0xa7, 0x7c, 0x90, 0xa9, 0x1a, 0x66, 0xb4, 0xc5, 0xbc, 0x8f, 0x29, - 0x41, 0xab, 0xeb, 0x8d, 0x99, 0xa6, 0xcc, 0x91, 0x64, 0xba, 0xdc, 0xc6, - 0xa6, 0x4c, 0xb3, 0xb4, 0x23, 0x26, 0x51, 0x72, 0x56, 0xf9, 0xf3, 0x74, - 0x55, 0x9f, 0x25, 0x75, 0x4f, 0x2b, -} - -var certSet2Cert6 = []byte{ - 0x30, 0x82, 0x04, 0x25, 0x30, 0x82, 0x03, 0x0d, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x03, 0x02, 0x3a, 0x77, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x42, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x34, 0x30, - 0x38, 0x32, 0x39, 0x32, 0x31, 0x33, 0x39, 0x33, 0x32, 0x5a, 0x17, 0x0d, - 0x32, 0x32, 0x30, 0x35, 0x32, 0x30, 0x32, 0x31, 0x33, 0x39, 0x33, 0x32, - 0x5a, 0x30, 0x47, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, - 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x03, 0x55, 0x04, - 0x03, 0x13, 0x17, 0x52, 0x61, 0x70, 0x69, 0x64, 0x53, 0x53, 0x4c, 0x20, - 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, - 0x47, 0x33, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, - 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xaf, - 0x54, 0x9b, 0xd9, 0x58, 0x5d, 0x1e, 0x2c, 0x56, 0xc6, 0xd5, 0xe8, 0x7f, - 0xf4, 0x7d, 0x16, 0x03, 0xff, 0xd0, 0x8b, 0x5a, 0xe4, 0x8e, 0xa7, 0xdd, - 0x54, 0x2e, 0xd4, 0x04, 0xc0, 0x5d, 0x98, 0x9c, 0x8d, 0x90, 0x0f, 0xbc, - 0x10, 0x65, 0x5f, 0xda, 0x9a, 0xd6, 0x44, 0x7c, 0xc0, 0x9f, 0xb5, 0xe9, - 0x4a, 0x8c, 0x0b, 0x06, 0x43, 0x04, 0xbb, 0xf4, 0x96, 0xe2, 0x26, 0xf6, - 0x61, 0x01, 0x91, 0x66, 0x31, 0x22, 0xc3, 0x34, 0x34, 0x5f, 0x3f, 0x3f, - 0x91, 0x2f, 0x44, 0x5f, 0xdc, 0xc7, 0x14, 0xb6, 0x03, 0x9f, 0x86, 0x4b, - 0x0e, 0xa3, 0xff, 0xa0, 0x80, 0x02, 0x83, 0xc3, 0xd3, 0x1f, 0x69, 0x52, - 0xd6, 0x9d, 0x64, 0x0f, 0xc9, 0x83, 0xe7, 0x1b, 0xc4, 0x70, 0xac, 0x94, - 0xe7, 0xc3, 0xa4, 0x6a, 0x2c, 0xbd, 0xb8, 0x9e, 0x69, 0xd8, 0xbe, 0x0a, - 0x8f, 0x16, 0x63, 0x5a, 0x68, 0x71, 0x80, 0x7b, 0x30, 0xde, 0x15, 0x04, - 0xbf, 0xcc, 0xd3, 0xbf, 0x3e, 0x48, 0x05, 0x55, 0x7a, 0xb3, 0xd7, 0x10, - 0x0c, 0x03, 0xfc, 0x9b, 0xfd, 0x08, 0xa7, 0x8c, 0x8c, 0xdb, 0xa7, 0x8e, - 0xf1, 0x1e, 0x63, 0xdc, 0xb3, 0x01, 0x2f, 0x7f, 0xaf, 0x57, 0xc3, 0x3c, - 0x48, 0xa7, 0x83, 0x68, 0x21, 0xa7, 0x2f, 0xe7, 0xa7, 0x3f, 0xf0, 0xb5, - 0x0c, 0xfc, 0xf5, 0x84, 0xd1, 0x53, 0xbc, 0x0e, 0x72, 0x4f, 0x60, 0x0c, - 0x42, 0xb8, 0x98, 0xad, 0x19, 0x88, 0x57, 0xd7, 0x04, 0xec, 0x87, 0xbf, - 0x7e, 0x87, 0x4e, 0xa3, 0x21, 0xf9, 0x53, 0xfd, 0x36, 0x98, 0x48, 0x8d, - 0xd6, 0xf8, 0xbb, 0x48, 0xf2, 0x29, 0xc8, 0x64, 0xd1, 0xcc, 0x54, 0x48, - 0x53, 0x8b, 0xaf, 0xb7, 0x65, 0x1e, 0xbf, 0x29, 0x33, 0x29, 0xd9, 0x29, - 0x60, 0x48, 0xf8, 0xff, 0x91, 0xbc, 0x57, 0x58, 0xe5, 0x35, 0x2e, 0xbb, - 0x69, 0xb6, 0x59, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x1d, - 0x30, 0x82, 0x01, 0x19, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, - 0x18, 0x30, 0x16, 0x80, 0x14, 0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb, - 0xab, 0x05, 0x64, 0x0c, 0x11, 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc, - 0x4e, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, - 0xc3, 0x9c, 0xf3, 0xfc, 0xd3, 0x46, 0x08, 0x34, 0xbb, 0xce, 0x46, 0x7f, - 0xa0, 0x7c, 0x5b, 0xf3, 0xe2, 0x08, 0xcb, 0x59, 0x30, 0x12, 0x06, 0x03, - 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, - 0xff, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, - 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x35, 0x06, 0x03, - 0x55, 0x1d, 0x1f, 0x04, 0x2e, 0x30, 0x2c, 0x30, 0x2a, 0xa0, 0x28, 0xa0, - 0x26, 0x86, 0x24, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x67, 0x2e, - 0x73, 0x79, 0x6d, 0x63, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x72, - 0x6c, 0x73, 0x2f, 0x67, 0x74, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x2e, - 0x63, 0x72, 0x6c, 0x30, 0x2e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x01, 0x01, 0x04, 0x22, 0x30, 0x20, 0x30, 0x1e, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x12, 0x68, 0x74, 0x74, - 0x70, 0x3a, 0x2f, 0x2f, 0x67, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x64, 0x2e, - 0x63, 0x6f, 0x6d, 0x30, 0x4c, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x45, - 0x30, 0x43, 0x30, 0x41, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, - 0x45, 0x01, 0x07, 0x36, 0x30, 0x33, 0x30, 0x31, 0x06, 0x08, 0x2b, 0x06, - 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x25, 0x68, 0x74, 0x74, 0x70, - 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x0d, 0x06, - 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, - 0x03, 0x82, 0x01, 0x01, 0x00, 0xa3, 0x58, 0x1e, 0xc6, 0x43, 0x32, 0xac, - 0xac, 0x2f, 0x93, 0x78, 0xb7, 0xea, 0xae, 0x54, 0x40, 0x47, 0x2d, 0x7e, - 0x78, 0x8d, 0x50, 0xf6, 0xf8, 0x66, 0xac, 0xd6, 0x4f, 0x73, 0xd6, 0x44, - 0xef, 0xaf, 0x0b, 0xcc, 0x5b, 0xc1, 0xf4, 0x4f, 0x9a, 0x8f, 0x49, 0x7e, - 0x60, 0xaf, 0xc2, 0x27, 0xc7, 0x16, 0xf1, 0xfb, 0x93, 0x81, 0x90, 0xa9, - 0x7c, 0xef, 0x6f, 0x7e, 0x6e, 0x45, 0x94, 0x16, 0x84, 0xbd, 0xec, 0x49, - 0xf1, 0xc4, 0x0e, 0xf4, 0xaf, 0x04, 0x59, 0x83, 0x87, 0x0f, 0x2c, 0x3b, - 0x97, 0xc3, 0x5a, 0x12, 0x9b, 0x7b, 0x04, 0x35, 0x7b, 0xa3, 0x95, 0x33, - 0x08, 0x7b, 0x93, 0x71, 0x22, 0x42, 0xb3, 0xa9, 0xd9, 0x6f, 0x4f, 0x81, - 0x92, 0xfc, 0x07, 0xb6, 0x79, 0xbc, 0x84, 0x4a, 0x9d, 0x77, 0x09, 0xf1, - 0xc5, 0x89, 0xf2, 0xf0, 0xb4, 0x9c, 0x54, 0xaa, 0x12, 0x7b, 0x0d, 0xba, - 0x4f, 0xef, 0x93, 0x19, 0xec, 0xef, 0x7d, 0x4e, 0x61, 0xa3, 0x8e, 0x76, - 0x9c, 0x59, 0xcf, 0x8c, 0x94, 0xb1, 0x84, 0x97, 0xf7, 0x1a, 0xb9, 0x07, - 0xb8, 0xb2, 0xc6, 0x4f, 0x13, 0x79, 0xdb, 0xbf, 0x4f, 0x51, 0x1b, 0x7f, - 0x69, 0x0d, 0x51, 0x2a, 0xc1, 0xd6, 0x15, 0xff, 0x37, 0x51, 0x34, 0x65, - 0x51, 0xf4, 0x1e, 0xbe, 0x38, 0x6a, 0xec, 0x0e, 0xab, 0xbf, 0x3d, 0x7b, - 0x39, 0x05, 0x7b, 0xf4, 0xf3, 0xfb, 0x1a, 0xa1, 0xd0, 0xc8, 0x7e, 0x4e, - 0x64, 0x8d, 0xcd, 0x8c, 0x61, 0x55, 0x90, 0xfe, 0x3a, 0xca, 0x5d, 0x25, - 0x0f, 0xf8, 0x1d, 0xa3, 0x4a, 0x74, 0x56, 0x4f, 0x1a, 0x55, 0x40, 0x70, - 0x75, 0x25, 0xa6, 0x33, 0x2e, 0xba, 0x4b, 0xa5, 0x5d, 0x53, 0x9a, 0x0d, - 0x30, 0xe1, 0x8d, 0x5f, 0x61, 0x2c, 0xaf, 0xcc, 0xef, 0xb0, 0x99, 0xa1, - 0x80, 0xff, 0x0b, 0xf2, 0x62, 0x4c, 0x70, 0x26, 0x98, -} - -var certSet2Cert7 = []byte{ - 0x30, 0x82, 0x04, 0x2b, 0x30, 0x82, 0x03, 0x13, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x12, 0x11, 0x20, 0x96, 0xf6, 0xc8, 0x03, 0x7c, 0x9e, 0x07, - 0xb1, 0x38, 0xbf, 0x2e, 0x72, 0x10, 0x8a, 0xd7, 0xed, 0x30, 0x0d, 0x06, - 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, - 0x30, 0x3d, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, - 0x02, 0x46, 0x52, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0a, - 0x13, 0x08, 0x43, 0x65, 0x72, 0x74, 0x70, 0x6c, 0x75, 0x73, 0x31, 0x1b, - 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x43, 0x6c, 0x61, - 0x73, 0x73, 0x20, 0x32, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, - 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x37, 0x30, 0x36, 0x30, - 0x35, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x39, - 0x30, 0x36, 0x32, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, - 0x40, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x46, 0x52, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x09, 0x4b, 0x45, 0x59, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x53, 0x31, 0x1d, - 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x14, 0x43, 0x4c, 0x41, - 0x53, 0x53, 0x20, 0x32, 0x20, 0x4b, 0x45, 0x59, 0x4e, 0x45, 0x43, 0x54, - 0x49, 0x53, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, - 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, - 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, - 0x01, 0x00, 0xc6, 0xbe, 0xfe, 0x44, 0x23, 0x04, 0xd4, 0xef, 0x2f, 0x3b, - 0x86, 0xaa, 0x35, 0x58, 0x81, 0xd1, 0xe1, 0x9a, 0xd6, 0xb1, 0xd4, 0x27, - 0x45, 0x28, 0xfc, 0xd1, 0x1e, 0x46, 0x85, 0xba, 0x54, 0x23, 0x11, 0x7d, - 0xe0, 0x66, 0x3f, 0xd4, 0xa3, 0x57, 0x66, 0x78, 0xf9, 0x6b, 0xeb, 0x74, - 0x7c, 0x2a, 0xb8, 0x37, 0xa5, 0xe8, 0x70, 0xae, 0x82, 0xb5, 0x4e, 0xd4, - 0x81, 0xfe, 0x5b, 0xe2, 0xea, 0xe7, 0x22, 0x16, 0xf8, 0xf9, 0xd7, 0xba, - 0x3a, 0xf6, 0x88, 0x56, 0xdc, 0xc4, 0xf2, 0xa0, 0xa4, 0xe5, 0x75, 0x06, - 0x60, 0x72, 0x2b, 0xfb, 0xf5, 0x94, 0xee, 0x2c, 0x83, 0x28, 0xde, 0x91, - 0x9a, 0xb3, 0x83, 0x3a, 0xb0, 0x9f, 0x08, 0xfa, 0xdd, 0xd8, 0x9e, 0x8c, - 0x24, 0xe6, 0xdf, 0x66, 0x5b, 0xc8, 0x7e, 0xa3, 0x62, 0x4d, 0x3f, 0x3a, - 0x85, 0x23, 0xec, 0xe8, 0x71, 0x8f, 0x0a, 0x00, 0xac, 0x89, 0x6d, 0x7e, - 0xd8, 0x72, 0xe5, 0xdd, 0xc1, 0x94, 0x8e, 0x5f, 0xe4, 0x73, 0xe6, 0xc1, - 0xc6, 0x0c, 0x87, 0x58, 0x4f, 0x37, 0xda, 0xd1, 0xa9, 0x88, 0x26, 0x76, - 0xb4, 0xee, 0x11, 0x8d, 0xf6, 0xad, 0xb2, 0xa7, 0xbc, 0x73, 0xc4, 0xcd, - 0x1c, 0x6e, 0x1a, 0xe6, 0x8d, 0x72, 0x56, 0x44, 0xa0, 0x98, 0xf7, 0x92, - 0xf9, 0xd7, 0x79, 0x9b, 0x03, 0xe6, 0x68, 0x5f, 0xa4, 0x5c, 0x7c, 0x3d, - 0x50, 0xb4, 0x83, 0xcc, 0xe5, 0xac, 0x0d, 0xe1, 0x3e, 0x4f, 0x14, 0xf2, - 0xb4, 0xe4, 0x7d, 0xbf, 0x71, 0xa4, 0xc3, 0x97, 0x73, 0x38, 0xd6, 0x52, - 0x7c, 0xc8, 0xa4, 0xb5, 0xea, 0xe9, 0xb2, 0x54, 0x56, 0xd4, 0xeb, 0xb8, - 0x57, 0x3a, 0x40, 0x52, 0x5a, 0x5e, 0x46, 0x27, 0xa3, 0x7b, 0x30, 0x2d, - 0x08, 0x3d, 0x85, 0x1e, 0x9a, 0xf0, 0x32, 0xa8, 0xf2, 0x10, 0xa2, 0x83, - 0x9b, 0xe2, 0x28, 0xf6, 0x9d, 0xcb, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, - 0x82, 0x01, 0x20, 0x30, 0x82, 0x01, 0x1c, 0x30, 0x12, 0x06, 0x03, 0x55, - 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, - 0x02, 0x01, 0x00, 0x30, 0x7d, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x76, - 0x30, 0x74, 0x30, 0x38, 0x06, 0x0b, 0x2b, 0x06, 0x04, 0x01, 0x81, 0xad, - 0x5a, 0x02, 0x05, 0x03, 0x03, 0x30, 0x29, 0x30, 0x27, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1b, 0x68, 0x74, 0x74, - 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x6b, 0x65, 0x79, 0x6e, - 0x65, 0x63, 0x74, 0x69, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, 0x43, - 0x30, 0x38, 0x06, 0x0b, 0x2b, 0x06, 0x04, 0x01, 0x81, 0xad, 0x5a, 0x02, - 0x05, 0x01, 0x03, 0x30, 0x29, 0x30, 0x27, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1b, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x6b, 0x65, 0x79, 0x6e, 0x65, 0x63, - 0x74, 0x69, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, 0x43, 0x30, 0x37, - 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x30, 0x30, 0x2e, 0x30, 0x2c, 0xa0, - 0x2a, 0xa0, 0x28, 0x86, 0x26, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x77, 0x77, 0x77, 0x2e, 0x63, 0x65, 0x72, 0x74, 0x70, 0x6c, 0x75, 0x73, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x63, 0x6c, 0x61, - 0x73, 0x73, 0x32, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, - 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, - 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x00, 0x11, - 0x41, 0xdf, 0x3b, 0x9d, 0x3b, 0xcb, 0xb8, 0xa2, 0xc1, 0x33, 0x92, 0xa8, - 0x81, 0xcc, 0xe5, 0x7d, 0xe7, 0x99, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, - 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xe3, 0x73, 0x2d, 0xdf, 0xcb, - 0x0e, 0x28, 0x0c, 0xde, 0xdd, 0xb3, 0xa4, 0xca, 0x79, 0xb8, 0x8e, 0xbb, - 0xe8, 0x30, 0x89, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, - 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x08, - 0x88, 0xfe, 0x1f, 0xa2, 0xca, 0xcd, 0xe2, 0xa0, 0xf1, 0x2e, 0x7c, 0x67, - 0x49, 0xfb, 0xdc, 0x94, 0xac, 0x7f, 0x41, 0x0d, 0x78, 0x01, 0xba, 0x31, - 0xf7, 0x9b, 0xfb, 0x31, 0x18, 0x77, 0x2f, 0x66, 0x25, 0x94, 0xb8, 0x6d, - 0x16, 0x74, 0x81, 0xf1, 0xc0, 0xae, 0x67, 0xc6, 0x14, 0x45, 0x7a, 0x01, - 0xd1, 0x13, 0x88, 0xfc, 0xe2, 0x8d, 0x22, 0x1d, 0xbd, 0x1e, 0x0c, 0xc7, - 0xa9, 0x7e, 0xd0, 0xc3, 0x97, 0xf6, 0x37, 0x5b, 0x41, 0x5e, 0x67, 0x94, - 0x8e, 0xab, 0x69, 0x02, 0x17, 0x18, 0xf5, 0x4d, 0x38, 0xc2, 0x49, 0x28, - 0x09, 0x6e, 0x5a, 0x9b, 0xa6, 0x27, 0xdb, 0xc0, 0x5f, 0x8f, 0x44, 0x9c, - 0x90, 0x65, 0x99, 0xd8, 0xb3, 0x2e, 0xc1, 0x92, 0xee, 0x1a, 0x9d, 0x0f, - 0x72, 0x45, 0x20, 0xfa, 0x2c, 0x0c, 0x9c, 0x5d, 0xcd, 0x5b, 0x54, 0x41, - 0x54, 0x4f, 0xd3, 0xe2, 0xc7, 0x59, 0x84, 0x3f, 0x17, 0x7b, 0x7d, 0x0e, - 0xc2, 0xef, 0x62, 0xc7, 0xba, 0xb1, 0x26, 0x6c, 0x83, 0x4e, 0xd3, 0x19, - 0xc5, 0xff, 0x56, 0xa7, 0xb4, 0x45, 0x3f, 0x7a, 0x9e, 0xfa, 0xd0, 0x39, - 0x3e, 0x80, 0x46, 0x75, 0x5d, 0x5a, 0x79, 0x7a, 0x33, 0xc5, 0x01, 0xbc, - 0x02, 0x44, 0xce, 0x1b, 0xc0, 0x31, 0x4e, 0x47, 0x96, 0x15, 0x6e, 0xe7, - 0xe4, 0x76, 0xf0, 0xc2, 0x90, 0x0d, 0xa1, 0x78, 0xf4, 0x38, 0x00, 0x91, - 0x2b, 0x65, 0x7c, 0x79, 0x13, 0xa8, 0x3e, 0x91, 0x14, 0xdc, 0x88, 0x05, - 0x08, 0xd7, 0x6f, 0x53, 0xf6, 0x15, 0x43, 0xee, 0xc5, 0x53, 0x56, 0x1a, - 0x02, 0xb5, 0xa6, 0xa2, 0x46, 0x8d, 0x1e, 0x13, 0xe4, 0x67, 0xc2, 0x45, - 0x5f, 0x40, 0x5e, 0x10, 0x42, 0x58, 0xb5, 0xcd, 0x44, 0xa3, 0x94, 0x4c, - 0x1c, 0x54, 0x90, 0x4d, 0x91, 0x9a, 0x26, 0x8b, 0xad, 0xa2, 0x80, 0x50, - 0x8d, 0x14, 0x14, -} - -var certSet2Cert8 = []byte{ - 0x30, 0x82, 0x04, 0x38, 0x30, 0x82, 0x03, 0xa1, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x04, 0x07, 0x27, 0x6d, 0xb9, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x75, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f, - 0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b, - 0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, - 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21, - 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43, - 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, - 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, - 0x0d, 0x31, 0x30, 0x31, 0x31, 0x33, 0x30, 0x31, 0x36, 0x33, 0x35, 0x32, - 0x31, 0x5a, 0x17, 0x0d, 0x31, 0x38, 0x30, 0x38, 0x31, 0x30, 0x31, 0x35, - 0x33, 0x34, 0x32, 0x36, 0x5a, 0x30, 0x5a, 0x31, 0x0b, 0x30, 0x09, 0x06, - 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, 0x45, 0x31, 0x12, 0x30, 0x10, - 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09, 0x42, 0x61, 0x6c, 0x74, 0x69, - 0x6d, 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, - 0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, - 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x19, - 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x43, 0x79, - 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x52, 0x6f, 0x6f, - 0x74, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, - 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, - 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa3, 0x04, - 0xbb, 0x22, 0xab, 0x98, 0x3d, 0x57, 0xe8, 0x26, 0x72, 0x9a, 0xb5, 0x79, - 0xd4, 0x29, 0xe2, 0xe1, 0xe8, 0x95, 0x80, 0xb1, 0xb0, 0xe3, 0x5b, 0x8e, - 0x2b, 0x29, 0x9a, 0x64, 0xdf, 0xa1, 0x5d, 0xed, 0xb0, 0x09, 0x05, 0x6d, - 0xdb, 0x28, 0x2e, 0xce, 0x62, 0xa2, 0x62, 0xfe, 0xb4, 0x88, 0xda, 0x12, - 0xeb, 0x38, 0xeb, 0x21, 0x9d, 0xc0, 0x41, 0x2b, 0x01, 0x52, 0x7b, 0x88, - 0x77, 0xd3, 0x1c, 0x8f, 0xc7, 0xba, 0xb9, 0x88, 0xb5, 0x6a, 0x09, 0xe7, - 0x73, 0xe8, 0x11, 0x40, 0xa7, 0xd1, 0xcc, 0xca, 0x62, 0x8d, 0x2d, 0xe5, - 0x8f, 0x0b, 0xa6, 0x50, 0xd2, 0xa8, 0x50, 0xc3, 0x28, 0xea, 0xf5, 0xab, - 0x25, 0x87, 0x8a, 0x9a, 0x96, 0x1c, 0xa9, 0x67, 0xb8, 0x3f, 0x0c, 0xd5, - 0xf7, 0xf9, 0x52, 0x13, 0x2f, 0xc2, 0x1b, 0xd5, 0x70, 0x70, 0xf0, 0x8f, - 0xc0, 0x12, 0xca, 0x06, 0xcb, 0x9a, 0xe1, 0xd9, 0xca, 0x33, 0x7a, 0x77, - 0xd6, 0xf8, 0xec, 0xb9, 0xf1, 0x68, 0x44, 0x42, 0x48, 0x13, 0xd2, 0xc0, - 0xc2, 0xa4, 0xae, 0x5e, 0x60, 0xfe, 0xb6, 0xa6, 0x05, 0xfc, 0xb4, 0xdd, - 0x07, 0x59, 0x02, 0xd4, 0x59, 0x18, 0x98, 0x63, 0xf5, 0xa5, 0x63, 0xe0, - 0x90, 0x0c, 0x7d, 0x5d, 0xb2, 0x06, 0x7a, 0xf3, 0x85, 0xea, 0xeb, 0xd4, - 0x03, 0xae, 0x5e, 0x84, 0x3e, 0x5f, 0xff, 0x15, 0xed, 0x69, 0xbc, 0xf9, - 0x39, 0x36, 0x72, 0x75, 0xcf, 0x77, 0x52, 0x4d, 0xf3, 0xc9, 0x90, 0x2c, - 0xb9, 0x3d, 0xe5, 0xc9, 0x23, 0x53, 0x3f, 0x1f, 0x24, 0x98, 0x21, 0x5c, - 0x07, 0x99, 0x29, 0xbd, 0xc6, 0x3a, 0xec, 0xe7, 0x6e, 0x86, 0x3a, 0x6b, - 0x97, 0x74, 0x63, 0x33, 0xbd, 0x68, 0x18, 0x31, 0xf0, 0x78, 0x8d, 0x76, - 0xbf, 0xfc, 0x9e, 0x8e, 0x5d, 0x2a, 0x86, 0xa7, 0x4d, 0x90, 0xdc, 0x27, - 0x1a, 0x39, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x6a, 0x30, - 0x82, 0x01, 0x66, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, - 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x03, 0x30, - 0x4e, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x47, 0x30, 0x45, 0x30, 0x43, - 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x3b, 0x30, 0x39, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x2d, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x2e, 0x6f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, - 0x6f, 0x72, 0x79, 0x2e, 0x63, 0x66, 0x6d, 0x30, 0x0e, 0x06, 0x03, 0x55, - 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, - 0x81, 0x89, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x81, 0x81, 0x30, 0x7f, - 0xa1, 0x79, 0xa4, 0x77, 0x30, 0x75, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, - 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x18, 0x30, 0x16, 0x06, - 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f, 0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, - 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, - 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, - 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, - 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, - 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, - 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, - 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, - 0x6f, 0x6f, 0x74, 0x82, 0x02, 0x01, 0xa5, 0x30, 0x45, 0x06, 0x03, 0x55, - 0x1d, 0x1f, 0x04, 0x3e, 0x30, 0x3c, 0x30, 0x3a, 0xa0, 0x38, 0xa0, 0x36, - 0x86, 0x34, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, - 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72, 0x75, 0x73, - 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x67, 0x69, 0x2d, 0x62, 0x69, - 0x6e, 0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x32, 0x30, 0x31, 0x38, 0x2f, 0x63, - 0x64, 0x70, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, - 0x0e, 0x04, 0x16, 0x04, 0x14, 0xe5, 0x9d, 0x59, 0x30, 0x82, 0x47, 0x58, - 0xcc, 0xac, 0xfa, 0x08, 0x54, 0x36, 0x86, 0x7b, 0x3a, 0xb5, 0x04, 0x4d, - 0xf0, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x16, 0xb4, 0x2c, 0xc9, - 0xf1, 0x5e, 0xe1, 0xa2, 0x7b, 0x9b, 0x78, 0x20, 0x7a, 0x4a, 0x70, 0x70, - 0x86, 0x19, 0x00, 0xb7, 0x05, 0x2a, 0xe8, 0xc9, 0x25, 0x39, 0x0f, 0xc3, - 0x64, 0x3c, 0x75, 0x09, 0xd9, 0x89, 0x15, 0x80, 0x07, 0xc2, 0x8d, 0xbc, - 0x29, 0xa5, 0x64, 0x50, 0xcf, 0x71, 0x75, 0x47, 0x23, 0xbd, 0x4d, 0xd8, - 0x7f, 0x77, 0x9a, 0x51, 0x10, 0x6e, 0x4e, 0x1f, 0x20, 0x3c, 0x47, 0x9c, - 0x43, 0x74, 0x7f, 0x96, 0x84, 0x10, 0x4c, 0x13, 0x43, 0xbe, 0xf8, 0xe0, - 0x72, 0x2e, 0xff, 0xbf, 0xae, 0x3c, 0x0a, 0x03, 0x60, 0x82, 0x4b, 0x6f, - 0xf9, 0x9a, 0xc5, 0x1e, 0xf6, 0xaf, 0x90, 0x3b, 0x9f, 0x61, 0x3b, 0x3e, - 0xde, 0x9b, 0x05, 0x1a, 0xc6, 0x2c, 0x3c, 0x57, 0x21, 0x08, 0x0f, 0x54, - 0xfa, 0x28, 0x63, 0x6c, 0xe8, 0x1b, 0x9c, 0x0f, 0xcf, 0xdd, 0x30, 0x44, - 0x13, 0xb9, 0x57, 0xfe, -} - -var certSet2Cert9 = []byte{ - 0x30, 0x82, 0x04, 0x44, 0x30, 0x82, 0x03, 0x2c, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x03, 0x02, 0x3a, 0x78, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x42, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x34, 0x30, - 0x38, 0x32, 0x39, 0x32, 0x32, 0x32, 0x34, 0x35, 0x38, 0x5a, 0x17, 0x0d, - 0x32, 0x32, 0x30, 0x35, 0x32, 0x30, 0x32, 0x32, 0x32, 0x34, 0x35, 0x38, - 0x5a, 0x30, 0x66, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, - 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x1d, 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, - 0x0b, 0x13, 0x14, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x20, 0x56, 0x61, - 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x64, 0x20, 0x53, 0x53, 0x4c, 0x31, - 0x20, 0x30, 0x1e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x17, 0x47, 0x65, - 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x44, 0x56, 0x20, 0x53, 0x53, - 0x4c, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, 0x34, 0x30, 0x82, 0x01, - 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, - 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xdf, 0x41, 0x94, 0x7a, 0xda, 0xf7, - 0xe4, 0x31, 0x43, 0xb6, 0xea, 0x01, 0x1b, 0x5c, 0xce, 0x63, 0xea, 0xfa, - 0x6d, 0xa3, 0xd9, 0x6a, 0xee, 0x2d, 0x9a, 0x75, 0xf9, 0xd5, 0x9c, 0x5b, - 0xbd, 0x34, 0xdf, 0xd8, 0x1c, 0xc9, 0x6d, 0xd8, 0x04, 0x88, 0xda, 0x6e, - 0xb5, 0xb7, 0xb5, 0xf0, 0x30, 0xae, 0x40, 0xd6, 0x5d, 0xfa, 0xc4, 0x53, - 0xc1, 0xd4, 0x22, 0x9d, 0x04, 0x4e, 0x11, 0xa6, 0x95, 0xd5, 0x45, 0x7c, - 0x41, 0x05, 0x58, 0xe0, 0x4c, 0xdd, 0xf9, 0xee, 0x55, 0xbd, 0x5f, 0x46, - 0xdc, 0xad, 0x13, 0x08, 0x9d, 0x2c, 0xe4, 0xf7, 0x82, 0xe6, 0x07, 0x2b, - 0x9e, 0x0e, 0x8c, 0x34, 0xa1, 0xce, 0xc4, 0xa1, 0xe0, 0x81, 0x70, 0x86, - 0x00, 0x06, 0x3f, 0x2d, 0xea, 0x7c, 0x9b, 0x28, 0xae, 0x1b, 0x28, 0x8b, - 0x39, 0x09, 0xd3, 0xe7, 0xf0, 0x45, 0xa4, 0xb1, 0xba, 0x11, 0x67, 0x90, - 0x55, 0x7b, 0x8f, 0xde, 0xed, 0x38, 0x5c, 0xa1, 0xe1, 0xe3, 0x83, 0xc4, - 0xc3, 0x72, 0x91, 0x4f, 0x98, 0xee, 0x1c, 0xc2, 0x80, 0xaa, 0x64, 0xa5, - 0x3e, 0x83, 0x62, 0x1c, 0xcc, 0xe0, 0x9e, 0xf8, 0x5a, 0xc0, 0x13, 0x12, - 0x7d, 0xa2, 0xa7, 0x8b, 0xa3, 0xe7, 0x9f, 0x2a, 0xd7, 0x9b, 0xca, 0xcb, - 0xed, 0x97, 0x01, 0x9c, 0x28, 0x84, 0x51, 0x04, 0x50, 0x41, 0xbc, 0xb4, - 0xfc, 0x78, 0xe9, 0x1b, 0xcf, 0x14, 0xea, 0x1f, 0x0f, 0xfc, 0x2e, 0x01, - 0x32, 0x8d, 0xb6, 0x35, 0xcb, 0x0a, 0x18, 0x3b, 0xec, 0x5a, 0x3e, 0x3c, - 0x1b, 0xd3, 0x99, 0x43, 0x1e, 0x2f, 0xf7, 0xbd, 0xf3, 0x5b, 0x12, 0xb9, - 0x07, 0x5e, 0xed, 0x3e, 0xd1, 0xa9, 0x87, 0xcc, 0x77, 0x72, 0x27, 0xd4, - 0xd9, 0x75, 0xa2, 0x63, 0x4b, 0x93, 0x36, 0xbd, 0xe5, 0x5c, 0xd7, 0xbf, - 0x5f, 0x79, 0x0d, 0xb3, 0x32, 0xa7, 0x0b, 0xb2, 0x63, 0x23, 0x02, 0x03, - 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x1d, 0x30, 0x82, 0x01, 0x19, 0x30, - 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, - 0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb, 0xab, 0x05, 0x64, 0x0c, 0x11, - 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc, 0x4e, 0x30, 0x1d, 0x06, 0x03, - 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x0b, 0x50, 0xec, 0x77, 0xef, - 0x2a, 0x9b, 0xff, 0xec, 0x03, 0xa1, 0x0a, 0xff, 0xad, 0xc6, 0xe4, 0x2a, - 0x18, 0xc7, 0x3e, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, - 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, - 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, - 0x02, 0x01, 0x06, 0x30, 0x35, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2e, - 0x30, 0x2c, 0x30, 0x2a, 0xa0, 0x28, 0xa0, 0x26, 0x86, 0x24, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x67, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x67, 0x74, - 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x2e, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x22, - 0x30, 0x20, 0x30, 0x1e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x30, 0x01, 0x86, 0x12, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x67, - 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x64, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x4c, - 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x45, 0x30, 0x43, 0x30, 0x41, 0x06, - 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x07, 0x36, 0x30, - 0x33, 0x30, 0x31, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, - 0x01, 0x16, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, - 0x77, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x2f, 0x63, 0x70, 0x73, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, - 0x33, 0x24, 0xd5, 0x90, 0xaa, 0x29, 0x0c, 0x35, 0xb9, 0x2f, 0xc3, 0xc7, - 0x42, 0x93, 0xc0, 0xc6, 0x10, 0x4b, 0x03, 0x08, 0x76, 0x84, 0x10, 0xa2, - 0xe0, 0xe7, 0x53, 0x12, 0x27, 0xf2, 0x0a, 0xda, 0x7f, 0x3a, 0xdc, 0xfd, - 0x5c, 0x79, 0x5a, 0x8f, 0x17, 0x74, 0x43, 0x53, 0xb1, 0xd5, 0xd1, 0x5d, - 0x59, 0xb9, 0xa6, 0x84, 0x64, 0xca, 0xf1, 0x3a, 0x0a, 0x59, 0x96, 0x10, - 0xbf, 0xa9, 0x81, 0x57, 0x8b, 0x5c, 0x87, 0xdc, 0x7f, 0xe3, 0xe4, 0xbb, - 0x05, 0x7a, 0xa0, 0x32, 0x09, 0x13, 0x4e, 0x10, 0x81, 0x28, 0x1f, 0x9c, - 0x03, 0x62, 0xbc, 0xf4, 0x01, 0xb5, 0x29, 0x83, 0x46, 0x07, 0xb9, 0xe7, - 0xb8, 0x5d, 0xc8, 0xe9, 0xd1, 0xdd, 0xad, 0x3b, 0xf8, 0x34, 0xdb, 0xc1, - 0xd1, 0x95, 0xa9, 0x91, 0x18, 0xed, 0x3c, 0x2c, 0x37, 0x11, 0x4d, 0xcc, - 0xfe, 0x53, 0x3e, 0x50, 0x43, 0xf9, 0xc3, 0x56, 0x41, 0xac, 0x53, 0x9b, - 0x6c, 0x05, 0xb2, 0x9a, 0xe2, 0xe0, 0x59, 0x57, 0x30, 0x32, 0xb6, 0x26, - 0x4e, 0x13, 0x25, 0xcd, 0xfa, 0x48, 0x70, 0x0f, 0x75, 0x55, 0x60, 0x11, - 0xf5, 0x3b, 0xd5, 0x5e, 0x5a, 0x3c, 0x8b, 0x5b, 0x0f, 0x0f, 0x62, 0x42, - 0x48, 0x61, 0x85, 0x8b, 0x10, 0xf4, 0xc1, 0x88, 0xbf, 0x7f, 0x5f, 0x8a, - 0xc2, 0xd7, 0xcd, 0x2b, 0x94, 0x5c, 0x1f, 0x34, 0x4a, 0x08, 0xaf, 0xeb, - 0xae, 0x89, 0xa8, 0x48, 0x75, 0x55, 0x95, 0x1d, 0xbb, 0xc0, 0x9a, 0x01, - 0xb9, 0xf4, 0x03, 0x22, 0x3e, 0xd4, 0xe6, 0x52, 0x30, 0x0d, 0x67, 0xb9, - 0xc0, 0x91, 0xfd, 0x2d, 0x4c, 0x30, 0x8e, 0xbd, 0x8c, 0xa5, 0x04, 0x91, - 0xbb, 0xa4, 0xab, 0x7f, 0x0f, 0xd8, 0x6f, 0xf0, 0x66, 0x00, 0xc9, 0xa3, - 0x5c, 0xf5, 0xb0, 0x8f, 0x83, 0xe6, 0x9c, 0x5a, 0xe6, 0xb6, 0xb9, 0xc5, - 0xbc, 0xbe, 0xe4, 0x02, -} - -var certSet2Cert10 = []byte{ - 0x30, 0x82, 0x04, 0x45, 0x30, 0x82, 0x03, 0xae, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x33, 0x65, 0x50, 0x08, 0x79, 0xad, 0x73, 0xe2, 0x30, - 0xb9, 0xe0, 0x1d, 0x0d, 0x7f, 0xac, 0x91, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81, - 0xce, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x5a, 0x41, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, - 0x0c, 0x57, 0x65, 0x73, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x43, 0x61, 0x70, - 0x65, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x09, - 0x43, 0x61, 0x70, 0x65, 0x20, 0x54, 0x6f, 0x77, 0x6e, 0x31, 0x1d, 0x30, - 0x1b, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x14, 0x54, 0x68, 0x61, 0x77, - 0x74, 0x65, 0x20, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x74, 0x69, 0x6e, - 0x67, 0x20, 0x63, 0x63, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, - 0x0b, 0x13, 0x1f, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x73, 0x20, 0x44, 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x21, - 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x18, 0x54, 0x68, 0x61, - 0x77, 0x74, 0x65, 0x20, 0x50, 0x72, 0x65, 0x6d, 0x69, 0x75, 0x6d, 0x20, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x31, 0x28, 0x30, - 0x26, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, - 0x16, 0x19, 0x70, 0x72, 0x65, 0x6d, 0x69, 0x75, 0x6d, 0x2d, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x40, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, - 0x63, 0x6f, 0x6d, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x31, - 0x37, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30, - 0x31, 0x32, 0x33, 0x30, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, - 0x81, 0xa9, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, - 0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, - 0x13, 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, - 0x63, 0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, - 0x1f, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, - 0x44, 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x38, 0x30, 0x36, - 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, - 0x30, 0x30, 0x36, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, - 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, - 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, - 0x55, 0x04, 0x03, 0x13, 0x16, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, - 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, - 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, - 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, - 0xac, 0xa0, 0xf0, 0xfb, 0x80, 0x59, 0xd4, 0x9c, 0xc7, 0xa4, 0xcf, 0x9d, - 0xa1, 0x59, 0x73, 0x09, 0x10, 0x45, 0x0c, 0x0d, 0x2c, 0x6e, 0x68, 0xf1, - 0x6c, 0x5b, 0x48, 0x68, 0x49, 0x59, 0x37, 0xfc, 0x0b, 0x33, 0x19, 0xc2, - 0x77, 0x7f, 0xcc, 0x10, 0x2d, 0x95, 0x34, 0x1c, 0xe6, 0xeb, 0x4d, 0x09, - 0xa7, 0x1c, 0xd2, 0xb8, 0xc9, 0x97, 0x36, 0x02, 0xb7, 0x89, 0xd4, 0x24, - 0x5f, 0x06, 0xc0, 0xcc, 0x44, 0x94, 0x94, 0x8d, 0x02, 0x62, 0x6f, 0xeb, - 0x5a, 0xdd, 0x11, 0x8d, 0x28, 0x9a, 0x5c, 0x84, 0x90, 0x10, 0x7a, 0x0d, - 0xbd, 0x74, 0x66, 0x2f, 0x6a, 0x38, 0xa0, 0xe2, 0xd5, 0x54, 0x44, 0xeb, - 0x1d, 0x07, 0x9f, 0x07, 0xba, 0x6f, 0xee, 0xe9, 0xfd, 0x4e, 0x0b, 0x29, - 0xf5, 0x3e, 0x84, 0xa0, 0x01, 0xf1, 0x9c, 0xab, 0xf8, 0x1c, 0x7e, 0x89, - 0xa4, 0xe8, 0xa1, 0xd8, 0x71, 0x65, 0x0d, 0xa3, 0x51, 0x7b, 0xee, 0xbc, - 0xd2, 0x22, 0x60, 0x0d, 0xb9, 0x5b, 0x9d, 0xdf, 0xba, 0xfc, 0x51, 0x5b, - 0x0b, 0xaf, 0x98, 0xb2, 0xe9, 0x2e, 0xe9, 0x04, 0xe8, 0x62, 0x87, 0xde, - 0x2b, 0xc8, 0xd7, 0x4e, 0xc1, 0x4c, 0x64, 0x1e, 0xdd, 0xcf, 0x87, 0x58, - 0xba, 0x4a, 0x4f, 0xca, 0x68, 0x07, 0x1d, 0x1c, 0x9d, 0x4a, 0xc6, 0xd5, - 0x2f, 0x91, 0xcc, 0x7c, 0x71, 0x72, 0x1c, 0xc5, 0xc0, 0x67, 0xeb, 0x32, - 0xfd, 0xc9, 0x92, 0x5c, 0x94, 0xda, 0x85, 0xc0, 0x9b, 0xbf, 0x53, 0x7d, - 0x2b, 0x09, 0xf4, 0x8c, 0x9d, 0x91, 0x1f, 0x97, 0x6a, 0x52, 0xcb, 0xde, - 0x09, 0x36, 0xa4, 0x77, 0xd8, 0x7b, 0x87, 0x50, 0x44, 0xd5, 0x3e, 0x6e, - 0x29, 0x69, 0xfb, 0x39, 0x49, 0x26, 0x1e, 0x09, 0xa5, 0x80, 0x7b, 0x40, - 0x2d, 0xeb, 0xe8, 0x27, 0x85, 0xc9, 0xfe, 0x61, 0xfd, 0x7e, 0xe6, 0x7c, - 0x97, 0x1d, 0xd5, 0x9d, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xc2, - 0x30, 0x81, 0xbf, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, - 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x3b, 0x06, 0x03, - 0x55, 0x1d, 0x20, 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0x06, 0x04, 0x55, - 0x1d, 0x20, 0x00, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x73, - 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, - 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x0e, 0x06, - 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, - 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, - 0x7b, 0x5b, 0x45, 0xcf, 0xaf, 0xce, 0xcb, 0x7a, 0xfd, 0x31, 0x92, 0x1a, - 0x6a, 0xb6, 0xf3, 0x46, 0xeb, 0x57, 0x48, 0x50, 0x30, 0x40, 0x06, 0x03, - 0x55, 0x1d, 0x1f, 0x04, 0x39, 0x30, 0x37, 0x30, 0x35, 0xa0, 0x33, 0xa0, - 0x31, 0x86, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, - 0x6c, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x50, 0x72, 0x65, 0x6d, 0x69, - 0x75, 0x6d, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x41, 0x2e, 0x63, - 0x72, 0x6c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, - 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x84, 0xa8, 0x4c, - 0xc9, 0x3e, 0x2a, 0xbc, 0x9a, 0xe2, 0xcc, 0x8f, 0x0b, 0xb2, 0x25, 0x77, - 0xc4, 0x61, 0x89, 0x89, 0x63, 0x5a, 0xd4, 0xa3, 0x15, 0x40, 0xd4, 0xfb, - 0x5e, 0x3f, 0xb4, 0x43, 0xea, 0x63, 0x17, 0x2b, 0x6b, 0x99, 0x74, 0x9e, - 0x09, 0xa8, 0xdd, 0xd4, 0x56, 0x15, 0x2e, 0x7a, 0x79, 0x31, 0x5f, 0x63, - 0x96, 0x53, 0x1b, 0x34, 0xd9, 0x15, 0xea, 0x4f, 0x6d, 0x70, 0xca, 0xbe, - 0xf6, 0x82, 0xa9, 0xed, 0xda, 0x85, 0x77, 0xcc, 0x76, 0x1c, 0x6a, 0x81, - 0x0a, 0x21, 0xd8, 0x41, 0x99, 0x7f, 0x5e, 0x2e, 0x82, 0xc1, 0xe8, 0xaa, - 0xf7, 0x93, 0x81, 0x05, 0xaa, 0x92, 0xb4, 0x1f, 0xb7, 0x9a, 0xc0, 0x07, - 0x17, 0xf5, 0xcb, 0xc6, 0xb4, 0x4c, 0x0e, 0xd7, 0x56, 0xdc, 0x71, 0x20, - 0x74, 0x38, 0xd6, 0x74, 0xc6, 0xd6, 0x8f, 0x6b, 0xaf, 0x8b, 0x8d, 0xa0, - 0x6c, 0x29, 0x0b, 0x61, 0xe0, -} - -var certSet2Cert11 = []byte{ - 0x30, 0x82, 0x04, 0x4d, 0x30, 0x82, 0x03, 0x35, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x44, 0x4e, 0xf0, - 0x36, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, - 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09, 0x06, - 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17, - 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61, - 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31, - 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x52, 0x6f, - 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, - 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x1e, - 0x17, 0x0d, 0x31, 0x34, 0x30, 0x32, 0x32, 0x30, 0x31, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x34, 0x30, 0x32, 0x32, 0x30, 0x31, - 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x4c, 0x31, 0x0b, 0x30, 0x09, - 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, - 0x17, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, - 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x19, 0x41, - 0x6c, 0x70, 0x68, 0x61, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x20, 0x2d, - 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x2d, 0x20, 0x47, 0x32, - 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, - 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xda, 0x01, 0xec, - 0xe4, 0xec, 0x73, 0x60, 0xfb, 0x7e, 0x8f, 0x6a, 0xb7, 0xc6, 0x17, 0xe3, - 0x92, 0x64, 0x32, 0xd4, 0xac, 0x00, 0xd9, 0xa2, 0x0f, 0xb9, 0xed, 0xee, - 0x6b, 0x8a, 0x86, 0xca, 0x92, 0x67, 0xd9, 0x74, 0xd7, 0x5d, 0x47, 0x02, - 0x3c, 0x8f, 0x40, 0xd6, 0x9e, 0x6d, 0x14, 0xcd, 0xc3, 0xda, 0x29, 0x39, - 0xa7, 0x0f, 0x05, 0x0a, 0x68, 0xa2, 0x66, 0x1a, 0x1e, 0xc4, 0xb2, 0x8b, - 0x76, 0x58, 0xe5, 0xab, 0x5d, 0x1d, 0x8f, 0x40, 0xb3, 0x39, 0x8b, 0xef, - 0x1e, 0x83, 0x7d, 0x22, 0xd0, 0xe3, 0xa9, 0x00, 0x2e, 0xec, 0x53, 0xcf, - 0x62, 0x19, 0x85, 0x44, 0x28, 0x4c, 0xc0, 0x27, 0xcb, 0x7b, 0x0e, 0xec, - 0x10, 0x64, 0x00, 0x10, 0xa4, 0x05, 0xcc, 0xa0, 0x72, 0xbe, 0x41, 0x6c, - 0x31, 0x5b, 0x48, 0xe4, 0xb1, 0xec, 0xb9, 0x23, 0xeb, 0x55, 0x4d, 0xd0, - 0x7d, 0x62, 0x4a, 0xa5, 0xb4, 0xa5, 0xa4, 0x59, 0x85, 0xc5, 0x25, 0x91, - 0xa6, 0xfe, 0xa6, 0x09, 0x9f, 0x06, 0x10, 0x6d, 0x8f, 0x81, 0x0c, 0x64, - 0x40, 0x5e, 0x73, 0x00, 0x9a, 0xe0, 0x2e, 0x65, 0x98, 0x54, 0x10, 0x00, - 0x70, 0x98, 0xc8, 0xe1, 0xed, 0x34, 0x5f, 0xd8, 0x9c, 0xc7, 0x0d, 0xc0, - 0xd6, 0x23, 0x59, 0x45, 0xfc, 0xfe, 0x55, 0x7a, 0x86, 0xee, 0x94, 0x60, - 0x22, 0xf1, 0xae, 0xd1, 0xe6, 0x55, 0x46, 0xf6, 0x99, 0xc5, 0x1b, 0x08, - 0x74, 0x5f, 0xac, 0xb0, 0x64, 0x84, 0x8f, 0x89, 0x38, 0x1c, 0xa1, 0xa7, - 0x90, 0x21, 0x4f, 0x02, 0x6e, 0xbd, 0xe0, 0x61, 0x67, 0xd4, 0xf8, 0x42, - 0x87, 0x0f, 0x0a, 0xf7, 0xc9, 0x04, 0x6d, 0x2a, 0xa9, 0x2f, 0xef, 0x42, - 0xa5, 0xdf, 0xdd, 0xa3, 0x53, 0xdb, 0x98, 0x1e, 0x81, 0xf9, 0x9a, 0x72, - 0x7b, 0x5a, 0xde, 0x4f, 0x3e, 0x7f, 0xa2, 0x58, 0xa0, 0xe2, 0x17, 0xad, - 0x67, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x23, 0x30, 0x82, - 0x01, 0x1f, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, - 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, - 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, - 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, - 0x14, 0xf5, 0xcd, 0xd5, 0x3c, 0x08, 0x50, 0xf9, 0x6a, 0x4f, 0x3a, 0xb7, - 0x97, 0xda, 0x56, 0x83, 0xe6, 0x69, 0xd2, 0x68, 0xf7, 0x30, 0x45, 0x06, - 0x03, 0x55, 0x1d, 0x20, 0x04, 0x3e, 0x30, 0x3c, 0x30, 0x3a, 0x06, 0x04, - 0x55, 0x1d, 0x20, 0x00, 0x30, 0x32, 0x30, 0x30, 0x06, 0x08, 0x2b, 0x06, - 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x24, 0x68, 0x74, 0x74, 0x70, - 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, - 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x30, 0x33, 0x06, 0x03, - 0x55, 0x1d, 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, - 0x24, 0x86, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, - 0x6c, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, - 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, - 0x6c, 0x30, 0x3d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, - 0x01, 0x04, 0x31, 0x30, 0x2f, 0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, - 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x6f, - 0x6f, 0x74, 0x72, 0x31, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, - 0x18, 0x30, 0x16, 0x80, 0x14, 0x60, 0x7b, 0x66, 0x1a, 0x45, 0x0d, 0x97, - 0xca, 0x89, 0x50, 0x2f, 0x7d, 0x04, 0xcd, 0x34, 0xa8, 0xff, 0xfc, 0xfd, - 0x4b, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x60, 0x40, 0x68, - 0x16, 0x47, 0xe7, 0x16, 0x8d, 0xdb, 0x5c, 0xa1, 0x56, 0x2a, 0xcb, 0xf4, - 0x5c, 0x9b, 0xb0, 0x1e, 0xa2, 0x4b, 0xf5, 0xcb, 0x02, 0x3f, 0xf8, 0x0b, - 0xa1, 0xf2, 0xa7, 0x42, 0xd4, 0xb7, 0x4c, 0xeb, 0xe3, 0x66, 0x80, 0xf3, - 0x25, 0x43, 0x78, 0x2e, 0x1b, 0x17, 0x56, 0x07, 0x52, 0x18, 0xcb, 0xd1, - 0xa8, 0xec, 0xe6, 0xfb, 0x73, 0x3e, 0xa4, 0x62, 0x8c, 0x80, 0xb4, 0xd2, - 0xc5, 0x12, 0x73, 0xa3, 0xd3, 0xfa, 0x02, 0x38, 0xbe, 0x63, 0x3d, 0x84, - 0xb8, 0x99, 0xc1, 0xf1, 0xba, 0xf7, 0x9f, 0xc3, 0x40, 0xd1, 0x58, 0x18, - 0x53, 0xc1, 0x62, 0xdd, 0xaf, 0x18, 0x42, 0x7f, 0x34, 0x4e, 0xc5, 0x43, - 0xd5, 0x71, 0xb0, 0x30, 0x00, 0xc7, 0xe3, 0x90, 0xae, 0x3f, 0x57, 0x86, - 0x97, 0xce, 0xea, 0x0c, 0x12, 0x8e, 0x22, 0x70, 0xe3, 0x66, 0xa7, 0x54, - 0x7f, 0x2e, 0x28, 0xcb, 0xd4, 0x54, 0xd0, 0xb3, 0x1e, 0x62, 0x67, 0x08, - 0xf9, 0x27, 0xe1, 0xcb, 0xe3, 0x66, 0xb8, 0x24, 0x1b, 0x89, 0x6a, 0x89, - 0x44, 0x65, 0xf2, 0xd9, 0x4c, 0xd2, 0x58, 0x1c, 0x8c, 0x4e, 0xc0, 0x95, - 0xa1, 0xd4, 0xef, 0x67, 0x2f, 0x38, 0x20, 0xe8, 0x2e, 0xff, 0x96, 0x51, - 0xf0, 0xba, 0xd8, 0x3d, 0x92, 0x70, 0x47, 0x65, 0x1c, 0x9e, 0x73, 0x72, - 0xb4, 0x60, 0x0c, 0x5c, 0xe2, 0xd1, 0x73, 0x76, 0xe0, 0xaf, 0x4e, 0xe2, - 0xe5, 0x37, 0xa5, 0x45, 0x2f, 0x8a, 0x23, 0x3e, 0x87, 0xc7, 0x30, 0xe6, - 0x31, 0x38, 0x7c, 0xf4, 0xdd, 0x52, 0xca, 0xf3, 0x53, 0x04, 0x25, 0x57, - 0x56, 0x66, 0x94, 0xe8, 0x0b, 0xee, 0xe6, 0x03, 0x14, 0x4e, 0xee, 0xfd, - 0x6d, 0x94, 0x64, 0x9e, 0x5e, 0xce, 0x79, 0xd4, 0xb2, 0xa6, 0xcf, 0x40, - 0xb1, 0x44, 0xa8, 0x3e, 0x87, 0x19, 0x5e, 0xe9, 0xf8, 0x21, 0x16, 0x59, - 0x53, -} - -var certSet2Cert12 = []byte{ - 0x30, 0x82, 0x04, 0x4f, 0x30, 0x82, 0x03, 0x37, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x03, 0x02, 0x3a, 0x6f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x42, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x33, 0x31, - 0x31, 0x30, 0x35, 0x32, 0x31, 0x33, 0x36, 0x35, 0x30, 0x5a, 0x17, 0x0d, - 0x32, 0x32, 0x30, 0x35, 0x32, 0x30, 0x32, 0x31, 0x33, 0x36, 0x35, 0x30, - 0x5a, 0x30, 0x44, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, - 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x1d, 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, - 0x03, 0x13, 0x14, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x30, - 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, - 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, - 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xe3, 0xbe, 0x7e, 0x0a, - 0x86, 0xa3, 0xcf, 0x6b, 0x6d, 0x3d, 0x2b, 0xa1, 0x97, 0xad, 0x49, 0x24, - 0x4d, 0xd7, 0x77, 0xb9, 0x34, 0x79, 0x08, 0xa5, 0x9e, 0xa2, 0x9e, 0xde, - 0x47, 0x12, 0x92, 0x3d, 0x7e, 0xea, 0x19, 0x86, 0xb1, 0xe8, 0x4f, 0x3d, - 0x5f, 0xf7, 0xd0, 0xa7, 0x77, 0x9a, 0x5b, 0x1f, 0x0a, 0x03, 0xb5, 0x19, - 0x53, 0xdb, 0xa5, 0x21, 0x94, 0x69, 0x63, 0x9d, 0x6a, 0x4c, 0x91, 0x0c, - 0x10, 0x47, 0xbe, 0x11, 0xfa, 0x6c, 0x86, 0x25, 0xb7, 0xab, 0x04, 0x68, - 0x42, 0x38, 0x09, 0x65, 0xf0, 0x14, 0xda, 0x19, 0x9e, 0xfa, 0x6b, 0x0b, - 0xab, 0x62, 0xef, 0x8d, 0xa7, 0xef, 0x63, 0x70, 0x23, 0xa8, 0xaf, 0x81, - 0xf3, 0xd1, 0x6e, 0x88, 0x67, 0x53, 0xec, 0x12, 0xa4, 0x29, 0x75, 0x8a, - 0xa7, 0xf2, 0x57, 0x3d, 0xa2, 0x83, 0x98, 0x97, 0xf2, 0x0a, 0x7d, 0xd4, - 0xe7, 0x43, 0x6e, 0x30, 0x78, 0x62, 0x22, 0x59, 0x59, 0xb8, 0x71, 0x27, - 0x45, 0xaa, 0x0f, 0x66, 0xc6, 0x55, 0x3f, 0xfa, 0x32, 0x17, 0x2b, 0x31, - 0x8f, 0x46, 0xa0, 0xfa, 0x69, 0x14, 0x7c, 0x9d, 0x9f, 0x5a, 0xe2, 0xeb, - 0x33, 0x4e, 0x10, 0xa6, 0xb3, 0xed, 0x77, 0x63, 0xd8, 0xc3, 0x9e, 0xf4, - 0xdd, 0xdf, 0x79, 0x9a, 0x7a, 0xd4, 0xee, 0xde, 0xdd, 0x9a, 0xcc, 0xc3, - 0xb7, 0xa9, 0x5d, 0xcc, 0x11, 0x3a, 0x07, 0xbb, 0x6f, 0x97, 0xa4, 0x01, - 0x23, 0x47, 0x95, 0x1f, 0xa3, 0x77, 0xfa, 0x58, 0x92, 0xc6, 0xc7, 0xd0, - 0xbd, 0xcf, 0x93, 0x18, 0x42, 0xb7, 0x7e, 0xf7, 0x9e, 0x65, 0xea, 0xd5, - 0x3b, 0xca, 0xed, 0xac, 0xc5, 0x70, 0xa1, 0xfe, 0xd4, 0x10, 0x9a, 0xf0, - 0x12, 0x04, 0x44, 0xac, 0x1a, 0x5b, 0x78, 0x50, 0x45, 0x57, 0x4c, 0x6f, - 0xbd, 0x80, 0xcb, 0x81, 0x5c, 0x2d, 0xb3, 0xbc, 0x76, 0xa1, 0x1e, 0x65, - 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x4a, 0x30, 0x82, 0x01, - 0x46, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, - 0x80, 0x14, 0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb, 0xab, 0x05, 0x64, - 0x0c, 0x11, 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc, 0x4e, 0x30, 0x1d, - 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xd2, 0x6f, 0xf7, - 0x96, 0xf4, 0x85, 0x3f, 0x72, 0x3c, 0x30, 0x7d, 0x23, 0xda, 0x85, 0x78, - 0x9b, 0xa3, 0x7c, 0x5a, 0x7c, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, - 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, - 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, - 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x36, 0x06, 0x03, 0x55, 0x1d, 0x1f, - 0x04, 0x2f, 0x30, 0x2d, 0x30, 0x2b, 0xa0, 0x29, 0xa0, 0x27, 0x86, 0x25, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x67, 0x31, 0x2e, 0x73, 0x79, - 0x6d, 0x63, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, - 0x2f, 0x67, 0x74, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x2e, 0x63, 0x72, - 0x6c, 0x30, 0x2f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, - 0x01, 0x04, 0x23, 0x30, 0x21, 0x30, 0x1f, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x13, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x67, 0x32, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x30, 0x4c, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x45, 0x30, - 0x43, 0x30, 0x41, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, - 0x01, 0x07, 0x36, 0x30, 0x33, 0x30, 0x31, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, - 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x29, 0x06, 0x03, - 0x55, 0x1d, 0x11, 0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, 0x31, - 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x53, 0x79, - 0x6d, 0x61, 0x6e, 0x74, 0x65, 0x63, 0x50, 0x4b, 0x49, 0x2d, 0x31, 0x2d, - 0x35, 0x33, 0x39, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, - 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0xa0, - 0xd4, 0xf7, 0x2c, 0xfb, 0x74, 0x0b, 0x7f, 0x64, 0xf1, 0xcd, 0x43, 0x6a, - 0x9f, 0x62, 0x53, 0x1c, 0x02, 0x7c, 0x98, 0x90, 0xa2, 0xee, 0x4f, 0x68, - 0xd4, 0x20, 0x1a, 0x73, 0x12, 0x3e, 0x77, 0xb3, 0x50, 0xeb, 0x72, 0xbc, - 0xee, 0x88, 0xbe, 0x7f, 0x17, 0xea, 0x77, 0x8f, 0x83, 0x61, 0x95, 0x4f, - 0x84, 0xa1, 0xcb, 0x32, 0x4f, 0x6c, 0x21, 0xbe, 0xd2, 0x69, 0x96, 0x7d, - 0x63, 0xbd, 0xdc, 0x2b, 0xa8, 0x1f, 0xd0, 0x13, 0x84, 0x70, 0xfe, 0xf6, - 0x35, 0x95, 0x89, 0xf9, 0xa6, 0x77, 0xb0, 0x46, 0xc8, 0xbb, 0xb7, 0x13, - 0xf5, 0xc9, 0x60, 0x69, 0xd6, 0x4c, 0xfe, 0xd2, 0x8e, 0xef, 0xd3, 0x60, - 0xc1, 0x80, 0x80, 0xe1, 0xe7, 0xfb, 0x8b, 0x6f, 0x21, 0x79, 0x4a, 0xe0, - 0xdc, 0xa9, 0x1b, 0xc1, 0xb7, 0xfb, 0xc3, 0x49, 0x59, 0x5c, 0xb5, 0x77, - 0x07, 0x44, 0xd4, 0x97, 0xfc, 0x49, 0x00, 0x89, 0x6f, 0x06, 0x4e, 0x01, - 0x70, 0x19, 0xac, 0x2f, 0x11, 0xc0, 0xe2, 0xe6, 0x0f, 0x2f, 0x86, 0x4b, - 0x8d, 0x7b, 0xc3, 0xb9, 0xa7, 0x2e, 0xf4, 0xf1, 0xac, 0x16, 0x3e, 0x39, - 0x49, 0x51, 0x9e, 0x17, 0x4b, 0x4f, 0x10, 0x3a, 0x5b, 0xa5, 0xa8, 0x92, - 0x6f, 0xfd, 0xfa, 0xd6, 0x0b, 0x03, 0x4d, 0x47, 0x56, 0x57, 0x19, 0xf3, - 0xcb, 0x6b, 0xf5, 0xf3, 0xd6, 0xcf, 0xb0, 0xf5, 0xf5, 0xa3, 0x11, 0xd2, - 0x20, 0x53, 0x13, 0x34, 0x37, 0x05, 0x2c, 0x43, 0x5a, 0x63, 0xdf, 0x8d, - 0x40, 0xd6, 0x85, 0x1e, 0x51, 0xe9, 0x51, 0x17, 0x1e, 0x03, 0x56, 0xc9, - 0xf1, 0x30, 0xad, 0xe7, 0x9b, 0x11, 0xa2, 0xb9, 0xd0, 0x31, 0x81, 0x9b, - 0x68, 0xb1, 0xd9, 0xe8, 0xf3, 0xe6, 0x94, 0x7e, 0xc7, 0xae, 0x13, 0x2f, - 0x87, 0xed, 0xd0, 0x25, 0xb0, 0x68, 0xf9, 0xde, 0x08, 0x5a, 0xf3, 0x29, - 0xcc, 0xd4, 0x92, -} - -var certSet2Cert13 = []byte{ - 0x30, 0x82, 0x04, 0x59, 0x30, 0x82, 0x03, 0x41, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x03, 0x02, 0x3a, 0x63, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x42, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x32, 0x30, - 0x38, 0x32, 0x37, 0x32, 0x30, 0x34, 0x30, 0x34, 0x30, 0x5a, 0x17, 0x0d, - 0x32, 0x32, 0x30, 0x35, 0x32, 0x30, 0x32, 0x30, 0x34, 0x30, 0x34, 0x30, - 0x5a, 0x30, 0x44, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, - 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x1d, 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, - 0x03, 0x13, 0x14, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, - 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, - 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, - 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb9, 0x27, 0xf9, 0x4f, - 0xd8, 0xf6, 0xb7, 0x15, 0x3f, 0x8f, 0xcd, 0xce, 0xd6, 0x8d, 0x1c, 0x6b, - 0xfd, 0x7f, 0xda, 0x54, 0x21, 0x4e, 0x03, 0xd8, 0xca, 0xd0, 0x72, 0x52, - 0x15, 0xb8, 0xc9, 0x82, 0x5b, 0x58, 0x79, 0x84, 0xff, 0x24, 0x72, 0x6f, - 0xf2, 0x69, 0x7f, 0xbc, 0x96, 0xd9, 0x9a, 0x7a, 0xc3, 0x3e, 0xa9, 0xcf, - 0x50, 0x22, 0x13, 0x0e, 0x86, 0x19, 0xdb, 0xe8, 0x49, 0xef, 0x8b, 0xe6, - 0xd6, 0x47, 0xf2, 0xfd, 0x73, 0x45, 0x08, 0xae, 0x8f, 0xac, 0x5e, 0xb6, - 0xf8, 0x9e, 0x7c, 0xf7, 0x10, 0xff, 0x92, 0x43, 0x66, 0xef, 0x1c, 0xd4, - 0xee, 0xa1, 0x46, 0x88, 0x11, 0x89, 0x49, 0x79, 0x7a, 0x25, 0xce, 0x4b, - 0x6a, 0xf0, 0xd7, 0x1c, 0x76, 0x1a, 0x29, 0x3c, 0xc9, 0xe4, 0xfd, 0x1e, - 0x85, 0xdc, 0xe0, 0x31, 0x65, 0x05, 0x47, 0x16, 0xac, 0x0a, 0x07, 0x4b, - 0x2e, 0x70, 0x5e, 0x6b, 0x06, 0xa7, 0x6b, 0x3a, 0x6c, 0xaf, 0x05, 0x12, - 0xc4, 0xb2, 0x11, 0x25, 0xd6, 0x3e, 0x97, 0x29, 0xf0, 0x83, 0x6c, 0x57, - 0x1c, 0xd8, 0xa5, 0xef, 0xcc, 0xec, 0xfd, 0xd6, 0x12, 0xf1, 0x3f, 0xdb, - 0x40, 0xb4, 0xae, 0x0f, 0x18, 0xd3, 0xc5, 0xaf, 0x40, 0x92, 0x5d, 0x07, - 0x5e, 0x4e, 0xfe, 0x62, 0x17, 0x37, 0x89, 0xe9, 0x8b, 0x74, 0x26, 0xa2, - 0xed, 0xb8, 0x0a, 0xe7, 0x6c, 0x15, 0x5b, 0x35, 0x90, 0x72, 0xdd, 0xd8, - 0x4d, 0x21, 0xd4, 0x40, 0x23, 0x5c, 0x8f, 0xee, 0x80, 0x31, 0x16, 0xab, - 0x68, 0x55, 0xf4, 0x0e, 0x3b, 0x54, 0xe9, 0x04, 0x4d, 0xf0, 0xcc, 0x4e, - 0x81, 0x5e, 0xe9, 0x6f, 0x52, 0x69, 0x4e, 0xbe, 0xa6, 0x16, 0x6d, 0x42, - 0xf5, 0x51, 0xff, 0xe0, 0x0b, 0x56, 0x3c, 0x98, 0x4f, 0x73, 0x8f, 0x0e, - 0x6f, 0x1a, 0x23, 0xf1, 0xc9, 0xc8, 0xd9, 0xdf, 0xbc, 0xec, 0x52, 0xd7, - 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x54, 0x30, 0x82, 0x01, - 0x50, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, - 0x80, 0x14, 0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb, 0xab, 0x05, 0x64, - 0x0c, 0x11, 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc, 0x4e, 0x30, 0x1d, - 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x11, 0x4a, 0xd0, - 0x73, 0x39, 0xd5, 0x5b, 0x69, 0x08, 0x5c, 0xba, 0x3d, 0xbf, 0x64, 0x9a, - 0xa8, 0x8b, 0x1c, 0x55, 0xbc, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, - 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, - 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, - 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x3a, 0x06, 0x03, 0x55, 0x1d, 0x1f, - 0x04, 0x33, 0x30, 0x31, 0x30, 0x2f, 0xa0, 0x2d, 0xa0, 0x2b, 0x86, 0x29, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x67, - 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x67, 0x74, 0x67, 0x6c, 0x6f, 0x62, 0x61, - 0x6c, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, - 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, - 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, - 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, - 0x4c, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x45, 0x30, 0x43, 0x30, 0x41, - 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x07, 0x36, - 0x30, 0x33, 0x30, 0x31, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x02, 0x01, 0x16, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, - 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x73, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x2a, 0x06, 0x03, 0x55, 0x1d, 0x11, - 0x04, 0x23, 0x30, 0x21, 0xa4, 0x1f, 0x30, 0x1d, 0x31, 0x1b, 0x30, 0x19, - 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x56, 0x65, 0x72, 0x69, 0x53, - 0x69, 0x67, 0x6e, 0x4d, 0x50, 0x4b, 0x49, 0x2d, 0x32, 0x2d, 0x32, 0x35, - 0x34, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x3c, 0xe5, 0x3d, - 0x5a, 0x1b, 0xa2, 0x37, 0x2a, 0xe3, 0x46, 0xcf, 0x36, 0x96, 0x18, 0x3c, - 0x7b, 0xf1, 0x84, 0xc5, 0x57, 0x86, 0x77, 0x40, 0x9d, 0x35, 0xf0, 0x12, - 0xf0, 0x78, 0x18, 0xfb, 0x22, 0xa4, 0xde, 0x98, 0x4b, 0x78, 0x81, 0xe6, - 0x4d, 0x86, 0xe3, 0x91, 0x0f, 0x42, 0xe3, 0xb9, 0xdc, 0xa0, 0xd6, 0xff, - 0xa9, 0xf8, 0xb1, 0x79, 0x97, 0x99, 0xd1, 0xc3, 0x6c, 0x42, 0xa5, 0x92, - 0x94, 0xe0, 0x5d, 0x0c, 0x33, 0x18, 0x25, 0xc9, 0x2b, 0x95, 0x53, 0xe0, - 0xe5, 0xa9, 0x0c, 0x7d, 0x47, 0xfe, 0x7f, 0x51, 0x31, 0x44, 0x5e, 0xf7, - 0x2a, 0x1e, 0x35, 0xa2, 0x94, 0x32, 0xf7, 0xc9, 0xee, 0xc0, 0xb6, 0xc6, - 0x9a, 0xac, 0xde, 0x99, 0x21, 0x6a, 0x23, 0xa0, 0x38, 0x64, 0xee, 0xa3, - 0xc4, 0x88, 0x73, 0x32, 0x3b, 0x50, 0xce, 0xbf, 0xad, 0xd3, 0x75, 0x1e, - 0xa6, 0xf4, 0xe9, 0xf9, 0x42, 0x6b, 0x60, 0xb2, 0xdd, 0x45, 0xfd, 0x5d, - 0x57, 0x08, 0xce, 0x2d, 0x50, 0xe6, 0x12, 0x32, 0x16, 0x13, 0x8a, 0xf2, - 0x94, 0xa2, 0x9b, 0x47, 0xa8, 0x86, 0x7f, 0xd9, 0x98, 0xe5, 0xf7, 0xe5, - 0x76, 0x74, 0x64, 0xd8, 0x91, 0xbc, 0x84, 0x16, 0x28, 0xd8, 0x25, 0x44, - 0x30, 0x7e, 0x82, 0xd8, 0xac, 0xb1, 0xe4, 0xc0, 0xe4, 0x15, 0x6c, 0xdb, - 0xb6, 0x24, 0x27, 0x02, 0x2a, 0x01, 0x12, 0x85, 0xba, 0x31, 0x88, 0x58, - 0x47, 0x74, 0xe3, 0xb8, 0xd2, 0x64, 0xa6, 0xc3, 0x32, 0x59, 0x2e, 0x29, - 0x4b, 0x45, 0xf1, 0x5b, 0x89, 0x49, 0x2e, 0x82, 0x9a, 0xc6, 0x18, 0x15, - 0x44, 0xd0, 0x2e, 0x64, 0x01, 0x15, 0x68, 0x38, 0xf9, 0xf6, 0xf9, 0x66, - 0x03, 0x0c, 0x55, 0x1b, 0x9d, 0xbf, 0x00, 0x40, 0xae, 0xf0, 0x48, 0x27, - 0x4c, 0xe0, 0x80, 0x5e, 0x2d, 0xb9, 0x2a, 0x15, 0x7a, 0xbc, 0x66, 0xf8, - 0x35, -} - -var certSet2Cert14 = []byte{ - 0x30, 0x82, 0x04, 0x63, 0x30, 0x82, 0x03, 0x4b, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x44, 0x4e, 0xf0, - 0x3e, 0x20, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, - 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09, 0x06, - 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17, - 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61, - 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31, - 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x52, 0x6f, - 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, - 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x1e, - 0x17, 0x0d, 0x31, 0x34, 0x30, 0x32, 0x32, 0x30, 0x31, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x34, 0x30, 0x32, 0x32, 0x30, 0x31, - 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x60, 0x31, 0x0b, 0x30, 0x09, - 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, - 0x17, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, - 0x31, 0x36, 0x30, 0x34, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2d, 0x47, - 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x44, 0x6f, - 0x6d, 0x61, 0x69, 0x6e, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x53, 0x48, 0x41, - 0x32, 0x35, 0x36, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, - 0x02, 0x82, 0x01, 0x01, 0x00, 0xa9, 0xdd, 0xcc, 0x0e, 0xb3, 0xe2, 0x32, - 0x39, 0xdd, 0x49, 0x22, 0xa8, 0x13, 0x69, 0x93, 0x87, 0x88, 0xe1, 0x0c, - 0xee, 0x71, 0x7d, 0xbd, 0x90, 0x87, 0x96, 0x5d, 0x59, 0xf2, 0xcc, 0xb3, - 0xd2, 0x58, 0x57, 0x57, 0xf9, 0x46, 0xef, 0x6c, 0x26, 0xd8, 0x36, 0x42, - 0x8e, 0x7e, 0x30, 0xb3, 0x2f, 0x9a, 0x3e, 0x53, 0x7b, 0x1f, 0x6e, 0xb6, - 0xa2, 0x4c, 0x45, 0x1f, 0x3c, 0xd3, 0x15, 0x93, 0x1c, 0x89, 0xed, 0x3c, - 0xf4, 0x57, 0xde, 0xca, 0xbd, 0xec, 0x06, 0x9a, 0x6a, 0x2a, 0xa0, 0x19, - 0x52, 0x7f, 0x51, 0xd1, 0x74, 0x39, 0x08, 0x9f, 0xab, 0xeb, 0xd7, 0x86, - 0x13, 0x15, 0x97, 0xae, 0x36, 0xc3, 0x54, 0x66, 0x0e, 0x5a, 0xf2, 0xa0, - 0x73, 0x85, 0x31, 0xe3, 0xb2, 0x64, 0x14, 0x6a, 0xff, 0xa5, 0xa2, 0x8e, - 0x24, 0xbb, 0xbd, 0x85, 0x52, 0x15, 0xa2, 0x79, 0xee, 0xf0, 0xb5, 0xee, - 0x3d, 0xb8, 0xf4, 0x7d, 0x80, 0xbc, 0xd9, 0x90, 0x35, 0x65, 0xb8, 0x17, - 0xa9, 0xad, 0xb3, 0x98, 0x9f, 0xa0, 0x7e, 0x7d, 0x6e, 0xfb, 0x3f, 0xad, - 0x7c, 0xc2, 0x1b, 0x59, 0x36, 0x96, 0xda, 0x37, 0x32, 0x4b, 0x4b, 0x5d, - 0x35, 0x02, 0x63, 0x8e, 0xdb, 0xa7, 0xcf, 0x62, 0xee, 0xcc, 0x2e, 0xd4, - 0x8d, 0xc9, 0xbd, 0x3c, 0x6a, 0x91, 0x72, 0xa2, 0x22, 0xa7, 0x72, 0x2d, - 0x20, 0xd1, 0xfa, 0xca, 0x37, 0xda, 0x18, 0x98, 0xe6, 0x16, 0x24, 0x71, - 0x25, 0x4b, 0xc4, 0xe5, 0x7b, 0x89, 0x52, 0x09, 0x02, 0xfd, 0x59, 0x2b, - 0x04, 0x6e, 0xca, 0x07, 0x81, 0xd4, 0xb3, 0xda, 0xda, 0xdb, 0xe3, 0xcc, - 0x80, 0xa8, 0x56, 0x07, 0x06, 0x7c, 0x96, 0x08, 0x37, 0x9d, 0xdb, 0x38, - 0xb6, 0x62, 0x34, 0x91, 0x62, 0x07, 0x74, 0x01, 0x38, 0xd8, 0x72, 0x30, - 0xe2, 0xeb, 0x90, 0x71, 0x26, 0x62, 0xc0, 0x57, 0xf3, 0x02, 0x03, 0x01, - 0x00, 0x01, 0xa3, 0x82, 0x01, 0x25, 0x30, 0x82, 0x01, 0x21, 0x30, 0x0e, - 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, - 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, - 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x1d, - 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xea, 0x4e, 0x7c, - 0xd4, 0x80, 0x2d, 0xe5, 0x15, 0x81, 0x86, 0x26, 0x8c, 0x82, 0x6d, 0xc0, - 0x98, 0xa4, 0xcf, 0x97, 0x0f, 0x30, 0x47, 0x06, 0x03, 0x55, 0x1d, 0x20, - 0x04, 0x40, 0x30, 0x3e, 0x30, 0x3c, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, - 0x30, 0x34, 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x02, 0x01, 0x16, 0x26, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, - 0x77, 0x77, 0x77, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, - 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, - 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x30, 0x33, 0x06, 0x03, 0x55, 0x1d, - 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86, - 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, - 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x6e, - 0x65, 0x74, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, - 0x3d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, - 0x31, 0x30, 0x2f, 0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x30, 0x01, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, - 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x6f, 0x6f, 0x74, - 0x72, 0x31, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, - 0x16, 0x80, 0x14, 0x60, 0x7b, 0x66, 0x1a, 0x45, 0x0d, 0x97, 0xca, 0x89, - 0x50, 0x2f, 0x7d, 0x04, 0xcd, 0x34, 0xa8, 0xff, 0xfc, 0xfd, 0x4b, 0x30, - 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, - 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0xd7, 0x45, 0x9e, 0xa0, 0xdc, - 0xe0, 0xe3, 0x61, 0x5a, 0x0b, 0x7d, 0x77, 0x84, 0x17, 0x2d, 0x65, 0x5a, - 0x82, 0x9a, 0x8d, 0xa3, 0x27, 0x2a, 0x85, 0xf7, 0xc9, 0xef, 0xe9, 0x86, - 0xfd, 0xd4, 0x47, 0xcd, 0x01, 0x52, 0x96, 0xc5, 0x43, 0xbd, 0x37, 0xb1, - 0xe1, 0xb8, 0xf2, 0xa9, 0xd2, 0x8a, 0x11, 0x84, 0x71, 0x91, 0x15, 0x89, - 0xdc, 0x02, 0x9d, 0x0b, 0xcb, 0x6c, 0x33, 0x85, 0x34, 0x28, 0x9e, 0x20, - 0xb2, 0xb1, 0x97, 0xdc, 0x6d, 0x0b, 0x10, 0xc1, 0x3c, 0xcd, 0x5f, 0xea, - 0x5d, 0xd7, 0x98, 0x31, 0xc5, 0x34, 0x99, 0x5c, 0x00, 0x61, 0x55, 0xc4, - 0x1b, 0x02, 0x5b, 0xc5, 0xe3, 0x89, 0xc8, 0xb4, 0xb8, 0x6f, 0x1e, 0x38, - 0xf2, 0x56, 0x26, 0xe9, 0x41, 0xef, 0x3d, 0xcd, 0xac, 0x99, 0x4f, 0x59, - 0x4a, 0x57, 0x2d, 0x4b, 0x7d, 0xae, 0xc7, 0x88, 0xfb, 0xd6, 0x98, 0x3b, - 0xf5, 0xe5, 0xf0, 0xe8, 0x89, 0x89, 0xb9, 0x8b, 0x03, 0xcb, 0x5a, 0x23, - 0x1f, 0xa4, 0xfd, 0xb8, 0xea, 0xfb, 0x2e, 0x9d, 0xae, 0x6a, 0x73, 0x09, - 0xbc, 0xfc, 0xd5, 0xa0, 0xb5, 0x44, 0x82, 0xab, 0x44, 0x91, 0x2e, 0x50, - 0x2e, 0x57, 0xc1, 0x43, 0xd8, 0x91, 0x04, 0x8b, 0xe9, 0x11, 0x2e, 0x5f, - 0xb4, 0x3f, 0x79, 0xdf, 0x1e, 0xfb, 0x3f, 0x30, 0x00, 0x8b, 0x53, 0xe3, - 0xb7, 0x2c, 0x1d, 0x3b, 0x4d, 0x8b, 0xdc, 0xe4, 0x64, 0x1d, 0x04, 0x58, - 0x33, 0xaf, 0x1b, 0x55, 0xe7, 0xab, 0x0c, 0xbf, 0x30, 0x04, 0x74, 0xe4, - 0xf3, 0x0e, 0x2f, 0x30, 0x39, 0x8d, 0x4b, 0x04, 0x8c, 0x1e, 0x75, 0x66, - 0x66, 0x49, 0xe0, 0xbe, 0x40, 0x34, 0xc7, 0x5c, 0x5a, 0x51, 0x92, 0xba, - 0x12, 0x3c, 0x52, 0xd5, 0x04, 0x82, 0x55, 0x2d, 0x67, 0xa5, 0xdf, 0xb7, - 0x95, 0x7c, 0xee, 0x3f, 0xc3, 0x08, 0xba, 0x04, 0xbe, 0xc0, 0x46, -} - -var certSet2Cert15 = []byte{ - 0x30, 0x82, 0x04, 0x69, 0x30, 0x82, 0x03, 0x51, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x44, 0x4e, 0xf0, - 0x42, 0x47, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, - 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09, 0x06, - 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17, - 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61, - 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31, - 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x52, 0x6f, - 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, - 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x1e, - 0x17, 0x0d, 0x31, 0x34, 0x30, 0x32, 0x32, 0x30, 0x31, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x34, 0x30, 0x32, 0x32, 0x30, 0x31, - 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x66, 0x31, 0x0b, 0x30, 0x09, - 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, - 0x17, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, - 0x31, 0x3c, 0x30, 0x3a, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x33, 0x47, - 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x4f, 0x72, - 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x56, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x43, 0x41, - 0x20, 0x2d, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x2d, 0x20, - 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, - 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xc7, - 0x0e, 0x6c, 0x3f, 0x23, 0x93, 0x7f, 0xcc, 0x70, 0xa5, 0x9d, 0x20, 0xc3, - 0x0e, 0x53, 0x3f, 0x7e, 0xc0, 0x4e, 0xc2, 0x98, 0x49, 0xca, 0x47, 0xd5, - 0x23, 0xef, 0x03, 0x34, 0x85, 0x74, 0xc8, 0xa3, 0x02, 0x2e, 0x46, 0x5c, - 0x0b, 0x7d, 0xc9, 0x88, 0x9d, 0x4f, 0x8b, 0xf0, 0xf8, 0x9c, 0x6c, 0x8c, - 0x55, 0x35, 0xdb, 0xbf, 0xf2, 0xb3, 0xea, 0xfb, 0xe3, 0x56, 0xe7, 0x4a, - 0x46, 0xd9, 0x13, 0x22, 0xca, 0x36, 0xd5, 0x9b, 0xc1, 0xa8, 0xe3, 0x96, - 0x43, 0x93, 0xf2, 0x0c, 0xbc, 0xe6, 0xf9, 0xe6, 0xe8, 0x99, 0xc8, 0x63, - 0x48, 0x78, 0x7f, 0x57, 0x36, 0x69, 0x1a, 0x19, 0x1d, 0x5a, 0xd1, 0xd4, - 0x7d, 0xc2, 0x9c, 0xd4, 0x7f, 0xe1, 0x80, 0x12, 0xae, 0x7a, 0xea, 0x88, - 0xea, 0x57, 0xd8, 0xca, 0x0a, 0x0a, 0x3a, 0x12, 0x49, 0xa2, 0x62, 0x19, - 0x7a, 0x0d, 0x24, 0xf7, 0x37, 0xeb, 0xb4, 0x73, 0x92, 0x7b, 0x05, 0x23, - 0x9b, 0x12, 0xb5, 0xce, 0xeb, 0x29, 0xdf, 0xa4, 0x14, 0x02, 0xb9, 0x01, - 0xa5, 0xd4, 0xa6, 0x9c, 0x43, 0x64, 0x88, 0xde, 0xf8, 0x7e, 0xfe, 0xe3, - 0xf5, 0x1e, 0xe5, 0xfe, 0xdc, 0xa3, 0xa8, 0xe4, 0x66, 0x31, 0xd9, 0x4c, - 0x25, 0xe9, 0x18, 0xb9, 0x89, 0x59, 0x09, 0xae, 0xe9, 0x9d, 0x1c, 0x6d, - 0x37, 0x0f, 0x4a, 0x1e, 0x35, 0x20, 0x28, 0xe2, 0xaf, 0xd4, 0x21, 0x8b, - 0x01, 0xc4, 0x45, 0xad, 0x6e, 0x2b, 0x63, 0xab, 0x92, 0x6b, 0x61, 0x0a, - 0x4d, 0x20, 0xed, 0x73, 0xba, 0x7c, 0xce, 0xfe, 0x16, 0xb5, 0xdb, 0x9f, - 0x80, 0xf0, 0xd6, 0x8b, 0x6c, 0xd9, 0x08, 0x79, 0x4a, 0x4f, 0x78, 0x65, - 0xda, 0x92, 0xbc, 0xbe, 0x35, 0xf9, 0xb3, 0xc4, 0xf9, 0x27, 0x80, 0x4e, - 0xff, 0x96, 0x52, 0xe6, 0x02, 0x20, 0xe1, 0x07, 0x73, 0xe9, 0x5d, 0x2b, - 0xbd, 0xb2, 0xf1, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x25, - 0x30, 0x82, 0x01, 0x21, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, - 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, - 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, - 0xff, 0x02, 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, - 0x16, 0x04, 0x14, 0x96, 0xde, 0x61, 0xf1, 0xbd, 0x1c, 0x16, 0x29, 0x53, - 0x1c, 0xc0, 0xcc, 0x7d, 0x3b, 0x83, 0x00, 0x40, 0xe6, 0x1a, 0x7c, 0x30, - 0x47, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x40, 0x30, 0x3e, 0x30, 0x3c, - 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x34, 0x30, 0x32, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x26, 0x68, 0x74, - 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x6c, - 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, - 0x30, 0x33, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, - 0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, - 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x6f, 0x6f, - 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3d, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x31, 0x30, 0x2f, 0x30, 0x2d, 0x06, - 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x21, 0x68, - 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, - 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x72, 0x31, 0x30, 0x1f, 0x06, 0x03, - 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x60, 0x7b, 0x66, - 0x1a, 0x45, 0x0d, 0x97, 0xca, 0x89, 0x50, 0x2f, 0x7d, 0x04, 0xcd, 0x34, - 0xa8, 0xff, 0xfc, 0xfd, 0x4b, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, - 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, - 0x00, 0x46, 0x2a, 0xee, 0x5e, 0xbd, 0xae, 0x01, 0x60, 0x37, 0x31, 0x11, - 0x86, 0x71, 0x74, 0xb6, 0x46, 0x49, 0xc8, 0x10, 0x16, 0xfe, 0x2f, 0x62, - 0x23, 0x17, 0xab, 0x1f, 0x87, 0xf8, 0x82, 0xed, 0xca, 0xdf, 0x0e, 0x2c, - 0xdf, 0x64, 0x75, 0x8e, 0xe5, 0x18, 0x72, 0xa7, 0x8c, 0x3a, 0x8b, 0xc9, - 0xac, 0xa5, 0x77, 0x50, 0xf7, 0xef, 0x9e, 0xa4, 0xe0, 0xa0, 0x8f, 0x14, - 0x57, 0xa3, 0x2a, 0x5f, 0xec, 0x7e, 0x6d, 0x10, 0xe6, 0xba, 0x8d, 0xb0, - 0x08, 0x87, 0x76, 0x0e, 0x4c, 0xb2, 0xd9, 0x51, 0xbb, 0x11, 0x02, 0xf2, - 0x5c, 0xdd, 0x1c, 0xbd, 0xf3, 0x55, 0x96, 0x0f, 0xd4, 0x06, 0xc0, 0xfc, - 0xe2, 0x23, 0x8a, 0x24, 0x70, 0xd3, 0xbb, 0xf0, 0x79, 0x1a, 0xa7, 0x61, - 0x70, 0x83, 0x8a, 0xaf, 0x06, 0xc5, 0x20, 0xd8, 0xa1, 0x63, 0xd0, 0x6c, - 0xae, 0x4f, 0x32, 0xd7, 0xae, 0x7c, 0x18, 0x45, 0x75, 0x05, 0x29, 0x77, - 0xdf, 0x42, 0x40, 0x64, 0x64, 0x86, 0xbe, 0x2a, 0x76, 0x09, 0x31, 0x6f, - 0x1d, 0x24, 0xf4, 0x99, 0xd0, 0x85, 0xfe, 0xf2, 0x21, 0x08, 0xf9, 0xc6, - 0xf6, 0xf1, 0xd0, 0x59, 0xed, 0xd6, 0x56, 0x3c, 0x08, 0x28, 0x03, 0x67, - 0xba, 0xf0, 0xf9, 0xf1, 0x90, 0x16, 0x47, 0xae, 0x67, 0xe6, 0xbc, 0x80, - 0x48, 0xe9, 0x42, 0x76, 0x34, 0x97, 0x55, 0x69, 0x24, 0x0e, 0x83, 0xd6, - 0xa0, 0x2d, 0xb4, 0xf5, 0xf3, 0x79, 0x8a, 0x49, 0x28, 0x74, 0x1a, 0x41, - 0xa1, 0xc2, 0xd3, 0x24, 0x88, 0x35, 0x30, 0x60, 0x94, 0x17, 0xb4, 0xe1, - 0x04, 0x22, 0x31, 0x3d, 0x3b, 0x2f, 0x17, 0x06, 0xb2, 0xb8, 0x9d, 0x86, - 0x2b, 0x5a, 0x69, 0xef, 0x83, 0xf5, 0x4b, 0xc4, 0xaa, 0xb4, 0x2a, 0xf8, - 0x7c, 0xa1, 0xb1, 0x85, 0x94, 0x8c, 0xf4, 0x0c, 0x87, 0x0c, 0xf4, 0xac, - 0x40, 0xf8, 0x59, 0x49, 0x98, -} - -var certSet2Cert16 = []byte{ - 0x30, 0x82, 0x04, 0x6c, 0x30, 0x82, 0x03, 0x54, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x4d, 0x5f, 0x2c, 0x34, 0x08, 0xb2, 0x4c, 0x20, 0xcd, - 0x6d, 0x50, 0x7e, 0x24, 0x4d, 0xc9, 0xec, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81, - 0xa9, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, - 0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1f, - 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x44, - 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x38, 0x30, 0x36, 0x06, - 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, - 0x30, 0x36, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, - 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x16, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50, - 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, - 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x32, 0x30, 0x38, - 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30, - 0x32, 0x30, 0x37, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x3c, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, - 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0d, 0x54, - 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, - 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, - 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0x99, 0xe4, 0x85, - 0x5b, 0x76, 0x49, 0x7d, 0x2f, 0x05, 0xd8, 0xc5, 0xac, 0xc8, 0xc8, 0xa9, - 0xd3, 0xdc, 0x98, 0xe6, 0xd7, 0x34, 0xa6, 0x2f, 0x0c, 0xf2, 0x22, 0x26, - 0xd8, 0xa3, 0xc9, 0x14, 0x4c, 0x8f, 0x05, 0xa4, 0x45, 0xe8, 0x14, 0x0c, - 0x58, 0x90, 0x05, 0x1a, 0xb7, 0xc5, 0xc1, 0x06, 0xa5, 0x80, 0xaf, 0xbb, - 0x1d, 0x49, 0x6b, 0x52, 0x34, 0x88, 0xc3, 0x59, 0xe7, 0xef, 0x6b, 0xc4, - 0x27, 0x41, 0x8c, 0x2b, 0x66, 0x1d, 0xd0, 0xe0, 0xa3, 0x97, 0x98, 0x19, - 0x34, 0x4b, 0x41, 0xd5, 0x98, 0xd5, 0xc7, 0x05, 0xad, 0xa2, 0xe4, 0xd7, - 0xed, 0x0c, 0xad, 0x4f, 0xc1, 0xb5, 0xb0, 0x21, 0xfd, 0x3e, 0x50, 0x53, - 0xb2, 0xc4, 0x90, 0xd0, 0xd4, 0x30, 0x67, 0x6c, 0x9a, 0xf1, 0x0e, 0x74, - 0xc4, 0xc2, 0xdc, 0x8a, 0xe8, 0x97, 0xff, 0xc9, 0x92, 0xae, 0x01, 0x8a, - 0x56, 0x0a, 0x98, 0x32, 0xb0, 0x00, 0x23, 0xec, 0x90, 0x1a, 0x60, 0xc3, - 0xed, 0xbb, 0x3a, 0xcb, 0x0f, 0x63, 0x9f, 0x0d, 0x44, 0xc9, 0x52, 0xe1, - 0x25, 0x96, 0xbf, 0xed, 0x50, 0x95, 0x89, 0x7f, 0x56, 0x14, 0xb1, 0xb7, - 0x61, 0x1d, 0x1c, 0x07, 0x8c, 0x3a, 0x2c, 0xf7, 0xff, 0x80, 0xde, 0x39, - 0x45, 0xd5, 0xaf, 0x1a, 0xd1, 0x78, 0xd8, 0xc7, 0x71, 0x6a, 0xa3, 0x19, - 0xa7, 0x32, 0x50, 0x21, 0xe9, 0xf2, 0x0e, 0xa1, 0xc6, 0x13, 0x03, 0x44, - 0x48, 0xd1, 0x66, 0xa8, 0x52, 0x57, 0xd7, 0x11, 0xb4, 0x93, 0x8b, 0xe5, - 0x99, 0x9f, 0x5d, 0xe7, 0x78, 0x51, 0xe5, 0x4d, 0xf6, 0xb7, 0x59, 0xb4, - 0x76, 0xb5, 0x09, 0x37, 0x4d, 0x06, 0x38, 0x13, 0x7a, 0x1c, 0x08, 0x98, - 0x5c, 0xc4, 0x48, 0x4a, 0xcb, 0x52, 0xa0, 0xa9, 0xf8, 0xb1, 0x9d, 0x8e, - 0x7b, 0x79, 0xb0, 0x20, 0x2f, 0x3c, 0x96, 0xa8, 0x11, 0x62, 0x47, 0xbb, - 0x11, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xfb, 0x30, 0x81, 0xf8, - 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, - 0x04, 0x26, 0x30, 0x24, 0x30, 0x22, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x30, 0x01, 0x86, 0x16, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, - 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, - 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, - 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, - 0x30, 0x34, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2d, 0x30, 0x2b, 0x30, - 0x29, 0xa0, 0x27, 0xa0, 0x25, 0x86, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x50, - 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, - 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x28, - 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x21, 0x30, 0x1f, 0xa4, 0x1d, 0x30, - 0x1b, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x10, - 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x50, 0x4b, 0x49, - 0x2d, 0x32, 0x2d, 0x39, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, - 0x16, 0x04, 0x14, 0xa7, 0xa2, 0x83, 0xbb, 0x34, 0x45, 0x40, 0x3d, 0xfc, - 0xd5, 0x30, 0x4f, 0x12, 0xb9, 0x3e, 0xa1, 0x01, 0x9f, 0xf6, 0xdb, 0x30, - 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, - 0x7b, 0x5b, 0x45, 0xcf, 0xaf, 0xce, 0xcb, 0x7a, 0xfd, 0x31, 0x92, 0x1a, - 0x6a, 0xb6, 0xf3, 0x46, 0xeb, 0x57, 0x48, 0x50, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, - 0x82, 0x01, 0x01, 0x00, 0x80, 0x22, 0x80, 0xe0, 0x6c, 0xc8, 0x95, 0x16, - 0xd7, 0x57, 0x26, 0x87, 0xf3, 0x72, 0x34, 0xdb, 0xc6, 0x72, 0x56, 0x27, - 0x3e, 0xd3, 0x96, 0xf6, 0x2e, 0x25, 0x91, 0xa5, 0x3e, 0x33, 0x97, 0xa7, - 0x4b, 0xe5, 0x2f, 0xfb, 0x25, 0x7d, 0x2f, 0x07, 0x61, 0xfa, 0x6f, 0x83, - 0x74, 0x4c, 0x4c, 0x53, 0x72, 0x20, 0xa4, 0x7a, 0xcf, 0x51, 0x51, 0x56, - 0x81, 0x88, 0xb0, 0x6d, 0x1f, 0x36, 0x2c, 0xc8, 0x2b, 0xb1, 0x88, 0x99, - 0xc1, 0xfe, 0x44, 0xab, 0x48, 0x51, 0x7c, 0xd8, 0xf2, 0x44, 0x64, 0x2a, - 0xd8, 0x71, 0xa7, 0xfb, 0x1a, 0x2f, 0xf9, 0x19, 0x8d, 0x34, 0xb2, 0x23, - 0xbf, 0xc4, 0x4c, 0x55, 0x1d, 0x8e, 0x44, 0xe8, 0xaa, 0x5d, 0x9a, 0xdd, - 0x9f, 0xfd, 0x03, 0xc7, 0xba, 0x24, 0x43, 0x8d, 0x2d, 0x47, 0x44, 0xdb, - 0xf6, 0xd8, 0x98, 0xc8, 0xb2, 0xf9, 0xda, 0xef, 0xed, 0x29, 0x5c, 0x69, - 0x12, 0xfa, 0xd1, 0x23, 0x96, 0x0f, 0xbf, 0x9c, 0x0d, 0xf2, 0x79, 0x45, - 0x53, 0x37, 0x9a, 0x56, 0x2f, 0xe8, 0x57, 0x10, 0x70, 0xf6, 0xee, 0x89, - 0x0c, 0x49, 0x89, 0x9a, 0xc1, 0x23, 0xf5, 0xc2, 0x2a, 0xcc, 0x41, 0xcf, - 0x22, 0xab, 0x65, 0x6e, 0xb7, 0x94, 0x82, 0x6d, 0x2f, 0x40, 0x5f, 0x58, - 0xde, 0xeb, 0x95, 0x2b, 0xa6, 0x72, 0x68, 0x52, 0x19, 0x91, 0x2a, 0xae, - 0x75, 0x9d, 0x4e, 0x92, 0xe6, 0xca, 0xde, 0x54, 0xea, 0x18, 0xab, 0x25, - 0x3c, 0xe6, 0x64, 0xa6, 0x79, 0x1f, 0x26, 0x7d, 0x61, 0xed, 0x7d, 0xd2, - 0xe5, 0x71, 0x55, 0xd8, 0x93, 0x17, 0x7c, 0x14, 0x38, 0x30, 0x3c, 0xdf, - 0x86, 0xe3, 0x4c, 0xad, 0x49, 0xe3, 0x97, 0x59, 0xce, 0x1b, 0x9b, 0x2b, - 0xce, 0xdc, 0x65, 0xd4, 0x0b, 0x28, 0x6b, 0x4e, 0x84, 0x46, 0x51, 0x44, - 0xf7, 0x33, 0x08, 0x2d, 0x58, 0x97, 0x21, 0xae, -} - -var certSet2Cert17 = []byte{ - 0x30, 0x82, 0x04, 0x6e, 0x30, 0x82, 0x03, 0x56, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x6e, 0x8a, 0x90, 0xeb, 0xcf, 0xf0, 0x44, 0x8a, 0x72, - 0x0d, 0x08, 0x05, 0xd0, 0x82, 0xa5, 0x44, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x58, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, - 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, - 0x2e, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x28, - 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x50, 0x72, 0x69, - 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x33, 0x31, 0x30, - 0x33, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, - 0x33, 0x31, 0x30, 0x33, 0x30, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, - 0x30, 0x47, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, - 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, - 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x03, 0x55, 0x04, 0x03, - 0x13, 0x17, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, - 0x56, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, - 0x34, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, - 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, - 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd9, 0xb4, - 0x05, 0xf2, 0x38, 0x67, 0x0f, 0x09, 0xe7, 0x7c, 0xf5, 0x63, 0x2a, 0xe5, - 0xb9, 0x5e, 0xa8, 0x11, 0xae, 0x75, 0x71, 0xd9, 0x4c, 0x84, 0x67, 0xad, - 0x89, 0x5d, 0xfc, 0x28, 0x3d, 0x2a, 0xb0, 0xa5, 0xd5, 0xd4, 0xe6, 0x30, - 0x0a, 0x84, 0xd4, 0xe4, 0x18, 0xcb, 0x85, 0x37, 0xc5, 0x46, 0x71, 0xeb, - 0x1c, 0x7b, 0x69, 0xdb, 0x65, 0x69, 0x8c, 0x30, 0x05, 0x3e, 0x07, 0xe1, - 0x6f, 0x3c, 0xc1, 0x0b, 0x61, 0xe6, 0x38, 0x44, 0xfc, 0xbc, 0x8c, 0x2f, - 0x4e, 0x75, 0x57, 0xf5, 0x96, 0x99, 0x7c, 0x3e, 0x87, 0x1f, 0x0f, 0x90, - 0x4b, 0x70, 0xc3, 0x3f, 0x39, 0x45, 0x3b, 0x3a, 0x6b, 0xcb, 0xbb, 0x7b, - 0x40, 0x54, 0xd1, 0x8b, 0x4b, 0xa1, 0x72, 0xd2, 0x04, 0xe9, 0xe0, 0x72, - 0x1a, 0x93, 0x11, 0x7a, 0x2f, 0xf1, 0xab, 0x9d, 0x9c, 0x98, 0x58, 0xae, - 0x2c, 0xea, 0x77, 0x5f, 0x2f, 0x2e, 0x87, 0xaf, 0xb8, 0x6b, 0xe3, 0xe2, - 0xe2, 0x3f, 0xd6, 0x3d, 0xe0, 0x96, 0x44, 0xdf, 0x11, 0x55, 0x63, 0x52, - 0x2f, 0xf4, 0x26, 0x78, 0xc4, 0x0f, 0x20, 0x4d, 0x0a, 0xc0, 0x68, 0x70, - 0x15, 0x86, 0x38, 0xee, 0xb7, 0x76, 0x88, 0xab, 0x18, 0x8f, 0x4f, 0x35, - 0x1e, 0xd4, 0x8c, 0xc9, 0xdb, 0x7e, 0x3d, 0x44, 0xd4, 0x36, 0x8c, 0xc1, - 0x37, 0xb5, 0x59, 0x5b, 0x87, 0xf9, 0xe9, 0xf1, 0xd4, 0xc5, 0x28, 0xbd, - 0x1d, 0xdc, 0xcc, 0x96, 0x72, 0xd1, 0x7a, 0xa1, 0xa7, 0x20, 0xb5, 0xb8, - 0xaf, 0xf8, 0x6e, 0xa5, 0x60, 0x7b, 0x2b, 0x8d, 0x1f, 0xee, 0xf4, 0x2b, - 0xd6, 0x69, 0xcd, 0xaf, 0xca, 0x80, 0x58, 0x29, 0xe8, 0x4c, 0x00, 0x20, - 0x8a, 0x49, 0x0a, 0x6e, 0x8e, 0x8c, 0xa8, 0xd1, 0x00, 0x12, 0x84, 0xb6, - 0xc5, 0xe2, 0x95, 0xa2, 0xc0, 0x3b, 0xa4, 0x6b, 0xf0, 0x82, 0xd0, 0x96, - 0x5d, 0x25, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x43, 0x30, - 0x82, 0x01, 0x3f, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, - 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, - 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, - 0x02, 0x01, 0x06, 0x30, 0x2f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x01, 0x01, 0x04, 0x23, 0x30, 0x21, 0x30, 0x1f, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x13, 0x68, 0x74, 0x74, - 0x70, 0x3a, 0x2f, 0x2f, 0x67, 0x32, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x47, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, - 0x40, 0x30, 0x3e, 0x30, 0x3c, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, - 0x34, 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, - 0x01, 0x16, 0x26, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, - 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x73, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x34, 0x06, 0x03, 0x55, 0x1d, 0x1f, - 0x04, 0x2d, 0x30, 0x2b, 0x30, 0x29, 0xa0, 0x27, 0xa0, 0x25, 0x86, 0x23, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x67, 0x31, 0x2e, 0x73, 0x79, - 0x6d, 0x63, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x47, 0x65, 0x6f, 0x54, - 0x72, 0x75, 0x73, 0x74, 0x50, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, - 0x29, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, - 0x30, 0x1c, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, - 0x11, 0x53, 0x79, 0x6d, 0x61, 0x6e, 0x74, 0x65, 0x63, 0x50, 0x4b, 0x49, - 0x2d, 0x31, 0x2d, 0x35, 0x33, 0x38, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, - 0x0e, 0x04, 0x16, 0x04, 0x14, 0xde, 0xcf, 0x5c, 0x50, 0xb7, 0xae, 0x02, - 0x1f, 0x15, 0x17, 0xaa, 0x16, 0xe8, 0x0d, 0xb5, 0x28, 0x9d, 0x6a, 0x5a, - 0xf3, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, - 0x80, 0x14, 0x2c, 0xd5, 0x50, 0x41, 0x97, 0x15, 0x8b, 0xf0, 0x8f, 0x36, - 0x61, 0x5b, 0x4a, 0xfb, 0x6b, 0xd9, 0x99, 0xc9, 0x33, 0x92, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, - 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0xb4, 0x8e, 0xbd, 0x07, 0xb9, 0x9a, - 0x85, 0xec, 0x3b, 0x67, 0xbd, 0x07, 0x60, 0x61, 0xe6, 0x84, 0xd1, 0xd4, - 0xef, 0xeb, 0x1b, 0xba, 0x0b, 0x82, 0x4b, 0x95, 0x64, 0xb6, 0x66, 0x53, - 0x23, 0xbd, 0xb7, 0x84, 0xdd, 0xe4, 0x7b, 0x8d, 0x09, 0xda, 0xcf, 0xb2, - 0xf5, 0xf1, 0xc3, 0xbf, 0x87, 0x84, 0xbe, 0x4e, 0xa6, 0xa8, 0xc2, 0xe7, - 0x12, 0x39, 0x28, 0x34, 0xe0, 0xa4, 0x56, 0x44, 0x40, 0x0c, 0x9f, 0x88, - 0xa3, 0x15, 0xd3, 0xe8, 0xd3, 0x5e, 0xe3, 0x1c, 0x04, 0x60, 0xfb, 0x69, - 0x36, 0x4f, 0x6a, 0x7e, 0x0c, 0x2a, 0x28, 0xc1, 0xf3, 0xaa, 0x58, 0x0e, - 0x6c, 0xce, 0x1d, 0x07, 0xc3, 0x4a, 0xc0, 0x9c, 0x8d, 0xc3, 0x74, 0xb1, - 0xae, 0x82, 0xf0, 0x1a, 0xe1, 0xf9, 0x4e, 0x29, 0xbd, 0x46, 0xde, 0xb7, - 0x1d, 0xf9, 0x7d, 0xdb, 0xd9, 0x0f, 0x84, 0xcb, 0x92, 0x45, 0xcc, 0x1c, - 0xb3, 0x18, 0xf6, 0xa0, 0xcf, 0x71, 0x6f, 0x0c, 0x2e, 0x9b, 0xd2, 0x2d, - 0xb3, 0x99, 0x93, 0x83, 0x44, 0xac, 0x15, 0xaa, 0x9b, 0x2e, 0x67, 0xec, - 0x4f, 0x88, 0x69, 0x05, 0x56, 0x7b, 0x8b, 0xb2, 0x43, 0xa9, 0x3a, 0x6c, - 0x1c, 0x13, 0x33, 0x25, 0x1b, 0xfd, 0xa8, 0xc8, 0x57, 0x02, 0xfb, 0x1c, - 0xe0, 0xd1, 0xbd, 0x3b, 0x56, 0x44, 0x65, 0xc3, 0x63, 0xf5, 0x1b, 0xef, - 0xec, 0x30, 0xd9, 0xe3, 0x6e, 0x2e, 0x13, 0xe9, 0x39, 0x08, 0x2a, 0x0c, - 0x72, 0xf3, 0x9a, 0xcc, 0xf6, 0x27, 0x29, 0x84, 0xd3, 0xef, 0x4c, 0xc7, - 0x84, 0x11, 0x65, 0x1f, 0xc6, 0xe3, 0x81, 0x03, 0xdb, 0x87, 0xcc, 0x78, - 0xf7, 0xb5, 0x9d, 0x96, 0x3e, 0x6a, 0x7f, 0xbc, 0x11, 0x85, 0x7a, 0x75, - 0xe6, 0x41, 0x7d, 0x0d, 0xcf, 0xf9, 0xe5, 0x85, 0x69, 0x25, 0x8f, 0xc7, - 0x8d, 0x07, 0x2d, 0xf8, 0x69, 0x0f, 0xcb, 0x41, 0x53, 0x00, -} - -var certSet2Cert18 = []byte{ - 0x30, 0x82, 0x04, 0x7d, 0x30, 0x82, 0x03, 0x65, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x03, 0x1b, 0xe7, 0x15, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x63, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x18, 0x54, - 0x68, 0x65, 0x20, 0x47, 0x6f, 0x20, 0x44, 0x61, 0x64, 0x64, 0x79, 0x20, - 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, - 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x28, 0x47, 0x6f, - 0x20, 0x44, 0x61, 0x64, 0x64, 0x79, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, - 0x20, 0x32, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x34, 0x30, 0x31, 0x30, 0x31, - 0x30, 0x37, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x33, 0x31, 0x30, - 0x35, 0x33, 0x30, 0x30, 0x37, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x81, - 0x83, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, - 0x07, 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31, 0x13, 0x30, 0x11, - 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0a, 0x53, 0x63, 0x6f, 0x74, 0x74, - 0x73, 0x64, 0x61, 0x6c, 0x65, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, - 0x04, 0x0a, 0x13, 0x11, 0x47, 0x6f, 0x44, 0x61, 0x64, 0x64, 0x79, 0x2e, - 0x63, 0x6f, 0x6d, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x31, 0x30, - 0x2f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x28, 0x47, 0x6f, 0x20, 0x44, - 0x61, 0x64, 0x64, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, - 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, - 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, - 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xbf, 0x71, 0x62, - 0x08, 0xf1, 0xfa, 0x59, 0x34, 0xf7, 0x1b, 0xc9, 0x18, 0xa3, 0xf7, 0x80, - 0x49, 0x58, 0xe9, 0x22, 0x83, 0x13, 0xa6, 0xc5, 0x20, 0x43, 0x01, 0x3b, - 0x84, 0xf1, 0xe6, 0x85, 0x49, 0x9f, 0x27, 0xea, 0xf6, 0x84, 0x1b, 0x4e, - 0xa0, 0xb4, 0xdb, 0x70, 0x98, 0xc7, 0x32, 0x01, 0xb1, 0x05, 0x3e, 0x07, - 0x4e, 0xee, 0xf4, 0xfa, 0x4f, 0x2f, 0x59, 0x30, 0x22, 0xe7, 0xab, 0x19, - 0x56, 0x6b, 0xe2, 0x80, 0x07, 0xfc, 0xf3, 0x16, 0x75, 0x80, 0x39, 0x51, - 0x7b, 0xe5, 0xf9, 0x35, 0xb6, 0x74, 0x4e, 0xa9, 0x8d, 0x82, 0x13, 0xe4, - 0xb6, 0x3f, 0xa9, 0x03, 0x83, 0xfa, 0xa2, 0xbe, 0x8a, 0x15, 0x6a, 0x7f, - 0xde, 0x0b, 0xc3, 0xb6, 0x19, 0x14, 0x05, 0xca, 0xea, 0xc3, 0xa8, 0x04, - 0x94, 0x3b, 0x46, 0x7c, 0x32, 0x0d, 0xf3, 0x00, 0x66, 0x22, 0xc8, 0x8d, - 0x69, 0x6d, 0x36, 0x8c, 0x11, 0x18, 0xb7, 0xd3, 0xb2, 0x1c, 0x60, 0xb4, - 0x38, 0xfa, 0x02, 0x8c, 0xce, 0xd3, 0xdd, 0x46, 0x07, 0xde, 0x0a, 0x3e, - 0xeb, 0x5d, 0x7c, 0xc8, 0x7c, 0xfb, 0xb0, 0x2b, 0x53, 0xa4, 0x92, 0x62, - 0x69, 0x51, 0x25, 0x05, 0x61, 0x1a, 0x44, 0x81, 0x8c, 0x2c, 0xa9, 0x43, - 0x96, 0x23, 0xdf, 0xac, 0x3a, 0x81, 0x9a, 0x0e, 0x29, 0xc5, 0x1c, 0xa9, - 0xe9, 0x5d, 0x1e, 0xb6, 0x9e, 0x9e, 0x30, 0x0a, 0x39, 0xce, 0xf1, 0x88, - 0x80, 0xfb, 0x4b, 0x5d, 0xcc, 0x32, 0xec, 0x85, 0x62, 0x43, 0x25, 0x34, - 0x02, 0x56, 0x27, 0x01, 0x91, 0xb4, 0x3b, 0x70, 0x2a, 0x3f, 0x6e, 0xb1, - 0xe8, 0x9c, 0x88, 0x01, 0x7d, 0x9f, 0xd4, 0xf9, 0xdb, 0x53, 0x6d, 0x60, - 0x9d, 0xbf, 0x2c, 0xe7, 0x58, 0xab, 0xb8, 0x5f, 0x46, 0xfc, 0xce, 0xc4, - 0x1b, 0x03, 0x3c, 0x09, 0xeb, 0x49, 0x31, 0x5c, 0x69, 0x46, 0xb3, 0xe0, - 0x47, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x17, 0x30, 0x82, - 0x01, 0x13, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, - 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, - 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, - 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x3a, 0x9a, - 0x85, 0x07, 0x10, 0x67, 0x28, 0xb6, 0xef, 0xf6, 0xbd, 0x05, 0x41, 0x6e, - 0x20, 0xc1, 0x94, 0xda, 0x0f, 0xde, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, - 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xd2, 0xc4, 0xb0, 0xd2, 0x91, - 0xd4, 0x4c, 0x11, 0x71, 0xb3, 0x61, 0xcb, 0x3d, 0xa1, 0xfe, 0xdd, 0xa8, - 0x6a, 0xd4, 0xe3, 0x30, 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x01, 0x01, 0x04, 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, - 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6f, 0x64, - 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x32, 0x06, - 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2b, 0x30, 0x29, 0x30, 0x27, 0xa0, 0x25, - 0xa0, 0x23, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, - 0x72, 0x6c, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x67, 0x64, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, - 0x6c, 0x30, 0x46, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x3f, 0x30, 0x3d, - 0x30, 0x3b, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x33, 0x30, 0x31, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x25, - 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, - 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, - 0x2f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x59, 0x0b, 0x53, - 0xbd, 0x92, 0x86, 0x11, 0xa7, 0x24, 0x7b, 0xed, 0x5b, 0x31, 0xcf, 0x1d, - 0x1f, 0x6c, 0x70, 0xc5, 0xb8, 0x6e, 0xbe, 0x4e, 0xbb, 0xf6, 0xbe, 0x97, - 0x50, 0xe1, 0x30, 0x7f, 0xba, 0x28, 0x5c, 0x62, 0x94, 0xc2, 0xe3, 0x7e, - 0x33, 0xf7, 0xfb, 0x42, 0x76, 0x85, 0xdb, 0x95, 0x1c, 0x8c, 0x22, 0x58, - 0x75, 0x09, 0x0c, 0x88, 0x65, 0x67, 0x39, 0x0a, 0x16, 0x09, 0xc5, 0xa0, - 0x38, 0x97, 0xa4, 0xc5, 0x23, 0x93, 0x3f, 0xb4, 0x18, 0xa6, 0x01, 0x06, - 0x44, 0x91, 0xe3, 0xa7, 0x69, 0x27, 0xb4, 0x5a, 0x25, 0x7f, 0x3a, 0xb7, - 0x32, 0xcd, 0xdd, 0x84, 0xff, 0x2a, 0x38, 0x29, 0x33, 0xa4, 0xdd, 0x67, - 0xb2, 0x85, 0xfe, 0xa1, 0x88, 0x20, 0x1c, 0x50, 0x89, 0xc8, 0xdc, 0x2a, - 0xf6, 0x42, 0x03, 0x37, 0x4c, 0xe6, 0x88, 0xdf, 0xd5, 0xaf, 0x24, 0xf2, - 0xb1, 0xc3, 0xdf, 0xcc, 0xb5, 0xec, 0xe0, 0x99, 0x5e, 0xb7, 0x49, 0x54, - 0x20, 0x3c, 0x94, 0x18, 0x0c, 0xc7, 0x1c, 0x52, 0x18, 0x49, 0xa4, 0x6d, - 0xe1, 0xb3, 0x58, 0x0b, 0xc9, 0xd8, 0xec, 0xd9, 0xae, 0x1c, 0x32, 0x8e, - 0x28, 0x70, 0x0d, 0xe2, 0xfe, 0xa6, 0x17, 0x9e, 0x84, 0x0f, 0xbd, 0x57, - 0x70, 0xb3, 0x5a, 0xe9, 0x1f, 0xa0, 0x86, 0x53, 0xbb, 0xef, 0x7c, 0xff, - 0x69, 0x0b, 0xe0, 0x48, 0xc3, 0xb7, 0x93, 0x0b, 0xc8, 0x0a, 0x54, 0xc4, - 0xac, 0x5d, 0x14, 0x67, 0x37, 0x6c, 0xca, 0xa5, 0x2f, 0x31, 0x08, 0x37, - 0xaa, 0x6e, 0x6f, 0x8c, 0xbc, 0x9b, 0xe2, 0x57, 0x5d, 0x24, 0x81, 0xaf, - 0x97, 0x97, 0x9c, 0x84, 0xad, 0x6c, 0xac, 0x37, 0x4c, 0x66, 0xf3, 0x61, - 0x91, 0x11, 0x20, 0xe4, 0xbe, 0x30, 0x9f, 0x7a, 0xa4, 0x29, 0x09, 0xb0, - 0xe1, 0x34, 0x5f, 0x64, 0x77, 0x18, 0x40, 0x51, 0xdf, 0x8c, 0x30, 0xa6, - 0xaf, -} - -var certSet2Cert19 = []byte{ - 0x30, 0x82, 0x04, 0x8f, 0x30, 0x82, 0x03, 0x77, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x06, 0x9e, 0x1d, 0xb7, 0x7f, 0xcf, 0x1d, 0xfb, 0xa9, - 0x7a, 0xf5, 0xe5, 0xc9, 0xa2, 0x40, 0x37, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x61, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, - 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, - 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, - 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, - 0x63, 0x6f, 0x6d, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x03, 0x55, 0x04, 0x03, - 0x13, 0x17, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x47, - 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, - 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x33, 0x30, 0x33, 0x30, 0x38, 0x31, - 0x32, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x33, 0x30, 0x33, - 0x30, 0x38, 0x31, 0x32, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x48, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, - 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, - 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x19, 0x44, 0x69, - 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, - 0x65, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, - 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, - 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, - 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xbb, 0x57, 0xe4, 0x21, - 0xa9, 0xd5, 0x9b, 0x60, 0x37, 0x7e, 0x8e, 0xa1, 0x61, 0x7f, 0x81, 0xe2, - 0x1a, 0xc2, 0x75, 0x64, 0xd9, 0x91, 0x50, 0x0b, 0xe4, 0x36, 0x44, 0x24, - 0x6e, 0x30, 0xd2, 0x9b, 0x7a, 0x27, 0xfa, 0xc2, 0x6a, 0xae, 0x6a, 0x70, - 0x09, 0x38, 0xb9, 0x20, 0x0a, 0xc8, 0x65, 0x10, 0x4a, 0x88, 0xac, 0x31, - 0xf2, 0xdc, 0x92, 0xf2, 0x63, 0xa1, 0x5d, 0x80, 0x63, 0x59, 0x80, 0x92, - 0x23, 0x1c, 0xe6, 0xef, 0x76, 0x4a, 0x50, 0x35, 0xc9, 0xd8, 0x71, 0x38, - 0xb9, 0xed, 0xf0, 0xe6, 0x42, 0xae, 0xd3, 0x38, 0x26, 0x79, 0x30, 0xf9, - 0x22, 0x94, 0xc6, 0xdb, 0xa6, 0x3f, 0x41, 0x78, 0x90, 0xd8, 0xde, 0x5c, - 0x7e, 0x69, 0x7d, 0xf8, 0x90, 0x15, 0x3a, 0xd0, 0xa1, 0xa0, 0xbe, 0xfa, - 0xb2, 0xb2, 0x19, 0xa1, 0xd8, 0x2b, 0xd1, 0xce, 0xbf, 0x6b, 0xdd, 0x49, - 0xab, 0xa3, 0x92, 0xfe, 0xb5, 0xab, 0xc8, 0xc1, 0x3e, 0xee, 0x01, 0x00, - 0xd8, 0xa9, 0x44, 0xb8, 0x42, 0x73, 0x88, 0xc3, 0x61, 0xf5, 0xab, 0x4a, - 0x83, 0x28, 0x0a, 0xd2, 0xd4, 0x49, 0xfa, 0x6a, 0xb1, 0xcd, 0xdf, 0x57, - 0x2c, 0x94, 0xe5, 0xe2, 0xca, 0x83, 0x5f, 0xb7, 0xba, 0x62, 0x5c, 0x2f, - 0x68, 0xa5, 0xf0, 0xc0, 0xb9, 0xfd, 0x2b, 0xd1, 0xe9, 0x1f, 0xd8, 0x1a, - 0x62, 0x15, 0xbd, 0xff, 0x3d, 0xa6, 0xf7, 0xcb, 0xef, 0xe6, 0xdb, 0x65, - 0x2f, 0x25, 0x38, 0xec, 0xfb, 0xe6, 0x20, 0x66, 0x58, 0x96, 0x34, 0x19, - 0xd2, 0x15, 0xce, 0x21, 0xd3, 0x24, 0xcc, 0xd9, 0x14, 0x6f, 0xd8, 0xfe, - 0x55, 0xc7, 0xe7, 0x6f, 0xb6, 0x0f, 0x1a, 0x8c, 0x49, 0xbe, 0x29, 0xf2, - 0xba, 0x5a, 0x9a, 0x81, 0x26, 0x37, 0x24, 0x6f, 0xd7, 0x48, 0x12, 0x6c, - 0x2e, 0x59, 0xf5, 0x9c, 0x18, 0xbb, 0xd9, 0xf6, 0x68, 0xe2, 0xdf, 0x45, - 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x5a, 0x30, 0x82, 0x01, - 0x56, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, - 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x06, - 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, - 0x86, 0x30, 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, - 0x01, 0x04, 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, - 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x7b, 0x06, 0x03, 0x55, - 0x1d, 0x1f, 0x04, 0x74, 0x30, 0x72, 0x30, 0x37, 0xa0, 0x35, 0xa0, 0x33, - 0x86, 0x31, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, - 0x33, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x47, - 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x2e, - 0x63, 0x72, 0x6c, 0x30, 0x37, 0xa0, 0x35, 0xa0, 0x33, 0x86, 0x31, 0x68, - 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x34, 0x2e, 0x64, - 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x47, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, - 0x30, 0x3d, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, 0x30, 0x34, 0x30, - 0x32, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, 0x30, 0x28, 0x06, - 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, - 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x64, - 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x43, 0x50, 0x53, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, - 0x04, 0x14, 0x90, 0x71, 0xdb, 0x37, 0xeb, 0x73, 0xc8, 0xef, 0xdc, 0xd5, - 0x1e, 0x12, 0xb6, 0x34, 0xba, 0x2b, 0x5a, 0xa0, 0xa6, 0x92, 0x30, 0x1f, - 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x03, - 0xde, 0x50, 0x35, 0x56, 0xd1, 0x4c, 0xbb, 0x66, 0xf0, 0xa3, 0xe2, 0x1b, - 0x1b, 0xc3, 0x97, 0xb2, 0x3d, 0xd1, 0x55, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, - 0x01, 0x01, 0x00, 0x30, 0xce, 0xd1, 0x95, 0x51, 0x00, 0xae, 0x06, 0x0b, - 0xa1, 0x0e, 0x02, 0xc0, 0x17, 0xac, 0xb6, 0x7f, 0x8f, 0x20, 0xf6, 0x40, - 0x75, 0x74, 0x1c, 0xcc, 0x78, 0xb1, 0xa4, 0x4f, 0xea, 0xf4, 0xd0, 0xc4, - 0x9d, 0xa2, 0xde, 0x81, 0x07, 0x26, 0x1f, 0x40, 0x88, 0x51, 0xf0, 0x1f, - 0xcf, 0xb7, 0x4c, 0x40, 0x99, 0xd0, 0xf4, 0x3c, 0x71, 0x98, 0x73, 0x88, - 0x97, 0x2c, 0x19, 0xd7, 0x6e, 0x84, 0x8f, 0xa4, 0x1f, 0x9c, 0x5a, 0x20, - 0xe3, 0x51, 0x5c, 0xb0, 0xc5, 0x9e, 0x99, 0x6a, 0x4f, 0xc8, 0x69, 0xf7, - 0x10, 0xff, 0x4e, 0xad, 0x19, 0xd9, 0xc9, 0x58, 0xb3, 0x33, 0xae, 0x0c, - 0xd9, 0x96, 0x29, 0x9e, 0x71, 0xb2, 0x70, 0x63, 0xa3, 0xb6, 0x99, 0x16, - 0x42, 0x1d, 0x65, 0xf3, 0xf7, 0xa0, 0x1e, 0x7d, 0xc5, 0xd4, 0x65, 0x14, - 0xb2, 0x62, 0x84, 0xd4, 0x6c, 0x5c, 0x08, 0x0c, 0xd8, 0x6c, 0x93, 0x2b, - 0xb4, 0x76, 0x59, 0x8a, 0xd1, 0x7f, 0xff, 0x03, 0xd8, 0xc2, 0x5d, 0xb8, - 0x2f, 0x22, 0xd6, 0x38, 0xf0, 0xf6, 0x9c, 0x6b, 0x7d, 0x46, 0xeb, 0x99, - 0x74, 0xf7, 0xeb, 0x4a, 0x0e, 0xa9, 0xa6, 0x04, 0xeb, 0x7b, 0xce, 0xf0, - 0x5c, 0x6b, 0x98, 0x31, 0x5a, 0x98, 0x40, 0xeb, 0x69, 0xc4, 0x05, 0xf4, - 0x20, 0xa8, 0xca, 0x08, 0x3a, 0x65, 0x6c, 0x38, 0x15, 0xf5, 0x5c, 0x2c, - 0xb2, 0x55, 0xe4, 0x2c, 0x6b, 0x41, 0xf0, 0xbe, 0x5c, 0x46, 0xca, 0x4a, - 0x29, 0xa0, 0x48, 0x5e, 0x20, 0xd2, 0x45, 0xff, 0x05, 0xde, 0x34, 0xaf, - 0x70, 0x4b, 0x81, 0x39, 0xe2, 0xca, 0x07, 0x57, 0x7c, 0xb6, 0x31, 0xdc, - 0x21, 0x29, 0xe2, 0xbe, 0x97, 0x0e, 0x77, 0x90, 0x14, 0x51, 0x40, 0xe1, - 0xbf, 0xe3, 0xcc, 0x1b, 0x19, 0x9c, 0x25, 0xca, 0xa7, 0x06, 0xb2, 0x53, - 0xdf, 0x23, 0xb2, 0xcf, 0x12, 0x19, 0xa3, -} - -var certSet2Cert20 = []byte{ - 0x30, 0x82, 0x04, 0x90, 0x30, 0x82, 0x03, 0xf9, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x1b, 0x09, 0x3b, 0x78, 0x60, 0x96, 0xda, 0x37, 0xbb, - 0xa4, 0x51, 0x94, 0x46, 0xc8, 0x96, 0x78, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5f, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, - 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, - 0x63, 0x2e, 0x31, 0x37, 0x30, 0x35, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, - 0x2e, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, - 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, - 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, - 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x30, 0x38, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x31, 0x30, 0x37, - 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xca, 0x31, 0x0b, - 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, - 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65, - 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56, - 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, - 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3a, 0x30, - 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28, 0x63, 0x29, 0x20, - 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, - 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, - 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, - 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x45, 0x30, - 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56, 0x65, 0x72, 0x69, - 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, - 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, - 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30, 0x82, 0x01, 0x22, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, - 0x02, 0x82, 0x01, 0x01, 0x00, 0xaf, 0x24, 0x08, 0x08, 0x29, 0x7a, 0x35, - 0x9e, 0x60, 0x0c, 0xaa, 0xe7, 0x4b, 0x3b, 0x4e, 0xdc, 0x7c, 0xbc, 0x3c, - 0x45, 0x1c, 0xbb, 0x2b, 0xe0, 0xfe, 0x29, 0x02, 0xf9, 0x57, 0x08, 0xa3, - 0x64, 0x85, 0x15, 0x27, 0xf5, 0xf1, 0xad, 0xc8, 0x31, 0x89, 0x5d, 0x22, - 0xe8, 0x2a, 0xaa, 0xa6, 0x42, 0xb3, 0x8f, 0xf8, 0xb9, 0x55, 0xb7, 0xb1, - 0xb7, 0x4b, 0xb3, 0xfe, 0x8f, 0x7e, 0x07, 0x57, 0xec, 0xef, 0x43, 0xdb, - 0x66, 0x62, 0x15, 0x61, 0xcf, 0x60, 0x0d, 0xa4, 0xd8, 0xde, 0xf8, 0xe0, - 0xc3, 0x62, 0x08, 0x3d, 0x54, 0x13, 0xeb, 0x49, 0xca, 0x59, 0x54, 0x85, - 0x26, 0xe5, 0x2b, 0x8f, 0x1b, 0x9f, 0xeb, 0xf5, 0xa1, 0x91, 0xc2, 0x33, - 0x49, 0xd8, 0x43, 0x63, 0x6a, 0x52, 0x4b, 0xd2, 0x8f, 0xe8, 0x70, 0x51, - 0x4d, 0xd1, 0x89, 0x69, 0x7b, 0xc7, 0x70, 0xf6, 0xb3, 0xdc, 0x12, 0x74, - 0xdb, 0x7b, 0x5d, 0x4b, 0x56, 0xd3, 0x96, 0xbf, 0x15, 0x77, 0xa1, 0xb0, - 0xf4, 0xa2, 0x25, 0xf2, 0xaf, 0x1c, 0x92, 0x67, 0x18, 0xe5, 0xf4, 0x06, - 0x04, 0xef, 0x90, 0xb9, 0xe4, 0x00, 0xe4, 0xdd, 0x3a, 0xb5, 0x19, 0xff, - 0x02, 0xba, 0xf4, 0x3c, 0xee, 0xe0, 0x8b, 0xeb, 0x37, 0x8b, 0xec, 0xf4, - 0xd7, 0xac, 0xf2, 0xf6, 0xf0, 0x3d, 0xaf, 0xdd, 0x75, 0x91, 0x33, 0x19, - 0x1d, 0x1c, 0x40, 0xcb, 0x74, 0x24, 0x19, 0x21, 0x93, 0xd9, 0x14, 0xfe, - 0xac, 0x2a, 0x52, 0xc7, 0x8f, 0xd5, 0x04, 0x49, 0xe4, 0x8d, 0x63, 0x47, - 0x88, 0x3c, 0x69, 0x83, 0xcb, 0xfe, 0x47, 0xbd, 0x2b, 0x7e, 0x4f, 0xc5, - 0x95, 0xae, 0x0e, 0x9d, 0xd4, 0xd1, 0x43, 0xc0, 0x67, 0x73, 0xe3, 0x14, - 0x08, 0x7e, 0xe5, 0x3f, 0x9f, 0x73, 0xb8, 0x33, 0x0a, 0xcf, 0x5d, 0x3f, - 0x34, 0x87, 0x96, 0x8a, 0xee, 0x53, 0xe8, 0x25, 0x15, 0x02, 0x03, 0x01, - 0x00, 0x01, 0xa3, 0x82, 0x01, 0x5b, 0x30, 0x82, 0x01, 0x57, 0x30, 0x0f, - 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, - 0x01, 0x01, 0xff, 0x30, 0x31, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2a, - 0x30, 0x28, 0x30, 0x26, 0xa0, 0x24, 0xa0, 0x22, 0x86, 0x20, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, - 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, - 0x61, 0x33, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, - 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x3d, - 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, 0x30, 0x34, 0x30, 0x32, 0x06, - 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, 0x30, 0x28, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, 0x74, - 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72, - 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, - 0x73, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, - 0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0, 0x30, 0x09, 0xf3, - 0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30, 0x6d, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x0c, 0x04, 0x61, 0x30, 0x5f, - 0xa1, 0x5d, 0xa0, 0x5b, 0x30, 0x59, 0x30, 0x57, 0x30, 0x55, 0x16, 0x09, - 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, 0x69, 0x66, 0x30, 0x21, 0x30, - 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14, - 0x8f, 0xe5, 0xd3, 0x1a, 0x86, 0xac, 0x8d, 0x8e, 0x6b, 0xc3, 0xcf, 0x80, - 0x6a, 0xd4, 0x48, 0x18, 0x2c, 0x7b, 0x19, 0x2e, 0x30, 0x25, 0x16, 0x23, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, - 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x76, 0x73, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x67, 0x69, 0x66, 0x30, - 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, - 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, - 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, - 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, - 0xa3, 0xcd, 0x7d, 0x1e, 0xf7, 0xc7, 0x75, 0x8d, 0x48, 0xe7, 0x56, 0x34, - 0x4c, 0x00, 0x90, 0x75, 0xa9, 0x51, 0xa5, 0x56, 0xc1, 0x6d, 0xbc, 0xfe, - 0xf5, 0x53, 0x22, 0xe9, 0x98, 0xa2, 0xac, 0x9a, 0x7e, 0x70, 0x1e, 0xb3, - 0x8e, 0x3b, 0x45, 0xe3, 0x86, 0x95, 0x31, 0xda, 0x6d, 0x4c, 0xfb, 0x34, - 0x50, 0x80, 0x96, 0xcd, 0x24, 0xf2, 0x40, 0xdf, 0x04, 0x3f, 0xe2, 0x65, - 0xce, 0x34, 0x22, 0x61, 0x15, 0xea, 0x66, 0x70, 0x64, 0xd2, 0xf1, 0x6e, - 0xf3, 0xca, 0x18, 0x59, 0x6a, 0x41, 0x46, 0x7e, 0x82, 0xde, 0x19, 0xb0, - 0x70, 0x31, 0x56, 0x69, 0x0d, 0x0c, 0xe6, 0x1d, 0x9d, 0x71, 0x58, 0xdc, - 0xcc, 0xde, 0x62, 0xf5, 0xe1, 0x7a, 0x10, 0x02, 0xd8, 0x7a, 0xdc, 0x3b, - 0xfa, 0x57, 0xbd, 0xc9, 0xe9, 0x8f, 0x46, 0x21, 0x39, 0x9f, 0x51, 0x65, - 0x4c, 0x8e, 0x3a, 0xbe, 0x28, 0x41, 0x70, 0x1d, -} - -var certSet2Cert21 = []byte{ - 0x30, 0x82, 0x04, 0x94, 0x30, 0x82, 0x03, 0x7c, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x01, 0xfd, 0xa3, 0xeb, 0x6e, 0xca, 0x75, 0xc8, 0x88, - 0x43, 0x8b, 0x72, 0x4b, 0xcf, 0xbc, 0x91, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x61, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, - 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, - 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, - 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, - 0x63, 0x6f, 0x6d, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x03, 0x55, 0x04, 0x03, - 0x13, 0x17, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x47, - 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, - 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x33, 0x30, 0x33, 0x30, 0x38, 0x31, - 0x32, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x33, 0x30, 0x33, - 0x30, 0x38, 0x31, 0x32, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x4d, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, - 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, - 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1e, 0x44, 0x69, - 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x53, 0x48, 0x41, 0x32, 0x20, - 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, - 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, - 0x00, 0xdc, 0xae, 0x58, 0x90, 0x4d, 0xc1, 0xc4, 0x30, 0x15, 0x90, 0x35, - 0x5b, 0x6e, 0x3c, 0x82, 0x15, 0xf5, 0x2c, 0x5c, 0xbd, 0xe3, 0xdb, 0xff, - 0x71, 0x43, 0xfa, 0x64, 0x25, 0x80, 0xd4, 0xee, 0x18, 0xa2, 0x4d, 0xf0, - 0x66, 0xd0, 0x0a, 0x73, 0x6e, 0x11, 0x98, 0x36, 0x17, 0x64, 0xaf, 0x37, - 0x9d, 0xfd, 0xfa, 0x41, 0x84, 0xaf, 0xc7, 0xaf, 0x8c, 0xfe, 0x1a, 0x73, - 0x4d, 0xcf, 0x33, 0x97, 0x90, 0xa2, 0x96, 0x87, 0x53, 0x83, 0x2b, 0xb9, - 0xa6, 0x75, 0x48, 0x2d, 0x1d, 0x56, 0x37, 0x7b, 0xda, 0x31, 0x32, 0x1a, - 0xd7, 0xac, 0xab, 0x06, 0xf4, 0xaa, 0x5d, 0x4b, 0xb7, 0x47, 0x46, 0xdd, - 0x2a, 0x93, 0xc3, 0x90, 0x2e, 0x79, 0x80, 0x80, 0xef, 0x13, 0x04, 0x6a, - 0x14, 0x3b, 0xb5, 0x9b, 0x92, 0xbe, 0xc2, 0x07, 0x65, 0x4e, 0xfc, 0xda, - 0xfc, 0xff, 0x7a, 0xae, 0xdc, 0x5c, 0x7e, 0x55, 0x31, 0x0c, 0xe8, 0x39, - 0x07, 0xa4, 0xd7, 0xbe, 0x2f, 0xd3, 0x0b, 0x6a, 0xd2, 0xb1, 0xdf, 0x5f, - 0xfe, 0x57, 0x74, 0x53, 0x3b, 0x35, 0x80, 0xdd, 0xae, 0x8e, 0x44, 0x98, - 0xb3, 0x9f, 0x0e, 0xd3, 0xda, 0xe0, 0xd7, 0xf4, 0x6b, 0x29, 0xab, 0x44, - 0xa7, 0x4b, 0x58, 0x84, 0x6d, 0x92, 0x4b, 0x81, 0xc3, 0xda, 0x73, 0x8b, - 0x12, 0x97, 0x48, 0x90, 0x04, 0x45, 0x75, 0x1a, 0xdd, 0x37, 0x31, 0x97, - 0x92, 0xe8, 0xcd, 0x54, 0x0d, 0x3b, 0xe4, 0xc1, 0x3f, 0x39, 0x5e, 0x2e, - 0xb8, 0xf3, 0x5c, 0x7e, 0x10, 0x8e, 0x86, 0x41, 0x00, 0x8d, 0x45, 0x66, - 0x47, 0xb0, 0xa1, 0x65, 0xce, 0xa0, 0xaa, 0x29, 0x09, 0x4e, 0xf3, 0x97, - 0xeb, 0xe8, 0x2e, 0xab, 0x0f, 0x72, 0xa7, 0x30, 0x0e, 0xfa, 0xc7, 0xf4, - 0xfd, 0x14, 0x77, 0xc3, 0xa4, 0x5b, 0x28, 0x57, 0xc2, 0xb3, 0xf9, 0x82, - 0xfd, 0xb7, 0x45, 0x58, 0x9b, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, - 0x01, 0x5a, 0x30, 0x82, 0x01, 0x56, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, - 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, - 0x01, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, - 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x34, 0x06, 0x08, 0x2b, 0x06, - 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x28, 0x30, 0x26, 0x30, 0x24, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, - 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, - 0x30, 0x7b, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x74, 0x30, 0x72, 0x30, - 0x37, 0xa0, 0x35, 0xa0, 0x33, 0x86, 0x31, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x33, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, - 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x69, 0x67, 0x69, - 0x43, 0x65, 0x72, 0x74, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x52, 0x6f, - 0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x37, 0xa0, 0x35, - 0xa0, 0x33, 0x86, 0x31, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, - 0x72, 0x6c, 0x34, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, - 0x74, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x52, 0x6f, 0x6f, 0x74, 0x43, - 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x1d, 0x20, - 0x04, 0x36, 0x30, 0x34, 0x30, 0x32, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, - 0x30, 0x2a, 0x30, 0x28, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, - 0x77, 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x50, 0x53, 0x30, 0x1d, 0x06, 0x03, - 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x0f, 0x80, 0x61, 0x1c, 0x82, - 0x31, 0x61, 0xd5, 0x2f, 0x28, 0xe7, 0x8d, 0x46, 0x38, 0xb4, 0x2c, 0xe1, - 0xc6, 0xd9, 0xe2, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, - 0x30, 0x16, 0x80, 0x14, 0x03, 0xde, 0x50, 0x35, 0x56, 0xd1, 0x4c, 0xbb, - 0x66, 0xf0, 0xa3, 0xe2, 0x1b, 0x1b, 0xc3, 0x97, 0xb2, 0x3d, 0xd1, 0x55, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x23, 0x3e, 0xdf, 0x4b, - 0xd2, 0x31, 0x42, 0xa5, 0xb6, 0x7e, 0x42, 0x5c, 0x1a, 0x44, 0xcc, 0x69, - 0xd1, 0x68, 0xb4, 0x5d, 0x4b, 0xe0, 0x04, 0x21, 0x6c, 0x4b, 0xe2, 0x6d, - 0xcc, 0xb1, 0xe0, 0x97, 0x8f, 0xa6, 0x53, 0x09, 0xcd, 0xaa, 0x2a, 0x65, - 0xe5, 0x39, 0x4f, 0x1e, 0x83, 0xa5, 0x6e, 0x5c, 0x98, 0xa2, 0x24, 0x26, - 0xe6, 0xfb, 0xa1, 0xed, 0x93, 0xc7, 0x2e, 0x02, 0xc6, 0x4d, 0x4a, 0xbf, - 0xb0, 0x42, 0xdf, 0x78, 0xda, 0xb3, 0xa8, 0xf9, 0x6d, 0xff, 0x21, 0x85, - 0x53, 0x36, 0x60, 0x4c, 0x76, 0xce, 0xec, 0x38, 0xdc, 0xd6, 0x51, 0x80, - 0xf0, 0xc5, 0xd6, 0xe5, 0xd4, 0x4d, 0x27, 0x64, 0xab, 0x9b, 0xc7, 0x3e, - 0x71, 0xfb, 0x48, 0x97, 0xb8, 0x33, 0x6d, 0xc9, 0x13, 0x07, 0xee, 0x96, - 0xa2, 0x1b, 0x18, 0x15, 0xf6, 0x5c, 0x4c, 0x40, 0xed, 0xb3, 0xc2, 0xec, - 0xff, 0x71, 0xc1, 0xe3, 0x47, 0xff, 0xd4, 0xb9, 0x00, 0xb4, 0x37, 0x42, - 0xda, 0x20, 0xc9, 0xea, 0x6e, 0x8a, 0xee, 0x14, 0x06, 0xae, 0x7d, 0xa2, - 0x59, 0x98, 0x88, 0xa8, 0x1b, 0x6f, 0x2d, 0xf4, 0xf2, 0xc9, 0x14, 0x5f, - 0x26, 0xcf, 0x2c, 0x8d, 0x7e, 0xed, 0x37, 0xc0, 0xa9, 0xd5, 0x39, 0xb9, - 0x82, 0xbf, 0x19, 0x0c, 0xea, 0x34, 0xaf, 0x00, 0x21, 0x68, 0xf8, 0xad, - 0x73, 0xe2, 0xc9, 0x32, 0xda, 0x38, 0x25, 0x0b, 0x55, 0xd3, 0x9a, 0x1d, - 0xf0, 0x68, 0x86, 0xed, 0x2e, 0x41, 0x34, 0xef, 0x7c, 0xa5, 0x50, 0x1d, - 0xbf, 0x3a, 0xf9, 0xd3, 0xc1, 0x08, 0x0c, 0xe6, 0xed, 0x1e, 0x8a, 0x58, - 0x25, 0xe4, 0xb8, 0x77, 0xad, 0x2d, 0x6e, 0xf5, 0x52, 0xdd, 0xb4, 0x74, - 0x8f, 0xab, 0x49, 0x2e, 0x9d, 0x3b, 0x93, 0x34, 0x28, 0x1f, 0x78, 0xce, - 0x94, 0xea, 0xc7, 0xbd, 0xd3, 0xc9, 0x6d, 0x1c, 0xde, 0x5c, 0x32, 0xf3, -} - -var certSet2Cert22 = []byte{ - 0x30, 0x82, 0x04, 0x9a, 0x30, 0x82, 0x03, 0x82, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x0b, 0x1d, 0xb1, 0xa9, 0x19, 0xf2, 0x4c, 0x3c, 0x4e, - 0xfc, 0xb5, 0x7a, 0x6a, 0x4e, 0x6c, 0xbf, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x58, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, - 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, - 0x2e, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x28, - 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x50, 0x72, 0x69, - 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x32, 0x30, 0x38, - 0x32, 0x33, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, - 0x32, 0x30, 0x38, 0x32, 0x32, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, - 0x30, 0x58, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, - 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, - 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x03, - 0x13, 0x28, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, - 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x20, 0x56, 0x61, 0x6c, 0x69, - 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, - 0x41, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, - 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, - 0x01, 0x01, 0x00, 0x9e, 0xc6, 0x21, 0xcd, 0x2e, 0x3d, 0xd0, 0xbb, 0x2a, - 0x4d, 0xa4, 0x7b, 0x1f, 0xa8, 0x1a, 0xc2, 0x03, 0xa6, 0xff, 0x43, 0x62, - 0x5b, 0xbf, 0x91, 0xd1, 0x66, 0x52, 0xa9, 0x81, 0x90, 0x68, 0x31, 0x86, - 0x16, 0xbb, 0x1d, 0x85, 0x58, 0xa9, 0x7e, 0x91, 0x6a, 0x1e, 0x4c, 0x31, - 0xca, 0x21, 0xc4, 0xbe, 0x70, 0x1b, 0x9f, 0x8c, 0xe4, 0x05, 0x2d, 0x9c, - 0xed, 0x11, 0x79, 0xad, 0x8f, 0x9c, 0x25, 0x86, 0x4c, 0xba, 0xf2, 0xe5, - 0x62, 0x79, 0x8e, 0x22, 0x5f, 0x85, 0x7c, 0x22, 0x35, 0x38, 0x23, 0x8d, - 0x80, 0x3c, 0xac, 0xcc, 0x2d, 0xfc, 0x58, 0xf2, 0x35, 0xbf, 0x66, 0x5b, - 0xeb, 0xc1, 0x24, 0xf8, 0x70, 0x80, 0x74, 0x32, 0xf9, 0x46, 0xde, 0x32, - 0x19, 0x80, 0x8c, 0xb7, 0xe7, 0x1a, 0xa1, 0xaa, 0x64, 0x98, 0x8d, 0xca, - 0xce, 0x0e, 0xdc, 0x6b, 0xf7, 0xe2, 0x90, 0x0a, 0x6c, 0x1c, 0xa5, 0xf4, - 0x90, 0x32, 0x52, 0xe5, 0xf1, 0x00, 0x42, 0x31, 0x91, 0x48, 0x42, 0x89, - 0xa8, 0x5d, 0x7f, 0x63, 0x8d, 0x31, 0xb2, 0xd6, 0x48, 0x5c, 0x45, 0x45, - 0x22, 0xc9, 0xc5, 0x59, 0x12, 0xab, 0x41, 0x94, 0xea, 0xfe, 0x9c, 0x46, - 0x4d, 0x9a, 0xbc, 0x9c, 0xe0, 0xe2, 0xc6, 0x46, 0xb3, 0xe6, 0x7f, 0xdc, - 0xf5, 0x0f, 0xa3, 0x13, 0x45, 0x86, 0x6d, 0x79, 0x78, 0xfc, 0xe1, 0x50, - 0xcf, 0x09, 0x86, 0xe5, 0x9f, 0xbf, 0xcb, 0x3a, 0xd4, 0xe0, 0xb1, 0xd4, - 0xff, 0xa8, 0x3f, 0x7d, 0x62, 0x1f, 0xc0, 0x6d, 0x78, 0x48, 0xc3, 0xd7, - 0xa3, 0xa5, 0x23, 0x61, 0xc5, 0x3e, 0x35, 0x4d, 0xb2, 0xe5, 0xf8, 0xfd, - 0x94, 0x4b, 0xbc, 0x73, 0x53, 0xaf, 0xe3, 0x9a, 0x69, 0x55, 0xbe, 0xcb, - 0x67, 0xab, 0xe1, 0xbe, 0xef, 0x1b, 0xc2, 0x4d, 0xac, 0xcb, 0x29, 0x5c, - 0xbc, 0xed, 0xb8, 0x62, 0x9d, 0x10, 0xe9, 0x02, 0x03, 0x01, 0x00, 0x01, - 0xa3, 0x82, 0x01, 0x5e, 0x30, 0x82, 0x01, 0x5a, 0x30, 0x3d, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x31, 0x30, 0x2f, - 0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, - 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x45, 0x56, 0x53, - 0x65, 0x63, 0x75, 0x72, 0x65, 0x2d, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, - 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, - 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, - 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x46, 0x06, 0x03, 0x55, - 0x1d, 0x20, 0x04, 0x3f, 0x30, 0x3d, 0x30, 0x3b, 0x06, 0x04, 0x55, 0x1d, - 0x20, 0x00, 0x30, 0x33, 0x30, 0x31, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x02, 0x01, 0x16, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, - 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, - 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x41, 0x06, 0x03, 0x55, - 0x1d, 0x1f, 0x04, 0x3a, 0x30, 0x38, 0x30, 0x36, 0xa0, 0x34, 0xa0, 0x32, - 0x86, 0x30, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x45, 0x56, 0x53, - 0x65, 0x63, 0x75, 0x72, 0x65, 0x2d, 0x63, 0x72, 0x6c, 0x2e, 0x67, 0x65, - 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x50, 0x43, 0x41, 0x2e, 0x63, - 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, - 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x2a, 0x06, 0x03, 0x55, 0x1d, - 0x11, 0x04, 0x23, 0x30, 0x21, 0xa4, 0x1f, 0x30, 0x1d, 0x31, 0x1b, 0x30, - 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x56, 0x65, 0x72, 0x69, - 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x50, 0x4b, 0x49, 0x2d, 0x32, 0x2d, 0x32, - 0x35, 0x33, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, - 0x14, 0x6f, 0x26, 0x56, 0xd9, 0x5c, 0xe7, 0xf7, 0xc9, 0x04, 0x20, 0xf8, - 0x1e, 0xba, 0x7c, 0x91, 0x27, 0x2f, 0x8c, 0xfa, 0x07, 0x30, 0x1f, 0x06, - 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x2c, 0xd5, - 0x50, 0x41, 0x97, 0x15, 0x8b, 0xf0, 0x8f, 0x36, 0x61, 0x5b, 0x4a, 0xfb, - 0x6b, 0xd9, 0x99, 0xc9, 0x33, 0x92, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, - 0x01, 0x00, 0x92, 0x77, 0xe9, 0x57, 0xc9, 0xeb, 0xc4, 0x45, 0x6f, 0xc9, - 0x4c, 0x6e, 0x7d, 0x00, 0x12, 0x71, 0xa5, 0xe3, 0x39, 0xfe, 0x13, 0x84, - 0x49, 0x6c, 0xe7, 0x49, 0x71, 0xf5, 0x2c, 0xc7, 0xc0, 0x36, 0xc2, 0x08, - 0x58, 0xf3, 0x83, 0x75, 0xc5, 0x72, 0xd8, 0x8d, 0x78, 0xf4, 0x65, 0xea, - 0x8c, 0xd5, 0xe3, 0xa5, 0x0e, 0xa9, 0xad, 0xeb, 0xe3, 0xa1, 0x23, 0xae, - 0x93, 0xb7, 0xd8, 0x75, 0x75, 0x4a, 0x59, 0xcb, 0xf2, 0x9e, 0xdb, 0x40, - 0xbf, 0x4e, 0x89, 0xfe, 0x95, 0x42, 0x29, 0x34, 0x7b, 0xf4, 0xdd, 0x6a, - 0x0d, 0x74, 0x5f, 0xc7, 0x11, 0x13, 0x2e, 0xdd, 0x11, 0x6e, 0xc6, 0xe3, - 0x5b, 0xb3, 0xcf, 0xa6, 0x8d, 0xe5, 0xf7, 0x67, 0x7b, 0xba, 0xb3, 0xb3, - 0x69, 0x70, 0x14, 0xb0, 0xc2, 0x99, 0xb4, 0xd2, 0x76, 0x5b, 0x38, 0x17, - 0x39, 0x45, 0x1b, 0x82, 0xf1, 0x53, 0xb8, 0x3d, 0x55, 0x39, 0x0b, 0x7f, - 0xff, 0x98, 0xad, 0x6e, 0x96, 0x9a, 0xb6, 0x6a, 0x4c, 0x7a, 0x5e, 0xbd, - 0xb1, 0x86, 0x12, 0x9d, 0x7c, 0x2c, 0x62, 0xbb, 0x09, 0x93, 0x5f, 0x3f, - 0xd8, 0xb5, 0x8a, 0xc3, 0x49, 0x28, 0x0f, 0x0b, 0xf9, 0x39, 0x22, 0x1a, - 0xfe, 0x5d, 0xd3, 0xe8, 0x18, 0x5f, 0x9d, 0x5f, 0xb4, 0xc0, 0x20, 0xc6, - 0xa9, 0x49, 0x0d, 0x55, 0x73, 0x6a, 0x09, 0x7a, 0xff, 0xa2, 0x99, 0xbf, - 0xd8, 0xbb, 0x91, 0xdc, 0x30, 0x39, 0xae, 0x28, 0x4b, 0xf6, 0xc5, 0x77, - 0x24, 0xe8, 0xd6, 0xc6, 0xa7, 0xa0, 0x4e, 0xf2, 0xa6, 0x99, 0x75, 0xcd, - 0xdd, 0x57, 0xdd, 0x0a, 0x47, 0x92, 0xcb, 0xbb, 0xb7, 0x48, 0xfa, 0x21, - 0xf0, 0x69, 0x21, 0xff, 0xe5, 0x0c, 0xaa, 0x0c, 0xb1, 0xea, 0xdd, 0x05, - 0x1c, 0x19, 0x8e, 0xd1, 0x2a, 0x79, 0x68, 0x02, 0x5e, 0xcc, 0x38, 0xe6, - 0x29, 0xc4, 0x77, 0xf5, 0x19, 0x1c, -} - -var certSet2Cert23 = []byte{ - 0x30, 0x82, 0x04, 0xa0, 0x30, 0x82, 0x03, 0x88, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x03, 0x39, 0x14, 0x84, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x68, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c, 0x53, - 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x54, 0x65, 0x63, - 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x65, 0x73, 0x2c, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x31, 0x32, 0x30, 0x30, 0x06, 0x03, 0x55, 0x04, 0x0b, - 0x13, 0x29, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, - 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x32, 0x20, 0x43, 0x65, 0x72, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x31, - 0x34, 0x30, 0x31, 0x30, 0x31, 0x30, 0x37, 0x30, 0x30, 0x30, 0x30, 0x5a, - 0x17, 0x0d, 0x33, 0x31, 0x30, 0x35, 0x33, 0x30, 0x30, 0x37, 0x30, 0x30, - 0x30, 0x30, 0x5a, 0x30, 0x81, 0x8f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, - 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, - 0x03, 0x55, 0x04, 0x08, 0x13, 0x07, 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, - 0x61, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0a, - 0x53, 0x63, 0x6f, 0x74, 0x74, 0x73, 0x64, 0x61, 0x6c, 0x65, 0x31, 0x25, - 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c, 0x53, 0x74, 0x61, - 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6e, - 0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x65, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, - 0x2e, 0x31, 0x32, 0x30, 0x30, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x29, - 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x52, 0x6f, - 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, - 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, - 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, - 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, - 0x01, 0x00, 0xbd, 0xed, 0xc1, 0x03, 0xfc, 0xf6, 0x8f, 0xfc, 0x02, 0xb1, - 0x6f, 0x5b, 0x9f, 0x48, 0xd9, 0x9d, 0x79, 0xe2, 0xa2, 0xb7, 0x03, 0x61, - 0x56, 0x18, 0xc3, 0x47, 0xb6, 0xd7, 0xca, 0x3d, 0x35, 0x2e, 0x89, 0x43, - 0xf7, 0xa1, 0x69, 0x9b, 0xde, 0x8a, 0x1a, 0xfd, 0x13, 0x20, 0x9c, 0xb4, - 0x49, 0x77, 0x32, 0x29, 0x56, 0xfd, 0xb9, 0xec, 0x8c, 0xdd, 0x22, 0xfa, - 0x72, 0xdc, 0x27, 0x61, 0x97, 0xee, 0xf6, 0x5a, 0x84, 0xec, 0x6e, 0x19, - 0xb9, 0x89, 0x2c, 0xdc, 0x84, 0x5b, 0xd5, 0x74, 0xfb, 0x6b, 0x5f, 0xc5, - 0x89, 0xa5, 0x10, 0x52, 0x89, 0x46, 0x55, 0xf4, 0xb8, 0x75, 0x1c, 0xe6, - 0x7f, 0xe4, 0x54, 0xae, 0x4b, 0xf8, 0x55, 0x72, 0x57, 0x02, 0x19, 0xf8, - 0x17, 0x71, 0x59, 0xeb, 0x1e, 0x28, 0x07, 0x74, 0xc5, 0x9d, 0x48, 0xbe, - 0x6c, 0xb4, 0xf4, 0xa4, 0xb0, 0xf3, 0x64, 0x37, 0x79, 0x92, 0xc0, 0xec, - 0x46, 0x5e, 0x7f, 0xe1, 0x6d, 0x53, 0x4c, 0x62, 0xaf, 0xcd, 0x1f, 0x0b, - 0x63, 0xbb, 0x3a, 0x9d, 0xfb, 0xfc, 0x79, 0x00, 0x98, 0x61, 0x74, 0xcf, - 0x26, 0x82, 0x40, 0x63, 0xf3, 0xb2, 0x72, 0x6a, 0x19, 0x0d, 0x99, 0xca, - 0xd4, 0x0e, 0x75, 0xcc, 0x37, 0xfb, 0x8b, 0x89, 0xc1, 0x59, 0xf1, 0x62, - 0x7f, 0x5f, 0xb3, 0x5f, 0x65, 0x30, 0xf8, 0xa7, 0xb7, 0x4d, 0x76, 0x5a, - 0x1e, 0x76, 0x5e, 0x34, 0xc0, 0xe8, 0x96, 0x56, 0x99, 0x8a, 0xb3, 0xf0, - 0x7f, 0xa4, 0xcd, 0xbd, 0xdc, 0x32, 0x31, 0x7c, 0x91, 0xcf, 0xe0, 0x5f, - 0x11, 0xf8, 0x6b, 0xaa, 0x49, 0x5c, 0xd1, 0x99, 0x94, 0xd1, 0xa2, 0xe3, - 0x63, 0x5b, 0x09, 0x76, 0xb5, 0x56, 0x62, 0xe1, 0x4b, 0x74, 0x1d, 0x96, - 0xd4, 0x26, 0xd4, 0x08, 0x04, 0x59, 0xd0, 0x98, 0x0e, 0x0e, 0xe6, 0xde, - 0xfc, 0xc3, 0xec, 0x1f, 0x90, 0xf1, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, - 0x82, 0x01, 0x29, 0x30, 0x82, 0x01, 0x25, 0x30, 0x0f, 0x06, 0x03, 0x55, - 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, - 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, - 0x03, 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, - 0x16, 0x04, 0x14, 0x7c, 0x0c, 0x32, 0x1f, 0xa7, 0xd9, 0x30, 0x7f, 0xc4, - 0x7d, 0x68, 0xa3, 0x62, 0xa8, 0xa1, 0xce, 0xab, 0x07, 0x5b, 0x27, 0x30, - 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, - 0xbf, 0x5f, 0xb7, 0xd1, 0xce, 0xdd, 0x1f, 0x86, 0xf4, 0x5b, 0x55, 0xac, - 0xdc, 0xd7, 0x10, 0xc2, 0x0e, 0xa9, 0x88, 0xe7, 0x30, 0x3a, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x2e, 0x30, 0x2c, - 0x30, 0x2a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, - 0x86, 0x1e, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, - 0x70, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x74, - 0x65, 0x63, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x38, 0x06, 0x03, - 0x55, 0x1d, 0x1f, 0x04, 0x31, 0x30, 0x2f, 0x30, 0x2d, 0xa0, 0x2b, 0xa0, - 0x29, 0x86, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, - 0x6c, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x74, - 0x65, 0x63, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x66, 0x72, 0x6f, - 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x4c, 0x06, 0x03, 0x55, 0x1d, - 0x20, 0x04, 0x45, 0x30, 0x43, 0x30, 0x41, 0x06, 0x04, 0x55, 0x1d, 0x20, - 0x00, 0x30, 0x39, 0x30, 0x37, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x02, 0x01, 0x16, 0x2b, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, - 0x2f, 0x63, 0x65, 0x72, 0x74, 0x73, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x66, - 0x69, 0x65, 0x6c, 0x64, 0x74, 0x65, 0x63, 0x68, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x85, 0x63, 0xc1, 0xd9, - 0xdd, 0xb9, 0xff, 0xa9, 0xbd, 0xa6, 0x19, 0xdc, 0xbf, 0x13, 0x3a, 0x11, - 0x38, 0x22, 0x54, 0xb1, 0xac, 0x05, 0x10, 0xfb, 0x7c, 0xb3, 0x96, 0x3f, - 0x31, 0x8b, 0x66, 0xff, 0x88, 0xf3, 0xe1, 0xbf, 0xfb, 0xc7, 0x1f, 0x00, - 0xff, 0x46, 0x6a, 0x8b, 0x61, 0x32, 0xc9, 0x01, 0x51, 0x76, 0xfb, 0x9a, - 0xc6, 0xfa, 0x20, 0x51, 0xc8, 0x46, 0xc4, 0x98, 0xd7, 0x79, 0xa3, 0xe3, - 0x04, 0x72, 0x3f, 0x8b, 0x4d, 0x34, 0x53, 0x67, 0xec, 0x33, 0x2c, 0x7b, - 0xe8, 0x94, 0x01, 0x28, 0x7c, 0x3a, 0x34, 0x5b, 0x02, 0x77, 0x16, 0x8d, - 0x40, 0x25, 0x33, 0xb0, 0xbc, 0x6c, 0x97, 0xd7, 0x05, 0x7a, 0xff, 0x8c, - 0x85, 0xce, 0x6f, 0xa0, 0x53, 0x00, 0x17, 0x6e, 0x1e, 0x6c, 0xbd, 0x22, - 0xd7, 0x0a, 0x88, 0x37, 0xf6, 0x7d, 0xeb, 0x99, 0x41, 0xef, 0x27, 0xcb, - 0x8c, 0x60, 0x6b, 0x4c, 0x01, 0x7e, 0x65, 0x50, 0x0b, 0x4f, 0xb8, 0x95, - 0x9a, 0x9a, 0x6e, 0x34, 0xfd, 0x73, 0x3a, 0x33, 0xf1, 0x91, 0xd5, 0xf3, - 0x4e, 0x2d, 0x74, 0xe8, 0xef, 0xd3, 0x90, 0x35, 0xf1, 0x06, 0x68, 0x64, - 0xd4, 0xd0, 0x13, 0xfd, 0x52, 0xd3, 0xc6, 0x6d, 0xc1, 0x3a, 0x8a, 0x31, - 0xdd, 0x05, 0x26, 0x35, 0x4a, 0x8c, 0x65, 0xb8, 0x52, 0x6b, 0x81, 0xec, - 0xd2, 0x9c, 0xb5, 0x34, 0x10, 0x97, 0x9c, 0x3e, 0xc6, 0x2f, 0xed, 0x8e, - 0x42, 0x42, 0x24, 0x2e, 0xe9, 0x73, 0x9a, 0x25, 0xf9, 0x11, 0xf1, 0xf2, - 0x23, 0x69, 0xcb, 0xe5, 0x94, 0x69, 0xa0, 0xd2, 0xdc, 0xb0, 0xfc, 0x44, - 0x89, 0xac, 0x17, 0xa8, 0xcc, 0xd5, 0x37, 0x77, 0x16, 0xc5, 0x80, 0xb9, - 0x0c, 0x8f, 0x57, 0x02, 0x55, 0x99, 0x85, 0x7b, 0x49, 0xf0, 0x2e, 0x5b, - 0xa0, 0xc2, 0x57, 0x53, 0x5d, 0xa2, 0xe8, 0xa6, 0x37, 0xc3, 0x01, 0xfa, -} - -var certSet2Cert24 = []byte{ - 0x30, 0x82, 0x04, 0xa6, 0x30, 0x82, 0x03, 0x8e, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x28, 0x1c, 0x89, 0x29, 0x66, 0x14, 0x43, 0x80, 0x42, - 0x63, 0x55, 0x3a, 0x32, 0x40, 0xae, 0xb3, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, - 0x98, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, - 0x63, 0x2e, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, - 0x30, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x38, 0x20, 0x47, 0x65, - 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, - 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, - 0x79, 0x31, 0x36, 0x30, 0x34, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2d, - 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x50, 0x72, 0x69, - 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x30, 0x1e, 0x17, - 0x0d, 0x31, 0x35, 0x30, 0x36, 0x33, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, - 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x35, 0x30, 0x36, 0x32, 0x39, 0x32, 0x33, - 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x47, 0x31, 0x0b, 0x30, 0x09, 0x06, - 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, - 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, - 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x20, 0x30, 0x1e, - 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x17, 0x52, 0x61, 0x70, 0x69, 0x64, - 0x53, 0x53, 0x4c, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x43, - 0x41, 0x20, 0x2d, 0x20, 0x47, 0x34, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, - 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, - 0x01, 0x01, 0x00, 0xc0, 0x9e, 0x3a, 0x0f, 0x9a, 0xb2, 0xba, 0xd3, 0xd2, - 0xdc, 0x15, 0xec, 0xd0, 0x30, 0x54, 0x59, 0x30, 0x4d, 0x40, 0x51, 0xae, - 0x42, 0x71, 0x71, 0xd2, 0x8d, 0x53, 0x73, 0x81, 0xfe, 0xb8, 0xe0, 0xc4, - 0x96, 0xc5, 0x8e, 0x7e, 0xc2, 0xf1, 0xb7, 0x63, 0x4a, 0xcf, 0xa7, 0x1e, - 0x3f, 0xa8, 0xe7, 0xce, 0x53, 0xa0, 0xfa, 0x2d, 0xf7, 0xd6, 0xe6, 0xce, - 0x70, 0x11, 0xa6, 0xee, 0xe1, 0x03, 0x52, 0xd2, 0x68, 0xde, 0x3d, 0x08, - 0x0d, 0x87, 0xfd, 0x1c, 0xd7, 0x0b, 0x97, 0x62, 0x6d, 0x82, 0x30, 0x76, - 0x1b, 0x47, 0x3a, 0xc4, 0xf7, 0xce, 0xed, 0x1d, 0x7c, 0x8c, 0xb7, 0x17, - 0x8e, 0x53, 0x80, 0x1e, 0x1d, 0x0f, 0x5d, 0x8c, 0xf9, 0x90, 0xe4, 0x04, - 0x1e, 0x02, 0x7e, 0xcb, 0xb0, 0x49, 0xef, 0xda, 0x52, 0x25, 0xfb, 0xfb, - 0x67, 0xed, 0xdd, 0x84, 0x74, 0x59, 0x84, 0x0e, 0xf3, 0xde, 0x70, 0x66, - 0x8d, 0xe4, 0x52, 0x38, 0xf7, 0x53, 0x5a, 0x37, 0x13, 0x67, 0x0b, 0x3e, - 0xbb, 0xa8, 0x58, 0xb7, 0x2e, 0xed, 0xff, 0xb7, 0x5e, 0x11, 0x73, 0xb9, - 0x77, 0x45, 0x52, 0x67, 0x46, 0xae, 0xc4, 0xdc, 0x24, 0x81, 0x89, 0x76, - 0x0a, 0xca, 0xa1, 0x6c, 0x66, 0x73, 0x04, 0x82, 0xaa, 0xf5, 0x70, 0x6c, - 0x5f, 0x1b, 0x9a, 0x00, 0x79, 0x46, 0xd6, 0x7f, 0x7a, 0x26, 0x17, 0x30, - 0xcf, 0x39, 0x4b, 0x2c, 0x74, 0xd9, 0x89, 0x44, 0x76, 0x10, 0xd0, 0xed, - 0xf7, 0x8b, 0xbb, 0x89, 0x05, 0x75, 0x4d, 0x0b, 0x0d, 0xb3, 0xda, 0xe9, - 0xbf, 0xf1, 0x6a, 0x7d, 0x2a, 0x11, 0xdb, 0x1e, 0x9f, 0x8c, 0xe3, 0xc4, - 0x06, 0x69, 0xe1, 0x1d, 0x88, 0x45, 0x39, 0xd1, 0x6e, 0x55, 0xd8, 0xaa, - 0xb7, 0x9b, 0x6f, 0xea, 0xf4, 0xde, 0xac, 0x17, 0x11, 0x92, 0x5d, 0x40, - 0x9b, 0x83, 0x7b, 0x9a, 0xe2, 0xf7, 0xa9, 0x02, 0x03, 0x01, 0x00, 0x01, - 0xa3, 0x82, 0x01, 0x3a, 0x30, 0x82, 0x01, 0x36, 0x30, 0x2e, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x22, 0x30, 0x20, - 0x30, 0x1e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, - 0x86, 0x12, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x67, 0x2e, 0x73, - 0x79, 0x6d, 0x63, 0x64, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x12, 0x06, 0x03, - 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, - 0xff, 0x02, 0x01, 0x00, 0x30, 0x49, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, - 0x42, 0x30, 0x40, 0x30, 0x3e, 0x06, 0x06, 0x67, 0x81, 0x0c, 0x01, 0x02, - 0x01, 0x30, 0x34, 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x02, 0x01, 0x16, 0x26, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, - 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, - 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x36, 0x06, 0x03, 0x55, - 0x1d, 0x1f, 0x04, 0x2f, 0x30, 0x2d, 0x30, 0x2b, 0xa0, 0x29, 0xa0, 0x27, - 0x86, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x67, 0x2e, 0x73, - 0x79, 0x6d, 0x63, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x47, 0x65, 0x6f, - 0x54, 0x72, 0x75, 0x73, 0x74, 0x50, 0x43, 0x41, 0x2d, 0x47, 0x33, 0x2e, - 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x16, - 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x30, 0x0e, - 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, - 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, - 0x14, 0xf3, 0xb5, 0x56, 0x0c, 0xc4, 0x09, 0xb0, 0xb4, 0xcf, 0x1f, 0xaa, - 0xf9, 0xdd, 0x23, 0x56, 0xf0, 0x77, 0xe8, 0xa1, 0xf9, 0x30, 0x1f, 0x06, - 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xc4, 0x79, - 0xca, 0x8e, 0xa1, 0x4e, 0x03, 0x1d, 0x1c, 0xdc, 0x6b, 0xdb, 0x31, 0x5b, - 0x94, 0x3e, 0x3f, 0x30, 0x7f, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, - 0x01, 0x00, 0xc3, 0x7e, 0xd8, 0x83, 0x4b, 0x04, 0x4c, 0x55, 0x29, 0x2a, - 0x4f, 0x14, 0x9d, 0x9a, 0x6e, 0xde, 0x90, 0x70, 0xc1, 0xa4, 0x26, 0x4c, - 0x88, 0x8e, 0x78, 0x48, 0xef, 0xbd, 0x9c, 0xb0, 0xa0, 0xf5, 0xf0, 0x66, - 0xfc, 0xfe, 0x59, 0x26, 0xe1, 0x79, 0xef, 0xc8, 0xb7, 0x60, 0x64, 0xa8, - 0x8b, 0x47, 0xea, 0x2f, 0xe0, 0x83, 0x99, 0xda, 0x41, 0x19, 0xd7, 0xc5, - 0xbe, 0x05, 0xfa, 0xf2, 0x90, 0x11, 0xf0, 0x0a, 0xff, 0x6c, 0xdc, 0x05, - 0xb4, 0xd8, 0x06, 0x6f, 0xa4, 0x6f, 0x8d, 0xbe, 0x20, 0x2b, 0x54, 0xdb, - 0xf9, 0xa2, 0x45, 0x83, 0x9a, 0x1e, 0xa5, 0x21, 0x89, 0x35, 0x1d, 0x7c, - 0x20, 0x5c, 0x17, 0xfd, 0x04, 0x2e, 0x45, 0xd8, 0xb2, 0xc6, 0xf8, 0x42, - 0x99, 0xfc, 0x54, 0x08, 0x4e, 0x4b, 0x80, 0x5f, 0x39, 0x37, 0xba, 0x95, - 0x4e, 0xa6, 0x37, 0x0a, 0x9e, 0x93, 0x5e, 0x87, 0x5b, 0xe9, 0x90, 0xd6, - 0xa8, 0xb6, 0x65, 0x08, 0x8d, 0x61, 0x49, 0xeb, 0x83, 0x20, 0xa9, 0x5d, - 0x1b, 0x16, 0x60, 0x62, 0x6b, 0x2f, 0x54, 0xfb, 0x5a, 0x02, 0x0d, 0x7a, - 0x27, 0xe2, 0x4b, 0xe1, 0x05, 0x14, 0xc2, 0xe4, 0xe9, 0xf9, 0x70, 0xc0, - 0xd9, 0xf7, 0x34, 0x65, 0x0e, 0xa2, 0x91, 0x4b, 0xac, 0x28, 0xf2, 0xb7, - 0x08, 0x0f, 0x98, 0xca, 0xd7, 0x3e, 0x70, 0xb6, 0xc8, 0x0b, 0xf1, 0x8b, - 0x9c, 0x51, 0xf8, 0xc6, 0x10, 0x6c, 0xd2, 0x53, 0x4f, 0x62, 0x8c, 0x11, - 0x00, 0x3e, 0x88, 0xdf, 0xbf, 0xe6, 0xd2, 0xcc, 0x70, 0xbd, 0xed, 0x25, - 0x9c, 0xfb, 0xdd, 0x24, 0x0a, 0xbd, 0x59, 0x91, 0x4a, 0x42, 0x03, 0x38, - 0x12, 0x71, 0x32, 0x88, 0x76, 0xa0, 0x8e, 0x7c, 0xbb, 0x32, 0xef, 0x88, - 0x2a, 0x1b, 0xd4, 0x6a, 0x6f, 0x50, 0xb9, 0x52, 0x67, 0x8b, 0xab, 0x30, - 0xfa, 0x1f, 0xfd, 0xe3, 0x24, 0x9a, -} - -var certSet2Cert25 = []byte{ - 0x30, 0x82, 0x04, 0xaf, 0x30, 0x82, 0x03, 0x97, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x5d, 0x72, 0xfb, 0x33, 0x76, 0x20, 0xf6, 0x4c, 0x72, - 0x80, 0xdb, 0xe9, 0x12, 0x81, 0xff, 0x6a, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, - 0xa9, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, - 0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1f, - 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x44, - 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x38, 0x30, 0x36, 0x06, - 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, - 0x30, 0x36, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, - 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x16, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50, - 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, - 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x33, 0x31, 0x30, 0x33, 0x31, - 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x33, 0x31, - 0x30, 0x33, 0x30, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x44, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, - 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x15, 0x74, - 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x45, 0x56, 0x20, 0x53, 0x53, 0x4c, - 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x30, 0x82, 0x01, 0x22, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, - 0x02, 0x82, 0x01, 0x01, 0x00, 0xc4, 0xdd, 0xda, 0x94, 0x1e, 0x32, 0xb2, - 0x2e, 0xa0, 0x83, 0xc0, 0xa6, 0x7d, 0x5f, 0x65, 0x2d, 0xfd, 0x27, 0xb8, - 0x73, 0x0e, 0xf8, 0x0b, 0xa9, 0xd4, 0x56, 0x26, 0x69, 0x98, 0x67, 0x35, - 0x39, 0x64, 0x58, 0xce, 0x82, 0x6f, 0x98, 0x94, 0xd1, 0x8f, 0xe0, 0x90, - 0xd6, 0xed, 0x55, 0x4b, 0x98, 0x4b, 0xd7, 0x10, 0x59, 0x34, 0x02, 0x1b, - 0xe7, 0x51, 0x31, 0x51, 0xc4, 0x38, 0xc2, 0xbc, 0xdb, 0x03, 0x5c, 0xca, - 0xe1, 0x7c, 0xdc, 0x4f, 0x59, 0x97, 0xea, 0x07, 0x7f, 0x0f, 0x85, 0x3e, - 0x92, 0xea, 0xaa, 0xa7, 0xd9, 0xbe, 0x01, 0x41, 0xe4, 0x62, 0x56, 0x47, - 0x36, 0xbd, 0x57, 0x91, 0xe6, 0x21, 0xd3, 0xf8, 0x41, 0x0b, 0xd8, 0xba, - 0xe8, 0xed, 0x81, 0xad, 0x70, 0xc0, 0x8b, 0x6e, 0xf3, 0x89, 0x6e, 0x27, - 0x9e, 0xa6, 0xa6, 0x73, 0x59, 0xbb, 0x71, 0x00, 0xd4, 0x4f, 0x4b, 0x48, - 0xe9, 0xd5, 0xc9, 0x27, 0x36, 0x9c, 0x7c, 0x1c, 0x02, 0xaa, 0xac, 0xbd, - 0x3b, 0xd1, 0x53, 0x83, 0x6a, 0x1f, 0xe6, 0x08, 0x47, 0x33, 0xa7, 0xb1, - 0x9f, 0x02, 0xbe, 0x9b, 0x47, 0xed, 0x33, 0x04, 0xdc, 0x1c, 0x80, 0x27, - 0xd1, 0x4a, 0x33, 0xa0, 0x8c, 0xeb, 0x01, 0x47, 0xa1, 0x32, 0x90, 0x64, - 0x7b, 0xc4, 0xe0, 0x84, 0xc9, 0x32, 0xe9, 0xdd, 0x34, 0x1f, 0x8a, 0x68, - 0x67, 0xf3, 0xad, 0x10, 0x63, 0xeb, 0xee, 0x8a, 0x9a, 0xb1, 0x2a, 0x1b, - 0x26, 0x74, 0xa1, 0x2a, 0xb0, 0x8f, 0xfe, 0x52, 0x98, 0x46, 0x97, 0xcf, - 0xa3, 0x56, 0x1c, 0x6f, 0x6e, 0x99, 0x97, 0x8d, 0x26, 0x0e, 0xa9, 0xec, - 0xc2, 0x53, 0x70, 0xfc, 0x7a, 0xa5, 0x19, 0x49, 0xbd, 0xb5, 0x17, 0x82, - 0x55, 0xde, 0x97, 0xe0, 0x5d, 0x62, 0x84, 0x81, 0xf0, 0x70, 0xa8, 0x34, - 0x53, 0x4f, 0x14, 0xfd, 0x3d, 0x5d, 0x3d, 0x6f, 0xb9, 0x02, 0x03, 0x01, - 0x00, 0x01, 0xa3, 0x82, 0x01, 0x35, 0x30, 0x82, 0x01, 0x31, 0x30, 0x12, - 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, - 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, - 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x2f, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x23, - 0x30, 0x21, 0x30, 0x1f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x30, 0x01, 0x86, 0x13, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x74, - 0x32, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x30, - 0x3b, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, - 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, - 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x74, 0x68, - 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73, - 0x30, 0x32, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2b, 0x30, 0x29, 0x30, - 0x27, 0xa0, 0x25, 0xa0, 0x23, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x74, 0x31, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x50, 0x43, 0x41, - 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x29, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, - 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, 0x31, 0x1a, 0x30, 0x18, 0x06, - 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x53, 0x79, 0x6d, 0x61, 0x6e, 0x74, - 0x65, 0x63, 0x50, 0x4b, 0x49, 0x2d, 0x31, 0x2d, 0x35, 0x33, 0x36, 0x30, - 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xf0, 0x70, - 0x51, 0xda, 0xd3, 0x2a, 0x91, 0x4f, 0x52, 0x77, 0xd7, 0x86, 0x77, 0x74, - 0x0f, 0xce, 0x71, 0x1a, 0x6c, 0x22, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, - 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x7b, 0x5b, 0x45, 0xcf, 0xaf, - 0xce, 0xcb, 0x7a, 0xfd, 0x31, 0x92, 0x1a, 0x6a, 0xb6, 0xf3, 0x46, 0xeb, - 0x57, 0x48, 0x50, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, - 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0xa1, - 0x2e, 0x94, 0x3e, 0x9b, 0x16, 0xf4, 0x58, 0x1a, 0x6f, 0xc1, 0xfa, 0xc1, - 0x7e, 0x43, 0x93, 0xb2, 0xc3, 0xf7, 0x89, 0xeb, 0x13, 0x62, 0x5d, 0xdd, - 0xcc, 0x61, 0x13, 0x2b, 0x1d, 0x4e, 0x88, 0x79, 0x11, 0x62, 0x14, 0x37, - 0x30, 0x46, 0xff, 0x89, 0x62, 0x10, 0x85, 0x2a, 0x87, 0x1e, 0xf8, 0xe2, - 0xaf, 0xfe, 0x93, 0x02, 0x93, 0xca, 0xf2, 0xe9, 0x46, 0x03, 0x6b, 0xa1, - 0x1a, 0xac, 0xd5, 0xf0, 0x80, 0x1b, 0x98, 0x6f, 0xb8, 0x3a, 0x50, 0xf8, - 0x54, 0x71, 0x06, 0x03, 0xe7, 0x84, 0xcc, 0x8e, 0x61, 0xd2, 0x5f, 0x4d, - 0x0c, 0x97, 0x02, 0x65, 0xb5, 0x8c, 0x26, 0xbc, 0x05, 0x98, 0xf4, 0xdc, - 0xc6, 0xaf, 0xe4, 0x57, 0x7f, 0xe3, 0xdc, 0xa1, 0xd7, 0x27, 0x47, 0x2a, - 0xe0, 0x2c, 0x3f, 0x09, 0x74, 0xdc, 0x5a, 0xe5, 0xb5, 0x7c, 0xfa, 0x82, - 0x9a, 0x15, 0xfa, 0x74, 0x2b, 0x84, 0x2e, 0x6b, 0xac, 0xef, 0x35, 0xa6, - 0x30, 0xfa, 0x47, 0x4a, 0xaa, 0x36, 0x44, 0xf6, 0x5a, 0x91, 0x07, 0xd3, - 0xe4, 0x4e, 0x97, 0x3f, 0xa6, 0x53, 0xd8, 0x29, 0x33, 0x32, 0x6f, 0x8b, - 0x3d, 0xb5, 0xa5, 0x0d, 0xe5, 0xe4, 0x8a, 0xe8, 0xf5, 0xc0, 0xfa, 0xaf, - 0xd8, 0x37, 0x28, 0x27, 0xc3, 0xed, 0x34, 0x31, 0xd9, 0x7c, 0xa6, 0xaf, - 0x4d, 0x12, 0x4f, 0xd0, 0x2b, 0x92, 0x9c, 0x69, 0x95, 0xf2, 0x28, 0xa6, - 0xfe, 0xa8, 0xc6, 0xe0, 0x2c, 0x4d, 0x36, 0xeb, 0x11, 0x34, 0xd6, 0xe1, - 0x81, 0x99, 0x9d, 0x41, 0xf2, 0xe7, 0xc5, 0x57, 0x05, 0x0e, 0x19, 0xca, - 0xaf, 0x42, 0x39, 0x1f, 0xa7, 0x27, 0x5e, 0xe0, 0x0a, 0x17, 0xb8, 0xae, - 0x47, 0xab, 0x92, 0xf1, 0x8a, 0x04, 0xdf, 0x30, 0xe0, 0xbb, 0x4f, 0x8a, - 0xf9, 0x1b, 0x88, 0x4f, 0x03, 0xb4, 0x25, 0x7a, 0x78, 0xde, 0x2e, 0x7d, - 0x29, 0xd1, 0x31, -} - -var certSet2Cert26 = []byte{ - 0x30, 0x82, 0x04, 0xb1, 0x30, 0x82, 0x03, 0x99, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x04, 0xe1, 0xe7, 0xa4, 0xdc, 0x5c, 0xf2, 0xf3, 0x6d, - 0xc0, 0x2b, 0x42, 0xb8, 0x5d, 0x15, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x6c, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, - 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, - 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, - 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, - 0x63, 0x6f, 0x6d, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03, - 0x13, 0x22, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, - 0x69, 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, - 0x65, 0x20, 0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, - 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x33, 0x31, 0x30, 0x32, 0x32, 0x31, 0x32, - 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x38, 0x31, 0x30, 0x32, - 0x32, 0x31, 0x32, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x70, 0x31, 0x0b, - 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, - 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69, - 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x19, - 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, 0x77, 0x77, - 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, - 0x6d, 0x31, 0x2f, 0x30, 0x2d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x26, - 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x53, 0x48, 0x41, - 0x32, 0x20, 0x48, 0x69, 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, - 0x61, 0x6e, 0x63, 0x65, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, - 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, - 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb6, - 0xe0, 0x2f, 0xc2, 0x24, 0x06, 0xc8, 0x6d, 0x04, 0x5f, 0xd7, 0xef, 0x0a, - 0x64, 0x06, 0xb2, 0x7d, 0x22, 0x26, 0x65, 0x16, 0xae, 0x42, 0x40, 0x9b, - 0xce, 0xdc, 0x9f, 0x9f, 0x76, 0x07, 0x3e, 0xc3, 0x30, 0x55, 0x87, 0x19, - 0xb9, 0x4f, 0x94, 0x0e, 0x5a, 0x94, 0x1f, 0x55, 0x56, 0xb4, 0xc2, 0x02, - 0x2a, 0xaf, 0xd0, 0x98, 0xee, 0x0b, 0x40, 0xd7, 0xc4, 0xd0, 0x3b, 0x72, - 0xc8, 0x14, 0x9e, 0xef, 0x90, 0xb1, 0x11, 0xa9, 0xae, 0xd2, 0xc8, 0xb8, - 0x43, 0x3a, 0xd9, 0x0b, 0x0b, 0xd5, 0xd5, 0x95, 0xf5, 0x40, 0xaf, 0xc8, - 0x1d, 0xed, 0x4d, 0x9c, 0x5f, 0x57, 0xb7, 0x86, 0x50, 0x68, 0x99, 0xf5, - 0x8a, 0xda, 0xd2, 0xc7, 0x05, 0x1f, 0xa8, 0x97, 0xc9, 0xdc, 0xa4, 0xb1, - 0x82, 0x84, 0x2d, 0xc6, 0xad, 0xa5, 0x9c, 0xc7, 0x19, 0x82, 0xa6, 0x85, - 0x0f, 0x5e, 0x44, 0x58, 0x2a, 0x37, 0x8f, 0xfd, 0x35, 0xf1, 0x0b, 0x08, - 0x27, 0x32, 0x5a, 0xf5, 0xbb, 0x8b, 0x9e, 0xa4, 0xbd, 0x51, 0xd0, 0x27, - 0xe2, 0xdd, 0x3b, 0x42, 0x33, 0xa3, 0x05, 0x28, 0xc4, 0xbb, 0x28, 0xcc, - 0x9a, 0xac, 0x2b, 0x23, 0x0d, 0x78, 0xc6, 0x7b, 0xe6, 0x5e, 0x71, 0xb7, - 0x4a, 0x3e, 0x08, 0xfb, 0x81, 0xb7, 0x16, 0x16, 0xa1, 0x9d, 0x23, 0x12, - 0x4d, 0xe5, 0xd7, 0x92, 0x08, 0xac, 0x75, 0xa4, 0x9c, 0xba, 0xcd, 0x17, - 0xb2, 0x1e, 0x44, 0x35, 0x65, 0x7f, 0x53, 0x25, 0x39, 0xd1, 0x1c, 0x0a, - 0x9a, 0x63, 0x1b, 0x19, 0x92, 0x74, 0x68, 0x0a, 0x37, 0xc2, 0xc2, 0x52, - 0x48, 0xcb, 0x39, 0x5a, 0xa2, 0xb6, 0xe1, 0x5d, 0xc1, 0xdd, 0xa0, 0x20, - 0xb8, 0x21, 0xa2, 0x93, 0x26, 0x6f, 0x14, 0x4a, 0x21, 0x41, 0xc7, 0xed, - 0x6d, 0x9b, 0xf2, 0x48, 0x2f, 0xf3, 0x03, 0xf5, 0xa2, 0x68, 0x92, 0x53, - 0x2f, 0x5e, 0xe3, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x49, - 0x30, 0x82, 0x01, 0x45, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, - 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, - 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, - 0x03, 0x02, 0x01, 0x86, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, - 0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, - 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x30, - 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, - 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, - 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x4b, 0x06, 0x03, 0x55, 0x1d, 0x1f, - 0x04, 0x44, 0x30, 0x42, 0x30, 0x40, 0xa0, 0x3e, 0xa0, 0x3c, 0x86, 0x3a, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x34, 0x2e, - 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x48, 0x69, 0x67, - 0x68, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x45, 0x56, - 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3d, - 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, 0x30, 0x34, 0x30, 0x32, 0x06, - 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, 0x30, 0x28, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, 0x74, - 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, - 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x50, - 0x53, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, - 0x51, 0x68, 0xff, 0x90, 0xaf, 0x02, 0x07, 0x75, 0x3c, 0xcc, 0xd9, 0x65, - 0x64, 0x62, 0xa2, 0x12, 0xb8, 0x59, 0x72, 0x3b, 0x30, 0x1f, 0x06, 0x03, - 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xb1, 0x3e, 0xc3, - 0x69, 0x03, 0xf8, 0xbf, 0x47, 0x01, 0xd4, 0x98, 0x26, 0x1a, 0x08, 0x02, - 0xef, 0x63, 0x64, 0x2b, 0xc3, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, - 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, - 0x00, 0x18, 0x8a, 0x95, 0x89, 0x03, 0xe6, 0x6d, 0xdf, 0x5c, 0xfc, 0x1d, - 0x68, 0xea, 0x4a, 0x8f, 0x83, 0xd6, 0x51, 0x2f, 0x8d, 0x6b, 0x44, 0x16, - 0x9e, 0xac, 0x63, 0xf5, 0xd2, 0x6e, 0x6c, 0x84, 0x99, 0x8b, 0xaa, 0x81, - 0x71, 0x84, 0x5b, 0xed, 0x34, 0x4e, 0xb0, 0xb7, 0x79, 0x92, 0x29, 0xcc, - 0x2d, 0x80, 0x6a, 0xf0, 0x8e, 0x20, 0xe1, 0x79, 0xa4, 0xfe, 0x03, 0x47, - 0x13, 0xea, 0xf5, 0x86, 0xca, 0x59, 0x71, 0x7d, 0xf4, 0x04, 0x96, 0x6b, - 0xd3, 0x59, 0x58, 0x3d, 0xfe, 0xd3, 0x31, 0x25, 0x5c, 0x18, 0x38, 0x84, - 0xa3, 0xe6, 0x9f, 0x82, 0xfd, 0x8c, 0x5b, 0x98, 0x31, 0x4e, 0xcd, 0x78, - 0x9e, 0x1a, 0xfd, 0x85, 0xcb, 0x49, 0xaa, 0xf2, 0x27, 0x8b, 0x99, 0x72, - 0xfc, 0x3e, 0xaa, 0xd5, 0x41, 0x0b, 0xda, 0xd5, 0x36, 0xa1, 0xbf, 0x1c, - 0x6e, 0x47, 0x49, 0x7f, 0x5e, 0xd9, 0x48, 0x7c, 0x03, 0xd9, 0xfd, 0x8b, - 0x49, 0xa0, 0x98, 0x26, 0x42, 0x40, 0xeb, 0xd6, 0x92, 0x11, 0xa4, 0x64, - 0x0a, 0x57, 0x54, 0xc4, 0xf5, 0x1d, 0xd6, 0x02, 0x5e, 0x6b, 0xac, 0xee, - 0xc4, 0x80, 0x9a, 0x12, 0x72, 0xfa, 0x56, 0x93, 0xd7, 0xff, 0xbf, 0x30, - 0x85, 0x06, 0x30, 0xbf, 0x0b, 0x7f, 0x4e, 0xff, 0x57, 0x05, 0x9d, 0x24, - 0xed, 0x85, 0xc3, 0x2b, 0xfb, 0xa6, 0x75, 0xa8, 0xac, 0x2d, 0x16, 0xef, - 0x7d, 0x79, 0x27, 0xb2, 0xeb, 0xc2, 0x9d, 0x0b, 0x07, 0xea, 0xaa, 0x85, - 0xd3, 0x01, 0xa3, 0x20, 0x28, 0x41, 0x59, 0x43, 0x28, 0xd2, 0x81, 0xe3, - 0xaa, 0xf6, 0xec, 0x7b, 0x3b, 0x77, 0xb6, 0x40, 0x62, 0x80, 0x05, 0x41, - 0x45, 0x01, 0xef, 0x17, 0x06, 0x3e, 0xde, 0xc0, 0x33, 0x9b, 0x67, 0xd3, - 0x61, 0x2e, 0x72, 0x87, 0xe4, 0x69, 0xfc, 0x12, 0x00, 0x57, 0x40, 0x1e, - 0x70, 0xf5, 0x1e, 0xc9, 0xb4, -} - -var certSet2Cert27 = []byte{ - 0x30, 0x82, 0x04, 0xb2, 0x30, 0x82, 0x03, 0x9a, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x16, 0x87, 0xd6, 0x88, 0x6d, 0xe2, 0x30, 0x06, 0x85, - 0x23, 0x3d, 0xbf, 0x11, 0xbf, 0x65, 0x97, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, - 0xa9, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, - 0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1f, - 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x44, - 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x38, 0x30, 0x36, 0x06, - 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, - 0x30, 0x36, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, - 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x16, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50, - 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, - 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x33, 0x31, 0x30, 0x33, 0x31, - 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x33, 0x31, - 0x30, 0x33, 0x30, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x41, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, - 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x74, - 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, - 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, - 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, - 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, - 0x01, 0x00, 0xb2, 0xfc, 0x06, 0xfb, 0x04, 0x93, 0xd2, 0xea, 0x59, 0x20, - 0x3b, 0x44, 0x85, 0x97, 0x52, 0x39, 0xe7, 0x10, 0xf0, 0x7a, 0xe0, 0xb0, - 0x94, 0x40, 0xda, 0x46, 0xf8, 0x0c, 0x28, 0xbb, 0xb9, 0xce, 0x60, 0x38, - 0x3f, 0xd2, 0xd8, 0x11, 0x42, 0x1b, 0x91, 0xad, 0x49, 0xee, 0x8f, 0xc7, - 0xde, 0x6c, 0xde, 0x37, 0x6f, 0xfd, 0x8b, 0x20, 0x3c, 0x6d, 0xe7, 0x74, - 0xd3, 0xdc, 0xd5, 0x24, 0x88, 0x41, 0x80, 0x89, 0xee, 0x36, 0xbe, 0xc4, - 0xd5, 0xbe, 0x8d, 0x53, 0x13, 0xaa, 0xe4, 0xa5, 0xb8, 0x93, 0x0a, 0xbe, - 0xec, 0xda, 0xcd, 0x3c, 0xd4, 0x32, 0x56, 0xef, 0xd0, 0x4e, 0xa0, 0xb8, - 0x97, 0xbb, 0x39, 0x50, 0x1e, 0x6e, 0x65, 0xc3, 0xfd, 0xb2, 0xce, 0xe0, - 0x59, 0xa9, 0x48, 0x09, 0xc6, 0xfe, 0xbe, 0xae, 0xfc, 0x3e, 0x3b, 0x81, - 0x20, 0x97, 0x8b, 0x8f, 0x46, 0xdf, 0x60, 0x64, 0x07, 0x75, 0xbb, 0x1b, - 0x86, 0x38, 0x9f, 0x47, 0x7b, 0x34, 0xce, 0xa1, 0xd1, 0x97, 0xad, 0x76, - 0xd8, 0x9f, 0xb7, 0x26, 0xdb, 0x79, 0x80, 0x36, 0x48, 0xf2, 0xc5, 0x37, - 0xf8, 0xd9, 0x32, 0xae, 0x7c, 0xa4, 0x53, 0x81, 0xc7, 0x99, 0xa1, 0x54, - 0x38, 0x2f, 0x4f, 0x75, 0xa0, 0xbb, 0x5a, 0xa5, 0xbb, 0xcd, 0xac, 0x02, - 0x5b, 0x19, 0x02, 0xd5, 0x13, 0x18, 0xa7, 0xce, 0xac, 0x74, 0x55, 0x12, - 0x05, 0x8b, 0x9b, 0xa2, 0x95, 0x46, 0x64, 0x72, 0x38, 0xcd, 0x5a, 0x1b, - 0x3a, 0x16, 0xa7, 0xbe, 0x71, 0x99, 0x8c, 0x54, 0x03, 0xb8, 0x96, 0x6c, - 0x01, 0xd3, 0x3e, 0x06, 0x98, 0x3f, 0x21, 0x81, 0x3b, 0x02, 0x7e, 0x00, - 0x47, 0x53, 0x01, 0x1e, 0x0e, 0x46, 0x43, 0xfb, 0x4b, 0x2d, 0xdc, 0x0b, - 0x1a, 0xe8, 0x2f, 0x98, 0xf8, 0x7e, 0xd1, 0x99, 0xab, 0x13, 0x6c, 0xa4, - 0x17, 0xde, 0x6f, 0xf6, 0x15, 0xf5, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, - 0x82, 0x01, 0x3b, 0x30, 0x82, 0x01, 0x37, 0x30, 0x12, 0x06, 0x03, 0x55, - 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, - 0x02, 0x01, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, - 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x32, 0x06, 0x03, 0x55, - 0x1d, 0x1f, 0x04, 0x2b, 0x30, 0x29, 0x30, 0x27, 0xa0, 0x25, 0xa0, 0x23, - 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x74, 0x31, 0x2e, - 0x73, 0x79, 0x6d, 0x63, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x54, 0x68, - 0x61, 0x77, 0x74, 0x65, 0x50, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, - 0x2f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, - 0x23, 0x30, 0x21, 0x30, 0x1f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x30, 0x01, 0x86, 0x13, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x74, 0x32, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x30, 0x41, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x3a, 0x30, 0x38, 0x30, - 0x36, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x07, - 0x36, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, - 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x29, 0x06, 0x03, 0x55, - 0x1d, 0x11, 0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, 0x31, 0x1a, - 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x53, 0x79, 0x6d, - 0x61, 0x6e, 0x74, 0x65, 0x63, 0x50, 0x4b, 0x49, 0x2d, 0x31, 0x2d, 0x35, - 0x33, 0x37, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, - 0x14, 0xc2, 0x4f, 0x48, 0x57, 0xfc, 0xd1, 0x4f, 0x9a, 0xc0, 0x5d, 0x38, - 0x7d, 0x0e, 0x05, 0xdb, 0xd9, 0x2e, 0xb5, 0x52, 0x60, 0x30, 0x1f, 0x06, - 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x7b, 0x5b, - 0x45, 0xcf, 0xaf, 0xce, 0xcb, 0x7a, 0xfd, 0x31, 0x92, 0x1a, 0x6a, 0xb6, - 0xf3, 0x46, 0xeb, 0x57, 0x48, 0x50, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, - 0x01, 0x00, 0x8d, 0x06, 0xde, 0x43, 0xc9, 0x76, 0x02, 0xca, 0xd9, 0x23, - 0x97, 0x5e, 0xf3, 0x63, 0xd7, 0x7d, 0x44, 0xc2, 0x0f, 0x6b, 0x0a, 0xf5, - 0x07, 0xe5, 0x8b, 0xb8, 0xfa, 0xe0, 0xa3, 0xfa, 0x6b, 0x80, 0x92, 0xb5, - 0x03, 0x2c, 0xc5, 0x37, 0xe0, 0xc2, 0xe5, 0x95, 0xb5, 0x92, 0x70, 0x18, - 0x28, 0x42, 0x94, 0xee, 0x4b, 0x77, 0x6a, 0x01, 0x0f, 0x8b, 0x23, 0xec, - 0x56, 0x4d, 0xf4, 0x00, 0x69, 0xe5, 0x84, 0xc8, 0xe2, 0xea, 0xde, 0x5b, - 0x3e, 0xf6, 0x3c, 0x07, 0x3a, 0x94, 0xca, 0x6c, 0x27, 0xb1, 0xcc, 0x83, - 0x1a, 0x60, 0x71, 0x27, 0xd2, 0xbf, 0x02, 0xf5, 0x1e, 0x44, 0xd3, 0x48, - 0xd5, 0xa6, 0xd3, 0x76, 0x21, 0x00, 0x9c, 0xfa, 0x98, 0x64, 0xeb, 0x17, - 0x36, 0x3f, 0xeb, 0x1b, 0x3c, 0x3e, 0xa6, 0xb1, 0xd9, 0x58, 0x06, 0x0e, - 0x72, 0xd9, 0x68, 0xbe, 0xf1, 0xa7, 0x20, 0xd7, 0x52, 0xe4, 0xa4, 0x77, - 0x1f, 0x71, 0x70, 0x9d, 0x55, 0x35, 0x85, 0x37, 0xe1, 0x1d, 0x4d, 0x94, - 0xc2, 0x70, 0x7f, 0x95, 0x40, 0x6e, 0x4b, 0x7d, 0xb2, 0xb4, 0x29, 0x2a, - 0x03, 0x79, 0xc8, 0xb9, 0x4c, 0x67, 0x61, 0x04, 0xa0, 0x8b, 0x27, 0xff, - 0x59, 0x00, 0xeb, 0x55, 0x7f, 0xc6, 0xb7, 0x33, 0x35, 0x2d, 0x5e, 0x4e, - 0xac, 0xb8, 0xea, 0x12, 0xc5, 0xe8, 0xf7, 0xb9, 0xab, 0xbe, 0x74, 0x92, - 0x2c, 0xb7, 0xd9, 0x4d, 0xca, 0x84, 0x2f, 0x1c, 0xc2, 0xf0, 0x72, 0x7c, - 0xb2, 0x31, 0x6e, 0xcf, 0x80, 0xe5, 0x88, 0x07, 0x36, 0x51, 0x7b, 0xba, - 0x61, 0xaf, 0x6d, 0x8d, 0x23, 0x5b, 0x34, 0xa3, 0x95, 0xbc, 0xa2, 0x31, - 0x7f, 0xf2, 0xf5, 0xe7, 0xb7, 0xe8, 0xef, 0xc4, 0xb5, 0x27, 0x32, 0xe9, - 0xf7, 0x9e, 0x69, 0xc7, 0x2b, 0xe8, 0xbe, 0xbb, 0x0c, 0xaa, 0xe7, 0xea, - 0x60, 0x12, 0xea, 0x26, 0x8a, 0x78, -} - -var certSet2Cert28 = []byte{ - 0x30, 0x82, 0x04, 0xb6, 0x30, 0x82, 0x03, 0x9e, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x0c, 0x79, 0xa9, 0x44, 0xb0, 0x8c, 0x11, 0x95, 0x20, - 0x92, 0x61, 0x5f, 0xe2, 0x6b, 0x1d, 0x83, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x6c, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, - 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, - 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, - 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, - 0x63, 0x6f, 0x6d, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03, - 0x13, 0x22, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, - 0x69, 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, - 0x65, 0x20, 0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, - 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x33, 0x31, 0x30, 0x32, 0x32, 0x31, 0x32, - 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x38, 0x31, 0x30, 0x32, - 0x32, 0x31, 0x32, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x75, 0x31, 0x0b, - 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, - 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69, - 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x19, - 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, 0x77, 0x77, - 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, - 0x6d, 0x31, 0x34, 0x30, 0x32, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2b, - 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x53, 0x48, 0x41, - 0x32, 0x20, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x20, 0x56, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, - 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, - 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, - 0x82, 0x01, 0x01, 0x00, 0xd7, 0x53, 0xa4, 0x04, 0x51, 0xf8, 0x99, 0xa6, - 0x16, 0x48, 0x4b, 0x67, 0x27, 0xaa, 0x93, 0x49, 0xd0, 0x39, 0xed, 0x0c, - 0xb0, 0xb0, 0x00, 0x87, 0xf1, 0x67, 0x28, 0x86, 0x85, 0x8c, 0x8e, 0x63, - 0xda, 0xbc, 0xb1, 0x40, 0x38, 0xe2, 0xd3, 0xf5, 0xec, 0xa5, 0x05, 0x18, - 0xb8, 0x3d, 0x3e, 0xc5, 0x99, 0x17, 0x32, 0xec, 0x18, 0x8c, 0xfa, 0xf1, - 0x0c, 0xa6, 0x64, 0x21, 0x85, 0xcb, 0x07, 0x10, 0x34, 0xb0, 0x52, 0x88, - 0x2b, 0x1f, 0x68, 0x9b, 0xd2, 0xb1, 0x8f, 0x12, 0xb0, 0xb3, 0xd2, 0xe7, - 0x88, 0x1f, 0x1f, 0xef, 0x38, 0x77, 0x54, 0x53, 0x5f, 0x80, 0x79, 0x3f, - 0x2e, 0x1a, 0xaa, 0xa8, 0x1e, 0x4b, 0x2b, 0x0d, 0xab, 0xb7, 0x63, 0xb9, - 0x35, 0xb7, 0x7d, 0x14, 0xbc, 0x59, 0x4b, 0xdf, 0x51, 0x4a, 0xd2, 0xa1, - 0xe2, 0x0c, 0xe2, 0x90, 0x82, 0x87, 0x6a, 0xae, 0xea, 0xd7, 0x64, 0xd6, - 0x98, 0x55, 0xe8, 0xfd, 0xaf, 0x1a, 0x50, 0x6c, 0x54, 0xbc, 0x11, 0xf2, - 0xfd, 0x4a, 0xf2, 0x9d, 0xbb, 0x7f, 0x0e, 0xf4, 0xd5, 0xbe, 0x8e, 0x16, - 0x89, 0x12, 0x55, 0xd8, 0xc0, 0x71, 0x34, 0xee, 0xf6, 0xdc, 0x2d, 0xec, - 0xc4, 0x87, 0x25, 0x86, 0x8d, 0xd8, 0x21, 0xe4, 0xb0, 0x4d, 0x0c, 0x89, - 0xdc, 0x39, 0x26, 0x17, 0xdd, 0xf6, 0xd7, 0x94, 0x85, 0xd8, 0x04, 0x21, - 0x70, 0x9d, 0x6f, 0x6f, 0xff, 0x5c, 0xba, 0x19, 0xe1, 0x45, 0xcb, 0x56, - 0x57, 0x28, 0x7e, 0x1c, 0x0d, 0x41, 0x57, 0xaa, 0xb7, 0xb8, 0x27, 0xbb, - 0xb1, 0xe4, 0xfa, 0x2a, 0xef, 0x21, 0x23, 0x75, 0x1a, 0xad, 0x2d, 0x9b, - 0x86, 0x35, 0x8c, 0x9c, 0x77, 0xb5, 0x73, 0xad, 0xd8, 0x94, 0x2d, 0xe4, - 0xf3, 0x0c, 0x9d, 0xee, 0xc1, 0x4e, 0x62, 0x7e, 0x17, 0xc0, 0x71, 0x9e, - 0x2c, 0xde, 0xf1, 0xf9, 0x10, 0x28, 0x19, 0x33, 0x02, 0x03, 0x01, 0x00, - 0x01, 0xa3, 0x82, 0x01, 0x49, 0x30, 0x82, 0x01, 0x45, 0x30, 0x12, 0x06, - 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, - 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, - 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x1d, 0x06, - 0x03, 0x55, 0x1d, 0x25, 0x04, 0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, - 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x03, 0x02, 0x30, 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x01, 0x01, 0x04, 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x64, 0x69, - 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x4b, - 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x44, 0x30, 0x42, 0x30, 0x40, 0xa0, - 0x3e, 0xa0, 0x3c, 0x86, 0x3a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x63, 0x72, 0x6c, 0x34, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, - 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, - 0x72, 0x74, 0x48, 0x69, 0x67, 0x68, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, - 0x6e, 0x63, 0x65, 0x45, 0x56, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x2e, - 0x63, 0x72, 0x6c, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, - 0x30, 0x34, 0x30, 0x32, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, - 0x30, 0x28, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, - 0x16, 0x1c, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, - 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x43, 0x50, 0x53, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, - 0x0e, 0x04, 0x16, 0x04, 0x14, 0x3d, 0xd3, 0x50, 0xa5, 0xd6, 0xa0, 0xad, - 0xee, 0xf3, 0x4a, 0x60, 0x0a, 0x65, 0xd3, 0x21, 0xd4, 0xf8, 0xf8, 0xd6, - 0x0f, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, - 0x80, 0x14, 0xb1, 0x3e, 0xc3, 0x69, 0x03, 0xf8, 0xbf, 0x47, 0x01, 0xd4, - 0x98, 0x26, 0x1a, 0x08, 0x02, 0xef, 0x63, 0x64, 0x2b, 0xc3, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, - 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x9d, 0xb6, 0xd0, 0x90, 0x86, 0xe1, - 0x86, 0x02, 0xed, 0xc5, 0xa0, 0xf0, 0x34, 0x1c, 0x74, 0xc1, 0x8d, 0x76, - 0xcc, 0x86, 0x0a, 0xa8, 0xf0, 0x4a, 0x8a, 0x42, 0xd6, 0x3f, 0xc8, 0xa9, - 0x4d, 0xad, 0x7c, 0x08, 0xad, 0xe6, 0xb6, 0x50, 0xb8, 0xa2, 0x1a, 0x4d, - 0x88, 0x07, 0xb1, 0x29, 0x21, 0xdc, 0xe7, 0xda, 0xc6, 0x3c, 0x21, 0xe0, - 0xe3, 0x11, 0x49, 0x70, 0xac, 0x7a, 0x1d, 0x01, 0xa4, 0xca, 0x11, 0x3a, - 0x57, 0xab, 0x7d, 0x57, 0x2a, 0x40, 0x74, 0xfd, 0xd3, 0x1d, 0x85, 0x18, - 0x50, 0xdf, 0x57, 0x47, 0x75, 0xa1, 0x7d, 0x55, 0x20, 0x2e, 0x47, 0x37, - 0x50, 0x72, 0x8c, 0x7f, 0x82, 0x1b, 0xd2, 0x62, 0x8f, 0x2d, 0x03, 0x5a, - 0xda, 0xc3, 0xc8, 0xa1, 0xce, 0x2c, 0x52, 0xa2, 0x00, 0x63, 0xeb, 0x73, - 0xba, 0x71, 0xc8, 0x49, 0x27, 0x23, 0x97, 0x64, 0x85, 0x9e, 0x38, 0x0e, - 0xad, 0x63, 0x68, 0x3c, 0xba, 0x52, 0x81, 0x58, 0x79, 0xa3, 0x2c, 0x0c, - 0xdf, 0xde, 0x6d, 0xeb, 0x31, 0xf2, 0xba, 0xa0, 0x7c, 0x6c, 0xf1, 0x2c, - 0xd4, 0xe1, 0xbd, 0x77, 0x84, 0x37, 0x03, 0xce, 0x32, 0xb5, 0xc8, 0x9a, - 0x81, 0x1a, 0x4a, 0x92, 0x4e, 0x3b, 0x46, 0x9a, 0x85, 0xfe, 0x83, 0xa2, - 0xf9, 0x9e, 0x8c, 0xa3, 0xcc, 0x0d, 0x5e, 0xb3, 0x3d, 0xcf, 0x04, 0x78, - 0x8f, 0x14, 0x14, 0x7b, 0x32, 0x9c, 0xc7, 0x00, 0xa6, 0x5c, 0xc4, 0xb5, - 0xa1, 0x55, 0x8d, 0x5a, 0x56, 0x68, 0xa4, 0x22, 0x70, 0xaa, 0x3c, 0x81, - 0x71, 0xd9, 0x9d, 0xa8, 0x45, 0x3b, 0xf4, 0xe5, 0xf6, 0xa2, 0x51, 0xdd, - 0xc7, 0x7b, 0x62, 0xe8, 0x6f, 0x0c, 0x74, 0xeb, 0xb8, 0xda, 0xf8, 0xbf, - 0x87, 0x0d, 0x79, 0x50, 0x91, 0x90, 0x9b, 0x18, 0x3b, 0x91, 0x59, 0x27, - 0xf1, 0x35, 0x28, 0x13, 0xab, 0x26, 0x7e, 0xd5, 0xf7, 0x7a, -} - -var certSet2Cert29 = []byte{ - 0x30, 0x82, 0x04, 0xc2, 0x30, 0x82, 0x03, 0xaa, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x36, 0x34, 0x9e, 0x18, 0xc9, 0x9c, 0x26, 0x69, 0xb6, - 0x56, 0x2e, 0x6c, 0xe5, 0xad, 0x71, 0x32, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, - 0xae, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, - 0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1f, - 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x44, - 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x38, 0x30, 0x36, 0x06, - 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, - 0x30, 0x38, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, - 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x24, 0x30, 0x22, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x1b, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50, - 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, - 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x30, 0x1e, 0x17, 0x0d, 0x31, - 0x33, 0x30, 0x35, 0x32, 0x33, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, - 0x17, 0x0d, 0x32, 0x33, 0x30, 0x35, 0x32, 0x32, 0x32, 0x33, 0x35, 0x39, - 0x35, 0x39, 0x5a, 0x30, 0x43, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, - 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, - 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, - 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x1d, 0x30, 0x1b, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x14, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x53, - 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, - 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, - 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa3, 0x63, 0x2b, - 0xd4, 0xba, 0x5d, 0x38, 0xae, 0xb0, 0xcf, 0xb9, 0x4c, 0x38, 0xdf, 0x20, - 0x7d, 0xf1, 0x2b, 0x47, 0x71, 0x1d, 0x8b, 0x68, 0xf3, 0x56, 0xf9, 0x9c, - 0xda, 0xaa, 0xe5, 0x84, 0x26, 0xde, 0xa5, 0x71, 0x30, 0xbc, 0xf3, 0x31, - 0x23, 0x9d, 0xe8, 0x3b, 0x80, 0xc8, 0x66, 0x57, 0x75, 0xb6, 0x57, 0x0e, - 0xdb, 0x93, 0xf5, 0x26, 0x8e, 0x70, 0xba, 0x64, 0x52, 0x66, 0x8a, 0x2a, - 0x88, 0x5c, 0x44, 0x18, 0x4d, 0xa8, 0xa2, 0x7c, 0xbd, 0x56, 0x61, 0x32, - 0x90, 0x12, 0xf9, 0x35, 0x87, 0x48, 0x60, 0xb0, 0x6e, 0x90, 0x67, 0x44, - 0x01, 0x8d, 0xe7, 0xc9, 0x0d, 0x63, 0x68, 0x72, 0x72, 0xab, 0x63, 0x3c, - 0x86, 0xb8, 0x1f, 0x7d, 0xad, 0x88, 0x25, 0xa7, 0x6a, 0x88, 0x29, 0xfb, - 0x59, 0xc6, 0x78, 0x71, 0x5f, 0x2c, 0xba, 0x89, 0xe6, 0xd3, 0x80, 0xfd, - 0x57, 0xec, 0xb9, 0x51, 0x5f, 0x43, 0x33, 0x2e, 0x7e, 0x25, 0x3b, 0xa4, - 0x04, 0xd1, 0x60, 0x8c, 0xb3, 0x44, 0x33, 0x93, 0x0c, 0xad, 0x2a, 0xb6, - 0x44, 0xa2, 0x19, 0x3b, 0xaf, 0xc4, 0x90, 0x6f, 0x7b, 0x05, 0x87, 0x86, - 0x9b, 0x2c, 0x6a, 0x9d, 0x2b, 0x6c, 0x77, 0xc9, 0x00, 0x9f, 0xc9, 0xcf, - 0xac, 0xed, 0x3e, 0x1b, 0xf7, 0xc3, 0xf3, 0xd9, 0xf8, 0x6c, 0xd4, 0xa0, - 0x57, 0xc4, 0xfb, 0x28, 0x32, 0xaa, 0x33, 0xf0, 0xe6, 0xba, 0x98, 0xdf, - 0xe5, 0xc2, 0x4e, 0x9c, 0x74, 0xbf, 0x8a, 0x48, 0xc2, 0xf2, 0x1b, 0xf0, - 0x77, 0x40, 0x41, 0x07, 0x04, 0xb2, 0x3a, 0xd5, 0x4c, 0xc4, 0x29, 0xa9, - 0x11, 0x40, 0x3f, 0x02, 0x46, 0xf0, 0x91, 0xd5, 0xd2, 0x81, 0x83, 0x86, - 0x13, 0xb3, 0x31, 0xed, 0x46, 0xab, 0xa8, 0x87, 0x76, 0xa9, 0x99, 0x7d, - 0xbc, 0xcd, 0x31, 0x50, 0xf4, 0xa5, 0xb5, 0xdc, 0xa5, 0x32, 0xb3, 0x8b, - 0x8b, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x44, 0x30, 0x82, - 0x01, 0x40, 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x01, 0x01, 0x04, 0x26, 0x30, 0x24, 0x30, 0x22, 0x06, 0x08, 0x2b, 0x06, - 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x16, 0x68, 0x74, 0x74, 0x70, - 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x74, 0x68, 0x61, 0x77, - 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, - 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, - 0x01, 0x00, 0x30, 0x41, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x3a, 0x30, - 0x38, 0x30, 0x36, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, - 0x01, 0x07, 0x36, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x73, - 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, - 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x37, 0x06, - 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x30, 0x30, 0x2e, 0x30, 0x2c, 0xa0, 0x2a, - 0xa0, 0x28, 0x86, 0x26, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, - 0x72, 0x6c, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x50, 0x43, 0x41, 0x2d, - 0x47, 0x33, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, - 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x2a, - 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x23, 0x30, 0x21, 0xa4, 0x1f, 0x30, - 0x1d, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, - 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x50, 0x4b, 0x49, - 0x2d, 0x32, 0x2d, 0x34, 0x31, 0x35, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, - 0x0e, 0x04, 0x16, 0x04, 0x14, 0x2b, 0x9a, 0x35, 0xae, 0x01, 0x18, 0x38, - 0x30, 0xe1, 0x70, 0x7a, 0x05, 0xe0, 0x11, 0x76, 0xa3, 0xce, 0xbd, 0x90, - 0x14, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, - 0x80, 0x14, 0xad, 0x6c, 0xaa, 0x94, 0x60, 0x9c, 0xed, 0xe4, 0xff, 0xfa, - 0x3e, 0x0a, 0x74, 0x2b, 0x63, 0x03, 0xf7, 0xb6, 0x59, 0xbf, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, - 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x74, 0xa6, 0x56, 0xe8, 0xaf, 0x93, - 0x96, 0x19, 0xfb, 0x26, 0xf9, 0x0d, 0xb0, 0x44, 0xa5, 0xcd, 0xe9, 0x7a, - 0x48, 0x03, 0x74, 0x01, 0x6c, 0x13, 0x71, 0xb7, 0xe0, 0x82, 0x90, 0x99, - 0x62, 0x23, 0xe3, 0xd6, 0x99, 0xaf, 0xf0, 0xc7, 0x1e, 0x9e, 0xa8, 0x18, - 0x21, 0xdb, 0xb4, 0x94, 0x3f, 0x34, 0x56, 0x1b, 0x99, 0x55, 0x2f, 0x8e, - 0xf0, 0x45, 0x33, 0x32, 0xb7, 0x72, 0xc1, 0x13, 0x5b, 0x34, 0xd3, 0xf5, - 0x60, 0xe5, 0x2e, 0x18, 0xd1, 0x5c, 0xc5, 0x6a, 0xc1, 0xaa, 0x87, 0x50, - 0x0c, 0x1c, 0x9d, 0x64, 0x2b, 0xff, 0x1b, 0xdc, 0xd5, 0x2e, 0x61, 0x0b, - 0xe7, 0xb9, 0xb6, 0x91, 0x53, 0x86, 0xd9, 0x03, 0x2a, 0xd1, 0x3d, 0x7b, - 0x4a, 0xda, 0x2b, 0x07, 0xbe, 0x29, 0xf2, 0x60, 0x42, 0xa9, 0x91, 0x1a, - 0x0e, 0x2e, 0x3c, 0xd1, 0x7d, 0xa5, 0x13, 0x14, 0x02, 0xfa, 0xee, 0x8b, - 0x8d, 0xb6, 0xc8, 0xb8, 0x3e, 0x56, 0x81, 0x57, 0x21, 0x24, 0x3f, 0x65, - 0xc3, 0xb4, 0xc9, 0xce, 0x5c, 0x8d, 0x46, 0xac, 0x53, 0xf3, 0xf9, 0x55, - 0x74, 0xc8, 0x2b, 0xfd, 0xd2, 0x78, 0x70, 0xf5, 0xf8, 0x11, 0xe5, 0xf4, - 0xa7, 0xad, 0x20, 0xf5, 0x9d, 0xf1, 0xec, 0x70, 0xf6, 0x13, 0xac, 0xe6, - 0x8c, 0x8d, 0xdb, 0x3f, 0xc6, 0xf2, 0x79, 0x0e, 0xab, 0x52, 0xf2, 0xcc, - 0x1b, 0x79, 0x27, 0xcf, 0x16, 0xb3, 0xd6, 0xf3, 0xc6, 0x36, 0x80, 0x43, - 0xec, 0xc5, 0x94, 0xf0, 0xdd, 0x90, 0x8d, 0xf8, 0xc6, 0x52, 0x46, 0x56, - 0xeb, 0x74, 0x47, 0xbe, 0xa6, 0xf3, 0x19, 0xae, 0x71, 0x4c, 0xc0, 0xe1, - 0xe7, 0xd4, 0xcf, 0xed, 0xd4, 0x06, 0x28, 0x2a, 0x11, 0x3c, 0xba, 0xd9, - 0x41, 0x6e, 0x00, 0xe7, 0x81, 0x37, 0x93, 0xe4, 0xda, 0x62, 0xc6, 0x1d, - 0x67, 0x6f, 0x63, 0xb4, 0x14, 0x86, 0xd9, 0xa6, 0x62, 0xf0, -} - -var certSet2Cert30 = []byte{ - 0x30, 0x82, 0x04, 0xc6, 0x30, 0x82, 0x04, 0x2f, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x35, 0x97, 0x31, 0x87, 0xf3, 0x87, 0x3a, 0x07, 0x32, - 0x7e, 0xce, 0x58, 0x0c, 0x9b, 0x7e, 0xda, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5f, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, - 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, - 0x63, 0x2e, 0x31, 0x37, 0x30, 0x35, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, - 0x2e, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, - 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, - 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, - 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x30, 0x38, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x31, 0x30, 0x37, - 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xca, 0x31, 0x0b, - 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, - 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65, - 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56, - 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, - 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3a, 0x30, - 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28, 0x63, 0x29, 0x20, - 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, - 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, - 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, - 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x45, 0x30, - 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56, 0x65, 0x72, 0x69, - 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, - 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, - 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30, 0x82, 0x01, 0x22, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, - 0x02, 0x82, 0x01, 0x01, 0x00, 0xaf, 0x24, 0x08, 0x08, 0x29, 0x7a, 0x35, - 0x9e, 0x60, 0x0c, 0xaa, 0xe7, 0x4b, 0x3b, 0x4e, 0xdc, 0x7c, 0xbc, 0x3c, - 0x45, 0x1c, 0xbb, 0x2b, 0xe0, 0xfe, 0x29, 0x02, 0xf9, 0x57, 0x08, 0xa3, - 0x64, 0x85, 0x15, 0x27, 0xf5, 0xf1, 0xad, 0xc8, 0x31, 0x89, 0x5d, 0x22, - 0xe8, 0x2a, 0xaa, 0xa6, 0x42, 0xb3, 0x8f, 0xf8, 0xb9, 0x55, 0xb7, 0xb1, - 0xb7, 0x4b, 0xb3, 0xfe, 0x8f, 0x7e, 0x07, 0x57, 0xec, 0xef, 0x43, 0xdb, - 0x66, 0x62, 0x15, 0x61, 0xcf, 0x60, 0x0d, 0xa4, 0xd8, 0xde, 0xf8, 0xe0, - 0xc3, 0x62, 0x08, 0x3d, 0x54, 0x13, 0xeb, 0x49, 0xca, 0x59, 0x54, 0x85, - 0x26, 0xe5, 0x2b, 0x8f, 0x1b, 0x9f, 0xeb, 0xf5, 0xa1, 0x91, 0xc2, 0x33, - 0x49, 0xd8, 0x43, 0x63, 0x6a, 0x52, 0x4b, 0xd2, 0x8f, 0xe8, 0x70, 0x51, - 0x4d, 0xd1, 0x89, 0x69, 0x7b, 0xc7, 0x70, 0xf6, 0xb3, 0xdc, 0x12, 0x74, - 0xdb, 0x7b, 0x5d, 0x4b, 0x56, 0xd3, 0x96, 0xbf, 0x15, 0x77, 0xa1, 0xb0, - 0xf4, 0xa2, 0x25, 0xf2, 0xaf, 0x1c, 0x92, 0x67, 0x18, 0xe5, 0xf4, 0x06, - 0x04, 0xef, 0x90, 0xb9, 0xe4, 0x00, 0xe4, 0xdd, 0x3a, 0xb5, 0x19, 0xff, - 0x02, 0xba, 0xf4, 0x3c, 0xee, 0xe0, 0x8b, 0xeb, 0x37, 0x8b, 0xec, 0xf4, - 0xd7, 0xac, 0xf2, 0xf6, 0xf0, 0x3d, 0xaf, 0xdd, 0x75, 0x91, 0x33, 0x19, - 0x1d, 0x1c, 0x40, 0xcb, 0x74, 0x24, 0x19, 0x21, 0x93, 0xd9, 0x14, 0xfe, - 0xac, 0x2a, 0x52, 0xc7, 0x8f, 0xd5, 0x04, 0x49, 0xe4, 0x8d, 0x63, 0x47, - 0x88, 0x3c, 0x69, 0x83, 0xcb, 0xfe, 0x47, 0xbd, 0x2b, 0x7e, 0x4f, 0xc5, - 0x95, 0xae, 0x0e, 0x9d, 0xd4, 0xd1, 0x43, 0xc0, 0x67, 0x73, 0xe3, 0x14, - 0x08, 0x7e, 0xe5, 0x3f, 0x9f, 0x73, 0xb8, 0x33, 0x0a, 0xcf, 0x5d, 0x3f, - 0x34, 0x87, 0x96, 0x8a, 0xee, 0x53, 0xe8, 0x25, 0x15, 0x02, 0x03, 0x01, - 0x00, 0x01, 0xa3, 0x82, 0x01, 0x91, 0x30, 0x82, 0x01, 0x8d, 0x30, 0x0f, - 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, - 0x01, 0x01, 0xff, 0x30, 0x31, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2a, - 0x30, 0x28, 0x30, 0x26, 0xa0, 0x24, 0xa0, 0x22, 0x86, 0x20, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, - 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, - 0x61, 0x33, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, - 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x3d, - 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, 0x30, 0x34, 0x30, 0x32, 0x06, - 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, 0x30, 0x28, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, 0x74, - 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72, - 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, - 0x73, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, - 0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0, 0x30, 0x09, 0xf3, - 0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30, 0x34, 0x06, 0x03, - 0x55, 0x1d, 0x25, 0x04, 0x2d, 0x30, 0x2b, 0x06, 0x09, 0x60, 0x86, 0x48, - 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, - 0x86, 0xf8, 0x45, 0x01, 0x08, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x03, 0x02, 0x30, 0x6d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x01, 0x0c, 0x04, 0x61, 0x30, 0x5f, 0xa1, 0x5d, 0xa0, 0x5b, 0x30, 0x59, - 0x30, 0x57, 0x30, 0x55, 0x16, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, - 0x67, 0x69, 0x66, 0x30, 0x21, 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, - 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14, 0x8f, 0xe5, 0xd3, 0x1a, 0x86, 0xac, - 0x8d, 0x8e, 0x6b, 0xc3, 0xcf, 0x80, 0x6a, 0xd4, 0x48, 0x18, 0x2c, 0x7b, - 0x19, 0x2e, 0x30, 0x25, 0x16, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, - 0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, - 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x73, 0x6c, 0x6f, 0x67, - 0x6f, 0x2e, 0x67, 0x69, 0x66, 0x30, 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, - 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, - 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x76, - 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x30, - 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, - 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x0f, 0x25, 0xae, 0x48, 0xed, 0x1b, - 0x33, 0x85, 0x4c, 0x0c, 0xb5, 0xc2, 0xd7, 0xfe, 0x4d, 0xd6, 0x83, 0x28, - 0x4c, 0x41, 0x65, 0x60, 0x00, 0x0b, 0x77, 0x48, 0x71, 0x82, 0xfe, 0x7f, - 0xdb, 0x5a, 0x0e, 0x20, 0xcc, 0xd2, 0xea, 0x47, 0xbc, 0x64, 0x42, 0x61, - 0x44, 0x34, 0x74, 0x30, 0x81, 0x81, 0x26, 0x8a, 0x4a, 0xf7, 0x44, 0x5d, - 0x7e, 0x34, 0x80, 0xa8, 0xb8, 0x83, 0xe2, 0x09, 0xd7, 0x6d, 0x23, 0xdd, - 0x89, 0xed, 0x28, 0x08, 0xbd, 0x63, 0x5a, 0x11, 0x57, 0x08, 0xc4, 0x9e, - 0xda, 0xe2, 0x68, 0x28, 0xaf, 0xdd, 0x50, 0x3c, 0xec, 0x82, 0x21, 0xd8, - 0x00, 0xc2, 0x55, 0x44, 0x50, 0x70, 0x41, 0xad, 0x83, 0x17, 0x79, 0xba, - 0x08, 0xf3, 0x2b, 0xde, 0xed, 0x34, 0x1d, 0x44, 0x9e, 0xd2, 0x04, 0x93, - 0xf4, 0xcb, 0x05, 0x17, 0x2d, 0x09, 0x2d, 0x2d, 0x63, 0xef, 0xf6, 0x26, - 0x0b, 0x7b, -} - -var certSet2Cert31 = []byte{ - 0x30, 0x82, 0x04, 0xd0, 0x30, 0x82, 0x03, 0xb8, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x01, 0x07, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, 0x83, 0x31, 0x0b, - 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, - 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x07, 0x41, 0x72, - 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, - 0x04, 0x07, 0x13, 0x0a, 0x53, 0x63, 0x6f, 0x74, 0x74, 0x73, 0x64, 0x61, - 0x6c, 0x65, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x11, 0x47, 0x6f, 0x44, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, - 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, - 0x55, 0x04, 0x03, 0x13, 0x28, 0x47, 0x6f, 0x20, 0x44, 0x61, 0x64, 0x64, - 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x1e, 0x17, - 0x0d, 0x31, 0x31, 0x30, 0x35, 0x30, 0x33, 0x30, 0x37, 0x30, 0x30, 0x30, - 0x30, 0x5a, 0x17, 0x0d, 0x33, 0x31, 0x30, 0x35, 0x30, 0x33, 0x30, 0x37, - 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x81, 0xb4, 0x31, 0x0b, 0x30, 0x09, - 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, - 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x07, 0x41, 0x72, 0x69, 0x7a, - 0x6f, 0x6e, 0x61, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x07, - 0x13, 0x0a, 0x53, 0x63, 0x6f, 0x74, 0x74, 0x73, 0x64, 0x61, 0x6c, 0x65, - 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x47, - 0x6f, 0x44, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x20, - 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03, 0x55, 0x04, - 0x0b, 0x13, 0x24, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, - 0x72, 0x74, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, - 0x72, 0x79, 0x2f, 0x31, 0x33, 0x30, 0x31, 0x06, 0x03, 0x55, 0x04, 0x03, - 0x13, 0x2a, 0x47, 0x6f, 0x20, 0x44, 0x61, 0x64, 0x64, 0x79, 0x20, 0x53, - 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, - 0x02, 0x82, 0x01, 0x01, 0x00, 0xb9, 0xe0, 0xcb, 0x10, 0xd4, 0xaf, 0x76, - 0xbd, 0xd4, 0x93, 0x62, 0xeb, 0x30, 0x64, 0xb8, 0x81, 0x08, 0x6c, 0xc3, - 0x04, 0xd9, 0x62, 0x17, 0x8e, 0x2f, 0xff, 0x3e, 0x65, 0xcf, 0x8f, 0xce, - 0x62, 0xe6, 0x3c, 0x52, 0x1c, 0xda, 0x16, 0x45, 0x4b, 0x55, 0xab, 0x78, - 0x6b, 0x63, 0x83, 0x62, 0x90, 0xce, 0x0f, 0x69, 0x6c, 0x99, 0xc8, 0x1a, - 0x14, 0x8b, 0x4c, 0xcc, 0x45, 0x33, 0xea, 0x88, 0xdc, 0x9e, 0xa3, 0xaf, - 0x2b, 0xfe, 0x80, 0x61, 0x9d, 0x79, 0x57, 0xc4, 0xcf, 0x2e, 0xf4, 0x3f, - 0x30, 0x3c, 0x5d, 0x47, 0xfc, 0x9a, 0x16, 0xbc, 0xc3, 0x37, 0x96, 0x41, - 0x51, 0x8e, 0x11, 0x4b, 0x54, 0xf8, 0x28, 0xbe, 0xd0, 0x8c, 0xbe, 0xf0, - 0x30, 0x38, 0x1e, 0xf3, 0xb0, 0x26, 0xf8, 0x66, 0x47, 0x63, 0x6d, 0xde, - 0x71, 0x26, 0x47, 0x8f, 0x38, 0x47, 0x53, 0xd1, 0x46, 0x1d, 0xb4, 0xe3, - 0xdc, 0x00, 0xea, 0x45, 0xac, 0xbd, 0xbc, 0x71, 0xd9, 0xaa, 0x6f, 0x00, - 0xdb, 0xdb, 0xcd, 0x30, 0x3a, 0x79, 0x4f, 0x5f, 0x4c, 0x47, 0xf8, 0x1d, - 0xef, 0x5b, 0xc2, 0xc4, 0x9d, 0x60, 0x3b, 0xb1, 0xb2, 0x43, 0x91, 0xd8, - 0xa4, 0x33, 0x4e, 0xea, 0xb3, 0xd6, 0x27, 0x4f, 0xad, 0x25, 0x8a, 0xa5, - 0xc6, 0xf4, 0xd5, 0xd0, 0xa6, 0xae, 0x74, 0x05, 0x64, 0x57, 0x88, 0xb5, - 0x44, 0x55, 0xd4, 0x2d, 0x2a, 0x3a, 0x3e, 0xf8, 0xb8, 0xbd, 0xe9, 0x32, - 0x0a, 0x02, 0x94, 0x64, 0xc4, 0x16, 0x3a, 0x50, 0xf1, 0x4a, 0xae, 0xe7, - 0x79, 0x33, 0xaf, 0x0c, 0x20, 0x07, 0x7f, 0xe8, 0xdf, 0x04, 0x39, 0xc2, - 0x69, 0x02, 0x6c, 0x63, 0x52, 0xfa, 0x77, 0xc1, 0x1b, 0xc8, 0x74, 0x87, - 0xc8, 0xb9, 0x93, 0x18, 0x50, 0x54, 0x35, 0x4b, 0x69, 0x4e, 0xbc, 0x3b, - 0xd3, 0x49, 0x2e, 0x1f, 0xdc, 0xc1, 0xd2, 0x52, 0xfb, 0x02, 0x03, 0x01, - 0x00, 0x01, 0xa3, 0x82, 0x01, 0x1a, 0x30, 0x82, 0x01, 0x16, 0x30, 0x0f, - 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, - 0x01, 0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, - 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, - 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x40, 0xc2, 0xbd, 0x27, 0x8e, 0xcc, - 0x34, 0x83, 0x30, 0xa2, 0x33, 0xd7, 0xfb, 0x6c, 0xb3, 0xf0, 0xb4, 0x2c, - 0x80, 0xce, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, - 0x16, 0x80, 0x14, 0x3a, 0x9a, 0x85, 0x07, 0x10, 0x67, 0x28, 0xb6, 0xef, - 0xf6, 0xbd, 0x05, 0x41, 0x6e, 0x20, 0xc1, 0x94, 0xda, 0x0f, 0xde, 0x30, - 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, - 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x35, 0x06, 0x03, 0x55, 0x1d, 0x1f, - 0x04, 0x2e, 0x30, 0x2c, 0x30, 0x2a, 0xa0, 0x28, 0xa0, 0x26, 0x86, 0x24, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x67, - 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, - 0x64, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x67, 0x32, 0x2e, 0x63, 0x72, 0x6c, - 0x30, 0x46, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x3f, 0x30, 0x3d, 0x30, - 0x3b, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x33, 0x30, 0x31, 0x06, - 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x25, 0x68, - 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x73, - 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x08, 0x7e, 0x6c, 0x93, - 0x10, 0xc8, 0x38, 0xb8, 0x96, 0xa9, 0x90, 0x4b, 0xff, 0xa1, 0x5f, 0x4f, - 0x04, 0xef, 0x6c, 0x3e, 0x9c, 0x88, 0x06, 0xc9, 0x50, 0x8f, 0xa6, 0x73, - 0xf7, 0x57, 0x31, 0x1b, 0xbe, 0xbc, 0xe4, 0x2f, 0xdb, 0xf8, 0xba, 0xd3, - 0x5b, 0xe0, 0xb4, 0xe7, 0xe6, 0x79, 0x62, 0x0e, 0x0c, 0xa2, 0xd7, 0x6a, - 0x63, 0x73, 0x31, 0xb5, 0xf5, 0xa8, 0x48, 0xa4, 0x3b, 0x08, 0x2d, 0xa2, - 0x5d, 0x90, 0xd7, 0xb4, 0x7c, 0x25, 0x4f, 0x11, 0x56, 0x30, 0xc4, 0xb6, - 0x44, 0x9d, 0x7b, 0x2c, 0x9d, 0xe5, 0x5e, 0xe6, 0xef, 0x0c, 0x61, 0xaa, - 0xbf, 0xe4, 0x2a, 0x1b, 0xee, 0x84, 0x9e, 0xb8, 0x83, 0x7d, 0xc1, 0x43, - 0xce, 0x44, 0xa7, 0x13, 0x70, 0x0d, 0x91, 0x1f, 0xf4, 0xc8, 0x13, 0xad, - 0x83, 0x60, 0xd9, 0xd8, 0x72, 0xa8, 0x73, 0x24, 0x1e, 0xb5, 0xac, 0x22, - 0x0e, 0xca, 0x17, 0x89, 0x62, 0x58, 0x44, 0x1b, 0xab, 0x89, 0x25, 0x01, - 0x00, 0x0f, 0xcd, 0xc4, 0x1b, 0x62, 0xdb, 0x51, 0xb4, 0xd3, 0x0f, 0x51, - 0x2a, 0x9b, 0xf4, 0xbc, 0x73, 0xfc, 0x76, 0xce, 0x36, 0xa4, 0xcd, 0xd9, - 0xd8, 0x2c, 0xea, 0xae, 0x9b, 0xf5, 0x2a, 0xb2, 0x90, 0xd1, 0x4d, 0x75, - 0x18, 0x8a, 0x3f, 0x8a, 0x41, 0x90, 0x23, 0x7d, 0x5b, 0x4b, 0xfe, 0xa4, - 0x03, 0x58, 0x9b, 0x46, 0xb2, 0xc3, 0x60, 0x60, 0x83, 0xf8, 0x7d, 0x50, - 0x41, 0xce, 0xc2, 0xa1, 0x90, 0xc3, 0xbb, 0xef, 0x02, 0x2f, 0xd2, 0x15, - 0x54, 0xee, 0x44, 0x15, 0xd9, 0x0a, 0xae, 0xa7, 0x8a, 0x33, 0xed, 0xb1, - 0x2d, 0x76, 0x36, 0x26, 0xdc, 0x04, 0xeb, 0x9f, 0xf7, 0x61, 0x1f, 0x15, - 0xdc, 0x87, 0x6f, 0xee, 0x46, 0x96, 0x28, 0xad, 0xa1, 0x26, 0x7d, 0x0a, - 0x09, 0xa7, 0x2e, 0x04, 0xa3, 0x8d, 0xbc, 0xf8, 0xbc, 0x04, 0x30, 0x01, -} - -var certSet2Cert32 = []byte{ - 0x30, 0x82, 0x04, 0xd0, 0x30, 0x82, 0x03, 0xb8, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x0a, 0x48, 0x9e, 0x88, 0x53, 0x7e, 0x8a, 0xa6, 0x45, - 0x4d, 0x6e, 0x2c, 0x4b, 0x2a, 0xeb, 0x20, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, - 0xae, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, - 0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1f, - 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x44, - 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x38, 0x30, 0x36, 0x06, - 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, - 0x30, 0x38, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, - 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x24, 0x30, 0x22, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x1b, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50, - 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, - 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x30, 0x1e, 0x17, 0x0d, 0x31, - 0x33, 0x30, 0x34, 0x30, 0x39, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, - 0x17, 0x0d, 0x32, 0x33, 0x30, 0x34, 0x30, 0x38, 0x32, 0x33, 0x35, 0x39, - 0x35, 0x39, 0x5a, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, - 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, - 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, - 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x28, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x45, - 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x20, 0x56, 0x61, 0x6c, 0x69, - 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, - 0x36, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, - 0x02, 0x82, 0x01, 0x01, 0x00, 0xf2, 0xc4, 0xbc, 0x74, 0xe8, 0x25, 0xf6, - 0x00, 0x62, 0x28, 0xe3, 0x4c, 0xe8, 0xb8, 0xdf, 0x13, 0x9f, 0x8b, 0x07, - 0x37, 0xef, 0x62, 0x4a, 0xf1, 0x57, 0x09, 0xf6, 0x82, 0xe8, 0x75, 0xf0, - 0x0a, 0xa9, 0x27, 0xcf, 0x93, 0x3b, 0xec, 0x36, 0x89, 0xa5, 0x6e, 0x1d, - 0xd6, 0x54, 0xf3, 0xb8, 0x04, 0x97, 0x72, 0xb4, 0x69, 0x25, 0xcc, 0xd1, - 0x42, 0x0e, 0x5b, 0xd5, 0x1c, 0x7f, 0xa2, 0x60, 0x6e, 0xb1, 0x52, 0x1a, - 0xdb, 0x93, 0x2f, 0xbb, 0x0b, 0x0d, 0x64, 0x53, 0x16, 0xcb, 0x1c, 0x09, - 0x24, 0x95, 0x29, 0x22, 0xb4, 0x8a, 0x18, 0x00, 0x89, 0xfe, 0xf7, 0x1f, - 0x72, 0xc8, 0xe8, 0x5c, 0x2f, 0x1a, 0x1b, 0xa2, 0x18, 0xb8, 0xef, 0x18, - 0x5c, 0xcb, 0xb5, 0xdb, 0x3a, 0x4e, 0xdb, 0x0f, 0xae, 0xdf, 0xc4, 0x79, - 0xe3, 0x1e, 0xaa, 0x5c, 0xa3, 0xa4, 0xe5, 0xac, 0x61, 0x9b, 0x37, 0x85, - 0x8f, 0x48, 0x75, 0x1b, 0xb9, 0xd5, 0x68, 0x96, 0xe9, 0x27, 0x79, 0x70, - 0x57, 0x23, 0x1a, 0xbb, 0x6c, 0x93, 0x90, 0xc7, 0x45, 0xd7, 0x17, 0xd2, - 0x37, 0x2a, 0x76, 0xb3, 0xcd, 0x82, 0xa9, 0x4f, 0xc0, 0x03, 0x7b, 0xe1, - 0x3d, 0x7a, 0x7e, 0x5b, 0xb8, 0x85, 0xf2, 0xf5, 0x15, 0xfb, 0x70, 0xa9, - 0xbd, 0xf5, 0x50, 0x65, 0x16, 0x9d, 0xe3, 0xb6, 0x6b, 0x61, 0x6e, 0xa1, - 0x7a, 0x9e, 0xe8, 0x0d, 0x1c, 0xf7, 0x2a, 0x8e, 0x69, 0x7e, 0x43, 0x30, - 0x8e, 0x78, 0xce, 0xee, 0x65, 0x1e, 0x3b, 0x9b, 0x87, 0x1e, 0x49, 0x1c, - 0xf8, 0x32, 0x46, 0x5d, 0x28, 0x46, 0x79, 0x2a, 0x4e, 0x27, 0x5d, 0x17, - 0x58, 0xa8, 0x37, 0xfe, 0xa8, 0x13, 0xa9, 0x69, 0x15, 0xdf, 0x36, 0x22, - 0x89, 0x75, 0xba, 0xca, 0x01, 0x40, 0x2e, 0xed, 0x9d, 0xd7, 0x0c, 0xaa, - 0x31, 0xce, 0x27, 0xae, 0x57, 0xd5, 0xd2, 0x51, 0xfb, 0x02, 0x03, 0x01, - 0x00, 0x01, 0xa3, 0x82, 0x01, 0x3e, 0x30, 0x82, 0x01, 0x3a, 0x30, 0x12, - 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, - 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, - 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x32, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x26, - 0x30, 0x24, 0x30, 0x22, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x30, 0x01, 0x86, 0x16, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, - 0x63, 0x73, 0x70, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63, - 0x6f, 0x6d, 0x30, 0x3b, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x34, 0x30, - 0x32, 0x30, 0x30, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x28, 0x30, - 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, - 0x1a, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, - 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x63, 0x70, 0x73, 0x30, 0x37, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x30, - 0x30, 0x2e, 0x30, 0x2c, 0xa0, 0x2a, 0xa0, 0x28, 0x86, 0x26, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x74, 0x68, 0x61, - 0x77, 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x54, 0x68, 0x61, 0x77, - 0x74, 0x65, 0x50, 0x43, 0x41, 0x2d, 0x47, 0x33, 0x2e, 0x63, 0x72, 0x6c, - 0x30, 0x2a, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x23, 0x30, 0x21, 0xa4, - 0x1f, 0x30, 0x1d, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, - 0x13, 0x12, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x50, - 0x4b, 0x49, 0x2d, 0x32, 0x2d, 0x33, 0x37, 0x34, 0x30, 0x1d, 0x06, 0x03, - 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x3b, 0x24, 0xc8, 0x31, 0xa0, - 0xb7, 0x5a, 0xd0, 0x6a, 0xb8, 0xd2, 0xca, 0x07, 0x74, 0xcc, 0x1e, 0x24, - 0xd4, 0xc4, 0xdc, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, - 0x30, 0x16, 0x80, 0x14, 0xad, 0x6c, 0xaa, 0x94, 0x60, 0x9c, 0xed, 0xe4, - 0xff, 0xfa, 0x3e, 0x0a, 0x74, 0x2b, 0x63, 0x03, 0xf7, 0xb6, 0x59, 0xbf, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x68, 0x98, 0x26, 0xaa, - 0xd4, 0x33, 0xc9, 0xba, 0x75, 0x70, 0xd4, 0x9f, 0x49, 0xad, 0xd6, 0xc1, - 0x54, 0xdc, 0xee, 0xaa, 0x56, 0x1f, 0x78, 0xa7, 0xf0, 0xa1, 0xa4, 0xee, - 0x0b, 0xf9, 0x12, 0xaf, 0xdf, 0xa6, 0xb8, 0xee, 0xc3, 0xcb, 0x35, 0x13, - 0x6a, 0x59, 0x2a, 0xf8, 0xc9, 0xe9, 0x4c, 0x2f, 0xbc, 0xb1, 0xbc, 0x2b, - 0xc2, 0x02, 0x30, 0xe1, 0xc3, 0xbe, 0xc2, 0xf0, 0x81, 0x8c, 0x99, 0x77, - 0x89, 0x58, 0x00, 0xa3, 0xcc, 0x7f, 0xa3, 0x02, 0x4c, 0x53, 0xb2, 0x6e, - 0x36, 0x4f, 0xfe, 0xdf, 0x87, 0x76, 0xb3, 0x3f, 0xec, 0x5a, 0x62, 0x50, - 0xb6, 0x00, 0x45, 0x58, 0xf2, 0x87, 0xac, 0x77, 0xe6, 0xd0, 0x20, 0x50, - 0x63, 0xc5, 0xe4, 0xb2, 0x70, 0x15, 0x18, 0x90, 0x05, 0x7b, 0x7b, 0xaf, - 0x2b, 0x46, 0xbe, 0x6b, 0x4e, 0x1f, 0x53, 0xfc, 0x84, 0x27, 0xae, 0x83, - 0xd2, 0x8d, 0x47, 0x53, 0xa7, 0x0e, 0x1f, 0x63, 0xb5, 0xba, 0xdb, 0x16, - 0xd8, 0x6a, 0x09, 0x25, 0x55, 0x7d, 0x8f, 0x3d, 0x4a, 0xc1, 0x83, 0xf9, - 0xb3, 0xb9, 0xa7, 0x04, 0x5a, 0xc8, 0xf3, 0x11, 0x04, 0x91, 0x53, 0x30, - 0xd9, 0x52, 0x87, 0xcb, 0x39, 0x00, 0x9c, 0xec, 0x53, 0xc3, 0x02, 0x09, - 0x7e, 0xa7, 0x36, 0x8e, 0x72, 0x21, 0x2f, 0x23, 0xbb, 0x4c, 0xc6, 0x47, - 0xa5, 0xa1, 0xee, 0x67, 0xc4, 0x2f, 0x5c, 0x3a, 0x47, 0x38, 0x61, 0xe2, - 0xc3, 0x1e, 0x37, 0x92, 0x9e, 0xc8, 0x2f, 0x6b, 0xfa, 0xef, 0xd2, 0xc3, - 0xcd, 0x29, 0x8d, 0x98, 0xf8, 0x52, 0x17, 0xed, 0xb5, 0x53, 0x3c, 0xdf, - 0xaf, 0xc9, 0x1b, 0x62, 0xad, 0xdf, 0x02, 0xee, 0x5d, 0x34, 0xf6, 0x41, - 0x4b, 0xcb, 0xc3, 0x55, 0xaf, 0xb1, 0xcb, 0xda, 0x9c, 0x73, 0xd5, 0x02, - 0xa8, 0x2d, 0xa7, 0xac, 0xfc, 0xe1, 0xe5, 0x07, 0xd0, 0x51, 0xe8, 0x35, -} - -var certSet2Cert33 = []byte{ - 0x30, 0x82, 0x04, 0xd0, 0x30, 0x82, 0x04, 0x39, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x25, 0x0c, 0xe8, 0xe0, 0x30, 0x61, 0x2e, 0x9f, 0x2b, - 0x89, 0xf7, 0x05, 0x4d, 0x7c, 0xf8, 0xfd, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5f, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, - 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, - 0x63, 0x2e, 0x31, 0x37, 0x30, 0x35, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, - 0x2e, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, - 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, - 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, - 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x30, 0x38, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x31, 0x30, 0x37, - 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xca, 0x31, 0x0b, - 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, - 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65, - 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56, - 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, - 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3a, 0x30, - 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28, 0x63, 0x29, 0x20, - 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, - 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, - 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, - 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x45, 0x30, - 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56, 0x65, 0x72, 0x69, - 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, - 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, - 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30, 0x82, 0x01, 0x22, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, - 0x02, 0x82, 0x01, 0x01, 0x00, 0xaf, 0x24, 0x08, 0x08, 0x29, 0x7a, 0x35, - 0x9e, 0x60, 0x0c, 0xaa, 0xe7, 0x4b, 0x3b, 0x4e, 0xdc, 0x7c, 0xbc, 0x3c, - 0x45, 0x1c, 0xbb, 0x2b, 0xe0, 0xfe, 0x29, 0x02, 0xf9, 0x57, 0x08, 0xa3, - 0x64, 0x85, 0x15, 0x27, 0xf5, 0xf1, 0xad, 0xc8, 0x31, 0x89, 0x5d, 0x22, - 0xe8, 0x2a, 0xaa, 0xa6, 0x42, 0xb3, 0x8f, 0xf8, 0xb9, 0x55, 0xb7, 0xb1, - 0xb7, 0x4b, 0xb3, 0xfe, 0x8f, 0x7e, 0x07, 0x57, 0xec, 0xef, 0x43, 0xdb, - 0x66, 0x62, 0x15, 0x61, 0xcf, 0x60, 0x0d, 0xa4, 0xd8, 0xde, 0xf8, 0xe0, - 0xc3, 0x62, 0x08, 0x3d, 0x54, 0x13, 0xeb, 0x49, 0xca, 0x59, 0x54, 0x85, - 0x26, 0xe5, 0x2b, 0x8f, 0x1b, 0x9f, 0xeb, 0xf5, 0xa1, 0x91, 0xc2, 0x33, - 0x49, 0xd8, 0x43, 0x63, 0x6a, 0x52, 0x4b, 0xd2, 0x8f, 0xe8, 0x70, 0x51, - 0x4d, 0xd1, 0x89, 0x69, 0x7b, 0xc7, 0x70, 0xf6, 0xb3, 0xdc, 0x12, 0x74, - 0xdb, 0x7b, 0x5d, 0x4b, 0x56, 0xd3, 0x96, 0xbf, 0x15, 0x77, 0xa1, 0xb0, - 0xf4, 0xa2, 0x25, 0xf2, 0xaf, 0x1c, 0x92, 0x67, 0x18, 0xe5, 0xf4, 0x06, - 0x04, 0xef, 0x90, 0xb9, 0xe4, 0x00, 0xe4, 0xdd, 0x3a, 0xb5, 0x19, 0xff, - 0x02, 0xba, 0xf4, 0x3c, 0xee, 0xe0, 0x8b, 0xeb, 0x37, 0x8b, 0xec, 0xf4, - 0xd7, 0xac, 0xf2, 0xf6, 0xf0, 0x3d, 0xaf, 0xdd, 0x75, 0x91, 0x33, 0x19, - 0x1d, 0x1c, 0x40, 0xcb, 0x74, 0x24, 0x19, 0x21, 0x93, 0xd9, 0x14, 0xfe, - 0xac, 0x2a, 0x52, 0xc7, 0x8f, 0xd5, 0x04, 0x49, 0xe4, 0x8d, 0x63, 0x47, - 0x88, 0x3c, 0x69, 0x83, 0xcb, 0xfe, 0x47, 0xbd, 0x2b, 0x7e, 0x4f, 0xc5, - 0x95, 0xae, 0x0e, 0x9d, 0xd4, 0xd1, 0x43, 0xc0, 0x67, 0x73, 0xe3, 0x14, - 0x08, 0x7e, 0xe5, 0x3f, 0x9f, 0x73, 0xb8, 0x33, 0x0a, 0xcf, 0x5d, 0x3f, - 0x34, 0x87, 0x96, 0x8a, 0xee, 0x53, 0xe8, 0x25, 0x15, 0x02, 0x03, 0x01, - 0x00, 0x01, 0xa3, 0x82, 0x01, 0x9b, 0x30, 0x82, 0x01, 0x97, 0x30, 0x0f, - 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, - 0x01, 0x01, 0xff, 0x30, 0x31, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2a, - 0x30, 0x28, 0x30, 0x26, 0xa0, 0x24, 0xa0, 0x22, 0x86, 0x20, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, - 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, - 0x61, 0x33, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, - 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x3d, - 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, 0x30, 0x34, 0x30, 0x32, 0x06, - 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, 0x30, 0x28, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, 0x74, - 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72, - 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, - 0x73, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, - 0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0, 0x30, 0x09, 0xf3, - 0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30, 0x6d, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x0c, 0x04, 0x61, 0x30, 0x5f, - 0xa1, 0x5d, 0xa0, 0x5b, 0x30, 0x59, 0x30, 0x57, 0x30, 0x55, 0x16, 0x09, - 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, 0x69, 0x66, 0x30, 0x21, 0x30, - 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14, - 0x8f, 0xe5, 0xd3, 0x1a, 0x86, 0xac, 0x8d, 0x8e, 0x6b, 0xc3, 0xcf, 0x80, - 0x6a, 0xd4, 0x48, 0x18, 0x2c, 0x7b, 0x19, 0x2e, 0x30, 0x25, 0x16, 0x23, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, - 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x76, 0x73, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x67, 0x69, 0x66, 0x30, - 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, - 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, - 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x3e, 0x06, 0x03, 0x55, 0x1d, 0x25, - 0x04, 0x37, 0x30, 0x35, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x03, 0x06, 0x09, - 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01, 0x06, 0x0a, 0x60, - 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x08, 0x01, 0x30, 0x0d, 0x06, - 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, - 0x03, 0x81, 0x81, 0x00, 0x13, 0x02, 0xdd, 0xf8, 0xe8, 0x86, 0x00, 0xf2, - 0x5a, 0xf8, 0xf8, 0x20, 0x0c, 0x59, 0x88, 0x62, 0x07, 0xce, 0xce, 0xf7, - 0x4e, 0xf9, 0xbb, 0x59, 0xa1, 0x98, 0xe5, 0xe1, 0x38, 0xdd, 0x4e, 0xbc, - 0x66, 0x18, 0xd3, 0xad, 0xeb, 0x18, 0xf2, 0x0d, 0xc9, 0x6d, 0x3e, 0x4a, - 0x94, 0x20, 0xc3, 0x3c, 0xba, 0xbd, 0x65, 0x54, 0xc6, 0xaf, 0x44, 0xb3, - 0x10, 0xad, 0x2c, 0x6b, 0x3e, 0xab, 0xd7, 0x07, 0xb6, 0xb8, 0x81, 0x63, - 0xc5, 0xf9, 0x5e, 0x2e, 0xe5, 0x2a, 0x67, 0xce, 0xcd, 0x33, 0x0c, 0x2a, - 0xd7, 0x89, 0x56, 0x03, 0x23, 0x1f, 0xb3, 0xbe, 0xe8, 0x3a, 0x08, 0x59, - 0xb4, 0xec, 0x45, 0x35, 0xf7, 0x8a, 0x5b, 0xff, 0x66, 0xcf, 0x50, 0xaf, - 0xc6, 0x6d, 0x57, 0x8d, 0x19, 0x78, 0xb7, 0xb9, 0xa2, 0xd1, 0x57, 0xea, - 0x1f, 0x9a, 0x4b, 0xaf, 0xba, 0xc9, 0x8e, 0x12, 0x7e, 0xc6, 0xbd, 0xff, -} - -var certSet2Cert34 = []byte{ - 0x30, 0x82, 0x04, 0xd2, 0x30, 0x82, 0x03, 0xba, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x2c, 0x69, 0xe1, 0x2f, 0x6a, 0x67, 0x0b, 0xd9, 0x9d, - 0xd2, 0x0f, 0x91, 0x9e, 0xf0, 0x9e, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, - 0xa9, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, - 0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1f, - 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x44, - 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x38, 0x30, 0x36, 0x06, - 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, - 0x30, 0x36, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, - 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x16, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50, - 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, - 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x34, 0x30, 0x36, 0x31, 0x30, - 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x34, 0x30, - 0x36, 0x30, 0x39, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x63, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, - 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1d, 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x14, 0x44, - 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, - 0x74, 0x65, 0x64, 0x20, 0x53, 0x53, 0x4c, 0x31, 0x1e, 0x30, 0x1c, 0x06, - 0x03, 0x55, 0x04, 0x03, 0x13, 0x15, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, - 0x20, 0x44, 0x56, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x20, 0x2d, - 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, - 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, - 0xea, 0x94, 0x07, 0x85, 0xc8, 0x41, 0x2c, 0xf6, 0x83, 0x12, 0x6c, 0x92, - 0x5f, 0xab, 0x1f, 0x00, 0xd4, 0x96, 0x6f, 0x74, 0xcd, 0x2e, 0x11, 0xe9, - 0x6c, 0x0f, 0x39, 0x01, 0xb9, 0x48, 0x90, 0x40, 0x39, 0x4d, 0xc4, 0xa2, - 0xc8, 0x79, 0x6a, 0xa5, 0x9a, 0xbd, 0x91, 0x44, 0x65, 0x77, 0x54, 0xad, - 0xff, 0x25, 0x5f, 0xee, 0x42, 0xfb, 0xb3, 0x02, 0x0f, 0xea, 0x5d, 0x7a, - 0xdd, 0x1a, 0x54, 0x9e, 0xd7, 0x73, 0x42, 0x9b, 0xcc, 0x79, 0x5f, 0xc5, - 0x4d, 0xf4, 0xb7, 0x0b, 0x18, 0x39, 0x20, 0x7a, 0xdd, 0x50, 0x01, 0x5d, - 0x34, 0x45, 0x5f, 0x4c, 0x11, 0x0e, 0xf5, 0x87, 0x26, 0x26, 0xb4, 0xb0, - 0xf3, 0x7e, 0x71, 0xa0, 0x31, 0x71, 0x50, 0x89, 0x68, 0x5a, 0x63, 0x8a, - 0x14, 0x62, 0xe5, 0x8c, 0x3a, 0x16, 0x55, 0x0d, 0x3e, 0xeb, 0xaa, 0x80, - 0x1d, 0x71, 0x7a, 0xe3, 0x87, 0x07, 0xab, 0xbd, 0xa2, 0x74, 0xcd, 0xda, - 0x08, 0x01, 0x9d, 0x1b, 0xcc, 0x27, 0x88, 0x8c, 0x47, 0xd4, 0x69, 0x25, - 0x42, 0xd6, 0xbb, 0x50, 0x6d, 0x85, 0x50, 0xd0, 0x48, 0x82, 0x0d, 0x08, - 0x9f, 0xe9, 0x23, 0xe3, 0x42, 0xc6, 0x3c, 0x98, 0xb8, 0xbb, 0x6e, 0xc5, - 0x70, 0x13, 0xdf, 0x19, 0x1d, 0x01, 0xfd, 0xd2, 0xb5, 0x4e, 0xe6, 0x62, - 0xf4, 0x07, 0xfa, 0x6b, 0x7d, 0x11, 0x77, 0xc4, 0x62, 0x4f, 0x40, 0x4e, - 0xa5, 0x78, 0x97, 0xab, 0x2c, 0x4d, 0x0c, 0xa7, 0x7c, 0xc3, 0xc4, 0x50, - 0x32, 0x9f, 0xd0, 0x70, 0x9b, 0x0f, 0xff, 0xff, 0x75, 0x59, 0x34, 0x85, - 0xad, 0x49, 0xd5, 0x35, 0xee, 0x4f, 0x5b, 0xd4, 0xd4, 0x36, 0x95, 0xa0, - 0x7e, 0xe8, 0xc5, 0xa1, 0x1c, 0xbd, 0x13, 0x4e, 0x7d, 0xee, 0x63, 0x6a, - 0x96, 0x19, 0x99, 0xc8, 0xa7, 0x2a, 0x00, 0xe6, 0x51, 0x8d, 0x46, 0xeb, - 0x30, 0x58, 0xe8, 0x2d, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, - 0x39, 0x30, 0x82, 0x01, 0x35, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, - 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, - 0x00, 0x30, 0x41, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x3a, 0x30, 0x38, - 0x30, 0x36, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, - 0x07, 0x36, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, - 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x0e, 0x06, 0x03, - 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, - 0x30, 0x2e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, - 0x04, 0x22, 0x30, 0x20, 0x30, 0x1e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x30, 0x01, 0x86, 0x12, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, - 0x2f, 0x74, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x64, 0x2e, 0x63, 0x6f, 0x6d, - 0x30, 0x31, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2a, 0x30, 0x28, 0x30, - 0x26, 0xa0, 0x24, 0xa0, 0x22, 0x86, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x74, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x50, 0x43, 0x41, 0x2e, - 0x63, 0x72, 0x6c, 0x30, 0x29, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x22, - 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, - 0x55, 0x04, 0x03, 0x13, 0x11, 0x53, 0x79, 0x6d, 0x61, 0x6e, 0x74, 0x65, - 0x63, 0x50, 0x4b, 0x49, 0x2d, 0x31, 0x2d, 0x36, 0x39, 0x38, 0x30, 0x1d, - 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x9f, 0xb8, 0xc1, - 0xa9, 0x6c, 0xf2, 0xf5, 0xc0, 0x22, 0x2a, 0x94, 0xed, 0x5c, 0x99, 0xac, - 0xd4, 0xec, 0xd7, 0xc6, 0x07, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, - 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x7b, 0x5b, 0x45, 0xcf, 0xaf, 0xce, - 0xcb, 0x7a, 0xfd, 0x31, 0x92, 0x1a, 0x6a, 0xb6, 0xf3, 0x46, 0xeb, 0x57, - 0x48, 0x50, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, - 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x53, 0x54, - 0xf2, 0x47, 0xa8, 0x02, 0xd7, 0xef, 0xaa, 0x35, 0x78, 0xbe, 0x4a, 0x08, - 0x0d, 0x90, 0x18, 0x4b, 0x6d, 0x9e, 0x2a, 0x53, 0x2b, 0xe9, 0x54, 0x17, - 0x77, 0x74, 0x29, 0x7e, 0xd0, 0x37, 0x07, 0x05, 0xb8, 0xe4, 0xfa, 0xb8, - 0xb4, 0x63, 0x98, 0x44, 0xdc, 0xc6, 0x4f, 0x81, 0x06, 0x8c, 0x3a, 0xbe, - 0xc7, 0x30, 0x57, 0xc6, 0x70, 0xfc, 0xd6, 0x93, 0x19, 0x9f, 0xc3, 0x55, - 0xd7, 0x3e, 0x1f, 0x72, 0x8a, 0x9d, 0x30, 0x5a, 0x35, 0x97, 0x32, 0xcb, - 0x63, 0xe4, 0xc6, 0x72, 0xdf, 0xfb, 0x68, 0xca, 0x69, 0x2f, 0xdb, 0xcd, - 0x50, 0x38, 0x3e, 0x2b, 0xbb, 0xab, 0x3b, 0x82, 0xc7, 0xfd, 0x4b, 0x9b, - 0xbd, 0x7c, 0x41, 0x98, 0xef, 0x01, 0x53, 0xd8, 0x35, 0x8f, 0x25, 0xc9, - 0x03, 0x06, 0xe6, 0x9c, 0x57, 0xc1, 0x51, 0x0f, 0x9e, 0xf6, 0x7d, 0x93, - 0x4d, 0xf8, 0x76, 0xc8, 0x3a, 0x6b, 0xf4, 0xc4, 0x8f, 0x33, 0x32, 0x7f, - 0x9d, 0x21, 0x84, 0x34, 0xd9, 0xa7, 0xf9, 0x92, 0xfa, 0x41, 0x91, 0x61, - 0x84, 0x05, 0x9d, 0xa3, 0x79, 0x46, 0xce, 0x67, 0xe7, 0x81, 0xf2, 0x5e, - 0xac, 0x4c, 0xbc, 0xa8, 0xab, 0x6a, 0x6d, 0x15, 0xe2, 0x9c, 0x4e, 0x5a, - 0xd9, 0x63, 0x80, 0xbc, 0xf7, 0x42, 0xeb, 0x9a, 0x44, 0xc6, 0x8c, 0x6b, - 0x06, 0x36, 0xb4, 0x8b, 0x32, 0x89, 0xde, 0xc2, 0xf1, 0xa8, 0x26, 0xaa, - 0xa9, 0xac, 0xff, 0xea, 0x71, 0xa6, 0xe7, 0x8c, 0x41, 0xfa, 0x17, 0x35, - 0xbb, 0xb3, 0x87, 0x31, 0xa9, 0x93, 0xc2, 0xc8, 0x58, 0xe1, 0x0a, 0x4e, - 0x95, 0x83, 0x9c, 0xb9, 0xed, 0x3b, 0xa5, 0xef, 0x08, 0xe0, 0x74, 0xf9, - 0xc3, 0x1b, 0xe6, 0x07, 0xa3, 0xee, 0x07, 0xd7, 0x42, 0x22, 0x79, 0x21, - 0xa0, 0xa1, 0xd4, 0x1d, 0x26, 0xd3, 0xd0, 0xd6, 0xa6, 0x5d, 0x2b, 0x41, - 0xc0, 0x79, -} - -var certSet2Cert35 = []byte{ - 0x30, 0x82, 0x04, 0xe4, 0x30, 0x82, 0x03, 0xcc, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x4f, 0xe3, 0xe2, 0x65, 0x21, 0x07, 0xab, 0x20, 0x37, - 0x41, 0x6e, 0x48, 0x70, 0xce, 0xd2, 0xc2, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53, - 0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b, - 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31, - 0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64, - 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77, - 0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03, - 0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, - 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52, - 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x35, 0x32, - 0x35, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30, - 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30, - 0x6b, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x24, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x20, 0x53, 0x65, 0x63, - 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, - 0x79, 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x24, - 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x20, 0x53, 0x65, 0x63, 0x75, - 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, - 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, - 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0x80, 0x0b, 0x42, - 0xc6, 0x06, 0x6c, 0xcf, 0x22, 0xb3, 0x1a, 0x9e, 0x11, 0x2e, 0x42, 0x6e, - 0x39, 0xbf, 0xe8, 0x12, 0xaf, 0x3c, 0x42, 0x21, 0x12, 0x95, 0x40, 0x5d, - 0x32, 0xb1, 0x6d, 0x1c, 0x21, 0xd1, 0x34, 0xe5, 0x4f, 0xa8, 0xd1, 0x43, - 0xa2, 0x26, 0x4e, 0x30, 0x7d, 0x73, 0x44, 0x2c, 0x73, 0xaa, 0xc5, 0x4d, - 0x66, 0x01, 0x19, 0xd2, 0xea, 0x50, 0x59, 0x65, 0xd0, 0x68, 0x9d, 0x05, - 0xa0, 0x7c, 0xa1, 0x79, 0x53, 0xd0, 0x21, 0x90, 0x59, 0x0e, 0x37, 0xdb, - 0x1e, 0xdc, 0x92, 0xa7, 0x8b, 0x0d, 0xc4, 0xf5, 0xf8, 0xe6, 0xff, 0xb5, - 0x35, 0x1a, 0xda, 0xa8, 0xb6, 0x9b, 0x20, 0x85, 0x65, 0xc4, 0xa2, 0x4d, - 0xdf, 0xf3, 0x94, 0x4d, 0x63, 0x7e, 0xee, 0x89, 0x07, 0xaf, 0xfe, 0xe1, - 0xba, 0x00, 0x15, 0x2d, 0xc6, 0x77, 0x8e, 0xa3, 0xfe, 0xad, 0xcf, 0x26, - 0x54, 0x5a, 0xdf, 0xfc, 0xd2, 0xde, 0xc2, 0xad, 0xf6, 0xb2, 0x23, 0xfd, - 0xa8, 0x83, 0xe5, 0x65, 0xbd, 0x27, 0xf7, 0x27, 0x1a, 0x18, 0x59, 0x6a, - 0x9e, 0x14, 0xf6, 0xb4, 0x86, 0xff, 0x1c, 0x58, 0x14, 0x43, 0x73, 0x96, - 0x24, 0xbf, 0x10, 0x43, 0xd5, 0x5c, 0x89, 0xf0, 0xce, 0xf7, 0xe1, 0x96, - 0x16, 0x5e, 0x18, 0x4a, 0x27, 0x28, 0x90, 0x80, 0x18, 0xfc, 0x32, 0xfe, - 0xf4, 0xc7, 0xb8, 0xd6, 0x82, 0x3d, 0x35, 0xaf, 0xbb, 0x4a, 0x1c, 0x5b, - 0x05, 0x78, 0xf6, 0xfd, 0x55, 0x3e, 0x82, 0x74, 0xb2, 0x73, 0xb8, 0x89, - 0x4e, 0xf7, 0x1b, 0x85, 0x9a, 0xd8, 0xca, 0xb1, 0x5a, 0xb1, 0x00, 0x20, - 0x41, 0x14, 0x30, 0x2b, 0x14, 0x24, 0xed, 0x37, 0x0e, 0x32, 0x3e, 0x23, - 0x88, 0x39, 0x7e, 0xb9, 0xd9, 0x38, 0x03, 0xe2, 0x4c, 0xd9, 0x0d, 0x43, - 0x41, 0x33, 0x10, 0xeb, 0x30, 0x72, 0x53, 0x88, 0xf7, 0x52, 0x9b, 0x4f, - 0x81, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x7e, 0x30, 0x82, - 0x01, 0x7a, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, - 0x16, 0x80, 0x14, 0xad, 0xbd, 0x98, 0x7a, 0x34, 0xb4, 0x26, 0xf7, 0xfa, - 0xc4, 0x26, 0x54, 0xef, 0x03, 0xbd, 0xe0, 0x24, 0xcb, 0x54, 0x1a, 0x30, - 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xcc, 0x03, - 0x5b, 0x96, 0x5a, 0x9e, 0x16, 0xcc, 0x26, 0x1e, 0xbd, 0xa3, 0x70, 0xfb, - 0xe3, 0xcb, 0x79, 0x19, 0xfc, 0x4d, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, - 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, - 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, - 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x18, 0x06, 0x03, 0x55, 0x1d, - 0x20, 0x04, 0x11, 0x30, 0x0f, 0x30, 0x0d, 0x06, 0x0b, 0x2b, 0x06, 0x01, - 0x04, 0x01, 0xb2, 0x31, 0x01, 0x02, 0x02, 0x08, 0x30, 0x44, 0x06, 0x03, - 0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0, 0x37, 0xa0, - 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, - 0x6c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, - 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f, - 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0xb3, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x81, 0xa6, 0x30, 0x81, - 0xa3, 0x30, 0x3f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, - 0x02, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, - 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, - 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f, - 0x6f, 0x74, 0x2e, 0x70, 0x37, 0x63, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, - 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70, - 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, - 0x54, 0x72, 0x75, 0x73, 0x74, 0x55, 0x54, 0x4e, 0x53, 0x47, 0x43, 0x43, - 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x25, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x19, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, - 0x82, 0x01, 0x01, 0x00, 0x7b, 0xf0, 0xfc, 0xa1, 0x28, 0x47, 0xbc, 0x2b, - 0xb4, 0x04, 0x73, 0x3f, 0x4b, 0xdd, 0x1e, 0xd1, 0xb9, 0xcd, 0x1c, 0xed, - 0x7d, 0xe5, 0xe8, 0xcb, 0x51, 0xf4, 0x92, 0xbf, 0xdd, 0x9c, 0x0d, 0x5c, - 0x6e, 0x1d, 0x95, 0xed, 0x5b, 0x70, 0x50, 0x89, 0xd4, 0x67, 0x9a, 0x15, - 0x54, 0xd1, 0x90, 0x0a, 0xfa, 0x09, 0x68, 0x06, 0x18, 0xbb, 0xd7, 0x27, - 0xe4, 0x93, 0xff, 0x43, 0x48, 0x81, 0x3b, 0xc8, 0x59, 0x49, 0x35, 0xea, - 0xac, 0xb6, 0xae, 0x46, 0xb5, 0xd4, 0xf3, 0xb8, 0xc3, 0xc6, 0xe4, 0x91, - 0xbf, 0xc9, 0x34, 0xfd, 0x7e, 0xd0, 0x59, 0x6e, 0x61, 0xa1, 0x1f, 0x48, - 0x63, 0x54, 0xb2, 0x7d, 0x46, 0xbf, 0xc8, 0xfa, 0xc3, 0xbf, 0x48, 0x58, - 0x98, 0xf6, 0x69, 0x84, 0xa7, 0x16, 0x69, 0x08, 0x27, 0xa4, 0x22, 0xcb, - 0xa2, 0x2c, 0xc8, 0xdf, 0x6e, 0xa9, 0xee, 0xf8, 0x41, 0xdf, 0x1b, 0xa8, - 0xb7, 0xf3, 0xe3, 0xae, 0xce, 0xa3, 0xfe, 0xd9, 0x27, 0x60, 0x50, 0x3f, - 0x04, 0x7d, 0x7a, 0x44, 0xea, 0x76, 0x42, 0x5c, 0xd3, 0x55, 0x46, 0xef, - 0x27, 0xc5, 0x6a, 0x4a, 0x80, 0xe7, 0x35, 0xa0, 0x91, 0xc6, 0x1b, 0xa6, - 0x86, 0x9c, 0x5a, 0x3b, 0x04, 0x83, 0x54, 0x34, 0xd7, 0xd1, 0x88, 0xa6, - 0x36, 0xe9, 0x7f, 0x40, 0x27, 0xda, 0x56, 0x0a, 0x50, 0x21, 0x9d, 0x29, - 0x8b, 0xa0, 0x84, 0xec, 0xfe, 0x71, 0x23, 0x53, 0x04, 0x18, 0x19, 0x70, - 0x67, 0x86, 0x44, 0x95, 0x72, 0x40, 0x55, 0xf6, 0xdd, 0xa3, 0xb4, 0x3d, - 0x2d, 0x09, 0x60, 0xa5, 0xe7, 0x5f, 0xfc, 0xac, 0x3b, 0xec, 0x0c, 0x91, - 0x9f, 0xf8, 0xee, 0x6a, 0xba, 0xb2, 0x3c, 0xfd, 0x95, 0x7d, 0x9a, 0x07, - 0xf4, 0xb0, 0x65, 0x43, 0xa2, 0xf6, 0xdf, 0x7d, 0xb8, 0x21, 0x49, 0x84, - 0x04, 0xee, 0xbd, 0xce, 0x53, 0x8f, 0x0f, 0x29, -} - -var certSet2Cert36 = []byte{ - 0x30, 0x82, 0x04, 0xf2, 0x30, 0x82, 0x03, 0xda, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x04, 0x38, 0x63, 0xe9, 0xfc, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81, - 0xb4, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b, - 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x31, - 0x40, 0x30, 0x3e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x14, 0x37, 0x77, 0x77, - 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, - 0x74, 0x2f, 0x43, 0x50, 0x53, 0x5f, 0x32, 0x30, 0x34, 0x38, 0x20, 0x69, - 0x6e, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x20, 0x62, 0x79, 0x20, 0x72, 0x65, - 0x66, 0x2e, 0x20, 0x28, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x20, 0x6c, - 0x69, 0x61, 0x62, 0x2e, 0x29, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, - 0x04, 0x0b, 0x13, 0x1c, 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x39, - 0x20, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, - 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x33, 0x30, 0x31, - 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2a, 0x45, 0x6e, 0x74, 0x72, 0x75, - 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x28, 0x32, 0x30, 0x34, 0x38, - 0x29, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x39, 0x31, 0x32, 0x31, 0x30, 0x32, - 0x30, 0x34, 0x33, 0x35, 0x34, 0x5a, 0x17, 0x0d, 0x31, 0x39, 0x31, 0x32, - 0x31, 0x30, 0x32, 0x31, 0x31, 0x33, 0x35, 0x34, 0x5a, 0x30, 0x81, 0xb1, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, - 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, - 0x2e, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x30, - 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, - 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x70, 0x61, 0x20, 0x69, 0x73, 0x20, 0x69, - 0x6e, 0x63, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, - 0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, - 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x28, - 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x39, 0x20, 0x45, 0x6e, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x2e, 0x30, - 0x2c, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x25, 0x45, 0x6e, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x4c, 0x31, 0x43, 0x30, 0x82, 0x01, - 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, - 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0x97, 0xa3, 0x2d, 0x3c, 0x9e, 0xde, - 0x05, 0xda, 0x13, 0xc2, 0x11, 0x8d, 0x9d, 0x8e, 0xe3, 0x7f, 0xc7, 0x4b, - 0x7e, 0x5a, 0x9f, 0xb3, 0xff, 0x62, 0xab, 0x73, 0xc8, 0x28, 0x6b, 0xba, - 0x10, 0x64, 0x82, 0x87, 0x13, 0xcd, 0x57, 0x18, 0xff, 0x28, 0xce, 0xc0, - 0xe6, 0x0e, 0x06, 0x91, 0x50, 0x29, 0x83, 0xd1, 0xf2, 0xc3, 0x2a, 0xdb, - 0xd8, 0xdb, 0x4e, 0x04, 0xcc, 0x00, 0xeb, 0x8b, 0xb6, 0x96, 0xdc, 0xbc, - 0xaa, 0xfa, 0x52, 0x77, 0x04, 0xc1, 0xdb, 0x19, 0xe4, 0xae, 0x9c, 0xfd, - 0x3c, 0x8b, 0x03, 0xef, 0x4d, 0xbc, 0x1a, 0x03, 0x65, 0xf9, 0xc1, 0xb1, - 0x3f, 0x72, 0x86, 0xf2, 0x38, 0xaa, 0x19, 0xae, 0x10, 0x88, 0x78, 0x28, - 0xda, 0x75, 0xc3, 0x3d, 0x02, 0x82, 0x02, 0x9c, 0xb9, 0xc1, 0x65, 0x77, - 0x76, 0x24, 0x4c, 0x98, 0xf7, 0x6d, 0x31, 0x38, 0xfb, 0xdb, 0xfe, 0xdb, - 0x37, 0x02, 0x76, 0xa1, 0x18, 0x97, 0xa6, 0xcc, 0xde, 0x20, 0x09, 0x49, - 0x36, 0x24, 0x69, 0x42, 0xf6, 0xe4, 0x37, 0x62, 0xf1, 0x59, 0x6d, 0xa9, - 0x3c, 0xed, 0x34, 0x9c, 0xa3, 0x8e, 0xdb, 0xdc, 0x3a, 0xd7, 0xf7, 0x0a, - 0x6f, 0xef, 0x2e, 0xd8, 0xd5, 0x93, 0x5a, 0x7a, 0xed, 0x08, 0x49, 0x68, - 0xe2, 0x41, 0xe3, 0x5a, 0x90, 0xc1, 0x86, 0x55, 0xfc, 0x51, 0x43, 0x9d, - 0xe0, 0xb2, 0xc4, 0x67, 0xb4, 0xcb, 0x32, 0x31, 0x25, 0xf0, 0x54, 0x9f, - 0x4b, 0xd1, 0x6f, 0xdb, 0xd4, 0xdd, 0xfc, 0xaf, 0x5e, 0x6c, 0x78, 0x90, - 0x95, 0xde, 0xca, 0x3a, 0x48, 0xb9, 0x79, 0x3c, 0x9b, 0x19, 0xd6, 0x75, - 0x05, 0xa0, 0xf9, 0x88, 0xd7, 0xc1, 0xe8, 0xa5, 0x09, 0xe4, 0x1a, 0x15, - 0xdc, 0x87, 0x23, 0xaa, 0xb2, 0x75, 0x8c, 0x63, 0x25, 0x87, 0xd8, 0xf8, - 0x3d, 0xa6, 0xc2, 0xcc, 0x66, 0xff, 0xa5, 0x66, 0x68, 0x55, 0x02, 0x03, - 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x0b, 0x30, 0x82, 0x01, 0x07, 0x30, - 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, - 0x02, 0x01, 0x06, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, - 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x33, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x27, 0x30, 0x25, - 0x30, 0x23, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, - 0x86, 0x17, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, - 0x70, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, - 0x74, 0x30, 0x32, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2b, 0x30, 0x29, - 0x30, 0x27, 0xa0, 0x25, 0xa0, 0x23, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, - 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, - 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x32, 0x30, 0x34, 0x38, 0x63, - 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3b, 0x06, 0x03, 0x55, 0x1d, 0x20, - 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, - 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, - 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, - 0x65, 0x74, 0x2f, 0x72, 0x70, 0x61, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, - 0x0e, 0x04, 0x16, 0x04, 0x14, 0x1e, 0xf1, 0xab, 0x89, 0x06, 0xf8, 0x49, - 0x0f, 0x01, 0x33, 0x77, 0xee, 0x14, 0x7a, 0xee, 0x19, 0x7c, 0x93, 0x28, - 0x4d, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, - 0x80, 0x14, 0x55, 0xe4, 0x81, 0xd1, 0x11, 0x80, 0xbe, 0xd8, 0x89, 0xb9, - 0x08, 0xa3, 0x31, 0xf9, 0xa1, 0x24, 0x09, 0x16, 0xb9, 0x70, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, - 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x07, 0xf6, 0x5f, 0x82, 0x84, 0x7f, - 0x80, 0x40, 0xc7, 0x90, 0x34, 0x46, 0x42, 0x24, 0x03, 0xce, 0x2f, 0xab, - 0xba, 0x83, 0x9e, 0x25, 0x73, 0x0d, 0xed, 0xac, 0x05, 0x69, 0xc6, 0x87, - 0xed, 0xa3, 0x5c, 0xf2, 0x57, 0xc1, 0xb1, 0x49, 0x76, 0x9a, 0x4d, 0xf2, - 0x3f, 0xdd, 0xe4, 0x0e, 0xfe, 0x0b, 0x3e, 0xb9, 0x98, 0xd9, 0x32, 0x95, - 0x1d, 0x32, 0xf4, 0x01, 0xee, 0x9c, 0xc8, 0xc8, 0xe5, 0x3f, 0xe0, 0x53, - 0x76, 0x62, 0xfc, 0xdd, 0xab, 0x6d, 0x3d, 0x94, 0x90, 0xf2, 0xc0, 0xb3, - 0x3c, 0x98, 0x27, 0x36, 0x5e, 0x28, 0x97, 0x22, 0xfc, 0x1b, 0x40, 0xd3, - 0x2b, 0x0d, 0xad, 0xb5, 0x57, 0x6d, 0xdf, 0x0f, 0xe3, 0x4b, 0xef, 0x73, - 0x02, 0x10, 0x65, 0xfa, 0x1b, 0xd0, 0xac, 0x31, 0xd5, 0xe3, 0x0f, 0xe8, - 0xba, 0x32, 0x30, 0x83, 0xee, 0x4a, 0xd0, 0xbf, 0xdf, 0x22, 0x90, 0x7a, - 0xbe, 0xec, 0x3a, 0x1b, 0xc4, 0x49, 0x04, 0x1d, 0xf1, 0xae, 0x80, 0x77, - 0x3c, 0x42, 0x08, 0xdb, 0xa7, 0x3b, 0x28, 0xa6, 0x80, 0x01, 0x03, 0xe6, - 0x39, 0xa3, 0xeb, 0xdf, 0x80, 0x59, 0x1b, 0xf3, 0x2c, 0xbe, 0xdc, 0x72, - 0x44, 0x79, 0xa0, 0x6c, 0x07, 0xa5, 0x6d, 0x4d, 0x44, 0x8e, 0x42, 0x68, - 0xca, 0x94, 0x7c, 0x2e, 0x36, 0xba, 0x85, 0x9e, 0xcd, 0xaa, 0xc4, 0x5e, - 0x3c, 0x54, 0xbe, 0xfe, 0x2f, 0xea, 0x69, 0x9d, 0x1c, 0x1e, 0x29, 0x9b, - 0x96, 0xd8, 0xc8, 0xfe, 0x51, 0x90, 0xf1, 0x24, 0xa6, 0x90, 0x06, 0xb3, - 0xf0, 0x29, 0xa2, 0xff, 0x78, 0x2e, 0x77, 0x5c, 0x45, 0x21, 0xd9, 0x44, - 0x00, 0x31, 0xf3, 0xbe, 0x32, 0x4f, 0xf5, 0x0a, 0x32, 0x0d, 0xfc, 0xfc, - 0xba, 0x16, 0x76, 0x56, 0xb2, 0xd6, 0x48, 0x92, 0xf2, 0x8b, 0xa6, 0x3e, - 0xb7, 0xac, 0x5c, 0x69, 0xea, 0x0b, 0x3f, 0x66, 0x45, 0xb9, -} - -var certSet2Cert37 = []byte{ - 0x30, 0x82, 0x04, 0xfc, 0x30, 0x82, 0x03, 0xe4, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x16, 0x90, 0xc3, 0x29, 0xb6, 0x78, 0x06, 0x07, 0x51, - 0x1f, 0x05, 0xb0, 0x34, 0x48, 0x46, 0xcb, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53, - 0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b, - 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31, - 0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64, - 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77, - 0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03, - 0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, - 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52, - 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x34, 0x31, - 0x36, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30, - 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30, - 0x81, 0x89, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, - 0x02, 0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08, - 0x13, 0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, - 0x6e, 0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, - 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, - 0x72, 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x11, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, - 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x2f, 0x30, 0x2d, 0x06, 0x03, - 0x55, 0x04, 0x03, 0x13, 0x26, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, - 0x48, 0x69, 0x67, 0x68, 0x2d, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, - 0x63, 0x65, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, - 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, - 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, - 0x82, 0x01, 0x01, 0x00, 0xe7, 0x87, 0xda, 0xc0, 0x77, 0xe4, 0xbb, 0x3a, - 0xfa, 0x6a, 0x24, 0xc8, 0x80, 0x41, 0xac, 0xd2, 0x16, 0x13, 0x15, 0x3d, - 0xfa, 0xf7, 0xf8, 0x2a, 0x76, 0xdc, 0xa8, 0x2d, 0x39, 0x08, 0xce, 0x48, - 0x4a, 0xbe, 0x0f, 0x7d, 0xf0, 0xde, 0xba, 0xbb, 0x47, 0xd5, 0xbd, 0x2d, - 0xd7, 0x1b, 0xab, 0x0f, 0x20, 0x81, 0x23, 0x08, 0x72, 0xb1, 0xc0, 0x11, - 0x95, 0x0d, 0xe6, 0xea, 0xa9, 0x87, 0xff, 0xc7, 0x6e, 0x1e, 0x4f, 0x66, - 0x32, 0xba, 0x53, 0xbc, 0x05, 0xaa, 0x1c, 0x2c, 0x0c, 0xef, 0x4d, 0x37, - 0x47, 0x6b, 0x10, 0x0c, 0xdb, 0xc5, 0xa0, 0x98, 0x7e, 0x58, 0xdb, 0x37, - 0xd6, 0xae, 0xe9, 0x06, 0xbd, 0xd7, 0xa8, 0x65, 0xf3, 0x37, 0xb9, 0xc7, - 0x6d, 0xce, 0x77, 0xc7, 0x26, 0xe0, 0xd7, 0x74, 0x1f, 0xa6, 0x98, 0x16, - 0xbb, 0x0c, 0x6b, 0xc8, 0xbe, 0x77, 0xd0, 0xef, 0x58, 0xa7, 0x29, 0xa0, - 0xb9, 0xb8, 0x69, 0x05, 0x36, 0xcb, 0xb2, 0xda, 0x58, 0xa3, 0x0b, 0x75, - 0xad, 0x3d, 0x8b, 0x22, 0x82, 0x20, 0x3e, 0x70, 0x86, 0x99, 0x1c, 0xb9, - 0x4f, 0xcf, 0x77, 0xa4, 0x07, 0x1a, 0x23, 0x63, 0xd1, 0x38, 0x56, 0x84, - 0xec, 0xbf, 0x8f, 0xc5, 0x4e, 0xf4, 0x18, 0x96, 0x9b, 0x1a, 0xe8, 0x93, - 0xec, 0x8d, 0xaf, 0x15, 0x9c, 0x24, 0xf0, 0x5a, 0x3b, 0xe8, 0x0f, 0xb9, - 0xa8, 0x5a, 0x01, 0xd3, 0xb2, 0x1c, 0x60, 0xc9, 0x9c, 0x52, 0x04, 0xdd, - 0x92, 0xa7, 0xfe, 0x0c, 0xac, 0xe2, 0x45, 0x8d, 0x03, 0x61, 0xbc, 0x79, - 0xe0, 0x77, 0x2e, 0x87, 0x41, 0x3c, 0x58, 0x5f, 0xcb, 0xf5, 0xc5, 0x77, - 0xf2, 0x58, 0xc8, 0x4d, 0x28, 0xd0, 0x9a, 0xfa, 0xf3, 0x73, 0x09, 0x24, - 0x68, 0x74, 0xbc, 0x20, 0x4c, 0xd8, 0x2c, 0xb0, 0xaa, 0xe8, 0xd9, 0x4e, - 0x6d, 0xf2, 0x8c, 0x24, 0xd3, 0x93, 0x5d, 0x91, 0x02, 0x03, 0x01, 0x00, - 0x01, 0xa3, 0x82, 0x01, 0x77, 0x30, 0x82, 0x01, 0x73, 0x30, 0x1f, 0x06, - 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xad, 0xbd, - 0x98, 0x7a, 0x34, 0xb4, 0x26, 0xf7, 0xfa, 0xc4, 0x26, 0x54, 0xef, 0x03, - 0xbd, 0xe0, 0x24, 0xcb, 0x54, 0x1a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, - 0x0e, 0x04, 0x16, 0x04, 0x14, 0x3f, 0xd5, 0xb5, 0xd0, 0xd6, 0x44, 0x79, - 0x50, 0x4a, 0x17, 0xa3, 0x9b, 0x8c, 0x4a, 0xdc, 0xb8, 0xb0, 0x22, 0x64, - 0x6b, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, - 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, - 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, - 0x00, 0x30, 0x11, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x0a, 0x30, 0x08, - 0x30, 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x44, 0x06, 0x03, - 0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0, 0x37, 0xa0, - 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, - 0x6c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, - 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f, - 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0xb3, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x81, 0xa6, 0x30, 0x81, - 0xa3, 0x30, 0x3f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, - 0x02, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, - 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, - 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f, - 0x6f, 0x74, 0x2e, 0x70, 0x37, 0x63, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, - 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70, - 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, - 0x54, 0x72, 0x75, 0x73, 0x74, 0x55, 0x54, 0x4e, 0x53, 0x47, 0x43, 0x43, - 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x25, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x19, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, - 0x82, 0x01, 0x01, 0x00, 0x13, 0x85, 0x1f, 0x52, 0x80, 0x18, 0xc9, 0x53, - 0xf7, 0xfe, 0x2e, 0x1a, 0xaf, 0xcc, 0xd9, 0x0b, 0x3c, 0xc2, 0xd3, 0x85, - 0x81, 0x10, 0xf0, 0x28, 0x8d, 0xb9, 0x40, 0x7e, 0x2c, 0x9e, 0x8f, 0xd6, - 0x36, 0x86, 0x0a, 0x4c, 0x14, 0x2d, 0xd6, 0x97, 0x43, 0x92, 0x41, 0x19, - 0x37, 0x4b, 0x96, 0x9e, 0xeb, 0xa9, 0x30, 0x79, 0x12, 0x95, 0xb3, 0x02, - 0x36, 0x57, 0xed, 0x2b, 0xb9, 0x1d, 0x98, 0x1a, 0xa3, 0x18, 0x0a, 0x3f, - 0x9b, 0x39, 0x8b, 0xcd, 0xa1, 0x49, 0x29, 0x4c, 0x2f, 0xf9, 0xd0, 0x95, - 0x8c, 0xc8, 0x4d, 0x95, 0xba, 0xa8, 0x43, 0xcf, 0x33, 0xaa, 0x25, 0x2a, - 0x5a, 0x0e, 0xaa, 0x27, 0xc9, 0x4e, 0x6b, 0xb1, 0xe6, 0x73, 0x1f, 0xb3, - 0x74, 0x04, 0xc3, 0xf3, 0x4c, 0xe2, 0xa8, 0xeb, 0x67, 0xb7, 0x5d, 0xb8, - 0x08, 0x05, 0x1a, 0x56, 0x9a, 0x54, 0x29, 0x85, 0xf5, 0x29, 0x4e, 0x80, - 0x3b, 0x95, 0xd0, 0x7b, 0x53, 0x96, 0x11, 0x56, 0xc1, 0x02, 0xd3, 0xea, - 0xb2, 0x7f, 0xca, 0x8f, 0x9c, 0x70, 0x4a, 0x14, 0x8d, 0x5a, 0xb9, 0x16, - 0x60, 0x75, 0xd6, 0xcd, 0x27, 0x1e, 0x16, 0xcd, 0x5b, 0x33, 0x8e, 0x79, - 0x40, 0xcf, 0x28, 0x48, 0xe7, 0xdc, 0x71, 0x16, 0x4e, 0x74, 0x91, 0x75, - 0xb9, 0x2a, 0x8c, 0xf1, 0x70, 0xac, 0x26, 0xdd, 0x04, 0xb9, 0x40, 0xc2, - 0x85, 0xde, 0x1c, 0x93, 0x40, 0xd0, 0xcc, 0x6e, 0xc3, 0x9b, 0xaa, 0xef, - 0x60, 0x65, 0xdf, 0x60, 0x22, 0xf0, 0x5a, 0xa5, 0x7a, 0xa2, 0x2f, 0xe4, - 0x70, 0x73, 0xee, 0x3c, 0xd4, 0x26, 0x2b, 0x68, 0x07, 0xc1, 0x20, 0x7a, - 0xe8, 0x98, 0x5a, 0x3e, 0x7b, 0x9f, 0x02, 0x8b, 0x62, 0xc0, 0x85, 0x81, - 0x80, 0x60, 0x35, 0x7e, 0xa5, 0x1d, 0x0c, 0xd2, 0x9c, 0xdf, 0x62, 0x45, - 0x0d, 0xdb, 0xfc, 0x37, 0xfb, 0xf5, 0x25, 0x22, -} - -var certSet2Cert38 = []byte{ - 0x30, 0x82, 0x04, 0xff, 0x30, 0x82, 0x03, 0xe7, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x04, 0x51, 0xd3, 0x40, 0x44, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, - 0xb0, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0d, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, - 0x63, 0x2e, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, - 0x30, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, - 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x20, 0x69, 0x73, 0x20, - 0x69, 0x6e, 0x63, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x65, 0x64, - 0x20, 0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, - 0x65, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, - 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x45, 0x6e, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x2d, - 0x30, 0x2b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x24, 0x45, 0x6e, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, - 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, - 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, - 0x0d, 0x31, 0x34, 0x30, 0x39, 0x32, 0x32, 0x31, 0x37, 0x31, 0x34, 0x35, - 0x37, 0x5a, 0x17, 0x0d, 0x32, 0x34, 0x30, 0x39, 0x32, 0x33, 0x30, 0x31, - 0x33, 0x31, 0x35, 0x33, 0x5a, 0x30, 0x81, 0xbe, 0x31, 0x0b, 0x30, 0x09, - 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, - 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x45, 0x6e, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x28, 0x30, - 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1f, 0x53, 0x65, 0x65, 0x20, - 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, - 0x6e, 0x65, 0x74, 0x2f, 0x6c, 0x65, 0x67, 0x61, 0x6c, 0x2d, 0x74, 0x65, - 0x72, 0x6d, 0x73, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, - 0x13, 0x30, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x39, 0x20, 0x45, - 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x20, 0x2d, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, - 0x6c, 0x79, 0x31, 0x32, 0x30, 0x30, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, - 0x29, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x20, 0x52, 0x6f, 0x6f, - 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, - 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, - 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, - 0x01, 0x01, 0x00, 0xba, 0x84, 0xb6, 0x72, 0xdb, 0x9e, 0x0c, 0x6b, 0xe2, - 0x99, 0xe9, 0x30, 0x01, 0xa7, 0x76, 0xea, 0x32, 0xb8, 0x95, 0x41, 0x1a, - 0xc9, 0xda, 0x61, 0x4e, 0x58, 0x72, 0xcf, 0xfe, 0xf6, 0x82, 0x79, 0xbf, - 0x73, 0x61, 0x06, 0x0a, 0xa5, 0x27, 0xd8, 0xb3, 0x5f, 0xd3, 0x45, 0x4e, - 0x1c, 0x72, 0xd6, 0x4e, 0x32, 0xf2, 0x72, 0x8a, 0x0f, 0xf7, 0x83, 0x19, - 0xd0, 0x6a, 0x80, 0x80, 0x00, 0x45, 0x1e, 0xb0, 0xc7, 0xe7, 0x9a, 0xbf, - 0x12, 0x57, 0x27, 0x1c, 0xa3, 0x68, 0x2f, 0x0a, 0x87, 0xbd, 0x6a, 0x6b, - 0x0e, 0x5e, 0x65, 0xf3, 0x1c, 0x77, 0xd5, 0xd4, 0x85, 0x8d, 0x70, 0x21, - 0xb4, 0xb3, 0x32, 0xe7, 0x8b, 0xa2, 0xd5, 0x86, 0x39, 0x02, 0xb1, 0xb8, - 0xd2, 0x47, 0xce, 0xe4, 0xc9, 0x49, 0xc4, 0x3b, 0xa7, 0xde, 0xfb, 0x54, - 0x7d, 0x57, 0xbe, 0xf0, 0xe8, 0x6e, 0xc2, 0x79, 0xb2, 0x3a, 0x0b, 0x55, - 0xe2, 0x50, 0x98, 0x16, 0x32, 0x13, 0x5c, 0x2f, 0x78, 0x56, 0xc1, 0xc2, - 0x94, 0xb3, 0xf2, 0x5a, 0xe4, 0x27, 0x9a, 0x9f, 0x24, 0xd7, 0xc6, 0xec, - 0xd0, 0x9b, 0x25, 0x82, 0xe3, 0xcc, 0xc2, 0xc4, 0x45, 0xc5, 0x8c, 0x97, - 0x7a, 0x06, 0x6b, 0x2a, 0x11, 0x9f, 0xa9, 0x0a, 0x6e, 0x48, 0x3b, 0x6f, - 0xdb, 0xd4, 0x11, 0x19, 0x42, 0xf7, 0x8f, 0x07, 0xbf, 0xf5, 0x53, 0x5f, - 0x9c, 0x3e, 0xf4, 0x17, 0x2c, 0xe6, 0x69, 0xac, 0x4e, 0x32, 0x4c, 0x62, - 0x77, 0xea, 0xb7, 0xe8, 0xe5, 0xbb, 0x34, 0xbc, 0x19, 0x8b, 0xae, 0x9c, - 0x51, 0xe7, 0xb7, 0x7e, 0xb5, 0x53, 0xb1, 0x33, 0x22, 0xe5, 0x6d, 0xcf, - 0x70, 0x3c, 0x1a, 0xfa, 0xe2, 0x9b, 0x67, 0xb6, 0x83, 0xf4, 0x8d, 0xa5, - 0xaf, 0x62, 0x4c, 0x4d, 0xe0, 0x58, 0xac, 0x64, 0x34, 0x12, 0x03, 0xf8, - 0xb6, 0x8d, 0x94, 0x63, 0x24, 0xa4, 0x71, 0x02, 0x03, 0x01, 0x00, 0x01, - 0xa3, 0x82, 0x01, 0x0f, 0x30, 0x82, 0x01, 0x0b, 0x30, 0x0e, 0x06, 0x03, - 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, - 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, - 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x01, 0x30, 0x33, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x27, 0x30, 0x25, - 0x30, 0x23, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, - 0x86, 0x17, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, - 0x70, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, - 0x74, 0x30, 0x33, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2c, 0x30, 0x2a, - 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86, 0x22, 0x68, 0x74, 0x74, 0x70, - 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, - 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x63, - 0x61, 0x31, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3b, 0x06, 0x03, 0x55, 0x1d, - 0x20, 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0x06, 0x04, 0x55, 0x1d, 0x20, - 0x00, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, - 0x6e, 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x30, 0x1d, 0x06, 0x03, 0x55, - 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x6a, 0x72, 0x26, 0x7a, 0xd0, 0x1e, - 0xef, 0x7d, 0xe7, 0x3b, 0x69, 0x51, 0xd4, 0x6c, 0x8d, 0x9f, 0x90, 0x12, - 0x66, 0xab, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, - 0x16, 0x80, 0x14, 0x68, 0x90, 0xe4, 0x67, 0xa4, 0xa6, 0x53, 0x80, 0xc7, - 0x86, 0x66, 0xa4, 0xf1, 0xf7, 0x4b, 0x43, 0xfb, 0x84, 0xbd, 0x6d, 0x30, - 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, - 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x69, 0x33, 0x83, 0xfc, 0x28, - 0x7a, 0x6f, 0x7d, 0xef, 0x9d, 0x55, 0xeb, 0xc5, 0x3e, 0x7a, 0x9d, 0x75, - 0xb3, 0xcc, 0xc3, 0x38, 0x36, 0xd9, 0x34, 0xa2, 0x28, 0x68, 0x18, 0xea, - 0x1e, 0x69, 0xd3, 0xbd, 0xe7, 0xd0, 0x77, 0xda, 0xb8, 0x00, 0x83, 0x4e, - 0x4a, 0xcf, 0x6f, 0xd1, 0xf1, 0xc1, 0x22, 0x3f, 0x74, 0xe4, 0xf7, 0x98, - 0x49, 0x9e, 0x9b, 0xb6, 0x9e, 0xe1, 0xdb, 0x98, 0x77, 0x2d, 0x56, 0x34, - 0xb1, 0xa8, 0x3c, 0xd9, 0xfd, 0xc0, 0xcd, 0xc7, 0xbf, 0x05, 0x03, 0xd4, - 0x02, 0xc5, 0xf1, 0xe5, 0xc6, 0xda, 0x08, 0xa5, 0x13, 0xc7, 0x62, 0x23, - 0x11, 0xd1, 0x61, 0x30, 0x1d, 0x60, 0x84, 0x45, 0xef, 0x79, 0xa8, 0xc6, - 0x26, 0x93, 0xa4, 0xb7, 0xcd, 0x34, 0xb8, 0x69, 0xc5, 0x13, 0xf6, 0x91, - 0xb3, 0xc9, 0x45, 0x73, 0x76, 0xb6, 0x92, 0xf6, 0x76, 0x0a, 0x5b, 0xe1, - 0x03, 0x47, 0xb7, 0xe9, 0x29, 0x4c, 0x91, 0x32, 0x23, 0x37, 0x4a, 0x9c, - 0x35, 0xd8, 0x78, 0xfd, 0x1d, 0x1f, 0xe4, 0x83, 0x89, 0x24, 0x80, 0xad, - 0xb7, 0xf9, 0xcf, 0xe4, 0x5d, 0xa5, 0xd4, 0x71, 0xc4, 0x85, 0x5b, 0x70, - 0x1f, 0xdb, 0x3f, 0x1c, 0x01, 0xeb, 0x1a, 0x45, 0x26, 0x31, 0x14, 0xcc, - 0x65, 0xbf, 0x67, 0xde, 0xca, 0xcc, 0x33, 0x65, 0xe5, 0x41, 0x91, 0xd7, - 0x37, 0xbe, 0x41, 0x1a, 0x96, 0x9d, 0xe6, 0x8a, 0x97, 0x9d, 0xa7, 0xce, - 0xac, 0x4e, 0x9a, 0x3d, 0xbd, 0x01, 0xa0, 0x6a, 0xd9, 0x4f, 0x22, 0x00, - 0x8b, 0x44, 0xd5, 0x69, 0x62, 0x7b, 0x2e, 0xeb, 0xcc, 0xba, 0xe7, 0x92, - 0x7d, 0x69, 0x67, 0x3d, 0xfc, 0xb8, 0x7c, 0xde, 0x41, 0x87, 0xd0, 0x69, - 0xea, 0xba, 0x0a, 0x18, 0x7a, 0x1a, 0x95, 0x43, 0xb3, 0x79, 0x71, 0x28, - 0x76, 0x6d, 0xa1, 0xfb, 0x57, 0x4a, 0xec, 0x4d, 0xc8, 0x0e, 0x10, -} - -var certSet2Cert39 = []byte{ - 0x30, 0x82, 0x05, 0x00, 0x30, 0x82, 0x03, 0xe8, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x01, 0x07, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, 0x8f, 0x31, 0x0b, - 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, - 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x07, 0x41, 0x72, - 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, - 0x04, 0x07, 0x13, 0x0a, 0x53, 0x63, 0x6f, 0x74, 0x74, 0x73, 0x64, 0x61, - 0x6c, 0x65, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x1c, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x54, - 0x65, 0x63, 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x65, 0x73, 0x2c, - 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x32, 0x30, 0x30, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x29, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, - 0x64, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x1e, 0x17, - 0x0d, 0x31, 0x31, 0x30, 0x35, 0x30, 0x33, 0x30, 0x37, 0x30, 0x30, 0x30, - 0x30, 0x5a, 0x17, 0x0d, 0x33, 0x31, 0x30, 0x35, 0x30, 0x33, 0x30, 0x37, - 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x81, 0xc6, 0x31, 0x0b, 0x30, 0x09, - 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, - 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x07, 0x41, 0x72, 0x69, 0x7a, - 0x6f, 0x6e, 0x61, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x07, - 0x13, 0x0a, 0x53, 0x63, 0x6f, 0x74, 0x74, 0x73, 0x64, 0x61, 0x6c, 0x65, - 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c, 0x53, - 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x54, 0x65, 0x63, - 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x65, 0x73, 0x2c, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x31, 0x33, 0x30, 0x31, 0x06, 0x03, 0x55, 0x04, 0x0b, - 0x13, 0x2a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, - 0x74, 0x73, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, - 0x74, 0x65, 0x63, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, - 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x31, 0x34, 0x30, 0x32, - 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2b, 0x53, 0x74, 0x61, 0x72, 0x66, - 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, - 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, - 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, - 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, - 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xe5, - 0x90, 0x66, 0x4b, 0xec, 0xf9, 0x46, 0x71, 0xa9, 0x20, 0x83, 0xbe, 0xe9, - 0x6c, 0xbf, 0x4a, 0xc9, 0x48, 0x69, 0x81, 0x75, 0x4e, 0x6d, 0x24, 0xf6, - 0xcb, 0x17, 0x13, 0xf8, 0xb0, 0x71, 0x59, 0x84, 0x7a, 0x6b, 0x2b, 0x85, - 0xa4, 0x34, 0xb5, 0x16, 0xe5, 0xcb, 0xcc, 0xe9, 0x41, 0x70, 0x2c, 0xa4, - 0x2e, 0xd6, 0xfa, 0x32, 0x7d, 0xe1, 0xa8, 0xde, 0x94, 0x10, 0xac, 0x31, - 0xc1, 0xc0, 0xd8, 0x6a, 0xff, 0x59, 0x27, 0xab, 0x76, 0xd6, 0xfc, 0x0b, - 0x74, 0x6b, 0xb8, 0xa7, 0xae, 0x3f, 0xc4, 0x54, 0xf4, 0xb4, 0x31, 0x44, - 0xdd, 0x93, 0x56, 0x8c, 0xa4, 0x4c, 0x5e, 0x9b, 0x89, 0xcb, 0x24, 0x83, - 0x9b, 0xe2, 0x57, 0x7d, 0xb7, 0xd8, 0x12, 0x1f, 0xc9, 0x85, 0x6d, 0xf4, - 0xd1, 0x80, 0xf1, 0x50, 0x9b, 0x87, 0xae, 0xd4, 0x0b, 0x10, 0x05, 0xfb, - 0x27, 0xba, 0x28, 0x6d, 0x17, 0xe9, 0x0e, 0xd6, 0x4d, 0xb9, 0x39, 0x55, - 0x06, 0xff, 0x0a, 0x24, 0x05, 0x7e, 0x2f, 0xc6, 0x1d, 0x72, 0x6c, 0xd4, - 0x8b, 0x29, 0x8c, 0x57, 0x7d, 0xda, 0xd9, 0xeb, 0x66, 0x1a, 0xd3, 0x4f, - 0xa7, 0xdf, 0x7f, 0x52, 0xc4, 0x30, 0xc5, 0xa5, 0xc9, 0x0e, 0x02, 0xc5, - 0x53, 0xbf, 0x77, 0x38, 0x68, 0x06, 0x24, 0xc3, 0x66, 0xc8, 0x37, 0x7e, - 0x30, 0x1e, 0x45, 0x71, 0x23, 0x35, 0xff, 0x90, 0xd8, 0x2a, 0x9d, 0x8d, - 0xe7, 0xb0, 0x92, 0x4d, 0x3c, 0x7f, 0x2a, 0x0a, 0x93, 0xdc, 0xcd, 0x16, - 0x46, 0x65, 0xf7, 0x60, 0x84, 0x8b, 0x76, 0x4b, 0x91, 0x27, 0x73, 0x14, - 0x92, 0xe0, 0xea, 0xee, 0x8f, 0x16, 0xea, 0x8d, 0x0e, 0x3e, 0x76, 0x17, - 0xbf, 0x7d, 0x89, 0x80, 0x80, 0x44, 0x43, 0xe7, 0x2d, 0xe0, 0x43, 0x09, - 0x75, 0xda, 0x36, 0xe8, 0xad, 0xdb, 0x89, 0x3a, 0xf5, 0x5d, 0x12, 0x8e, - 0x23, 0x04, 0x83, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x2c, - 0x30, 0x82, 0x01, 0x28, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, - 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0e, 0x06, - 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, - 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, - 0x25, 0x45, 0x81, 0x68, 0x50, 0x26, 0x38, 0x3d, 0x3b, 0x2d, 0x2c, 0xbe, - 0xcd, 0x6a, 0xd9, 0xb6, 0x3d, 0xb3, 0x66, 0x63, 0x30, 0x1f, 0x06, 0x03, - 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x7c, 0x0c, 0x32, - 0x1f, 0xa7, 0xd9, 0x30, 0x7f, 0xc4, 0x7d, 0x68, 0xa3, 0x62, 0xa8, 0xa1, - 0xce, 0xab, 0x07, 0x5b, 0x27, 0x30, 0x3a, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x2e, 0x30, 0x2c, 0x30, 0x2a, 0x06, - 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x1e, 0x68, - 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x73, - 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x74, 0x65, 0x63, 0x68, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x3b, 0x06, 0x03, 0x55, 0x1d, 0x1f, - 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0xa0, 0x2e, 0xa0, 0x2c, 0x86, 0x2a, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x73, - 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x74, 0x65, 0x63, 0x68, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x66, 0x72, 0x6f, 0x6f, 0x74, 0x2d, - 0x67, 0x32, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x4c, 0x06, 0x03, 0x55, 0x1d, - 0x20, 0x04, 0x45, 0x30, 0x43, 0x30, 0x41, 0x06, 0x04, 0x55, 0x1d, 0x20, - 0x00, 0x30, 0x39, 0x30, 0x37, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x02, 0x01, 0x16, 0x2b, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, - 0x2f, 0x63, 0x65, 0x72, 0x74, 0x73, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x66, - 0x69, 0x65, 0x6c, 0x64, 0x74, 0x65, 0x63, 0x68, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x56, 0x65, 0xca, 0xfe, - 0xf3, 0x3f, 0x0a, 0xa8, 0x93, 0x8b, 0x18, 0xc7, 0xde, 0x43, 0x69, 0x13, - 0x34, 0x20, 0xbe, 0x4e, 0x5f, 0x78, 0xa8, 0x6b, 0x9c, 0xdb, 0x6a, 0x4d, - 0x41, 0xdb, 0xc1, 0x13, 0xec, 0xdc, 0x31, 0x00, 0x22, 0x5e, 0xf7, 0x00, - 0x9e, 0x0c, 0xe0, 0x34, 0x65, 0x34, 0xf9, 0xb1, 0x3a, 0x4e, 0x48, 0xc8, - 0x12, 0x81, 0x88, 0x5c, 0x5b, 0x3e, 0x08, 0x53, 0x7a, 0xf7, 0x1a, 0x64, - 0xdf, 0xb8, 0x50, 0x61, 0xcc, 0x53, 0x51, 0x40, 0x29, 0x4b, 0xc2, 0xf4, - 0xae, 0x3a, 0x5f, 0xe4, 0xca, 0xad, 0x26, 0xcc, 0x4e, 0x61, 0x43, 0xe5, - 0xfd, 0x57, 0xa6, 0x37, 0x70, 0xce, 0x43, 0x2b, 0xb0, 0x94, 0xc3, 0x92, - 0xe9, 0xe1, 0x5f, 0xaa, 0x10, 0x49, 0xb7, 0x69, 0xe4, 0xe0, 0xd0, 0x1f, - 0x64, 0xa4, 0x2b, 0xcd, 0x1f, 0x6f, 0xa0, 0xf8, 0x84, 0x24, 0x18, 0xce, - 0x79, 0x3d, 0xa9, 0x91, 0xbf, 0x54, 0x18, 0x13, 0x89, 0x99, 0x54, 0x11, - 0x0d, 0x55, 0xc5, 0x26, 0x0b, 0x79, 0x4f, 0x5a, 0x1c, 0x6e, 0xf9, 0x63, - 0xdb, 0x14, 0x80, 0xa4, 0x07, 0xab, 0xfa, 0xb2, 0xa5, 0xb9, 0x88, 0xdd, - 0x91, 0xfe, 0x65, 0x3b, 0xa4, 0xa3, 0x79, 0xbe, 0x89, 0x4d, 0xe1, 0xd0, - 0xb0, 0xf4, 0xc8, 0x17, 0x0c, 0x0a, 0x96, 0x14, 0x7c, 0x09, 0xb7, 0x6c, - 0xe1, 0xc2, 0xd8, 0x55, 0xd4, 0x18, 0xa0, 0xaa, 0x41, 0x69, 0x70, 0x24, - 0xa3, 0xb9, 0xef, 0xe9, 0x5a, 0xdc, 0x3e, 0xeb, 0x94, 0x4a, 0xf0, 0xb7, - 0xde, 0x5f, 0x0e, 0x76, 0xfa, 0xfb, 0xfb, 0x69, 0x03, 0x45, 0x40, 0x50, - 0xee, 0x72, 0x0c, 0xa4, 0x12, 0x86, 0x81, 0xcd, 0x13, 0xd1, 0x4e, 0xc4, - 0x3c, 0xca, 0x4e, 0x0d, 0xd2, 0x26, 0xf1, 0x00, 0xb7, 0xb4, 0xa6, 0xa2, - 0xe1, 0x6e, 0x7a, 0x81, 0xfd, 0x30, 0xac, 0x7a, 0x1f, 0xc7, 0x59, 0x7b, -} - -var certSet2Cert40 = []byte{ - 0x30, 0x82, 0x05, 0x03, 0x30, 0x82, 0x03, 0xeb, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x04, 0x51, 0xd3, 0x60, 0xee, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, - 0xbe, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0d, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, - 0x63, 0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, - 0x1f, 0x53, 0x65, 0x65, 0x20, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x6c, 0x65, 0x67, - 0x61, 0x6c, 0x2d, 0x74, 0x65, 0x72, 0x6d, 0x73, 0x31, 0x39, 0x30, 0x37, - 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x30, 0x28, 0x63, 0x29, 0x20, 0x32, - 0x30, 0x30, 0x39, 0x20, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, - 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x66, 0x6f, 0x72, 0x20, - 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, - 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x32, 0x30, 0x30, 0x06, - 0x03, 0x55, 0x04, 0x03, 0x13, 0x29, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, - 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, - 0x1e, 0x17, 0x0d, 0x31, 0x34, 0x31, 0x30, 0x32, 0x32, 0x31, 0x37, 0x30, - 0x35, 0x31, 0x34, 0x5a, 0x17, 0x0d, 0x32, 0x34, 0x31, 0x30, 0x32, 0x33, - 0x30, 0x37, 0x33, 0x33, 0x32, 0x32, 0x5a, 0x30, 0x81, 0xba, 0x31, 0x0b, - 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, - 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x45, 0x6e, - 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, - 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1f, 0x53, 0x65, - 0x65, 0x20, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, - 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x6c, 0x65, 0x67, 0x61, 0x6c, 0x2d, - 0x74, 0x65, 0x72, 0x6d, 0x73, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, - 0x04, 0x0b, 0x13, 0x30, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x31, 0x32, - 0x20, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, - 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, - 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x2e, 0x30, 0x2c, 0x06, 0x03, 0x55, 0x04, - 0x03, 0x13, 0x25, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x20, 0x43, - 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, - 0x20, 0x4c, 0x31, 0x4b, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, - 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, - 0x00, 0xda, 0x3f, 0x96, 0xd0, 0x4d, 0xb9, 0x2f, 0x44, 0xe7, 0xdb, 0x39, - 0x5e, 0x9b, 0x50, 0xee, 0x5c, 0xa5, 0x61, 0xda, 0x41, 0x67, 0x53, 0x09, - 0xaa, 0x00, 0x9a, 0x8e, 0x57, 0x7f, 0x29, 0x6b, 0xdb, 0xc7, 0xe1, 0x21, - 0x24, 0xaa, 0x3a, 0xd0, 0x8d, 0x47, 0x23, 0xd2, 0xed, 0x72, 0x16, 0xf0, - 0x91, 0x21, 0xd2, 0x5d, 0xb7, 0xb8, 0x4b, 0xa8, 0x83, 0x8f, 0xb7, 0x91, - 0x32, 0x68, 0xcf, 0xce, 0x25, 0x93, 0x2c, 0xb2, 0x7d, 0x97, 0xc8, 0xfe, - 0xc1, 0xb4, 0x17, 0xba, 0x09, 0x9e, 0x03, 0x90, 0x93, 0x7b, 0x7c, 0x49, - 0x83, 0x22, 0x68, 0x8a, 0x9b, 0xde, 0x47, 0xc3, 0x31, 0x98, 0x7a, 0x2e, - 0x7d, 0x40, 0x0b, 0xd2, 0xef, 0x3e, 0xd3, 0xb2, 0x8c, 0xaa, 0x8f, 0x48, - 0xa9, 0xff, 0x00, 0xe8, 0x29, 0x58, 0x06, 0xf7, 0xb6, 0x93, 0x5a, 0x94, - 0x73, 0x26, 0x26, 0xad, 0x58, 0x0e, 0xe5, 0x42, 0xb8, 0xd5, 0xea, 0x73, - 0x79, 0x64, 0x68, 0x53, 0x25, 0xb8, 0x84, 0xcf, 0x94, 0x7a, 0xae, 0x06, - 0x45, 0x0c, 0xa3, 0x6b, 0x4d, 0xd0, 0xc6, 0xbe, 0xea, 0x18, 0xa4, 0x36, - 0xf0, 0x92, 0xb2, 0xba, 0x1c, 0x88, 0x8f, 0x3a, 0x52, 0x7f, 0xf7, 0x5e, - 0x6d, 0x83, 0x1c, 0x9d, 0xf0, 0x1f, 0xe5, 0xc3, 0xd6, 0xdd, 0xa5, 0x78, - 0x92, 0x3d, 0xb0, 0x6d, 0x2c, 0xea, 0xc9, 0xcf, 0x94, 0x41, 0x19, 0x71, - 0x44, 0x68, 0xba, 0x47, 0x3c, 0x04, 0xe9, 0x5d, 0xba, 0x3e, 0xf0, 0x35, - 0xf7, 0x15, 0xb6, 0x9e, 0xf2, 0x2e, 0x15, 0x1e, 0x3f, 0x47, 0xc8, 0xc8, - 0x38, 0xa7, 0x73, 0x45, 0x5d, 0x4d, 0xb0, 0x3b, 0xb1, 0x8e, 0x17, 0x29, - 0x37, 0xea, 0xdd, 0x05, 0x01, 0x22, 0xbb, 0x94, 0x36, 0x2a, 0x8d, 0x5b, - 0x35, 0xfe, 0x53, 0x19, 0x2f, 0x08, 0x46, 0xc1, 0x2a, 0xb3, 0x1a, 0x62, - 0x1d, 0x4e, 0x2b, 0xd9, 0x1b, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, - 0x01, 0x09, 0x30, 0x82, 0x01, 0x05, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, - 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x0f, - 0x06, 0x03, 0x55, 0x1d, 0x13, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, - 0x02, 0x01, 0x00, 0x30, 0x33, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x01, 0x01, 0x04, 0x27, 0x30, 0x25, 0x30, 0x23, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x17, 0x68, 0x74, 0x74, - 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x65, 0x6e, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x30, 0x30, 0x06, 0x03, - 0x55, 0x1d, 0x1f, 0x04, 0x29, 0x30, 0x27, 0x30, 0x25, 0xa0, 0x23, 0xa0, - 0x21, 0x86, 0x1f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, - 0x6c, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, - 0x74, 0x2f, 0x67, 0x32, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3b, - 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0x06, - 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, - 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x70, 0x61, 0x30, - 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x82, 0xa2, - 0x70, 0x74, 0xdd, 0xbc, 0x53, 0x3f, 0xcf, 0x7b, 0xd4, 0xf7, 0xcd, 0x7f, - 0xa7, 0x60, 0xc6, 0x0a, 0x4c, 0xbf, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, - 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x6a, 0x72, 0x26, 0x7a, 0xd0, - 0x1e, 0xef, 0x7d, 0xe7, 0x3b, 0x69, 0x51, 0xd4, 0x6c, 0x8d, 0x9f, 0x90, - 0x12, 0x66, 0xab, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, - 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x3f, - 0x1c, 0x1a, 0x5b, 0xff, 0x40, 0x22, 0x1d, 0x8f, 0x35, 0x0c, 0x2d, 0xaa, - 0x99, 0x27, 0xab, 0xc0, 0x11, 0x32, 0x70, 0xd7, 0x36, 0x28, 0x69, 0xa5, - 0x8d, 0xb1, 0x27, 0x99, 0x42, 0xbe, 0xc4, 0x93, 0xeb, 0x48, 0x57, 0x43, - 0x71, 0x23, 0xc4, 0xe5, 0x4e, 0xad, 0xae, 0x43, 0x6f, 0x92, 0x76, 0xc5, - 0x19, 0xef, 0xca, 0xbc, 0x6f, 0x42, 0x4c, 0x16, 0x9a, 0x86, 0xa9, 0x04, - 0x38, 0xc7, 0x65, 0xf0, 0xf5, 0x0c, 0xe0, 0x4a, 0xdf, 0xa2, 0xfa, 0xce, - 0x1a, 0x11, 0xa8, 0x9c, 0x69, 0x2f, 0x1b, 0xdf, 0xea, 0xe2, 0x32, 0xf3, - 0xce, 0x4c, 0xbc, 0x46, 0x0c, 0xc0, 0x89, 0x80, 0xd1, 0x87, 0x6b, 0xa2, - 0xcf, 0x6b, 0xd4, 0x7f, 0xfd, 0xf5, 0x60, 0x52, 0x67, 0x57, 0xa0, 0x6d, - 0xd1, 0x64, 0x41, 0x14, 0x6d, 0x34, 0x62, 0xed, 0x06, 0x6c, 0x24, 0xf2, - 0x06, 0xbc, 0x28, 0x02, 0xaf, 0x03, 0x2d, 0xc2, 0x33, 0x05, 0xfb, 0xcb, - 0xaa, 0x16, 0xe8, 0x65, 0x10, 0x43, 0xf5, 0x69, 0x5c, 0xe3, 0x81, 0x58, - 0x99, 0xcd, 0x6b, 0xd3, 0xb8, 0xc7, 0x7b, 0x19, 0x55, 0xc9, 0x40, 0xce, - 0x79, 0x55, 0xb8, 0x73, 0x89, 0xe9, 0x5c, 0x40, 0x66, 0x43, 0x12, 0x7f, - 0x07, 0xb8, 0x65, 0x56, 0xd5, 0x8d, 0xc3, 0xa7, 0xf5, 0xb1, 0xb6, 0x65, - 0x9e, 0xc0, 0x83, 0x36, 0x7f, 0x16, 0x45, 0x3c, 0x74, 0x4b, 0x93, 0x8a, - 0x3c, 0xf1, 0x2b, 0xf5, 0x35, 0x70, 0x73, 0x7b, 0xe7, 0x82, 0x04, 0xb1, - 0x18, 0x98, 0x0e, 0xd4, 0x9c, 0x6f, 0x1a, 0xfc, 0xfc, 0xa7, 0x33, 0xa5, - 0xbb, 0xbb, 0x18, 0xf3, 0x6b, 0x7a, 0x5d, 0x32, 0x87, 0xf7, 0x6d, 0x25, - 0xe4, 0xe2, 0x76, 0x86, 0x21, 0x1e, 0x11, 0x46, 0xcd, 0x76, 0x0e, 0x6f, - 0x4f, 0xa4, 0x21, 0x71, 0x0a, 0x84, 0xa7, 0x2d, 0x36, 0xa9, 0x48, 0x22, - 0x51, 0x7e, 0x82, -} - -var certSet2Cert41 = []byte{ - 0x30, 0x82, 0x05, 0x1f, 0x30, 0x82, 0x04, 0x07, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x04, 0x07, 0x27, 0xa4, 0x6b, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x5a, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, - 0x45, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09, - 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30, - 0x11, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65, - 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, - 0x55, 0x04, 0x03, 0x13, 0x19, 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, - 0x72, 0x65, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, - 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x34, - 0x30, 0x34, 0x30, 0x32, 0x31, 0x34, 0x33, 0x36, 0x31, 0x30, 0x5a, 0x17, - 0x0d, 0x32, 0x31, 0x30, 0x34, 0x30, 0x32, 0x31, 0x34, 0x33, 0x35, 0x35, - 0x32, 0x5a, 0x30, 0x81, 0x8d, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, - 0x04, 0x06, 0x13, 0x02, 0x4e, 0x4c, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, - 0x55, 0x04, 0x07, 0x13, 0x09, 0x41, 0x6d, 0x73, 0x74, 0x65, 0x72, 0x64, - 0x61, 0x6d, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x1c, 0x56, 0x65, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x20, 0x45, 0x6e, 0x74, - 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x20, 0x53, 0x6f, 0x6c, 0x75, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, - 0x04, 0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, 0x75, - 0x73, 0x74, 0x31, 0x2e, 0x30, 0x2c, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, - 0x25, 0x56, 0x65, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x20, 0x41, 0x6b, 0x61, - 0x6d, 0x61, 0x69, 0x20, 0x53, 0x75, 0x72, 0x65, 0x53, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x20, 0x43, 0x41, 0x20, 0x47, 0x31, 0x34, 0x2d, 0x53, 0x48, - 0x41, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, - 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xdd, - 0x6e, 0x9e, 0x02, 0x69, 0x02, 0xb5, 0xa3, 0x99, 0x2e, 0x08, 0x64, 0x32, - 0x6a, 0x59, 0xf3, 0xc6, 0x9e, 0xa6, 0x20, 0x07, 0xd2, 0x48, 0xd1, 0xa8, - 0x93, 0xc7, 0xea, 0x47, 0x8f, 0x83, 0x39, 0x40, 0xd7, 0x20, 0x5d, 0x8d, - 0x9a, 0xba, 0xab, 0xd8, 0x70, 0xec, 0x9d, 0x88, 0xd1, 0xbd, 0x62, 0xf6, - 0xdb, 0xec, 0x9d, 0x5e, 0x35, 0x01, 0x76, 0x03, 0x23, 0xe5, 0x6f, 0xd2, - 0xaf, 0x46, 0x35, 0x59, 0x5a, 0x5c, 0xd1, 0xa8, 0x23, 0xc1, 0xeb, 0xe9, - 0x20, 0xd4, 0x49, 0xd6, 0x3f, 0x00, 0xd8, 0xa8, 0x22, 0xde, 0x43, 0x79, - 0x81, 0xac, 0xe9, 0xa4, 0x92, 0xf5, 0x77, 0x70, 0x05, 0x1e, 0x5c, 0xb6, - 0xa0, 0xf7, 0x90, 0xa4, 0xcd, 0xab, 0x28, 0x2c, 0x90, 0xc2, 0xe7, 0x0f, - 0xc3, 0xaf, 0x1c, 0x47, 0x59, 0xd5, 0x84, 0x2e, 0xdf, 0x26, 0x07, 0x45, - 0x23, 0x5a, 0xc6, 0xe8, 0x90, 0xc8, 0x85, 0x4b, 0x8c, 0x16, 0x1e, 0x60, - 0xf9, 0x01, 0x13, 0xf1, 0x14, 0x1f, 0xe6, 0xe8, 0x14, 0xed, 0xc5, 0xd2, - 0x6f, 0x63, 0x28, 0x6e, 0x72, 0x8c, 0x49, 0xae, 0x08, 0x72, 0xc7, 0x93, - 0x95, 0xb4, 0x0b, 0x0c, 0xae, 0x8f, 0x9a, 0x67, 0x84, 0xf5, 0x57, 0x1b, - 0xdb, 0x81, 0xd7, 0x17, 0x9d, 0x41, 0x11, 0x43, 0x19, 0xbd, 0x6d, 0x4a, - 0x85, 0xed, 0x8f, 0x70, 0x25, 0xab, 0x66, 0xab, 0xf6, 0xfa, 0x6d, 0x1c, - 0x3c, 0xab, 0xed, 0x17, 0xbd, 0x56, 0x84, 0xe1, 0xdb, 0x75, 0x33, 0xb2, - 0x28, 0x4b, 0x99, 0x8e, 0xf9, 0x4b, 0x82, 0x33, 0x50, 0x9f, 0x92, 0x53, - 0xed, 0xfa, 0xad, 0x0f, 0x95, 0x9c, 0xa3, 0xf2, 0xcb, 0x60, 0xf0, 0x77, - 0x1d, 0xc9, 0x01, 0x8b, 0x5f, 0x2d, 0x86, 0xbe, 0xbf, 0x36, 0xb8, 0x24, - 0x96, 0x13, 0x7c, 0xc1, 0x86, 0x5a, 0x6c, 0xc1, 0x48, 0x2a, 0x7f, 0x3e, - 0x93, 0x60, 0xc5, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0xb7, - 0x30, 0x82, 0x01, 0xb3, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, - 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x02, - 0x30, 0x4c, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x45, 0x30, 0x43, 0x30, - 0x41, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb1, 0x3e, 0x01, 0x32, - 0x30, 0x34, 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x02, 0x01, 0x16, 0x26, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, - 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2e, 0x6f, 0x6d, 0x6e, 0x69, 0x72, - 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, - 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x30, 0x81, 0xba, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x81, 0xad, 0x30, 0x81, - 0xaa, 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, - 0x01, 0x86, 0x26, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, - 0x73, 0x70, 0x2e, 0x6f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, - 0x65, 0x72, 0x6f, 0x6f, 0x74, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x73, - 0x3a, 0x2f, 0x2f, 0x63, 0x61, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x6f, 0x6d, - 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, - 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x72, 0x6f, 0x6f, 0x74, - 0x2e, 0x63, 0x72, 0x74, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, - 0x2f, 0x2f, 0x63, 0x61, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x6f, 0x6d, 0x6e, - 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x61, - 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x72, 0x6f, 0x6f, 0x74, 0x2e, - 0x64, 0x65, 0x72, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, - 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0xc6, 0x30, 0x1f, 0x06, 0x03, 0x55, - 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xe5, 0x9d, 0x59, 0x30, - 0x82, 0x47, 0x58, 0xcc, 0xac, 0xfa, 0x08, 0x54, 0x36, 0x86, 0x7b, 0x3a, - 0xb5, 0x04, 0x4d, 0xf0, 0x30, 0x42, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, - 0x3b, 0x30, 0x39, 0x30, 0x37, 0xa0, 0x35, 0xa0, 0x33, 0x86, 0x31, 0x68, - 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x64, 0x70, 0x31, 0x2e, 0x70, - 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x4f, 0x6d, 0x6e, 0x69, - 0x72, 0x6f, 0x6f, 0x74, 0x32, 0x30, 0x32, 0x35, 0x2e, 0x63, 0x72, 0x6c, - 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xf8, - 0xbd, 0xfa, 0xaf, 0x73, 0x77, 0xc6, 0xc7, 0x1b, 0xf9, 0x4b, 0x4d, 0x11, - 0xa7, 0xd1, 0x33, 0xaf, 0xaf, 0x72, 0x11, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, - 0x01, 0x01, 0x00, 0x80, 0xd9, 0x7a, 0xed, 0x72, 0x05, 0x37, 0x8f, 0x61, - 0xaa, 0x73, 0x7c, 0x9a, 0x6a, 0xfc, 0xfe, 0x01, 0xe2, 0x19, 0x81, 0x70, - 0x07, 0x25, 0x32, 0xb0, 0xf0, 0x6f, 0x3b, 0xc7, 0x6a, 0x28, 0x3d, 0xe4, - 0x51, 0x87, 0xe6, 0x7e, 0x82, 0xec, 0xae, 0x48, 0xa7, 0xb1, 0x77, 0x38, - 0xc2, 0xd6, 0x56, 0xaf, 0x8f, 0xf2, 0x01, 0xfc, 0x65, 0x65, 0x10, 0x09, - 0xf7, 0x74, 0x29, 0xb5, 0x0e, 0x92, 0xee, 0x90, 0x98, 0xd1, 0x88, 0xa2, - 0x65, 0xb7, 0xcd, 0x9c, 0x0e, 0xa7, 0x86, 0x98, 0x28, 0xbc, 0xae, 0x15, - 0x83, 0xb6, 0x1a, 0xd7, 0x1d, 0xec, 0x19, 0xda, 0x7a, 0x8e, 0x40, 0xf9, - 0x99, 0x15, 0xd5, 0x7d, 0xa5, 0xba, 0xab, 0xfd, 0x26, 0x98, 0x6e, 0x9c, - 0x41, 0x3b, 0xb6, 0x81, 0x18, 0xec, 0x70, 0x48, 0xd7, 0x6e, 0x7f, 0xa6, - 0xe1, 0x77, 0x25, 0xd6, 0xdd, 0x62, 0xe8, 0x52, 0xf3, 0x8c, 0x16, 0x39, - 0x67, 0xe2, 0x22, 0x0d, 0x77, 0x2e, 0xfb, 0x11, 0x6c, 0xe4, 0xdd, 0x38, - 0xb4, 0x27, 0x5f, 0x03, 0xa8, 0x3d, 0x44, 0xe2, 0xf2, 0x84, 0x4b, 0x84, - 0xfd, 0x56, 0xa6, 0x9e, 0x4d, 0x7b, 0xa2, 0x16, 0x4f, 0x07, 0xf5, 0x34, - 0x24, 0x72, 0xa5, 0xa2, 0xfa, 0x16, 0x66, 0x2a, 0xa4, 0x4a, 0x0e, 0xc8, - 0x0d, 0x27, 0x44, 0x9c, 0x77, 0xd4, 0x12, 0x10, 0x87, 0xd2, 0x00, 0x2c, - 0x7a, 0xbb, 0x8e, 0x88, 0x22, 0x91, 0x15, 0xbe, 0xa2, 0x59, 0xca, 0x34, - 0xe0, 0x1c, 0x61, 0x94, 0x86, 0x20, 0x33, 0xcd, 0xe7, 0x4c, 0x5d, 0x3b, - 0x92, 0x3e, 0xcb, 0xd6, 0x2d, 0xea, 0x54, 0xfa, 0xfb, 0xaf, 0x54, 0xf5, - 0xa8, 0xc5, 0x0b, 0xca, 0x8b, 0x87, 0x00, 0xe6, 0x9f, 0xe6, 0x95, 0xbf, - 0xb7, 0xc4, 0xa3, 0x59, 0xf5, 0x16, 0x6c, 0x5f, 0x3e, 0x69, 0x55, 0x80, - 0x39, 0xf6, 0x75, 0x50, 0x14, 0x3e, 0x32, -} - -var certSet2Cert42 = []byte{ - 0x30, 0x82, 0x05, 0x2b, 0x30, 0x82, 0x04, 0x13, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x7e, 0xe1, 0x4a, 0x6f, 0x6f, 0xef, 0xf2, 0xd3, 0x7f, - 0x3f, 0xad, 0x65, 0x4d, 0x3a, 0xda, 0xb4, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, - 0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, - 0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, - 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, - 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28, - 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69, - 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, - 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, - 0x31, 0x45, 0x30, 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56, - 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, - 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, - 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30, - 0x1e, 0x17, 0x0d, 0x31, 0x33, 0x31, 0x30, 0x33, 0x31, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x33, 0x31, 0x30, 0x33, 0x30, - 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x77, 0x31, 0x0b, 0x30, - 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x1d, - 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x14, 0x53, 0x79, 0x6d, - 0x61, 0x6e, 0x74, 0x65, 0x63, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, - 0x04, 0x0b, 0x13, 0x16, 0x53, 0x79, 0x6d, 0x61, 0x6e, 0x74, 0x65, 0x63, - 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, - 0x72, 0x6b, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, - 0x1f, 0x53, 0x79, 0x6d, 0x61, 0x6e, 0x74, 0x65, 0x63, 0x20, 0x43, 0x6c, - 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x45, 0x56, 0x20, 0x53, 0x53, 0x4c, - 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x30, 0x82, 0x01, 0x22, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, - 0x02, 0x82, 0x01, 0x01, 0x00, 0xd8, 0xa1, 0x65, 0x74, 0x23, 0xe8, 0x2b, - 0x64, 0xe2, 0x32, 0xd7, 0x33, 0x37, 0x3d, 0x8e, 0xf5, 0x34, 0x16, 0x48, - 0xdd, 0x4f, 0x7f, 0x87, 0x1c, 0xf8, 0x44, 0x23, 0x13, 0x8e, 0xfb, 0x11, - 0xd8, 0x44, 0x5a, 0x18, 0x71, 0x8e, 0x60, 0x16, 0x26, 0x92, 0x9b, 0xfd, - 0x17, 0x0b, 0xe1, 0x71, 0x70, 0x42, 0xfe, 0xbf, 0xfa, 0x1c, 0xc0, 0xaa, - 0xa3, 0xa7, 0xb5, 0x71, 0xe8, 0xff, 0x18, 0x83, 0xf6, 0xdf, 0x10, 0x0a, - 0x13, 0x62, 0xc8, 0x3d, 0x9c, 0xa7, 0xde, 0x2e, 0x3f, 0x0c, 0xd9, 0x1d, - 0xe7, 0x2e, 0xfb, 0x2a, 0xce, 0xc8, 0x9a, 0x7f, 0x87, 0xbf, 0xd8, 0x4c, - 0x04, 0x15, 0x32, 0xc9, 0xd1, 0xcc, 0x95, 0x71, 0xa0, 0x4e, 0x28, 0x4f, - 0x84, 0xd9, 0x35, 0xfb, 0xe3, 0x86, 0x6f, 0x94, 0x53, 0xe6, 0x72, 0x8a, - 0x63, 0x67, 0x2e, 0xbe, 0x69, 0xf6, 0xf7, 0x6e, 0x8e, 0x9c, 0x60, 0x04, - 0xeb, 0x29, 0xfa, 0xc4, 0x47, 0x42, 0xd2, 0x78, 0x98, 0xe3, 0xec, 0x0b, - 0xa5, 0x92, 0xdc, 0xb7, 0x9a, 0xbd, 0x80, 0x64, 0x2b, 0x38, 0x7c, 0x38, - 0x09, 0x5b, 0x66, 0xf6, 0x2d, 0x95, 0x7a, 0x86, 0xb2, 0x34, 0x2e, 0x85, - 0x9e, 0x90, 0x0e, 0x5f, 0xb7, 0x5d, 0xa4, 0x51, 0x72, 0x46, 0x70, 0x13, - 0xbf, 0x67, 0xf2, 0xb6, 0xa7, 0x4d, 0x14, 0x1e, 0x6c, 0xb9, 0x53, 0xee, - 0x23, 0x1a, 0x4e, 0x8d, 0x48, 0x55, 0x43, 0x41, 0xb1, 0x89, 0x75, 0x6a, - 0x40, 0x28, 0xc5, 0x7d, 0xdd, 0xd2, 0x6e, 0xd2, 0x02, 0x19, 0x2f, 0x7b, - 0x24, 0x94, 0x4b, 0xeb, 0xf1, 0x1a, 0xa9, 0x9b, 0xe3, 0x23, 0x9a, 0xea, - 0xfa, 0x33, 0xab, 0x0a, 0x2c, 0xb7, 0xf4, 0x60, 0x08, 0xdd, 0x9f, 0x1c, - 0xcd, 0xdd, 0x2d, 0x01, 0x66, 0x80, 0xaf, 0xb3, 0x2f, 0x29, 0x1d, 0x23, - 0xb8, 0x8a, 0xe1, 0xa1, 0x70, 0x07, 0x0c, 0x34, 0x0f, 0x02, 0x03, 0x01, - 0x00, 0x01, 0xa3, 0x82, 0x01, 0x5d, 0x30, 0x82, 0x01, 0x59, 0x30, 0x2f, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x23, - 0x30, 0x21, 0x30, 0x1f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x30, 0x01, 0x86, 0x13, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x73, - 0x32, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x30, - 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, - 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x65, 0x06, 0x03, 0x55, - 0x1d, 0x20, 0x04, 0x5e, 0x30, 0x5c, 0x30, 0x5a, 0x06, 0x04, 0x55, 0x1d, - 0x20, 0x00, 0x30, 0x52, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, - 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73, 0x79, 0x6d, 0x61, 0x75, 0x74, 0x68, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x28, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x02, 0x30, 0x1c, 0x1a, 0x1a, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73, - 0x79, 0x6d, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, - 0x70, 0x61, 0x30, 0x30, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x29, 0x30, - 0x27, 0x30, 0x25, 0xa0, 0x23, 0xa0, 0x21, 0x86, 0x1f, 0x68, 0x74, 0x74, - 0x70, 0x3a, 0x2f, 0x2f, 0x73, 0x31, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, 0x61, 0x33, 0x2d, 0x67, 0x35, - 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, - 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x29, 0x06, 0x03, - 0x55, 0x1d, 0x11, 0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, 0x31, - 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x53, 0x79, - 0x6d, 0x61, 0x6e, 0x74, 0x65, 0x63, 0x50, 0x4b, 0x49, 0x2d, 0x31, 0x2d, - 0x35, 0x33, 0x33, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, - 0x04, 0x14, 0x01, 0x59, 0xab, 0xe7, 0xdd, 0x3a, 0x0b, 0x59, 0xa6, 0x64, - 0x63, 0xd6, 0xcf, 0x20, 0x07, 0x57, 0xd5, 0x91, 0xe7, 0x6a, 0x30, 0x1f, - 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x7f, - 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0, 0x30, 0x09, 0xf3, 0x43, - 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, - 0x01, 0x01, 0x00, 0x42, 0x01, 0x55, 0x7b, 0xd0, 0x16, 0x1a, 0x5d, 0x58, - 0xe8, 0xbb, 0x9b, 0xa8, 0x4d, 0xd7, 0xf3, 0xd7, 0xeb, 0x13, 0x94, 0x86, - 0xd6, 0x7f, 0x21, 0x0b, 0x47, 0xbc, 0x57, 0x9b, 0x92, 0x5d, 0x4f, 0x05, - 0x9f, 0x38, 0xa4, 0x10, 0x7c, 0xcf, 0x83, 0xbe, 0x06, 0x43, 0x46, 0x8d, - 0x08, 0xbc, 0x6a, 0xd7, 0x10, 0xa6, 0xfa, 0xab, 0xaf, 0x2f, 0x61, 0xa8, - 0x63, 0xf2, 0x65, 0xdf, 0x7f, 0x4c, 0x88, 0x12, 0x88, 0x4f, 0xb3, 0x69, - 0xd9, 0xff, 0x27, 0xc0, 0x0a, 0x97, 0x91, 0x8f, 0x56, 0xfb, 0x89, 0xc4, - 0xa8, 0xbb, 0x92, 0x2d, 0x1b, 0x73, 0xb0, 0xc6, 0xab, 0x36, 0xf4, 0x96, - 0x6c, 0x20, 0x08, 0xef, 0x0a, 0x1e, 0x66, 0x24, 0x45, 0x4f, 0x67, 0x00, - 0x40, 0xc8, 0x07, 0x54, 0x74, 0x33, 0x3b, 0xa6, 0xad, 0xbb, 0x23, 0x9f, - 0x66, 0xed, 0xa2, 0x44, 0x70, 0x34, 0xfb, 0x0e, 0xea, 0x01, 0xfd, 0xcf, - 0x78, 0x74, 0xdf, 0xa7, 0xad, 0x55, 0xb7, 0x5f, 0x4d, 0xf6, 0xd6, 0x3f, - 0xe0, 0x86, 0xce, 0x24, 0xc7, 0x42, 0xa9, 0x13, 0x14, 0x44, 0x35, 0x4b, - 0xb6, 0xdf, 0xc9, 0x60, 0xac, 0x0c, 0x7f, 0xd9, 0x93, 0x21, 0x4b, 0xee, - 0x9c, 0xe4, 0x49, 0x02, 0x98, 0xd3, 0x60, 0x7b, 0x5c, 0xbc, 0xd5, 0x30, - 0x2f, 0x07, 0xce, 0x44, 0x42, 0xc4, 0x0b, 0x99, 0xfe, 0xe6, 0x9f, 0xfc, - 0xb0, 0x78, 0x86, 0x51, 0x6d, 0xd1, 0x2c, 0x9d, 0xc6, 0x96, 0xfb, 0x85, - 0x82, 0xbb, 0x04, 0x2f, 0xf7, 0x62, 0x80, 0xef, 0x62, 0xda, 0x7f, 0xf6, - 0x0e, 0xac, 0x90, 0xb8, 0x56, 0xbd, 0x79, 0x3f, 0xf2, 0x80, 0x6e, 0xa3, - 0xd9, 0xb9, 0x0f, 0x5d, 0x3a, 0x07, 0x1d, 0x91, 0x93, 0x86, 0x4b, 0x29, - 0x4c, 0xe1, 0xdc, 0xb5, 0xe1, 0xe0, 0x33, 0x9d, 0xb3, 0xcb, 0x36, 0x91, - 0x4b, 0xfe, 0xa1, 0xb4, 0xee, 0xf0, 0xf9, -} - -var certSet2Cert43 = []byte{ - 0x30, 0x82, 0x05, 0x38, 0x30, 0x82, 0x04, 0x20, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x51, 0x3f, 0xb9, 0x74, 0x38, 0x70, 0xb7, 0x34, 0x40, - 0x41, 0x8d, 0x30, 0x93, 0x06, 0x99, 0xff, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, - 0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, - 0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, - 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, - 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28, - 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69, - 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, - 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, - 0x31, 0x45, 0x30, 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56, - 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, - 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, - 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30, - 0x1e, 0x17, 0x0d, 0x31, 0x33, 0x31, 0x30, 0x33, 0x31, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x33, 0x31, 0x30, 0x33, 0x30, - 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x7e, 0x31, 0x0b, 0x30, - 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x1d, - 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x14, 0x53, 0x79, 0x6d, - 0x61, 0x6e, 0x74, 0x65, 0x63, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, - 0x04, 0x0b, 0x13, 0x16, 0x53, 0x79, 0x6d, 0x61, 0x6e, 0x74, 0x65, 0x63, - 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, - 0x72, 0x6b, 0x31, 0x2f, 0x30, 0x2d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, - 0x26, 0x53, 0x79, 0x6d, 0x61, 0x6e, 0x74, 0x65, 0x63, 0x20, 0x43, 0x6c, - 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, - 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x20, 0x2d, - 0x20, 0x47, 0x34, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, - 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, - 0xb2, 0xd8, 0x05, 0xca, 0x1c, 0x74, 0x2d, 0xb5, 0x17, 0x56, 0x39, 0xc5, - 0x4a, 0x52, 0x09, 0x96, 0xe8, 0x4b, 0xd8, 0x0c, 0xf1, 0x68, 0x9f, 0x9a, - 0x42, 0x28, 0x62, 0xc3, 0xa5, 0x30, 0x53, 0x7e, 0x55, 0x11, 0x82, 0x5b, - 0x03, 0x7a, 0x0d, 0x2f, 0xe1, 0x79, 0x04, 0xc9, 0xb4, 0x96, 0x77, 0x19, - 0x81, 0x01, 0x94, 0x59, 0xf9, 0xbc, 0xf7, 0x7a, 0x99, 0x27, 0x82, 0x2d, - 0xb7, 0x83, 0xdd, 0x5a, 0x27, 0x7f, 0xb2, 0x03, 0x7a, 0x9c, 0x53, 0x25, - 0xe9, 0x48, 0x1f, 0x46, 0x4f, 0xc8, 0x9d, 0x29, 0xf8, 0xbe, 0x79, 0x56, - 0xf6, 0xf7, 0xfd, 0xd9, 0x3a, 0x68, 0xda, 0x8b, 0x4b, 0x82, 0x33, 0x41, - 0x12, 0xc3, 0xc8, 0x3c, 0xcc, 0xd6, 0x96, 0x7a, 0x84, 0x21, 0x1a, 0x22, - 0x04, 0x03, 0x27, 0x17, 0x8b, 0x1c, 0x68, 0x61, 0x93, 0x0f, 0x0e, 0x51, - 0x80, 0x33, 0x1d, 0xb4, 0xb5, 0xce, 0xeb, 0x7e, 0xd0, 0x62, 0xac, 0xee, - 0xb3, 0x7b, 0x01, 0x74, 0xef, 0x69, 0x35, 0xeb, 0xca, 0xd5, 0x3d, 0xa9, - 0xee, 0x97, 0x98, 0xca, 0x8d, 0xaa, 0x44, 0x0e, 0x25, 0x99, 0x4a, 0x15, - 0x96, 0xa4, 0xce, 0x6d, 0x02, 0x54, 0x1f, 0x2a, 0x6a, 0x26, 0xe2, 0x06, - 0x3a, 0x63, 0x48, 0xac, 0xb4, 0x4c, 0xd1, 0x75, 0x93, 0x50, 0xff, 0x13, - 0x2f, 0xd6, 0xda, 0xe1, 0xc6, 0x18, 0xf5, 0x9f, 0xc9, 0x25, 0x5d, 0xf3, - 0x00, 0x3a, 0xde, 0x26, 0x4d, 0xb4, 0x29, 0x09, 0xcd, 0x0f, 0x3d, 0x23, - 0x6f, 0x16, 0x4a, 0x81, 0x16, 0xfb, 0xf2, 0x83, 0x10, 0xc3, 0xb8, 0xd6, - 0xd8, 0x55, 0x32, 0x3d, 0xf1, 0xbd, 0x0f, 0xbd, 0x8c, 0x52, 0x95, 0x4a, - 0x16, 0x97, 0x7a, 0x52, 0x21, 0x63, 0x75, 0x2f, 0x16, 0xf9, 0xc4, 0x66, - 0xbe, 0xf5, 0xb5, 0x09, 0xd8, 0xff, 0x27, 0x00, 0xcd, 0x44, 0x7c, 0x6f, - 0x4b, 0x3f, 0xb0, 0xf7, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, - 0x63, 0x30, 0x82, 0x01, 0x5f, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, - 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, - 0x00, 0x30, 0x30, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x29, 0x30, 0x27, - 0x30, 0x25, 0xa0, 0x23, 0xa0, 0x21, 0x86, 0x1f, 0x68, 0x74, 0x74, 0x70, - 0x3a, 0x2f, 0x2f, 0x73, 0x31, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, 0x61, 0x33, 0x2d, 0x67, 0x35, 0x2e, - 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, - 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x2f, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x23, 0x30, 0x21, 0x30, - 0x1f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, - 0x13, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x73, 0x32, 0x2e, 0x73, - 0x79, 0x6d, 0x63, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x6b, 0x06, 0x03, - 0x55, 0x1d, 0x20, 0x04, 0x64, 0x30, 0x62, 0x30, 0x60, 0x06, 0x0a, 0x60, - 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x07, 0x36, 0x30, 0x52, 0x30, - 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, - 0x1a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, - 0x73, 0x79, 0x6d, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x63, 0x70, 0x73, 0x30, 0x28, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x02, 0x02, 0x30, 0x1c, 0x1a, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73, 0x79, 0x6d, 0x61, 0x75, 0x74, - 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x70, 0x61, 0x30, 0x29, 0x06, - 0x03, 0x55, 0x1d, 0x11, 0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, - 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x53, - 0x79, 0x6d, 0x61, 0x6e, 0x74, 0x65, 0x63, 0x50, 0x4b, 0x49, 0x2d, 0x31, - 0x2d, 0x35, 0x33, 0x34, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, - 0x16, 0x04, 0x14, 0x5f, 0x60, 0xcf, 0x61, 0x90, 0x55, 0xdf, 0x84, 0x43, - 0x14, 0x8a, 0x60, 0x2a, 0xb2, 0xf5, 0x7a, 0xf4, 0x43, 0x18, 0xef, 0x30, - 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, - 0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0, 0x30, 0x09, 0xf3, - 0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, - 0x82, 0x01, 0x01, 0x00, 0x5e, 0x94, 0x56, 0x49, 0xdd, 0x8e, 0x2d, 0x65, - 0xf5, 0xc1, 0x36, 0x51, 0xb6, 0x03, 0xe3, 0xda, 0x9e, 0x73, 0x19, 0xf2, - 0x1f, 0x59, 0xab, 0x58, 0x7e, 0x6c, 0x26, 0x05, 0x2c, 0xfa, 0x81, 0xd7, - 0x5c, 0x23, 0x17, 0x22, 0x2c, 0x37, 0x93, 0xf7, 0x86, 0xec, 0x85, 0xe6, - 0xb0, 0xa3, 0xfd, 0x1f, 0xe2, 0x32, 0xa8, 0x45, 0x6f, 0xe1, 0xd9, 0xfb, - 0xb9, 0xaf, 0xd2, 0x70, 0xa0, 0x32, 0x42, 0x65, 0xbf, 0x84, 0xfe, 0x16, - 0x2a, 0x8f, 0x3f, 0xc5, 0xa6, 0xd6, 0xa3, 0x93, 0x7d, 0x43, 0xe9, 0x74, - 0x21, 0x91, 0x35, 0x28, 0xf4, 0x63, 0xe9, 0x2e, 0xed, 0xf7, 0xf5, 0x5c, - 0x7f, 0x4b, 0x9a, 0xb5, 0x20, 0xe9, 0x0a, 0xbd, 0xe0, 0x45, 0x10, 0x0c, - 0x14, 0x94, 0x9a, 0x5d, 0xa5, 0xe3, 0x4b, 0x91, 0xe8, 0x24, 0x9b, 0x46, - 0x40, 0x65, 0xf4, 0x22, 0x72, 0xcd, 0x99, 0xf8, 0x88, 0x11, 0xf5, 0xf3, - 0x7f, 0xe6, 0x33, 0x82, 0xe6, 0xa8, 0xc5, 0x7e, 0xfe, 0xd0, 0x08, 0xe2, - 0x25, 0x58, 0x08, 0x71, 0x68, 0xe6, 0xcd, 0xa2, 0xe6, 0x14, 0xde, 0x4e, - 0x52, 0x24, 0x2d, 0xfd, 0xe5, 0x79, 0x13, 0x53, 0xe7, 0x5e, 0x2f, 0x2d, - 0x4d, 0x1b, 0x6d, 0x40, 0x15, 0x52, 0x2b, 0xf7, 0x87, 0x89, 0x78, 0x12, - 0x81, 0x6e, 0xd9, 0x4d, 0xaa, 0x2d, 0x78, 0xd4, 0xc2, 0x2c, 0x3d, 0x08, - 0x5f, 0x87, 0x91, 0x9e, 0x1f, 0x0e, 0xb0, 0xde, 0x30, 0x52, 0x64, 0x86, - 0x89, 0xaa, 0x9d, 0x66, 0x9c, 0x0e, 0x76, 0x0c, 0x80, 0xf2, 0x74, 0xd8, - 0x2a, 0xf8, 0xb8, 0x3a, 0xce, 0xd7, 0xd6, 0x0f, 0x11, 0xbe, 0x6b, 0xab, - 0x14, 0xf5, 0xbd, 0x41, 0xa0, 0x22, 0x63, 0x89, 0xf1, 0xba, 0x0f, 0x6f, - 0x29, 0x63, 0x66, 0x2d, 0x3f, 0xac, 0x8c, 0x72, 0xc5, 0xfb, 0xc7, 0xe4, - 0xd4, 0x0f, 0xf2, 0x3b, 0x4f, 0x8c, 0x29, 0xc7, -} - -var certSet2Cert44 = []byte{ - 0x30, 0x82, 0x05, 0x86, 0x30, 0x82, 0x04, 0x6e, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x04, 0x07, 0x27, 0x9a, 0xa9, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x5a, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, - 0x45, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09, - 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30, - 0x11, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65, - 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, - 0x55, 0x04, 0x03, 0x13, 0x19, 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, - 0x72, 0x65, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, - 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x33, - 0x31, 0x32, 0x31, 0x39, 0x32, 0x30, 0x30, 0x37, 0x33, 0x32, 0x5a, 0x17, - 0x0d, 0x31, 0x37, 0x31, 0x32, 0x31, 0x39, 0x32, 0x30, 0x30, 0x36, 0x35, - 0x35, 0x5a, 0x30, 0x81, 0x8b, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, - 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, - 0x55, 0x04, 0x08, 0x13, 0x0a, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, - 0x74, 0x6f, 0x6e, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, - 0x13, 0x07, 0x52, 0x65, 0x64, 0x6d, 0x6f, 0x6e, 0x64, 0x31, 0x1e, 0x30, - 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x15, 0x4d, 0x69, 0x63, 0x72, - 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, - 0x04, 0x0b, 0x13, 0x0c, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, - 0x74, 0x20, 0x49, 0x54, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, - 0x03, 0x13, 0x15, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, - 0x20, 0x49, 0x54, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x53, 0x48, 0x41, 0x32, - 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, - 0x30, 0x82, 0x02, 0x0a, 0x02, 0x82, 0x02, 0x01, 0x00, 0xd1, 0xe8, 0x37, - 0xa7, 0x76, 0x8a, 0x70, 0x4b, 0x19, 0xf0, 0x20, 0x37, 0x09, 0x24, 0x37, - 0x7f, 0xea, 0xfb, 0x78, 0xe6, 0x05, 0xba, 0x6a, 0xad, 0x4e, 0x27, 0x0d, - 0xfc, 0x72, 0x6a, 0xd9, 0x6c, 0x21, 0xc4, 0x64, 0x11, 0x95, 0x73, 0x10, - 0x0a, 0x5c, 0x25, 0x7b, 0x88, 0x6c, 0x94, 0x04, 0xfd, 0xc7, 0xdb, 0xae, - 0x7b, 0xdc, 0x4a, 0x08, 0xb3, 0x3e, 0x16, 0xf1, 0xd0, 0xad, 0xdb, 0x30, - 0x6d, 0xd7, 0x1a, 0x1e, 0x52, 0xb5, 0x3d, 0xf0, 0x47, 0x19, 0x03, 0xe2, - 0x7d, 0xa6, 0xbd, 0x57, 0x13, 0x3f, 0x54, 0xea, 0x3a, 0xa3, 0xb1, 0x77, - 0xfc, 0x42, 0xf0, 0x63, 0x49, 0x6a, 0x91, 0x80, 0x2e, 0x30, 0x49, 0xc0, - 0x8a, 0xeb, 0x2b, 0xaf, 0xfe, 0x3a, 0xeb, 0x07, 0x5d, 0x06, 0xf7, 0xe9, - 0xfd, 0x84, 0x0e, 0x91, 0xbd, 0x09, 0x20, 0x29, 0xe8, 0x6e, 0x5d, 0x09, - 0xce, 0x15, 0xd3, 0xe7, 0xef, 0xdb, 0x50, 0xeb, 0x44, 0xef, 0x18, 0x57, - 0xab, 0x04, 0x1d, 0xbc, 0x31, 0xf9, 0xf7, 0x7b, 0x2a, 0x13, 0xcf, 0xd1, - 0x3d, 0x51, 0xaf, 0x1b, 0xc5, 0xb5, 0x7b, 0xe7, 0xb0, 0xfc, 0x53, 0xbb, - 0x9a, 0xe7, 0x63, 0xde, 0x41, 0x33, 0xb6, 0x47, 0x24, 0x69, 0x5d, 0xb8, - 0x46, 0xa7, 0xff, 0xad, 0xab, 0xdf, 0x4f, 0x7a, 0x78, 0x25, 0x27, 0x21, - 0x26, 0x34, 0xca, 0x02, 0x6e, 0x37, 0x51, 0xf0, 0xed, 0x58, 0x1a, 0x60, - 0x94, 0xf6, 0xc4, 0x93, 0xd8, 0xdd, 0x30, 0x24, 0x25, 0xd7, 0x1c, 0xeb, - 0x19, 0x94, 0x35, 0x5d, 0x93, 0xb2, 0xae, 0xaa, 0x29, 0x83, 0x73, 0xc4, - 0x74, 0x59, 0x05, 0x52, 0x67, 0x9d, 0xda, 0x67, 0x51, 0x39, 0x05, 0x3a, - 0x36, 0xea, 0xf2, 0x1e, 0x76, 0x2b, 0x14, 0xae, 0xec, 0x3d, 0xf9, 0x14, - 0x99, 0x8b, 0x07, 0x6e, 0xbc, 0xe7, 0x0c, 0x56, 0xde, 0xac, 0xbe, 0xae, - 0xdb, 0x75, 0x32, 0x90, 0x9e, 0x63, 0xbd, 0x74, 0xbf, 0xe0, 0x0a, 0xca, - 0xf8, 0x34, 0x96, 0x67, 0x84, 0xcd, 0xd1, 0x42, 0x38, 0x78, 0xc7, 0x99, - 0xb6, 0x0c, 0xce, 0xb6, 0x0f, 0xe9, 0x1b, 0xcb, 0xf4, 0x59, 0xbe, 0x11, - 0x0e, 0xcb, 0x2c, 0x32, 0xc8, 0xfa, 0x83, 0x29, 0x64, 0x79, 0x3c, 0x8b, - 0x4b, 0xf0, 0x32, 0x74, 0x6c, 0xf3, 0x93, 0xb8, 0x96, 0x6b, 0x5d, 0x57, - 0x5a, 0x68, 0xc1, 0xcc, 0x0c, 0x79, 0x8a, 0x19, 0xde, 0xf5, 0x49, 0x02, - 0x5e, 0x08, 0x80, 0x01, 0x89, 0x0c, 0x32, 0xcd, 0xd2, 0xd6, 0x96, 0xd5, - 0x4b, 0xa0, 0xf3, 0xec, 0xbf, 0xab, 0xf4, 0x7d, 0xb3, 0xa1, 0xb9, 0x7c, - 0xda, 0x4e, 0xd7, 0xe5, 0xb7, 0xac, 0xb9, 0xf2, 0x25, 0x5f, 0x01, 0xcb, - 0x8c, 0x96, 0xa8, 0x28, 0xae, 0xc1, 0x33, 0x5a, 0xf6, 0x3f, 0x08, 0x90, - 0xdc, 0xeb, 0xff, 0x39, 0xd8, 0x26, 0xc8, 0x12, 0x9d, 0x1c, 0x9a, 0xaa, - 0xa9, 0xc0, 0x16, 0x8e, 0x86, 0xed, 0x67, 0x52, 0x96, 0x00, 0x7f, 0x0d, - 0x92, 0x3d, 0x3d, 0xd9, 0x70, 0x36, 0xe5, 0xea, 0x42, 0x6f, 0x1f, 0xae, - 0x95, 0xe5, 0x5b, 0x5d, 0xf8, 0xd0, 0x3a, 0xc7, 0xd4, 0xde, 0x77, 0x86, - 0xd0, 0xfc, 0x9e, 0x4e, 0xe2, 0xe2, 0xb8, 0xa9, 0x68, 0x37, 0x09, 0xc4, - 0x39, 0xe3, 0x85, 0xb8, 0x89, 0xf3, 0x1f, 0x6e, 0xb7, 0x6d, 0x1f, 0x4a, - 0x2f, 0x18, 0x09, 0x6f, 0xde, 0x4a, 0x01, 0x8f, 0x14, 0xc9, 0xb7, 0xa6, - 0xee, 0xa7, 0x63, 0x9f, 0x33, 0xa4, 0x54, 0x7c, 0x42, 0x83, 0x68, 0xb8, - 0xa5, 0xdf, 0xbf, 0xec, 0xb9, 0x1a, 0x5d, 0x13, 0x3b, 0xd9, 0xad, 0x68, - 0xfd, 0x20, 0x0a, 0x55, 0x91, 0x21, 0x64, 0xf9, 0xd7, 0x13, 0x01, 0xa0, - 0x08, 0x5d, 0x59, 0x89, 0x1b, 0x44, 0xaf, 0xa4, 0xac, 0xc7, 0x05, 0x10, - 0xfa, 0x41, 0x4a, 0xa8, 0xfb, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, - 0x01, 0x20, 0x30, 0x82, 0x01, 0x1c, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, - 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, - 0x01, 0x00, 0x30, 0x53, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x4c, 0x30, - 0x4a, 0x30, 0x48, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb1, 0x3e, - 0x01, 0x00, 0x30, 0x3b, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x02, 0x01, 0x16, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, - 0x2f, 0x63, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, - 0x6f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, - 0x63, 0x66, 0x6d, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, - 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x1d, 0x06, 0x03, 0x55, - 0x1d, 0x25, 0x04, 0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x03, 0x02, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, - 0x16, 0x80, 0x14, 0xe5, 0x9d, 0x59, 0x30, 0x82, 0x47, 0x58, 0xcc, 0xac, - 0xfa, 0x08, 0x54, 0x36, 0x86, 0x7b, 0x3a, 0xb5, 0x04, 0x4d, 0xf0, 0x30, - 0x42, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3b, 0x30, 0x39, 0x30, 0x37, - 0xa0, 0x35, 0xa0, 0x33, 0x86, 0x31, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, - 0x2f, 0x63, 0x64, 0x70, 0x31, 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, - 0x2d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, - 0x52, 0x4c, 0x2f, 0x4f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x32, - 0x30, 0x32, 0x35, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, - 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x51, 0xaf, 0x24, 0x26, 0x9c, 0xf4, - 0x68, 0x22, 0x57, 0x80, 0x26, 0x2b, 0x3b, 0x46, 0x62, 0x15, 0x7b, 0x1e, - 0xcc, 0xa5, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, - 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x76, 0x85, - 0xc5, 0x23, 0x31, 0x1f, 0xb4, 0x73, 0xea, 0xa0, 0xbc, 0xa5, 0xed, 0xdf, - 0x45, 0x43, 0x6a, 0x7f, 0x69, 0x20, 0x1b, 0x80, 0xb2, 0xfb, 0x1c, 0xdd, - 0xaa, 0x7f, 0x88, 0xd3, 0x31, 0x41, 0x36, 0xf7, 0xfb, 0xfb, 0x6b, 0xad, - 0x98, 0x8c, 0x78, 0x1f, 0x9d, 0x11, 0x67, 0x3a, 0xcd, 0x4b, 0xec, 0xa8, - 0xbc, 0x9d, 0x15, 0x19, 0xc4, 0x3b, 0x0b, 0xa7, 0x93, 0xce, 0xe8, 0xfc, - 0x9d, 0x5b, 0xe8, 0x1f, 0xcb, 0x56, 0xae, 0x76, 0x43, 0x2b, 0xc7, 0x13, - 0x51, 0x77, 0x41, 0xa8, 0x66, 0x4c, 0x5f, 0xa7, 0xd1, 0xd7, 0xaa, 0x75, - 0xc5, 0x1b, 0x29, 0x4c, 0xc9, 0xf4, 0x6d, 0xa1, 0x5e, 0xa1, 0x85, 0x93, - 0x16, 0xc2, 0xcb, 0x3b, 0xab, 0x14, 0x7d, 0x44, 0xfd, 0xda, 0x25, 0x29, - 0x86, 0x2a, 0xfe, 0x63, 0x20, 0xca, 0xd2, 0x0b, 0xc2, 0x34, 0x15, 0xbb, - 0xaf, 0x5b, 0x7f, 0x8a, 0xe0, 0xaa, 0xed, 0x45, 0xa6, 0xea, 0x79, 0xdb, - 0xd8, 0x35, 0x66, 0x54, 0x43, 0xde, 0x37, 0x33, 0xd1, 0xe4, 0xe0, 0xcd, - 0x57, 0xca, 0x71, 0xb0, 0x7d, 0xe9, 0x16, 0x77, 0x64, 0xe8, 0x59, 0x97, - 0xb9, 0xd5, 0x2e, 0xd1, 0xb4, 0x91, 0xda, 0x77, 0x71, 0xf3, 0x4a, 0x0f, - 0x48, 0xd2, 0x34, 0x99, 0x60, 0x95, 0x37, 0xac, 0x1f, 0x01, 0xcd, 0x10, - 0x9d, 0xe8, 0x2a, 0xa5, 0x20, 0xc7, 0x50, 0x9b, 0xb3, 0x6c, 0x49, 0x78, - 0x2b, 0x58, 0x92, 0x64, 0x89, 0xb8, 0x95, 0x36, 0xa8, 0x34, 0xaa, 0xf0, - 0x41, 0xd2, 0x95, 0x5a, 0x24, 0x54, 0x97, 0x4d, 0x6e, 0x05, 0xc4, 0x95, - 0xad, 0xc4, 0x7a, 0xa3, 0x39, 0xfb, 0x79, 0x06, 0x8a, 0x9b, 0xa6, 0x4f, - 0xd9, 0x22, 0xfa, 0x44, 0x4e, 0x36, 0xf3, 0xc9, 0x0f, 0xa6, 0x39, 0xe7, - 0x80, 0xb2, 0x5e, 0xbf, 0xbd, 0x39, 0xd1, 0x46, 0xe5, 0x55, 0x47, 0xdb, - 0xbc, 0x6e, -} - -var certSet2Cert45 = []byte{ - 0x30, 0x82, 0x05, 0xa3, 0x30, 0x82, 0x03, 0x8b, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x67, 0x3f, 0x33, 0x4f, 0x21, 0x53, 0x36, 0x52, 0xc3, - 0x5e, 0x15, 0xd2, 0xfd, 0xb3, 0x02, 0x0f, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x55, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x43, - 0x4e, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, - 0x57, 0x6f, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, - 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x2a, 0x30, 0x28, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x21, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x74, 0x79, 0x20, 0x6f, 0x66, 0x20, 0x57, 0x6f, 0x53, 0x69, 0x67, - 0x6e, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x39, 0x30, 0x38, 0x30, 0x38, 0x30, - 0x31, 0x30, 0x30, 0x30, 0x35, 0x5a, 0x17, 0x0d, 0x32, 0x34, 0x30, 0x38, - 0x30, 0x38, 0x30, 0x31, 0x30, 0x30, 0x30, 0x35, 0x5a, 0x30, 0x4f, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x43, 0x4e, - 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x57, - 0x6f, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, - 0x69, 0x74, 0x65, 0x64, 0x31, 0x24, 0x30, 0x22, 0x06, 0x03, 0x55, 0x04, - 0x03, 0x13, 0x1b, 0x57, 0x6f, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, - 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x4f, 0x56, 0x20, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, - 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, - 0x01, 0x01, 0x00, 0xbc, 0x89, 0xbe, 0x61, 0x51, 0x53, 0xc8, 0x2b, 0x96, - 0x75, 0xb3, 0x5a, 0xd3, 0x0e, 0x34, 0xfe, 0x4a, 0xc2, 0x9f, 0xa3, 0x18, - 0x83, 0xa2, 0xac, 0xe3, 0x2e, 0x5e, 0x93, 0x79, 0x0b, 0x13, 0x49, 0x5e, - 0x93, 0xb2, 0x8f, 0x84, 0x10, 0xed, 0x91, 0x8f, 0x82, 0xba, 0xad, 0x67, - 0xdf, 0x33, 0x1b, 0xae, 0x84, 0xf2, 0x55, 0xb0, 0x5b, 0xf4, 0xb3, 0x9e, - 0xbc, 0xe6, 0x04, 0x0f, 0x1d, 0xef, 0x04, 0x5a, 0xa8, 0x0b, 0xec, 0x12, - 0x6d, 0x56, 0x19, 0x64, 0x70, 0x49, 0x0f, 0x57, 0x92, 0xf3, 0x5f, 0x21, - 0xa6, 0x4d, 0xb4, 0xd2, 0x96, 0x2b, 0x3c, 0x32, 0xb3, 0xef, 0x8f, 0x59, - 0x0b, 0x14, 0xba, 0x6e, 0xa2, 0x9e, 0x71, 0xdb, 0xf2, 0x88, 0x3f, 0x28, - 0x3b, 0xec, 0xce, 0xbe, 0x47, 0xac, 0x45, 0xc7, 0x8a, 0x9e, 0xfa, 0x61, - 0x93, 0xc5, 0x49, 0x17, 0xb6, 0x46, 0xb6, 0xf7, 0x99, 0x16, 0x8c, 0x1c, - 0x6e, 0x31, 0xae, 0x69, 0xce, 0xed, 0xc6, 0x24, 0x92, 0x70, 0xa1, 0xcb, - 0x96, 0xc3, 0x6c, 0x16, 0xd0, 0xee, 0xcc, 0x4f, 0x86, 0x33, 0xb3, 0x41, - 0xe6, 0x3d, 0x3d, 0xdb, 0x0e, 0x8c, 0x33, 0x74, 0xbb, 0xc3, 0xfc, 0x0b, - 0xa7, 0xfc, 0xd1, 0x71, 0xe2, 0xc1, 0x0c, 0xd4, 0xf7, 0xba, 0x3e, 0x80, - 0x90, 0xd4, 0x48, 0xeb, 0xa2, 0x83, 0x70, 0xd8, 0xdb, 0x30, 0x07, 0x29, - 0x89, 0xf9, 0x81, 0x21, 0x2c, 0xff, 0xeb, 0x47, 0xf6, 0x7a, 0x6d, 0x43, - 0x96, 0x67, 0x17, 0x3e, 0xf3, 0xe2, 0x73, 0x51, 0xc7, 0x76, 0x1e, 0xe9, - 0x1c, 0xa0, 0xec, 0x11, 0x1a, 0xb1, 0xcf, 0x1e, 0x2d, 0x9c, 0x55, 0xee, - 0x3b, 0xc6, 0x2d, 0xae, 0xdc, 0x66, 0x65, 0x91, 0xa2, 0x66, 0x9c, 0xac, - 0x82, 0xf1, 0xa4, 0x17, 0xb5, 0xd7, 0x43, 0x83, 0xc3, 0x88, 0xa0, 0x64, - 0xde, 0xca, 0x72, 0x45, 0xdc, 0x38, 0xfb, 0x02, 0x03, 0x01, 0x00, 0x01, - 0xa3, 0x82, 0x01, 0x73, 0x30, 0x82, 0x01, 0x6f, 0x30, 0x0e, 0x06, 0x03, - 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, - 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x16, 0x30, 0x14, 0x06, - 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x30, 0x12, 0x06, 0x03, 0x55, - 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, - 0x02, 0x01, 0x00, 0x30, 0x30, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x29, - 0x30, 0x27, 0x30, 0x25, 0xa0, 0x23, 0xa0, 0x21, 0x86, 0x1f, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x31, 0x2e, 0x77, - 0x6f, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x61, - 0x31, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x71, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x65, 0x30, 0x63, 0x30, 0x27, 0x06, - 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x1b, 0x68, - 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x31, 0x2e, - 0x77, 0x6f, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, - 0x61, 0x31, 0x30, 0x38, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x30, 0x02, 0x86, 0x2c, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x61, - 0x69, 0x61, 0x31, 0x2e, 0x77, 0x6f, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x63, 0x61, 0x31, 0x2d, 0x63, 0x6c, 0x61, 0x73, 0x73, - 0x33, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x63, 0x65, 0x72, - 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x62, - 0x2e, 0x81, 0xd9, 0xe3, 0x42, 0x79, 0x14, 0xa3, 0xcd, 0xd9, 0x54, 0x8a, - 0x6e, 0xf8, 0xde, 0x95, 0xaa, 0x8f, 0x98, 0x30, 0x1f, 0x06, 0x03, 0x55, - 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xe1, 0x66, 0xcf, 0x0e, - 0xd1, 0xf1, 0xb3, 0x4b, 0xb7, 0x06, 0x20, 0x14, 0xfe, 0x87, 0x12, 0xd5, - 0xf6, 0xfe, 0xfb, 0x3e, 0x30, 0x45, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, - 0x3e, 0x30, 0x3c, 0x30, 0x3a, 0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, - 0x82, 0x9b, 0x51, 0x01, 0x03, 0x02, 0x30, 0x2b, 0x30, 0x29, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1d, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x6f, 0x73, - 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x2f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, - 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x02, 0x01, 0x00, 0xab, - 0x70, 0xaa, 0x64, 0xc4, 0x0b, 0x34, 0x91, 0xb9, 0x63, 0x20, 0x5e, 0xb0, - 0x9c, 0x21, 0xff, 0x25, 0x79, 0x6c, 0x57, 0x4e, 0x56, 0x44, 0x58, 0x83, - 0xb9, 0x00, 0xce, 0x2d, 0x65, 0xa8, 0x6d, 0x95, 0x38, 0xea, 0x82, 0x2d, - 0x55, 0x18, 0x60, 0x12, 0x7e, 0x1a, 0x1d, 0x6b, 0x62, 0x34, 0x2c, 0xd9, - 0xcd, 0x17, 0x00, 0x43, 0x84, 0x3e, 0xad, 0xbc, 0xff, 0x26, 0x85, 0x1f, - 0x4a, 0xa7, 0x46, 0x13, 0xb0, 0x7d, 0x3b, 0x0b, 0xd9, 0x4b, 0x9d, 0xb0, - 0xcf, 0x8d, 0xf4, 0x05, 0xcb, 0x12, 0x29, 0xfe, 0xe1, 0x97, 0xc7, 0xb7, - 0xc7, 0xaa, 0x53, 0x7e, 0x39, 0x2d, 0x9d, 0xf6, 0xd4, 0x5e, 0xb7, 0x8c, - 0x15, 0x6a, 0x81, 0xd2, 0x37, 0x1a, 0x43, 0x0e, 0xcb, 0xe6, 0x30, 0x21, - 0x43, 0x83, 0x69, 0x0f, 0xef, 0x6b, 0xcd, 0x10, 0xf9, 0x84, 0x60, 0xcf, - 0x89, 0xe9, 0x88, 0x10, 0x01, 0xaf, 0x09, 0xf3, 0x48, 0xbb, 0x07, 0x09, - 0x75, 0x01, 0x84, 0xfa, 0xb1, 0x1e, 0x51, 0x19, 0x8f, 0xc6, 0xc9, 0x85, - 0x65, 0x16, 0x5f, 0xe0, 0x56, 0x7e, 0xb7, 0xbf, 0x40, 0xc2, 0xd4, 0xd0, - 0x05, 0x1f, 0x93, 0x63, 0xc9, 0x24, 0x08, 0x3b, 0x91, 0xb2, 0x35, 0xe1, - 0xa4, 0x8f, 0x35, 0xdb, 0x24, 0x58, 0x75, 0x39, 0xe4, 0xdd, 0x10, 0x1a, - 0xb0, 0xdf, 0x13, 0x12, 0x73, 0x9e, 0x6d, 0xe7, 0x67, 0x3c, 0xdb, 0x1c, - 0x1c, 0xdd, 0x10, 0xdd, 0xcc, 0xf4, 0x07, 0x09, 0xb9, 0x2e, 0xe5, 0x75, - 0x6d, 0x97, 0xb7, 0x60, 0x5b, 0x89, 0x70, 0x81, 0xd2, 0x26, 0xd8, 0xc6, - 0x09, 0x2b, 0xb2, 0x05, 0x7f, 0xc4, 0xb8, 0x14, 0x41, 0x1e, 0x07, 0xf0, - 0x48, 0x41, 0x63, 0xcb, 0x0c, 0xaa, 0x45, 0x7e, 0x84, 0xf9, 0x33, 0xb3, - 0x58, 0x87, 0xbc, 0xb1, 0xd6, 0xc2, 0x65, 0xc7, 0x57, 0xc6, 0x95, 0xe8, - 0x85, 0x90, 0xb0, 0x62, 0x50, 0xf5, 0xee, 0x12, 0xf1, 0xd8, 0x7e, 0x73, - 0xcb, 0xc0, 0xc3, 0xa0, 0x25, 0x17, 0x23, 0x37, 0x91, 0xba, 0x63, 0xbd, - 0x84, 0xaf, 0xf3, 0x89, 0xe0, 0x51, 0xc2, 0x73, 0x35, 0x6d, 0x63, 0x86, - 0x21, 0xf2, 0x73, 0xbd, 0xc2, 0x47, 0xe0, 0x4d, 0x7e, 0x46, 0x37, 0x4b, - 0xd0, 0xf7, 0x61, 0x2a, 0xc7, 0x94, 0x50, 0x25, 0x36, 0xe8, 0xae, 0xda, - 0x2e, 0x1f, 0xb8, 0x08, 0xb2, 0x55, 0x7c, 0x6b, 0x66, 0x43, 0x8f, 0x02, - 0x1d, 0xdd, 0xa7, 0xeb, 0x98, 0x00, 0xa7, 0x25, 0x74, 0xf5, 0x93, 0x1b, - 0x6d, 0x26, 0xbb, 0x1d, 0xe5, 0xb7, 0xfc, 0x21, 0x25, 0x26, 0xd1, 0x77, - 0x1b, 0xa8, 0x6e, 0xaa, 0xc3, 0x4b, 0x64, 0x51, 0x7f, 0x91, 0x0e, 0x41, - 0x5c, 0x19, 0x83, 0xa1, 0xa8, 0x1f, 0x94, 0x99, 0x43, 0x0f, 0x99, 0xdb, - 0x18, 0xdc, 0x21, 0x6f, 0x76, 0xd1, 0x9e, 0xea, 0xa3, 0x76, 0xe0, 0xf0, - 0x09, 0xbc, 0xb9, 0xb4, 0xf7, 0x43, 0x6c, 0x1f, 0xd3, 0x2a, 0x86, 0x6a, - 0x2f, 0xe0, 0x6c, 0xf1, 0x83, 0x39, 0xd7, 0x70, 0xdb, 0xa2, 0x91, 0xab, - 0x54, 0xbe, 0xf4, 0x47, 0x88, 0x8c, 0xf0, 0x10, 0xd2, 0xe4, 0xad, 0xeb, - 0x7e, 0xb1, 0xba, 0x08, 0x4b, 0x67, 0x04, 0xa3, 0xf2, 0xe9, 0x90, 0x2b, - 0x81, 0xe3, 0x74, 0x76, 0x3d, 0x00, 0x9d, 0xd2, 0xbb, 0xfc, 0xa5, 0xa0, - 0x15, 0x1c, 0x28, 0xdf, 0x10, 0x4f, 0x47, 0xd7, 0x33, 0x46, 0x9d, 0xb2, - 0x57, 0xd2, 0xc6, 0x1f, 0xfb, 0xe4, 0x59, 0x4a, 0x2b, 0x28, 0xa9, 0x13, - 0xdd, 0xb9, 0xe9, 0x93, 0xb4, 0x88, 0xee, 0xe2, 0x5b, 0xa0, 0x07, 0x25, - 0xfe, 0x8a, 0x2e, 0x78, 0xe4, 0xb4, 0xe1, 0xd5, 0x1d, 0xf6, 0x1a, 0x3a, - 0xe3, 0x1c, 0x01, 0x2a, 0x1e, 0xa1, 0x86, 0x54, 0x9e, 0x49, 0xdc, 0xc9, - 0x59, 0xe3, 0x0d, 0x6d, 0x5a, 0x13, 0x36, -} - -var certSet2Cert46 = []byte{ - 0x30, 0x82, 0x05, 0xe1, 0x30, 0x82, 0x04, 0xc9, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x04, 0x07, 0x27, 0xaa, 0x47, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x5a, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, - 0x45, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09, - 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30, - 0x11, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65, - 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, - 0x55, 0x04, 0x03, 0x13, 0x19, 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, - 0x72, 0x65, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, - 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x34, - 0x30, 0x35, 0x30, 0x37, 0x31, 0x37, 0x30, 0x34, 0x30, 0x39, 0x5a, 0x17, - 0x0d, 0x31, 0x38, 0x30, 0x35, 0x30, 0x37, 0x31, 0x37, 0x30, 0x33, 0x33, - 0x30, 0x5a, 0x30, 0x81, 0x8b, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, - 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, - 0x55, 0x04, 0x08, 0x13, 0x0a, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, - 0x74, 0x6f, 0x6e, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, - 0x13, 0x07, 0x52, 0x65, 0x64, 0x6d, 0x6f, 0x6e, 0x64, 0x31, 0x1e, 0x30, - 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x15, 0x4d, 0x69, 0x63, 0x72, - 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, - 0x04, 0x0b, 0x13, 0x0c, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, - 0x74, 0x20, 0x49, 0x54, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, - 0x03, 0x13, 0x15, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, - 0x20, 0x49, 0x54, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x53, 0x48, 0x41, 0x32, - 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, - 0x30, 0x82, 0x02, 0x0a, 0x02, 0x82, 0x02, 0x01, 0x00, 0xd1, 0xe8, 0x37, - 0xa7, 0x76, 0x8a, 0x70, 0x4b, 0x19, 0xf0, 0x20, 0x37, 0x09, 0x24, 0x37, - 0x7f, 0xea, 0xfb, 0x78, 0xe6, 0x05, 0xba, 0x6a, 0xad, 0x4e, 0x27, 0x0d, - 0xfc, 0x72, 0x6a, 0xd9, 0x6c, 0x21, 0xc4, 0x64, 0x11, 0x95, 0x73, 0x10, - 0x0a, 0x5c, 0x25, 0x7b, 0x88, 0x6c, 0x94, 0x04, 0xfd, 0xc7, 0xdb, 0xae, - 0x7b, 0xdc, 0x4a, 0x08, 0xb3, 0x3e, 0x16, 0xf1, 0xd0, 0xad, 0xdb, 0x30, - 0x6d, 0xd7, 0x1a, 0x1e, 0x52, 0xb5, 0x3d, 0xf0, 0x47, 0x19, 0x03, 0xe2, - 0x7d, 0xa6, 0xbd, 0x57, 0x13, 0x3f, 0x54, 0xea, 0x3a, 0xa3, 0xb1, 0x77, - 0xfc, 0x42, 0xf0, 0x63, 0x49, 0x6a, 0x91, 0x80, 0x2e, 0x30, 0x49, 0xc0, - 0x8a, 0xeb, 0x2b, 0xaf, 0xfe, 0x3a, 0xeb, 0x07, 0x5d, 0x06, 0xf7, 0xe9, - 0xfd, 0x84, 0x0e, 0x91, 0xbd, 0x09, 0x20, 0x29, 0xe8, 0x6e, 0x5d, 0x09, - 0xce, 0x15, 0xd3, 0xe7, 0xef, 0xdb, 0x50, 0xeb, 0x44, 0xef, 0x18, 0x57, - 0xab, 0x04, 0x1d, 0xbc, 0x31, 0xf9, 0xf7, 0x7b, 0x2a, 0x13, 0xcf, 0xd1, - 0x3d, 0x51, 0xaf, 0x1b, 0xc5, 0xb5, 0x7b, 0xe7, 0xb0, 0xfc, 0x53, 0xbb, - 0x9a, 0xe7, 0x63, 0xde, 0x41, 0x33, 0xb6, 0x47, 0x24, 0x69, 0x5d, 0xb8, - 0x46, 0xa7, 0xff, 0xad, 0xab, 0xdf, 0x4f, 0x7a, 0x78, 0x25, 0x27, 0x21, - 0x26, 0x34, 0xca, 0x02, 0x6e, 0x37, 0x51, 0xf0, 0xed, 0x58, 0x1a, 0x60, - 0x94, 0xf6, 0xc4, 0x93, 0xd8, 0xdd, 0x30, 0x24, 0x25, 0xd7, 0x1c, 0xeb, - 0x19, 0x94, 0x35, 0x5d, 0x93, 0xb2, 0xae, 0xaa, 0x29, 0x83, 0x73, 0xc4, - 0x74, 0x59, 0x05, 0x52, 0x67, 0x9d, 0xda, 0x67, 0x51, 0x39, 0x05, 0x3a, - 0x36, 0xea, 0xf2, 0x1e, 0x76, 0x2b, 0x14, 0xae, 0xec, 0x3d, 0xf9, 0x14, - 0x99, 0x8b, 0x07, 0x6e, 0xbc, 0xe7, 0x0c, 0x56, 0xde, 0xac, 0xbe, 0xae, - 0xdb, 0x75, 0x32, 0x90, 0x9e, 0x63, 0xbd, 0x74, 0xbf, 0xe0, 0x0a, 0xca, - 0xf8, 0x34, 0x96, 0x67, 0x84, 0xcd, 0xd1, 0x42, 0x38, 0x78, 0xc7, 0x99, - 0xb6, 0x0c, 0xce, 0xb6, 0x0f, 0xe9, 0x1b, 0xcb, 0xf4, 0x59, 0xbe, 0x11, - 0x0e, 0xcb, 0x2c, 0x32, 0xc8, 0xfa, 0x83, 0x29, 0x64, 0x79, 0x3c, 0x8b, - 0x4b, 0xf0, 0x32, 0x74, 0x6c, 0xf3, 0x93, 0xb8, 0x96, 0x6b, 0x5d, 0x57, - 0x5a, 0x68, 0xc1, 0xcc, 0x0c, 0x79, 0x8a, 0x19, 0xde, 0xf5, 0x49, 0x02, - 0x5e, 0x08, 0x80, 0x01, 0x89, 0x0c, 0x32, 0xcd, 0xd2, 0xd6, 0x96, 0xd5, - 0x4b, 0xa0, 0xf3, 0xec, 0xbf, 0xab, 0xf4, 0x7d, 0xb3, 0xa1, 0xb9, 0x7c, - 0xda, 0x4e, 0xd7, 0xe5, 0xb7, 0xac, 0xb9, 0xf2, 0x25, 0x5f, 0x01, 0xcb, - 0x8c, 0x96, 0xa8, 0x28, 0xae, 0xc1, 0x33, 0x5a, 0xf6, 0x3f, 0x08, 0x90, - 0xdc, 0xeb, 0xff, 0x39, 0xd8, 0x26, 0xc8, 0x12, 0x9d, 0x1c, 0x9a, 0xaa, - 0xa9, 0xc0, 0x16, 0x8e, 0x86, 0xed, 0x67, 0x52, 0x96, 0x00, 0x7f, 0x0d, - 0x92, 0x3d, 0x3d, 0xd9, 0x70, 0x36, 0xe5, 0xea, 0x42, 0x6f, 0x1f, 0xae, - 0x95, 0xe5, 0x5b, 0x5d, 0xf8, 0xd0, 0x3a, 0xc7, 0xd4, 0xde, 0x77, 0x86, - 0xd0, 0xfc, 0x9e, 0x4e, 0xe2, 0xe2, 0xb8, 0xa9, 0x68, 0x37, 0x09, 0xc4, - 0x39, 0xe3, 0x85, 0xb8, 0x89, 0xf3, 0x1f, 0x6e, 0xb7, 0x6d, 0x1f, 0x4a, - 0x2f, 0x18, 0x09, 0x6f, 0xde, 0x4a, 0x01, 0x8f, 0x14, 0xc9, 0xb7, 0xa6, - 0xee, 0xa7, 0x63, 0x9f, 0x33, 0xa4, 0x54, 0x7c, 0x42, 0x83, 0x68, 0xb8, - 0xa5, 0xdf, 0xbf, 0xec, 0xb9, 0x1a, 0x5d, 0x13, 0x3b, 0xd9, 0xad, 0x68, - 0xfd, 0x20, 0x0a, 0x55, 0x91, 0x21, 0x64, 0xf9, 0xd7, 0x13, 0x01, 0xa0, - 0x08, 0x5d, 0x59, 0x89, 0x1b, 0x44, 0xaf, 0xa4, 0xac, 0xc7, 0x05, 0x10, - 0xfa, 0x41, 0x4a, 0xa8, 0xfb, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, - 0x01, 0x7b, 0x30, 0x82, 0x01, 0x77, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, - 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, - 0x01, 0x00, 0x30, 0x60, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x59, 0x30, - 0x57, 0x30, 0x48, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb1, 0x3e, - 0x01, 0x00, 0x30, 0x3b, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x02, 0x01, 0x16, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, - 0x2f, 0x63, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, - 0x6f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, - 0x63, 0x66, 0x6d, 0x30, 0x0b, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, - 0x82, 0x37, 0x2a, 0x01, 0x30, 0x42, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x01, 0x01, 0x04, 0x36, 0x30, 0x34, 0x30, 0x32, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x26, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x6f, 0x6d, - 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, - 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x72, 0x6f, 0x6f, 0x74, - 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, - 0x03, 0x02, 0x01, 0x86, 0x30, 0x27, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, - 0x20, 0x30, 0x1e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, - 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, - 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x09, 0x30, 0x1f, 0x06, - 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xe5, 0x9d, - 0x59, 0x30, 0x82, 0x47, 0x58, 0xcc, 0xac, 0xfa, 0x08, 0x54, 0x36, 0x86, - 0x7b, 0x3a, 0xb5, 0x04, 0x4d, 0xf0, 0x30, 0x42, 0x06, 0x03, 0x55, 0x1d, - 0x1f, 0x04, 0x3b, 0x30, 0x39, 0x30, 0x37, 0xa0, 0x35, 0xa0, 0x33, 0x86, - 0x31, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x64, 0x70, 0x31, - 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72, 0x75, 0x73, - 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x4f, 0x6d, - 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x32, 0x30, 0x32, 0x35, 0x2e, 0x63, - 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, - 0x14, 0x51, 0xaf, 0x24, 0x26, 0x9c, 0xf4, 0x68, 0x22, 0x57, 0x80, 0x26, - 0x2b, 0x3b, 0x46, 0x62, 0x15, 0x7b, 0x1e, 0xcc, 0xa5, 0x30, 0x0d, 0x06, - 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, - 0x03, 0x82, 0x01, 0x01, 0x00, 0x69, 0x62, 0xf6, 0x84, 0x91, 0x00, 0xc4, - 0x6f, 0x82, 0x7b, 0x24, 0xe1, 0x42, 0xa2, 0xa5, 0x8b, 0x82, 0x5c, 0xa7, - 0xc5, 0x44, 0xcb, 0xe7, 0x52, 0x76, 0x63, 0xd3, 0x76, 0x9e, 0x78, 0xe2, - 0x69, 0x35, 0xb1, 0x38, 0xba, 0xb0, 0x96, 0xc6, 0x1f, 0xac, 0x7b, 0xc6, - 0xb2, 0x65, 0x77, 0x8b, 0x7d, 0x8d, 0xae, 0x64, 0xb9, 0xa5, 0x8c, 0x17, - 0xca, 0x58, 0x65, 0xc3, 0xad, 0x82, 0xf5, 0xc5, 0xa2, 0xf5, 0x01, 0x13, - 0x93, 0xc6, 0x7e, 0x44, 0xe5, 0xc4, 0x61, 0xfa, 0x03, 0xb6, 0x56, 0xc1, - 0x72, 0xe1, 0xc8, 0x28, 0xc5, 0x69, 0x21, 0x8f, 0xac, 0x6e, 0xfd, 0x7f, - 0x43, 0x83, 0x36, 0xb8, 0xc0, 0xd6, 0xa0, 0x28, 0xfe, 0x1a, 0x45, 0xbe, - 0xfd, 0x93, 0x8c, 0x8d, 0xa4, 0x64, 0x79, 0x1f, 0x14, 0xdb, 0xa1, 0x9f, - 0x21, 0xdc, 0xc0, 0x4e, 0x7b, 0x17, 0x22, 0x17, 0xb1, 0xb6, 0x3c, 0xd3, - 0x9b, 0xe2, 0x0a, 0xa3, 0x7e, 0x99, 0xb0, 0xc1, 0xac, 0xd8, 0xf4, 0x86, - 0xdf, 0x3c, 0xda, 0x7d, 0x14, 0x9c, 0x40, 0xc1, 0x7c, 0xd2, 0x18, 0x6f, - 0xf1, 0x4f, 0x26, 0x45, 0x09, 0x95, 0x94, 0x5c, 0xda, 0xd0, 0x98, 0xf8, - 0xf4, 0x4c, 0x82, 0x96, 0x10, 0xde, 0xac, 0x30, 0xcb, 0x2b, 0xae, 0xf9, - 0x92, 0xea, 0xbf, 0x79, 0x03, 0xfc, 0x1e, 0x3f, 0xac, 0x09, 0xa4, 0x3f, - 0x65, 0xfd, 0x91, 0x4f, 0x96, 0x24, 0xa7, 0xce, 0xb4, 0x4e, 0x6a, 0x96, - 0x29, 0x17, 0xae, 0xc0, 0xa8, 0xdf, 0x17, 0x22, 0xf4, 0x17, 0xe3, 0xdc, - 0x1c, 0x39, 0x06, 0x56, 0x10, 0xea, 0xea, 0xb5, 0x74, 0x17, 0x3c, 0x4e, - 0xdd, 0x7e, 0x91, 0x0a, 0xa8, 0x0b, 0x78, 0x07, 0xa7, 0x31, 0x44, 0x08, - 0x31, 0xab, 0x18, 0x84, 0x0f, 0x12, 0x9c, 0xe7, 0xde, 0x84, 0x2c, 0xe9, - 0x6d, 0x93, 0x45, 0xbf, 0xa8, 0xc1, 0x3f, 0x34, 0xdc, -} - -var certSet2Cert47 = []byte{ - 0x30, 0x82, 0x05, 0xec, 0x30, 0x82, 0x04, 0xd4, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x6e, 0xcc, 0x7a, 0xa5, 0xa7, 0x03, 0x20, 0x09, 0xb8, - 0xce, 0xbc, 0xf4, 0xe9, 0x52, 0xd4, 0x91, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81, - 0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, - 0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, - 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, - 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28, - 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69, - 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, - 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, - 0x31, 0x45, 0x30, 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56, - 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, - 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, - 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30, - 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x32, 0x30, 0x38, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30, 0x32, 0x30, 0x37, - 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xb5, 0x31, 0x0b, - 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, - 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65, - 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56, - 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, - 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3b, 0x30, - 0x39, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x54, 0x65, 0x72, 0x6d, - 0x73, 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65, 0x20, 0x61, 0x74, 0x20, - 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, - 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x72, 0x70, 0x61, 0x20, 0x28, 0x63, 0x29, 0x31, 0x30, 0x31, 0x2f, - 0x30, 0x2d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x26, 0x56, 0x65, 0x72, - 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, - 0x33, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x30, - 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, - 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, - 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb1, 0x87, 0x84, 0x1f, - 0xc2, 0x0c, 0x45, 0xf5, 0xbc, 0xab, 0x25, 0x97, 0xa7, 0xad, 0xa2, 0x3e, - 0x9c, 0xba, 0xf6, 0xc1, 0x39, 0xb8, 0x8b, 0xca, 0xc2, 0xac, 0x56, 0xc6, - 0xe5, 0xbb, 0x65, 0x8e, 0x44, 0x4f, 0x4d, 0xce, 0x6f, 0xed, 0x09, 0x4a, - 0xd4, 0xaf, 0x4e, 0x10, 0x9c, 0x68, 0x8b, 0x2e, 0x95, 0x7b, 0x89, 0x9b, - 0x13, 0xca, 0xe2, 0x34, 0x34, 0xc1, 0xf3, 0x5b, 0xf3, 0x49, 0x7b, 0x62, - 0x83, 0x48, 0x81, 0x74, 0xd1, 0x88, 0x78, 0x6c, 0x02, 0x53, 0xf9, 0xbc, - 0x7f, 0x43, 0x26, 0x57, 0x58, 0x33, 0x83, 0x3b, 0x33, 0x0a, 0x17, 0xb0, - 0xd0, 0x4e, 0x91, 0x24, 0xad, 0x86, 0x7d, 0x64, 0x12, 0xdc, 0x74, 0x4a, - 0x34, 0xa1, 0x1d, 0x0a, 0xea, 0x96, 0x1d, 0x0b, 0x15, 0xfc, 0xa3, 0x4b, - 0x3b, 0xce, 0x63, 0x88, 0xd0, 0xf8, 0x2d, 0x0c, 0x94, 0x86, 0x10, 0xca, - 0xb6, 0x9a, 0x3d, 0xca, 0xeb, 0x37, 0x9c, 0x00, 0x48, 0x35, 0x86, 0x29, - 0x50, 0x78, 0xe8, 0x45, 0x63, 0xcd, 0x19, 0x41, 0x4f, 0xf5, 0x95, 0xec, - 0x7b, 0x98, 0xd4, 0xc4, 0x71, 0xb3, 0x50, 0xbe, 0x28, 0xb3, 0x8f, 0xa0, - 0xb9, 0x53, 0x9c, 0xf5, 0xca, 0x2c, 0x23, 0xa9, 0xfd, 0x14, 0x06, 0xe8, - 0x18, 0xb4, 0x9a, 0xe8, 0x3c, 0x6e, 0x81, 0xfd, 0xe4, 0xcd, 0x35, 0x36, - 0xb3, 0x51, 0xd3, 0x69, 0xec, 0x12, 0xba, 0x56, 0x6e, 0x6f, 0x9b, 0x57, - 0xc5, 0x8b, 0x14, 0xe7, 0x0e, 0xc7, 0x9c, 0xed, 0x4a, 0x54, 0x6a, 0xc9, - 0x4d, 0xc5, 0xbf, 0x11, 0xb1, 0xae, 0x1c, 0x67, 0x81, 0xcb, 0x44, 0x55, - 0x33, 0x99, 0x7f, 0x24, 0x9b, 0x3f, 0x53, 0x45, 0x7f, 0x86, 0x1a, 0xf3, - 0x3c, 0xfa, 0x6d, 0x7f, 0x81, 0xf5, 0xb8, 0x4a, 0xd3, 0xf5, 0x85, 0x37, - 0x1c, 0xb5, 0xa6, 0xd0, 0x09, 0xe4, 0x18, 0x7b, 0x38, 0x4e, 0xfa, 0x0f, - 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0xdf, 0x30, 0x82, 0x01, - 0xdb, 0x30, 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, - 0x01, 0x04, 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, - 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x12, 0x06, 0x03, 0x55, - 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, - 0x02, 0x01, 0x00, 0x30, 0x70, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x69, - 0x30, 0x67, 0x30, 0x65, 0x06, 0x0b, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, - 0x45, 0x01, 0x07, 0x17, 0x03, 0x30, 0x56, 0x30, 0x28, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, 0x74, - 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72, - 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, - 0x73, 0x30, 0x2a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, - 0x02, 0x30, 0x1e, 0x1a, 0x1c, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, - 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, - 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x70, 0x61, 0x30, 0x34, 0x06, - 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2d, 0x30, 0x2b, 0x30, 0x29, 0xa0, 0x27, - 0xa0, 0x25, 0x86, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, - 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, 0x61, 0x33, 0x2d, 0x67, 0x35, 0x2e, - 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, - 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x6d, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x0c, 0x04, 0x61, 0x30, 0x5f, 0xa1, - 0x5d, 0xa0, 0x5b, 0x30, 0x59, 0x30, 0x57, 0x30, 0x55, 0x16, 0x09, 0x69, - 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, 0x69, 0x66, 0x30, 0x21, 0x30, 0x1f, - 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14, 0x8f, - 0xe5, 0xd3, 0x1a, 0x86, 0xac, 0x8d, 0x8e, 0x6b, 0xc3, 0xcf, 0x80, 0x6a, - 0xd4, 0x48, 0x18, 0x2c, 0x7b, 0x19, 0x2e, 0x30, 0x25, 0x16, 0x23, 0x68, - 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x76, - 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x76, 0x73, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x67, 0x69, 0x66, 0x30, 0x28, - 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x21, 0x30, 0x1f, 0xa4, 0x1d, 0x30, - 0x1b, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x10, - 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x50, 0x4b, 0x49, - 0x2d, 0x32, 0x2d, 0x36, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, - 0x16, 0x04, 0x14, 0x0d, 0x44, 0x5c, 0x16, 0x53, 0x44, 0xc1, 0x82, 0x7e, - 0x1d, 0x20, 0xab, 0x25, 0xf4, 0x01, 0x63, 0xd8, 0xbe, 0x79, 0xa5, 0x30, - 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, - 0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0, 0x30, 0x09, 0xf3, - 0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, - 0x82, 0x01, 0x01, 0x00, 0x0c, 0x83, 0x24, 0xef, 0xdd, 0xc3, 0x0c, 0xd9, - 0x58, 0x9c, 0xfe, 0x36, 0xb6, 0xeb, 0x8a, 0x80, 0x4b, 0xd1, 0xa3, 0xf7, - 0x9d, 0xf3, 0xcc, 0x53, 0xef, 0x82, 0x9e, 0xa3, 0xa1, 0xe6, 0x97, 0xc1, - 0x58, 0x9d, 0x75, 0x6c, 0xe0, 0x1d, 0x1b, 0x4c, 0xfa, 0xd1, 0xc1, 0x2d, - 0x05, 0xc0, 0xea, 0x6e, 0xb2, 0x22, 0x70, 0x55, 0xd9, 0x20, 0x33, 0x40, - 0x33, 0x07, 0xc2, 0x65, 0x83, 0xfa, 0x8f, 0x43, 0x37, 0x9b, 0xea, 0x0e, - 0x9a, 0x6c, 0x70, 0xee, 0xf6, 0x9c, 0x80, 0x3b, 0xd9, 0x37, 0xf4, 0x7a, - 0x6d, 0xec, 0xd0, 0x18, 0x7d, 0x49, 0x4a, 0xca, 0x99, 0xc7, 0x19, 0x28, - 0xa2, 0xbe, 0xd8, 0x77, 0x24, 0xf7, 0x85, 0x26, 0x86, 0x6d, 0x87, 0x05, - 0x40, 0x41, 0x67, 0xd1, 0x27, 0x3a, 0xed, 0xdc, 0x48, 0x1d, 0x22, 0xcd, - 0x0b, 0x0b, 0x8b, 0xbc, 0xf4, 0xb1, 0x7b, 0xfd, 0xb4, 0x99, 0xa8, 0xe9, - 0x76, 0x2a, 0xe1, 0x1a, 0x2d, 0x87, 0x6e, 0x74, 0xd3, 0x88, 0xdd, 0x1e, - 0x22, 0xc6, 0xdf, 0x16, 0xb6, 0x2b, 0x82, 0x14, 0x0a, 0x94, 0x5c, 0xf2, - 0x50, 0xec, 0xaf, 0xce, 0xff, 0x62, 0x37, 0x0d, 0xad, 0x65, 0xd3, 0x06, - 0x41, 0x53, 0xed, 0x02, 0x14, 0xc8, 0xb5, 0x58, 0x28, 0xa1, 0xac, 0xe0, - 0x5b, 0xec, 0xb3, 0x7f, 0x95, 0x4a, 0xfb, 0x03, 0xc8, 0xad, 0x26, 0xdb, - 0xe6, 0x66, 0x78, 0x12, 0x4a, 0xd9, 0x9f, 0x42, 0xfb, 0xe1, 0x98, 0xe6, - 0x42, 0x83, 0x9b, 0x8f, 0x8f, 0x67, 0x24, 0xe8, 0x61, 0x19, 0xb5, 0xdd, - 0xcd, 0xb5, 0x0b, 0x26, 0x05, 0x8e, 0xc3, 0x6e, 0xc4, 0xc8, 0x75, 0xb8, - 0x46, 0xcf, 0xe2, 0x18, 0x06, 0x5e, 0xa9, 0xae, 0xa8, 0x81, 0x9a, 0x47, - 0x16, 0xde, 0x0c, 0x28, 0x6c, 0x25, 0x27, 0xb9, 0xde, 0xb7, 0x84, 0x58, - 0xc6, 0x1f, 0x38, 0x1e, 0xa4, 0xc4, 0xcb, 0x66, -} - -var certSet2Cert48 = []byte{ - 0x30, 0x82, 0x06, 0x1e, 0x30, 0x82, 0x05, 0x06, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x2c, 0x48, 0xdd, 0x93, 0x0d, 0xf5, 0x59, 0x8e, 0xf9, - 0x3c, 0x99, 0x54, 0x7a, 0x60, 0xed, 0x43, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81, - 0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, - 0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, - 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, - 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28, - 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69, - 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, - 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, - 0x31, 0x45, 0x30, 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56, - 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, - 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, - 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30, - 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x30, 0x38, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x36, 0x31, 0x31, 0x30, 0x37, - 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xbe, 0x31, 0x0b, - 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, - 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65, - 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56, - 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, - 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3b, 0x30, - 0x39, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x54, 0x65, 0x72, 0x6d, - 0x73, 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65, 0x20, 0x61, 0x74, 0x20, - 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, - 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x72, 0x70, 0x61, 0x20, 0x28, 0x63, 0x29, 0x30, 0x36, 0x31, 0x38, - 0x30, 0x36, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2f, 0x56, 0x65, 0x72, - 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, - 0x33, 0x20, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x20, 0x56, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x53, - 0x4c, 0x20, 0x53, 0x47, 0x43, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, - 0x02, 0x82, 0x01, 0x01, 0x00, 0xbd, 0x56, 0x88, 0xba, 0x88, 0x34, 0x64, - 0x64, 0xcf, 0xcd, 0xca, 0xb0, 0xee, 0xe7, 0x19, 0x73, 0xc5, 0x72, 0xd9, - 0xbb, 0x45, 0xbc, 0xb5, 0xa8, 0xff, 0x83, 0xbe, 0x1c, 0x03, 0xdb, 0xed, - 0x89, 0xb7, 0x2e, 0x10, 0x1a, 0x25, 0xbc, 0x55, 0xca, 0x41, 0xa1, 0x9f, - 0x0b, 0xcf, 0x19, 0x5e, 0x70, 0xb9, 0x5e, 0x39, 0x4b, 0x9e, 0x31, 0x1c, - 0x5f, 0x87, 0xae, 0x2a, 0xaa, 0xa8, 0x2b, 0xa2, 0x1b, 0x3b, 0x10, 0x23, - 0x5f, 0x13, 0xb1, 0xdd, 0x08, 0x8c, 0x4e, 0x14, 0xda, 0x83, 0x81, 0xe3, - 0xb5, 0x8c, 0xe3, 0x68, 0xed, 0x24, 0x67, 0xce, 0x56, 0xb6, 0xac, 0x9b, - 0x73, 0x96, 0x44, 0xdb, 0x8a, 0x8c, 0xb3, 0xd6, 0xf0, 0x71, 0x93, 0x8e, - 0xdb, 0x71, 0x54, 0x4a, 0xeb, 0x73, 0x59, 0x6a, 0x8f, 0x70, 0x51, 0x2c, - 0x03, 0x9f, 0x97, 0xd1, 0xcc, 0x11, 0x7a, 0xbc, 0x62, 0x0d, 0x95, 0x2a, - 0xc9, 0x1c, 0x75, 0x57, 0xe9, 0xf5, 0xc7, 0xea, 0xba, 0x84, 0x35, 0xcb, - 0xc7, 0x85, 0x5a, 0x7e, 0xe4, 0x4d, 0xe1, 0x11, 0x97, 0x7d, 0x0e, 0x20, - 0x34, 0x45, 0xdb, 0xf1, 0xa2, 0x09, 0xeb, 0xeb, 0x3d, 0x9e, 0xb8, 0x96, - 0x43, 0x5e, 0x34, 0x4b, 0x08, 0x25, 0x1e, 0x43, 0x1a, 0xa2, 0xd9, 0xb7, - 0x8a, 0x01, 0x34, 0x3d, 0xc3, 0xf8, 0xe5, 0xaf, 0x4f, 0x8c, 0xff, 0xcd, - 0x65, 0xf0, 0x23, 0x4e, 0xc5, 0x97, 0xb3, 0x5c, 0xda, 0x90, 0x1c, 0x82, - 0x85, 0x0d, 0x06, 0x0d, 0xc1, 0x22, 0xb6, 0x7b, 0x28, 0xa4, 0x03, 0xc3, - 0x4c, 0x53, 0xd1, 0x58, 0xbc, 0x72, 0xbc, 0x08, 0x39, 0xfc, 0xa0, 0x76, - 0xa8, 0xa8, 0xe9, 0x4b, 0x6e, 0x88, 0x3d, 0xe3, 0xb3, 0x31, 0x25, 0x8c, - 0x73, 0x29, 0x48, 0x0e, 0x32, 0x79, 0x06, 0xed, 0x3d, 0x43, 0xf4, 0xf6, - 0xe4, 0xe9, 0xfc, 0x7d, 0xbe, 0x8e, 0x08, 0xd5, 0x1f, 0x02, 0x03, 0x01, - 0x00, 0x01, 0xa3, 0x82, 0x02, 0x08, 0x30, 0x82, 0x02, 0x04, 0x30, 0x1d, - 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x4e, 0x43, 0xc8, - 0x1d, 0x76, 0xef, 0x37, 0x53, 0x7a, 0x4f, 0xf2, 0x58, 0x6f, 0x94, 0xf3, - 0x38, 0xe2, 0xd5, 0xbd, 0xdf, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, - 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, - 0x00, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, 0x30, 0x34, - 0x30, 0x32, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, 0x30, 0x28, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, - 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, - 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x63, 0x70, 0x73, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, - 0x36, 0x30, 0x34, 0x30, 0x32, 0xa0, 0x30, 0xa0, 0x2e, 0x86, 0x2c, 0x68, - 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x45, 0x56, 0x53, 0x65, 0x63, 0x75, - 0x72, 0x65, 0x2d, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, - 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, 0x61, 0x33, - 0x2d, 0x67, 0x35, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, - 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, - 0x11, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x01, - 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x6d, 0x06, 0x08, 0x2b, 0x06, - 0x01, 0x05, 0x05, 0x07, 0x01, 0x0c, 0x04, 0x61, 0x30, 0x5f, 0xa1, 0x5d, - 0xa0, 0x5b, 0x30, 0x59, 0x30, 0x57, 0x30, 0x55, 0x16, 0x09, 0x69, 0x6d, - 0x61, 0x67, 0x65, 0x2f, 0x67, 0x69, 0x66, 0x30, 0x21, 0x30, 0x1f, 0x30, - 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14, 0x8f, 0xe5, - 0xd3, 0x1a, 0x86, 0xac, 0x8d, 0x8e, 0x6b, 0xc3, 0xcf, 0x80, 0x6a, 0xd4, - 0x48, 0x18, 0x2c, 0x7b, 0x19, 0x2e, 0x30, 0x25, 0x16, 0x23, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x76, 0x65, - 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, - 0x73, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x67, 0x69, 0x66, 0x30, 0x29, 0x06, - 0x03, 0x55, 0x1d, 0x11, 0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, - 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x43, - 0x6c, 0x61, 0x73, 0x73, 0x33, 0x43, 0x41, 0x32, 0x30, 0x34, 0x38, 0x2d, - 0x31, 0x2d, 0x34, 0x38, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, - 0x18, 0x30, 0x16, 0x80, 0x14, 0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, - 0xbb, 0xf0, 0x30, 0x09, 0xf3, 0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, - 0x33, 0x30, 0x3d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, - 0x01, 0x04, 0x31, 0x30, 0x2f, 0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x45, 0x56, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2d, 0x6f, - 0x63, 0x73, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, - 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x34, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, - 0x2d, 0x30, 0x2b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, - 0x04, 0x01, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, - 0x08, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, - 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x27, 0x74, 0xa6, 0x34, 0xea, 0x1d, - 0x9d, 0xe1, 0x53, 0xd6, 0x1c, 0x9d, 0x0c, 0xa7, 0x5b, 0x4c, 0xa9, 0x67, - 0xf2, 0xf0, 0x32, 0xb7, 0x01, 0x0f, 0xfb, 0x42, 0x18, 0x38, 0xde, 0xe4, - 0xee, 0x49, 0xc8, 0x13, 0xc9, 0x0b, 0xec, 0x04, 0xc3, 0x40, 0x71, 0x18, - 0x72, 0x76, 0x43, 0x02, 0x23, 0x5d, 0xab, 0x7b, 0xc8, 0x48, 0x14, 0x1a, - 0xc8, 0x7b, 0x1d, 0xfc, 0xf6, 0x0a, 0x9f, 0x36, 0xa1, 0xd2, 0x09, 0x73, - 0x71, 0x66, 0x96, 0x75, 0x51, 0x34, 0xbf, 0x99, 0x30, 0x51, 0x67, 0x9d, - 0x54, 0xb7, 0x26, 0x45, 0xac, 0x73, 0x08, 0x23, 0x86, 0x26, 0x99, 0x71, - 0xf4, 0x8e, 0xd7, 0xea, 0x39, 0x9b, 0x06, 0x09, 0x23, 0xbf, 0x62, 0xdd, - 0xa8, 0xc4, 0xb6, 0x7d, 0xa4, 0x89, 0x07, 0x3e, 0xf3, 0x6d, 0xae, 0x40, - 0x59, 0x50, 0x79, 0x97, 0x37, 0x3d, 0x32, 0x78, 0x7d, 0xb2, 0x63, 0x4b, - 0xf9, 0xea, 0x08, 0x69, 0x0e, 0x13, 0xed, 0xe8, 0xcf, 0xbb, 0xac, 0x05, - 0x86, 0xca, 0x22, 0xcf, 0x88, 0x62, 0x5d, 0x3c, 0x22, 0x49, 0xd8, 0x63, - 0xd5, 0x24, 0xa6, 0xbd, 0xef, 0x5c, 0xe3, 0xcc, 0x20, 0x3b, 0x22, 0xea, - 0xfc, 0x44, 0xc6, 0xa8, 0xe5, 0x1f, 0xe1, 0x86, 0xcd, 0x0c, 0x4d, 0x8f, - 0x93, 0x53, 0xd9, 0x7f, 0xee, 0xa1, 0x08, 0xa7, 0xb3, 0x30, 0x96, 0x49, - 0x70, 0x6e, 0xa3, 0x6c, 0x3d, 0xd0, 0x63, 0xef, 0x25, 0x66, 0x63, 0xcc, - 0xaa, 0xb7, 0x18, 0x17, 0x4e, 0xea, 0x70, 0x76, 0xf6, 0xba, 0x42, 0xa6, - 0x80, 0x37, 0x09, 0x4e, 0x9f, 0x66, 0x88, 0x2e, 0x6b, 0x33, 0x66, 0xc8, - 0xc0, 0x71, 0xa4, 0x41, 0xeb, 0x5a, 0xe3, 0xfc, 0x14, 0x2e, 0x4b, 0x88, - 0xfd, 0xae, 0x6e, 0x5b, 0x65, 0xe9, 0x27, 0xe4, 0xbf, 0xe4, 0xb0, 0x23, - 0xc1, 0xb2, 0x7d, 0x5b, 0x62, 0x25, 0xd7, 0x3e, 0x10, 0xd4, -} - -var certSet2Cert49 = []byte{ - 0x30, 0x82, 0x06, 0x29, 0x30, 0x82, 0x05, 0x11, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x64, 0x1b, 0xe8, 0x20, 0xce, 0x02, 0x08, 0x13, 0xf3, - 0x2d, 0x4d, 0x2d, 0x95, 0xd6, 0x7e, 0x67, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81, - 0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, - 0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, - 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, - 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28, - 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69, - 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, - 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, - 0x31, 0x45, 0x30, 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56, - 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, - 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, - 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30, - 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x32, 0x30, 0x38, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30, 0x32, 0x30, 0x37, - 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xbc, 0x31, 0x0b, - 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, - 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65, - 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56, - 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, - 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3b, 0x30, - 0x39, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x54, 0x65, 0x72, 0x6d, - 0x73, 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65, 0x20, 0x61, 0x74, 0x20, - 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, - 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x72, 0x70, 0x61, 0x20, 0x28, 0x63, 0x29, 0x31, 0x30, 0x31, 0x36, - 0x30, 0x34, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2d, 0x56, 0x65, 0x72, - 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, - 0x33, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, - 0x41, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, - 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, - 0x01, 0x01, 0x00, 0x99, 0xd6, 0x9c, 0x62, 0xf0, 0x15, 0xf4, 0x81, 0x9a, - 0x41, 0x08, 0x59, 0x8f, 0x13, 0x9d, 0x17, 0xc9, 0x9f, 0x51, 0xdc, 0xda, - 0xb1, 0x52, 0xef, 0xff, 0xe3, 0x41, 0xdd, 0xe0, 0xdf, 0xc4, 0x28, 0xc6, - 0xe3, 0xad, 0x79, 0x1f, 0x27, 0x10, 0x98, 0xb8, 0xbb, 0x20, 0x97, 0xc1, - 0x28, 0x44, 0x41, 0x0f, 0xea, 0xa9, 0xa8, 0x52, 0xcf, 0x4d, 0x4e, 0x1b, - 0x8b, 0xbb, 0xb5, 0xc4, 0x76, 0xd9, 0xcc, 0x56, 0x06, 0xee, 0xb3, 0x55, - 0x20, 0x2a, 0xde, 0x15, 0x8d, 0x71, 0xcb, 0x54, 0xc8, 0x6f, 0x17, 0xcd, - 0x89, 0x00, 0xe4, 0xdc, 0xff, 0xe1, 0xc0, 0x1f, 0x68, 0x71, 0xe9, 0xc7, - 0x29, 0x2e, 0x7e, 0xbc, 0x3b, 0xfc, 0xe5, 0xbb, 0xab, 0x26, 0x54, 0x8b, - 0x66, 0x90, 0xcd, 0xf6, 0x92, 0xb9, 0x31, 0x24, 0x80, 0xbc, 0x9e, 0x6c, - 0xd5, 0xfc, 0x7e, 0xd2, 0xe1, 0x4b, 0x8c, 0xdc, 0x42, 0xfa, 0x44, 0x4b, - 0x5f, 0xf8, 0x18, 0xb5, 0x2e, 0x30, 0xf4, 0x3d, 0x12, 0x98, 0xd3, 0x62, - 0x05, 0x73, 0x54, 0xa6, 0x9c, 0xa2, 0x1d, 0xbe, 0x52, 0x83, 0x3a, 0x07, - 0x46, 0xc4, 0x3b, 0x02, 0x56, 0x21, 0xbf, 0xf2, 0x51, 0x4f, 0xd0, 0xa6, - 0x99, 0x39, 0xe9, 0xae, 0xa5, 0x3f, 0x89, 0x9b, 0x9c, 0x7d, 0xfe, 0x4d, - 0x60, 0x07, 0x25, 0x20, 0xf7, 0xbb, 0xd7, 0x69, 0x83, 0x2b, 0x82, 0x93, - 0x43, 0x37, 0xd9, 0x83, 0x41, 0x1b, 0x6b, 0x0b, 0xab, 0x4a, 0x66, 0x84, - 0x4f, 0x4a, 0x8e, 0xde, 0x7e, 0x34, 0x99, 0x8e, 0x68, 0xd6, 0xca, 0x39, - 0x06, 0x9b, 0x4c, 0xb3, 0x9a, 0x48, 0x4d, 0x13, 0x46, 0xb4, 0x58, 0x21, - 0x04, 0xc4, 0xfb, 0xa0, 0x4d, 0xac, 0x2e, 0x4b, 0x62, 0x12, 0xe3, 0xfb, - 0x4d, 0xf6, 0xc9, 0x51, 0x00, 0x01, 0x1f, 0xfc, 0x1e, 0x6a, 0x81, 0x2a, - 0x38, 0xe0, 0xb9, 0x4f, 0xd6, 0x2d, 0x45, 0x02, 0x03, 0x01, 0x00, 0x01, - 0xa3, 0x82, 0x02, 0x15, 0x30, 0x82, 0x02, 0x11, 0x30, 0x12, 0x06, 0x03, - 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, - 0xff, 0x02, 0x01, 0x00, 0x30, 0x70, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, - 0x69, 0x30, 0x67, 0x30, 0x65, 0x06, 0x0b, 0x60, 0x86, 0x48, 0x01, 0x86, - 0xf8, 0x45, 0x01, 0x07, 0x17, 0x03, 0x30, 0x56, 0x30, 0x28, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, - 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, - 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, - 0x70, 0x73, 0x30, 0x2a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x02, 0x02, 0x30, 0x1e, 0x1a, 0x1c, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, - 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, - 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x70, 0x61, 0x30, 0x0e, - 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, - 0x01, 0x06, 0x30, 0x6d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x01, 0x0c, 0x04, 0x61, 0x30, 0x5f, 0xa1, 0x5d, 0xa0, 0x5b, 0x30, 0x59, - 0x30, 0x57, 0x30, 0x55, 0x16, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, - 0x67, 0x69, 0x66, 0x30, 0x21, 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, - 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14, 0x8f, 0xe5, 0xd3, 0x1a, 0x86, 0xac, - 0x8d, 0x8e, 0x6b, 0xc3, 0xcf, 0x80, 0x6a, 0xd4, 0x48, 0x18, 0x2c, 0x7b, - 0x19, 0x2e, 0x30, 0x25, 0x16, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, - 0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, - 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x73, 0x6c, 0x6f, 0x67, - 0x6f, 0x2e, 0x67, 0x69, 0x66, 0x30, 0x34, 0x06, 0x03, 0x55, 0x1d, 0x25, - 0x04, 0x2d, 0x30, 0x2b, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, - 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01, 0x06, - 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x08, 0x01, 0x30, - 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, - 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, - 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x34, 0x06, 0x03, 0x55, 0x1d, 0x1f, - 0x04, 0x2d, 0x30, 0x2b, 0x30, 0x29, 0xa0, 0x27, 0xa0, 0x25, 0x86, 0x23, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x76, - 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x70, 0x63, 0x61, 0x33, 0x2d, 0x67, 0x35, 0x2e, 0x63, 0x72, 0x6c, 0x30, - 0x28, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x21, 0x30, 0x1f, 0xa4, 0x1d, - 0x30, 0x1b, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, - 0x10, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x50, 0x4b, - 0x49, 0x2d, 0x32, 0x2d, 0x37, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, - 0x04, 0x16, 0x04, 0x14, 0xd7, 0x9b, 0x7c, 0xd8, 0x22, 0xa0, 0x15, 0xf7, - 0xdd, 0xad, 0x5f, 0xce, 0x29, 0x9b, 0x58, 0xc3, 0xbc, 0x46, 0x00, 0xb5, - 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, - 0x14, 0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0, 0x30, 0x09, - 0xf3, 0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30, 0x0d, 0x06, - 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, - 0x03, 0x82, 0x01, 0x01, 0x00, 0x71, 0xb5, 0x7d, 0x73, 0x52, 0x4a, 0xdd, - 0xd7, 0x4d, 0x34, 0x2b, 0x2e, 0xaf, 0x94, 0x46, 0xa5, 0x49, 0x50, 0x02, - 0x4f, 0xf8, 0x2f, 0x17, 0x70, 0xf2, 0x13, 0xdc, 0x1f, 0x21, 0x86, 0xaa, - 0xc2, 0x4f, 0x7c, 0x37, 0x3c, 0xd4, 0x46, 0x78, 0xae, 0x5d, 0x78, 0x6f, - 0xd1, 0xba, 0x5a, 0xbc, 0x10, 0xab, 0x58, 0x36, 0xc5, 0x8c, 0x62, 0x15, - 0x45, 0x60, 0x17, 0x21, 0xe2, 0xd5, 0x42, 0xa8, 0x77, 0xa1, 0x55, 0xd8, - 0x43, 0x04, 0x51, 0xf6, 0x6e, 0xba, 0x48, 0xe6, 0x5d, 0x4c, 0xb7, 0x44, - 0xd3, 0x3e, 0xa4, 0xd5, 0xd6, 0x33, 0x9a, 0x9f, 0x0d, 0xe6, 0xd7, 0x4e, - 0x96, 0x44, 0x95, 0x5a, 0x6c, 0xd6, 0xa3, 0x16, 0x53, 0x0e, 0x98, 0x43, - 0xce, 0xa4, 0xb8, 0xc3, 0x66, 0x7a, 0x05, 0x5c, 0x62, 0x10, 0xe8, 0x1b, - 0x12, 0xdb, 0x7d, 0x2e, 0x76, 0x50, 0xff, 0xdf, 0xd7, 0x6b, 0x1b, 0xcc, - 0x8a, 0xcc, 0x71, 0xfa, 0xb3, 0x40, 0x56, 0x7c, 0x33, 0x7a, 0x77, 0x94, - 0x5b, 0xf5, 0x0b, 0x53, 0xfb, 0x0e, 0x5f, 0xbc, 0x68, 0xfb, 0xaf, 0x2a, - 0xee, 0x30, 0x37, 0x79, 0x16, 0x93, 0x25, 0x7f, 0x4d, 0x10, 0xff, 0x57, - 0xfb, 0xbf, 0x6e, 0x3b, 0x33, 0x21, 0xde, 0x79, 0xdc, 0x86, 0x17, 0x59, - 0x2d, 0x43, 0x64, 0xb7, 0xa6, 0x66, 0x87, 0xea, 0xbc, 0x96, 0x46, 0x19, - 0x1a, 0x86, 0x8b, 0x6f, 0xd7, 0xb7, 0x49, 0x00, 0x5b, 0xdb, 0xa3, 0xbf, - 0x29, 0x9a, 0xee, 0xf7, 0xd3, 0x33, 0xae, 0xa3, 0xf4, 0x9e, 0x4c, 0xca, - 0x5e, 0x69, 0xd4, 0x1b, 0xad, 0xb7, 0x90, 0x77, 0x6a, 0xd8, 0x59, 0x6f, - 0x79, 0xab, 0x01, 0xfa, 0x55, 0xf0, 0x8a, 0x21, 0x66, 0xe5, 0x65, 0x6e, - 0xfd, 0x7c, 0xd3, 0xdf, 0x1e, 0xeb, 0x7e, 0x3f, 0x06, 0x90, 0xfb, 0x19, - 0x0b, 0xd3, 0x06, 0x02, 0x1b, 0x78, 0x43, 0x99, 0xa8, -} - -var certSet2Cert50 = []byte{ - 0x30, 0x82, 0x06, 0x34, 0x30, 0x82, 0x04, 0x1c, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x01, 0x1a, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x7d, 0x31, 0x0b, 0x30, - 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, 0x4c, 0x31, 0x16, - 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x53, 0x74, 0x61, - 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x4c, 0x74, 0x64, 0x2e, 0x31, 0x2b, - 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x22, 0x53, 0x65, 0x63, - 0x75, 0x72, 0x65, 0x20, 0x44, 0x69, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x20, - 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, - 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x31, 0x29, 0x30, 0x27, 0x06, - 0x03, 0x55, 0x04, 0x03, 0x13, 0x20, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, - 0x6f, 0x6d, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x37, 0x31, 0x30, 0x32, 0x34, - 0x32, 0x30, 0x35, 0x37, 0x30, 0x39, 0x5a, 0x17, 0x0d, 0x31, 0x37, 0x31, - 0x30, 0x32, 0x34, 0x32, 0x30, 0x35, 0x37, 0x30, 0x39, 0x5a, 0x30, 0x81, - 0x8c, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x49, 0x4c, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x4c, 0x74, - 0x64, 0x2e, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, - 0x22, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x44, 0x69, 0x67, 0x69, - 0x74, 0x61, 0x6c, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x65, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x31, - 0x38, 0x30, 0x36, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2f, 0x53, 0x74, - 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, - 0x20, 0x32, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x49, - 0x6e, 0x74, 0x65, 0x72, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x20, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, - 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, - 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xe2, 0x4f, 0x39, 0x2f, 0xa1, 0x8c, - 0x9a, 0x85, 0xad, 0x08, 0x0e, 0x08, 0x3e, 0x57, 0xf2, 0x88, 0x01, 0x21, - 0x1b, 0x94, 0xa9, 0x6c, 0xe2, 0xb8, 0xdb, 0xaa, 0x19, 0x18, 0x46, 0x3a, - 0x52, 0xa1, 0xf5, 0x0f, 0xf4, 0x6e, 0x8c, 0xea, 0x96, 0x8c, 0x96, 0x87, - 0x79, 0x13, 0x40, 0x51, 0x2f, 0x22, 0xf2, 0x0c, 0x8b, 0x87, 0x0f, 0x65, - 0xdf, 0x71, 0x74, 0x34, 0x43, 0x55, 0xb1, 0x35, 0x09, 0x9b, 0xd9, 0xbc, - 0x1f, 0xfa, 0xeb, 0x42, 0xd0, 0x97, 0x40, 0x72, 0xb7, 0x43, 0x96, 0x3d, - 0xba, 0x96, 0x9d, 0x5d, 0x50, 0x02, 0x1c, 0x9b, 0x91, 0x8d, 0x9c, 0xc0, - 0xac, 0xd7, 0xbb, 0x2f, 0x17, 0xd7, 0xcb, 0x3e, 0x82, 0x9d, 0x73, 0xeb, - 0x07, 0x42, 0x92, 0xb2, 0xcd, 0x64, 0xb3, 0x74, 0x55, 0x1b, 0xb4, 0x4b, - 0x86, 0x21, 0x2c, 0xf7, 0x78, 0x87, 0x32, 0xe0, 0x16, 0xe4, 0xda, 0xbd, - 0x4c, 0x95, 0xea, 0xa4, 0x0a, 0x7e, 0xb6, 0x0a, 0x0d, 0x2e, 0x8a, 0xcf, - 0x55, 0xab, 0xc3, 0xe5, 0xdd, 0x41, 0x8a, 0x4e, 0xe6, 0x6f, 0x65, 0x6c, - 0xb2, 0x40, 0xcf, 0x17, 0x5d, 0xb9, 0xc3, 0x6a, 0x0b, 0x27, 0x11, 0x84, - 0x77, 0x61, 0xf6, 0xc2, 0x7c, 0xed, 0xc0, 0x8d, 0x78, 0x14, 0x18, 0x99, - 0x81, 0x99, 0x75, 0x63, 0xb7, 0xe8, 0x53, 0xd3, 0xba, 0x61, 0xe9, 0x0e, - 0xfa, 0xa2, 0x30, 0xf3, 0x46, 0xa2, 0xb9, 0xc9, 0x1f, 0x6c, 0x80, 0x5a, - 0x40, 0xac, 0x27, 0xed, 0x48, 0x47, 0x33, 0xb0, 0x54, 0xc6, 0x46, 0x1a, - 0xf3, 0x35, 0x61, 0xc1, 0x02, 0x29, 0x90, 0x54, 0x7e, 0x64, 0x4d, 0xc4, - 0x30, 0x52, 0x02, 0x82, 0xd7, 0xdf, 0xce, 0x21, 0x6e, 0x18, 0x91, 0xd7, - 0xb8, 0xab, 0x8c, 0x27, 0x17, 0xb5, 0xf0, 0xa3, 0x01, 0x2f, 0x8e, 0xd2, - 0x2e, 0x87, 0x3a, 0x3d, 0xb4, 0x29, 0x67, 0x8a, 0xc4, 0x03, 0x02, 0x03, - 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0xad, 0x30, 0x82, 0x01, 0xa9, 0x30, - 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, - 0x03, 0x01, 0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, - 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03, - 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x11, 0xdb, 0x23, 0x45, 0xfd, - 0x54, 0xcc, 0x6a, 0x71, 0x6f, 0x84, 0x8a, 0x03, 0xd7, 0xbe, 0xf7, 0x01, - 0x2f, 0x26, 0x86, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, - 0x30, 0x16, 0x80, 0x14, 0x4e, 0x0b, 0xef, 0x1a, 0xa4, 0x40, 0x5b, 0xa5, - 0x17, 0x69, 0x87, 0x30, 0xca, 0x34, 0x68, 0x43, 0xd0, 0x41, 0xae, 0xf2, - 0x30, 0x66, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, - 0x04, 0x5a, 0x30, 0x58, 0x30, 0x27, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x30, 0x01, 0x86, 0x1b, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, - 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x73, - 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x61, 0x30, 0x2d, 0x06, - 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x21, 0x68, - 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73, 0x74, - 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, - 0x66, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x5b, 0x06, 0x03, - 0x55, 0x1d, 0x1f, 0x04, 0x54, 0x30, 0x52, 0x30, 0x27, 0xa0, 0x25, 0xa0, - 0x23, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, - 0x77, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x73, 0x66, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x6c, - 0x30, 0x27, 0xa0, 0x25, 0xa0, 0x23, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, - 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x74, - 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x66, 0x73, 0x63, - 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0x80, 0x06, 0x03, 0x55, 0x1d, - 0x20, 0x04, 0x79, 0x30, 0x77, 0x30, 0x75, 0x06, 0x0b, 0x2b, 0x06, 0x01, - 0x04, 0x01, 0x81, 0xb5, 0x37, 0x01, 0x02, 0x01, 0x30, 0x66, 0x30, 0x2e, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x22, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x70, 0x64, 0x66, 0x30, 0x34, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x28, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, - 0x2e, 0x70, 0x64, 0x66, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x02, 0x01, 0x00, - 0x9d, 0x07, 0xe1, 0xee, 0x90, 0x76, 0x31, 0x67, 0x16, 0x45, 0x70, 0x8c, - 0xcb, 0x84, 0x8b, 0x4b, 0x57, 0x68, 0x44, 0xa5, 0x89, 0xc1, 0xf2, 0x7e, - 0xcb, 0x28, 0x8b, 0xf5, 0xe7, 0x70, 0x77, 0xd5, 0xb6, 0xf4, 0x0b, 0x21, - 0x60, 0xa5, 0xa1, 0x74, 0x73, 0x24, 0x22, 0x80, 0xd6, 0xd8, 0xba, 0x8d, - 0xa2, 0x62, 0x5d, 0x09, 0x35, 0x42, 0x29, 0xfb, 0x39, 0x63, 0x45, 0x0b, - 0xa4, 0xb0, 0x38, 0x1a, 0x68, 0xf4, 0x95, 0x13, 0xcc, 0xe0, 0x43, 0x94, - 0xec, 0xeb, 0x39, 0x1a, 0xec, 0x57, 0x29, 0xd9, 0x99, 0x6d, 0xf5, 0x84, - 0xcd, 0x8e, 0x73, 0xae, 0xc9, 0xdc, 0x6a, 0xfa, 0x9e, 0x9d, 0x16, 0x64, - 0x93, 0x08, 0xc7, 0x1c, 0xc2, 0x89, 0x54, 0x9e, 0x77, 0x80, 0x90, 0xf6, - 0xb9, 0x29, 0x76, 0xeb, 0x13, 0x67, 0x48, 0x59, 0xf8, 0x2e, 0x3a, 0x31, - 0xb8, 0xc9, 0xd3, 0x88, 0xe5, 0x5f, 0x4e, 0xd2, 0x19, 0x3d, 0x43, 0x8e, - 0xd7, 0x92, 0xff, 0xcf, 0x38, 0xb6, 0xe1, 0x5b, 0x8a, 0x53, 0x1d, 0xce, - 0xac, 0xb4, 0x76, 0x2f, 0xd8, 0xf7, 0x40, 0x63, 0xd5, 0xee, 0x69, 0xf3, - 0x45, 0x7d, 0xa0, 0x62, 0xc1, 0x61, 0xc3, 0x75, 0xed, 0xb2, 0x7b, 0x4d, - 0xac, 0x21, 0x27, 0x30, 0x4e, 0x59, 0x46, 0x6a, 0x93, 0x17, 0xca, 0xc8, - 0x39, 0x2d, 0x01, 0x73, 0x65, 0x5b, 0xe9, 0x41, 0x9b, 0x11, 0x17, 0x9c, - 0xc8, 0xc8, 0x4a, 0xef, 0xa1, 0x76, 0x60, 0x2d, 0xae, 0x93, 0xff, 0x0c, - 0xd5, 0x33, 0x13, 0x9f, 0x4f, 0x13, 0xce, 0xdd, 0x86, 0xf1, 0xfc, 0xf8, - 0x35, 0x54, 0x15, 0xa8, 0x5b, 0xe7, 0x85, 0x7e, 0xfa, 0x37, 0x09, 0xff, - 0x8b, 0xb8, 0x31, 0x49, 0x9e, 0x0d, 0x6e, 0xde, 0xb4, 0xd2, 0x12, 0x2d, - 0xb8, 0xed, 0xc8, 0xc3, 0xf1, 0xb6, 0x42, 0xa0, 0x4c, 0x97, 0x79, 0xdf, - 0xfe, 0xc3, 0xa3, 0x9f, 0xa1, 0xf4, 0x6d, 0x2c, 0x84, 0x77, 0xa4, 0xa2, - 0x05, 0xe1, 0x17, 0xff, 0x31, 0xdd, 0x9a, 0xf3, 0xb8, 0x7a, 0xc3, 0x52, - 0xc2, 0x11, 0x11, 0xb7, 0x50, 0x31, 0x8a, 0x7f, 0xcc, 0xe7, 0x5a, 0x89, - 0xcc, 0xf7, 0x86, 0x9a, 0x61, 0x92, 0x4f, 0x2f, 0x94, 0xb6, 0x98, 0xc7, - 0x78, 0xe0, 0x62, 0x4b, 0x43, 0x7d, 0x3c, 0xde, 0xd6, 0x9a, 0xb4, 0x10, - 0xa1, 0x40, 0x9c, 0x4b, 0x2a, 0xdc, 0xb8, 0xd0, 0xd4, 0x9e, 0xfd, 0xf1, - 0x84, 0x78, 0x1b, 0x0e, 0x57, 0x8f, 0x69, 0x54, 0x42, 0x68, 0x7b, 0xea, - 0xa0, 0xef, 0x75, 0x0f, 0x07, 0xa2, 0x8c, 0x73, 0x99, 0xab, 0x55, 0xf5, - 0x07, 0x09, 0xd2, 0xaf, 0x38, 0x03, 0x6a, 0x90, 0x03, 0x0c, 0x2f, 0x8f, - 0xe2, 0xe8, 0x43, 0xc2, 0x31, 0xe9, 0x6f, 0xad, 0x87, 0xe5, 0x8d, 0xbd, - 0x4e, 0x2c, 0x89, 0x4b, 0x51, 0xe6, 0x9c, 0x4c, 0x54, 0x76, 0xc0, 0x12, - 0x81, 0x53, 0x9b, 0xec, 0xa0, 0xfc, 0x2c, 0x9c, 0xda, 0x18, 0x95, 0x6e, - 0x1e, 0x38, 0x26, 0x42, 0x27, 0x78, 0x60, 0x08, 0xdf, 0x7f, 0x6d, 0x32, - 0xe8, 0xd8, 0xc0, 0x6f, 0x1f, 0xeb, 0x26, 0x75, 0x9f, 0x93, 0xfc, 0x7b, - 0x1b, 0xfe, 0x35, 0x90, 0xdc, 0x53, 0xa3, 0x07, 0xa6, 0x3f, 0x83, 0x55, - 0x0a, 0x2b, 0x4e, 0x62, 0x82, 0x25, 0xce, 0x66, 0x30, 0x5d, 0x2c, 0xe0, - 0xf9, 0x19, 0x1b, 0x75, 0xb9, 0x9d, 0x98, 0x56, 0xa6, 0x83, 0x27, 0x7a, - 0xd1, 0x8f, 0x8d, 0x59, 0x93, 0xfc, 0x3f, 0x73, 0xd7, 0x2e, 0xb4, 0x2c, - 0x95, 0xd8, 0x8b, 0xf7, 0xc9, 0x7e, 0xc7, 0xfc, 0x9d, 0xac, 0x72, 0x04, - 0x1f, 0xd2, 0xcc, 0x17, 0xf4, 0xed, 0x34, 0x60, 0x9b, 0x9e, 0x4a, 0x97, - 0x04, 0xfe, 0xdd, 0x72, 0x0e, 0x57, 0x54, 0x51, 0x06, 0x70, 0x4d, 0xef, - 0xaa, 0x1c, 0xa4, 0x82, 0xe0, 0x33, 0xc7, 0xf4, -} - -var certSet2Cert51 = []byte{ - 0x30, 0x82, 0x06, 0x58, 0x30, 0x82, 0x05, 0x40, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x0a, 0x5f, 0x11, 0x4d, 0x03, 0x5b, 0x17, 0x91, 0x17, - 0xd2, 0xef, 0xd4, 0x03, 0x8c, 0x3f, 0x3b, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6c, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, - 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, - 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, - 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, - 0x63, 0x6f, 0x6d, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03, - 0x13, 0x22, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, - 0x69, 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, - 0x65, 0x20, 0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, - 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x38, 0x30, 0x34, 0x30, 0x32, 0x31, 0x32, - 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x34, 0x30, - 0x33, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x66, 0x31, 0x0b, - 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, - 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69, - 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x19, - 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, 0x77, 0x77, - 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, - 0x6d, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1c, - 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, 0x69, 0x67, - 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x20, - 0x43, 0x41, 0x2d, 0x33, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, - 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, - 0x00, 0xbf, 0x61, 0x0a, 0x29, 0x10, 0x1f, 0x5e, 0xfe, 0x34, 0x37, 0x51, - 0x08, 0xf8, 0x1e, 0xfb, 0x22, 0xed, 0x61, 0xbe, 0x0b, 0x0d, 0x70, 0x4c, - 0x50, 0x63, 0x26, 0x75, 0x15, 0xb9, 0x41, 0x88, 0x97, 0xb6, 0xf0, 0xa0, - 0x15, 0xbb, 0x08, 0x60, 0xe0, 0x42, 0xe8, 0x05, 0x29, 0x10, 0x87, 0x36, - 0x8a, 0x28, 0x65, 0xa8, 0xef, 0x31, 0x07, 0x74, 0x6d, 0x36, 0x97, 0x2f, - 0x28, 0x46, 0x66, 0x04, 0xc7, 0x2a, 0x79, 0x26, 0x7a, 0x99, 0xd5, 0x8e, - 0xc3, 0x6d, 0x4f, 0xa0, 0x5e, 0xad, 0xbc, 0x3d, 0x91, 0xc2, 0x59, 0x7b, - 0x5e, 0x36, 0x6c, 0xc0, 0x53, 0xcf, 0x00, 0x08, 0x32, 0x3e, 0x10, 0x64, - 0x58, 0x10, 0x13, 0x69, 0xc7, 0x0c, 0xee, 0x9c, 0x42, 0x51, 0x00, 0xf9, - 0x05, 0x44, 0xee, 0x24, 0xce, 0x7a, 0x1f, 0xed, 0x8c, 0x11, 0xbd, 0x12, - 0xa8, 0xf3, 0x15, 0xf4, 0x1c, 0x7a, 0x31, 0x69, 0x01, 0x1b, 0xa7, 0xe6, - 0x5d, 0xc0, 0x9a, 0x6c, 0x7e, 0x09, 0x9e, 0xe7, 0x52, 0x44, 0x4a, 0x10, - 0x3a, 0x23, 0xe4, 0x9b, 0xb6, 0x03, 0xaf, 0xa8, 0x9c, 0xb4, 0x5b, 0x9f, - 0xd4, 0x4b, 0xad, 0x92, 0x8c, 0xce, 0xb5, 0x11, 0x2a, 0xaa, 0x37, 0x18, - 0x8d, 0xb4, 0xc2, 0xb8, 0xd8, 0x5c, 0x06, 0x8c, 0xf8, 0xff, 0x23, 0xbd, - 0x35, 0x5e, 0xd4, 0x7c, 0x3e, 0x7e, 0x83, 0x0e, 0x91, 0x96, 0x05, 0x98, - 0xc3, 0xb2, 0x1f, 0xe3, 0xc8, 0x65, 0xeb, 0xa9, 0x7b, 0x5d, 0xa0, 0x2c, - 0xcc, 0xfc, 0x3c, 0xd9, 0x6d, 0xed, 0xcc, 0xfa, 0x4b, 0x43, 0x8c, 0xc9, - 0xd4, 0xb8, 0xa5, 0x61, 0x1c, 0xb2, 0x40, 0xb6, 0x28, 0x12, 0xdf, 0xb9, - 0xf8, 0x5f, 0xfe, 0xd3, 0xb2, 0xc9, 0xef, 0x3d, 0xb4, 0x1e, 0x4b, 0x7c, - 0x1c, 0x4c, 0x99, 0x36, 0x9e, 0x3d, 0xeb, 0xec, 0xa7, 0x68, 0x5e, 0x1d, - 0xdf, 0x67, 0x6e, 0x5e, 0xfb, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, - 0x02, 0xfa, 0x30, 0x82, 0x02, 0xf6, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, - 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x82, - 0x01, 0xc6, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x82, 0x01, 0xbd, 0x30, - 0x82, 0x01, 0xb9, 0x30, 0x82, 0x01, 0xb5, 0x06, 0x0b, 0x60, 0x86, 0x48, - 0x01, 0x86, 0xfd, 0x6c, 0x01, 0x03, 0x00, 0x02, 0x30, 0x82, 0x01, 0xa4, - 0x30, 0x3a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, - 0x16, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, - 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x73, 0x73, 0x6c, 0x2d, 0x63, 0x70, 0x73, 0x2d, 0x72, 0x65, - 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x68, 0x74, 0x6d, - 0x30, 0x82, 0x01, 0x64, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x02, 0x02, 0x30, 0x82, 0x01, 0x56, 0x1e, 0x82, 0x01, 0x52, 0x00, 0x41, - 0x00, 0x6e, 0x00, 0x79, 0x00, 0x20, 0x00, 0x75, 0x00, 0x73, 0x00, 0x65, - 0x00, 0x20, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x20, 0x00, 0x74, 0x00, 0x68, - 0x00, 0x69, 0x00, 0x73, 0x00, 0x20, 0x00, 0x43, 0x00, 0x65, 0x00, 0x72, - 0x00, 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, 0x00, 0x63, 0x00, 0x61, - 0x00, 0x74, 0x00, 0x65, 0x00, 0x20, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6e, - 0x00, 0x73, 0x00, 0x74, 0x00, 0x69, 0x00, 0x74, 0x00, 0x75, 0x00, 0x74, - 0x00, 0x65, 0x00, 0x73, 0x00, 0x20, 0x00, 0x61, 0x00, 0x63, 0x00, 0x63, - 0x00, 0x65, 0x00, 0x70, 0x00, 0x74, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x63, - 0x00, 0x65, 0x00, 0x20, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x20, 0x00, 0x74, - 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x44, 0x00, 0x69, 0x00, 0x67, - 0x00, 0x69, 0x00, 0x43, 0x00, 0x65, 0x00, 0x72, 0x00, 0x74, 0x00, 0x20, - 0x00, 0x43, 0x00, 0x50, 0x00, 0x2f, 0x00, 0x43, 0x00, 0x50, 0x00, 0x53, - 0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x20, 0x00, 0x74, - 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x52, 0x00, 0x65, 0x00, 0x6c, - 0x00, 0x79, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x20, 0x00, 0x50, - 0x00, 0x61, 0x00, 0x72, 0x00, 0x74, 0x00, 0x79, 0x00, 0x20, 0x00, 0x41, - 0x00, 0x67, 0x00, 0x72, 0x00, 0x65, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, - 0x00, 0x6e, 0x00, 0x74, 0x00, 0x20, 0x00, 0x77, 0x00, 0x68, 0x00, 0x69, - 0x00, 0x63, 0x00, 0x68, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x6d, - 0x00, 0x69, 0x00, 0x74, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x61, - 0x00, 0x62, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x74, 0x00, 0x79, - 0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x20, 0x00, 0x61, - 0x00, 0x72, 0x00, 0x65, 0x00, 0x20, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x63, - 0x00, 0x6f, 0x00, 0x72, 0x00, 0x70, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x61, - 0x00, 0x74, 0x00, 0x65, 0x00, 0x64, 0x00, 0x20, 0x00, 0x68, 0x00, 0x65, - 0x00, 0x72, 0x00, 0x65, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x62, - 0x00, 0x79, 0x00, 0x20, 0x00, 0x72, 0x00, 0x65, 0x00, 0x66, 0x00, 0x65, - 0x00, 0x72, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x63, 0x00, 0x65, 0x00, 0x2e, - 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, - 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x34, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x28, 0x30, 0x26, - 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, - 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, - 0x70, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, - 0x6f, 0x6d, 0x30, 0x81, 0x8f, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x81, - 0x87, 0x30, 0x81, 0x84, 0x30, 0x40, 0xa0, 0x3e, 0xa0, 0x3c, 0x86, 0x3a, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x33, 0x2e, - 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x48, 0x69, 0x67, - 0x68, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x45, 0x56, - 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x40, - 0xa0, 0x3e, 0xa0, 0x3c, 0x86, 0x3a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, - 0x2f, 0x63, 0x72, 0x6c, 0x34, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, - 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x69, 0x67, 0x69, 0x43, - 0x65, 0x72, 0x74, 0x48, 0x69, 0x67, 0x68, 0x41, 0x73, 0x73, 0x75, 0x72, - 0x61, 0x6e, 0x63, 0x65, 0x45, 0x56, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, - 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, - 0x18, 0x30, 0x16, 0x80, 0x14, 0xb1, 0x3e, 0xc3, 0x69, 0x03, 0xf8, 0xbf, - 0x47, 0x01, 0xd4, 0x98, 0x26, 0x1a, 0x08, 0x02, 0xef, 0x63, 0x64, 0x2b, - 0xc3, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, - 0x50, 0xea, 0x73, 0x89, 0xdb, 0x29, 0xfb, 0x10, 0x8f, 0x9e, 0xe5, 0x01, - 0x20, 0xd4, 0xde, 0x79, 0x99, 0x48, 0x83, 0xf7, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, - 0x82, 0x01, 0x01, 0x00, 0x1e, 0xe2, 0xa5, 0x48, 0x9e, 0x6c, 0xdb, 0x53, - 0x38, 0x0f, 0xef, 0xa6, 0x1a, 0x2a, 0xac, 0xe2, 0x03, 0x43, 0xed, 0x9a, - 0xbc, 0x3e, 0x8e, 0x75, 0x1b, 0xf0, 0xfd, 0x2e, 0x22, 0x59, 0xac, 0x13, - 0xc0, 0x61, 0xe2, 0xe7, 0xfa, 0xe9, 0x99, 0xcd, 0x87, 0x09, 0x75, 0x54, - 0x28, 0xbf, 0x46, 0x60, 0xdc, 0xbe, 0x51, 0x2c, 0x92, 0xf3, 0x1b, 0x91, - 0x7c, 0x31, 0x08, 0x70, 0xe2, 0x37, 0xb9, 0xc1, 0x5b, 0xa8, 0xbd, 0xa3, - 0x0b, 0x00, 0xfb, 0x1a, 0x15, 0xfd, 0x03, 0xad, 0x58, 0x6a, 0xc5, 0xc7, - 0x24, 0x99, 0x48, 0x47, 0x46, 0x31, 0x1e, 0x92, 0xef, 0xb4, 0x5f, 0x4e, - 0x34, 0xc7, 0x90, 0xbf, 0x31, 0xc1, 0xf8, 0xb1, 0x84, 0x86, 0xd0, 0x9c, - 0x01, 0xaa, 0xdf, 0x8a, 0x56, 0x06, 0xce, 0x3a, 0xe9, 0x0e, 0xae, 0x97, - 0x74, 0x5d, 0xd7, 0x71, 0x9a, 0x42, 0x74, 0x5f, 0xde, 0x8d, 0x43, 0x7c, - 0xde, 0xe9, 0x55, 0xed, 0x69, 0x00, 0xcb, 0x05, 0xe0, 0x7a, 0x61, 0x61, - 0x33, 0xd1, 0x19, 0x4d, 0xf9, 0x08, 0xee, 0xa0, 0x39, 0xc5, 0x25, 0x35, - 0xb7, 0x2b, 0xc4, 0x0f, 0xb2, 0xdd, 0xf1, 0xa5, 0xb7, 0x0e, 0x24, 0xc4, - 0x26, 0x28, 0x8d, 0x79, 0x77, 0xf5, 0x2f, 0xf0, 0x57, 0xba, 0x7c, 0x07, - 0xd4, 0xe1, 0xfc, 0xcd, 0x5a, 0x30, 0x57, 0x7e, 0x86, 0x10, 0x47, 0xdd, - 0x31, 0x1f, 0xd7, 0xfc, 0xa2, 0xc2, 0xbf, 0x30, 0x7c, 0x5d, 0x24, 0xaa, - 0xe8, 0xf9, 0xae, 0x5f, 0x6a, 0x74, 0xc2, 0xce, 0x6b, 0xb3, 0x46, 0xd8, - 0x21, 0xbe, 0x29, 0xd4, 0x8e, 0x5e, 0x15, 0xd6, 0x42, 0x4a, 0xe7, 0x32, - 0x6f, 0xa4, 0xb1, 0x6b, 0x51, 0x83, 0x58, 0xbe, 0x3f, 0x6d, 0xc7, 0xfb, - 0xda, 0x03, 0x21, 0xcb, 0x6a, 0x16, 0x19, 0x4e, 0x0a, 0xf0, 0xad, 0x84, - 0xca, 0x5d, 0x94, 0xb3, 0x5a, 0x76, 0xf7, 0x61, -} - -var certSet2Cert52 = []byte{ - 0x30, 0x82, 0x06, 0x5c, 0x30, 0x82, 0x04, 0x44, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x07, 0x19, 0xc2, 0x85, 0x30, 0xe9, 0x3b, 0x36, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, - 0x00, 0x30, 0x7d, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, - 0x13, 0x02, 0x49, 0x4c, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x13, 0x0d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, - 0x4c, 0x74, 0x64, 0x2e, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, - 0x0b, 0x13, 0x22, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x44, 0x69, - 0x67, 0x69, 0x74, 0x61, 0x6c, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, - 0x67, 0x31, 0x29, 0x30, 0x27, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x20, - 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x43, 0x65, 0x72, - 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, - 0x30, 0x36, 0x30, 0x39, 0x31, 0x37, 0x32, 0x32, 0x34, 0x36, 0x33, 0x36, - 0x5a, 0x17, 0x0d, 0x31, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, - 0x39, 0x35, 0x39, 0x5a, 0x30, 0x55, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, - 0x55, 0x04, 0x06, 0x13, 0x02, 0x43, 0x4e, 0x31, 0x1a, 0x30, 0x18, 0x06, - 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x57, 0x6f, 0x53, 0x69, 0x67, 0x6e, - 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, - 0x2a, 0x30, 0x28, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x21, 0x43, 0x65, - 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, - 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x6f, 0x66, - 0x20, 0x57, 0x6f, 0x53, 0x69, 0x67, 0x6e, 0x30, 0x82, 0x02, 0x22, 0x30, - 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, - 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, 0x30, 0x82, 0x02, 0x0a, 0x02, - 0x82, 0x02, 0x01, 0x00, 0xbd, 0xca, 0x8d, 0xac, 0xb8, 0x91, 0x15, 0x56, - 0x97, 0x7b, 0x6b, 0x5c, 0x7a, 0xc2, 0xde, 0x6b, 0xd9, 0xa1, 0xb0, 0xc3, - 0x10, 0x23, 0xfa, 0xa7, 0xa1, 0xb2, 0xcc, 0x31, 0xfa, 0x3e, 0xd9, 0xa6, - 0x29, 0x6f, 0x16, 0x3d, 0xe0, 0x6b, 0xf8, 0xb8, 0x40, 0x5f, 0xdb, 0x39, - 0xa8, 0x00, 0x7a, 0x8b, 0xa0, 0x4d, 0x54, 0x7d, 0xc2, 0x22, 0x78, 0xfc, - 0x8e, 0x09, 0xb8, 0xa8, 0x85, 0xd7, 0xcc, 0x95, 0x97, 0x4b, 0x74, 0xd8, - 0x9e, 0x7e, 0xf0, 0x00, 0xe4, 0x0e, 0x89, 0xae, 0x49, 0x28, 0x44, 0x1a, - 0x10, 0x99, 0x32, 0x0f, 0x25, 0x88, 0x53, 0xa4, 0x0d, 0xb3, 0x0f, 0x12, - 0x08, 0x16, 0x0b, 0x03, 0x71, 0x27, 0x1c, 0x7f, 0xe1, 0xdb, 0xd2, 0xfd, - 0x67, 0x68, 0xc4, 0x05, 0x5d, 0x0a, 0x0e, 0x5d, 0x70, 0xd7, 0xd8, 0x97, - 0xa0, 0xbc, 0x53, 0x41, 0x9a, 0x91, 0x8d, 0xf4, 0x9e, 0x36, 0x66, 0x7a, - 0x7e, 0x56, 0xc1, 0x90, 0x5f, 0xe6, 0xb1, 0x68, 0x20, 0x36, 0xa4, 0x8c, - 0x24, 0x2c, 0x2c, 0x47, 0x0b, 0x59, 0x76, 0x66, 0x30, 0xb5, 0xbe, 0xde, - 0xed, 0x8f, 0xf8, 0x9d, 0xd3, 0xbb, 0x01, 0x30, 0xe6, 0xf2, 0xf3, 0x0e, - 0xe0, 0x2c, 0x92, 0x80, 0xf3, 0x85, 0xf9, 0x28, 0x8a, 0xb4, 0x54, 0x2e, - 0x9a, 0xed, 0xf7, 0x76, 0xfc, 0x15, 0x68, 0x16, 0xeb, 0x4a, 0x6c, 0xeb, - 0x2e, 0x12, 0x8f, 0xd4, 0xcf, 0xfe, 0x0c, 0xc7, 0x5c, 0x1d, 0x0b, 0x7e, - 0x05, 0x32, 0xbe, 0x5e, 0xb0, 0x09, 0x2a, 0x42, 0xd5, 0xc9, 0x4e, 0x90, - 0xb3, 0x59, 0x0d, 0xbb, 0x7a, 0x7e, 0xcd, 0xd5, 0x08, 0x5a, 0xb4, 0x7f, - 0xd8, 0x1c, 0x69, 0x11, 0xf9, 0x27, 0x0f, 0x7b, 0x06, 0xaf, 0x54, 0x83, - 0x18, 0x7b, 0xe1, 0xdd, 0x54, 0x7a, 0x51, 0x68, 0x6e, 0x77, 0xfc, 0xc6, - 0xbf, 0x52, 0x4a, 0x66, 0x46, 0xa1, 0xb2, 0x67, 0x1a, 0xbb, 0xa3, 0x4f, - 0x77, 0xa0, 0xbe, 0x5d, 0xff, 0xfc, 0x56, 0x0b, 0x43, 0x72, 0x77, 0x90, - 0xca, 0x9e, 0xf9, 0xf2, 0x39, 0xf5, 0x0d, 0xa9, 0xf4, 0xea, 0xd7, 0xe7, - 0xb3, 0x10, 0x2f, 0x30, 0x42, 0x37, 0x21, 0xcc, 0x30, 0x70, 0xc9, 0x86, - 0x98, 0x0f, 0xcc, 0x58, 0x4d, 0x83, 0xbb, 0x7d, 0xe5, 0x1a, 0xa5, 0x37, - 0x8d, 0xb6, 0xac, 0x32, 0x97, 0x00, 0x3a, 0x63, 0x71, 0x24, 0x1e, 0x9e, - 0x37, 0xc4, 0xff, 0x74, 0xd4, 0x37, 0xc0, 0xe2, 0xfe, 0x88, 0x46, 0x60, - 0x11, 0xdd, 0x08, 0x3f, 0x50, 0x36, 0xab, 0xb8, 0x7a, 0xa4, 0x95, 0x62, - 0x6a, 0x6e, 0xb0, 0xca, 0x6a, 0x21, 0x5a, 0x69, 0xf3, 0xf3, 0xfb, 0x1d, - 0x70, 0x39, 0x95, 0xf3, 0xa7, 0x6e, 0xa6, 0x81, 0x89, 0xa1, 0x88, 0xc5, - 0x3b, 0x71, 0xca, 0xa3, 0x52, 0xee, 0x83, 0xbb, 0xfd, 0xa0, 0x77, 0xf4, - 0xe4, 0x6f, 0xe7, 0x42, 0xdb, 0x6d, 0x4a, 0x99, 0x8a, 0x34, 0x48, 0xbc, - 0x17, 0xdc, 0xe4, 0x80, 0x08, 0x22, 0xb6, 0xf2, 0x31, 0xc0, 0x3f, 0x04, - 0x3e, 0xeb, 0x9f, 0x20, 0x79, 0xd6, 0xb8, 0x06, 0x64, 0x64, 0x02, 0x31, - 0xd7, 0xa9, 0xcd, 0x52, 0xfb, 0x84, 0x45, 0x69, 0x09, 0x00, 0x2a, 0xdc, - 0x55, 0x8b, 0xc4, 0x06, 0x46, 0x4b, 0xc0, 0x4a, 0x1d, 0x09, 0x5b, 0x39, - 0x28, 0xfd, 0xa9, 0xab, 0xce, 0x00, 0xf9, 0x2e, 0x48, 0x4b, 0x26, 0xe6, - 0x30, 0x4c, 0xa5, 0x58, 0xca, 0xb4, 0x44, 0x82, 0x4f, 0xe7, 0x91, 0x1e, - 0x33, 0xc3, 0xb0, 0x93, 0xff, 0x11, 0xfc, 0x81, 0xd2, 0xca, 0x1f, 0x71, - 0x29, 0xdd, 0x76, 0x4f, 0x92, 0x25, 0xaf, 0x1d, 0x81, 0xb7, 0x0f, 0x2f, - 0x8c, 0xc3, 0x06, 0xcc, 0x2f, 0x27, 0xa3, 0x4a, 0xe4, 0x0e, 0x99, 0xba, - 0x7c, 0x1e, 0x45, 0x1f, 0x7f, 0xaa, 0x19, 0x45, 0x96, 0xfd, 0xfc, 0x3d, - 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x07, 0x30, 0x82, 0x01, - 0x03, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, - 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x02, 0x30, 0x0e, 0x06, - 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, - 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, - 0xe1, 0x66, 0xcf, 0x0e, 0xd1, 0xf1, 0xb3, 0x4b, 0xb7, 0x06, 0x20, 0x14, - 0xfe, 0x87, 0x12, 0xd5, 0xf6, 0xfe, 0xfb, 0x3e, 0x30, 0x1f, 0x06, 0x03, - 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x4e, 0x0b, 0xef, - 0x1a, 0xa4, 0x40, 0x5b, 0xa5, 0x17, 0x69, 0x87, 0x30, 0xca, 0x34, 0x68, - 0x43, 0xd0, 0x41, 0xae, 0xf2, 0x30, 0x69, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x5d, 0x30, 0x5b, 0x30, 0x27, 0x06, - 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x1b, 0x68, - 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x63, 0x61, 0x30, 0x30, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x30, 0x02, 0x86, 0x24, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x61, - 0x69, 0x61, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x73, 0x2f, 0x63, 0x61, - 0x2e, 0x63, 0x72, 0x74, 0x30, 0x32, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, - 0x2b, 0x30, 0x29, 0x30, 0x27, 0xa0, 0x25, 0xa0, 0x23, 0x86, 0x21, 0x68, - 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x73, 0x74, - 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, - 0x66, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, - 0x82, 0x02, 0x01, 0x00, 0xb6, 0x6d, 0xf8, 0x70, 0xfb, 0xe2, 0x0d, 0x4c, - 0x98, 0xb3, 0x07, 0x49, 0x15, 0xf5, 0x04, 0xc4, 0x6c, 0xca, 0xca, 0xf5, - 0x68, 0xa0, 0x08, 0xfe, 0x12, 0x6d, 0x9c, 0x04, 0x06, 0xc9, 0xad, 0x9a, - 0x91, 0x52, 0x3e, 0x78, 0xc4, 0x5c, 0xee, 0x9f, 0x54, 0x1d, 0xee, 0xe3, - 0xf1, 0x5e, 0x30, 0xc9, 0x49, 0xe1, 0x39, 0xe0, 0xa6, 0x9d, 0x36, 0x6c, - 0x57, 0xfa, 0xe6, 0x34, 0x4f, 0x55, 0xe8, 0x87, 0xa8, 0x2c, 0xdd, 0x05, - 0xf1, 0x58, 0x12, 0x91, 0xe8, 0xca, 0xce, 0x28, 0x78, 0x8f, 0xdf, 0x07, - 0x85, 0x01, 0xa5, 0xdc, 0x45, 0x96, 0x05, 0xd4, 0x80, 0xb2, 0x2b, 0x05, - 0x9a, 0xcb, 0x9a, 0xa5, 0x8b, 0xe0, 0x3a, 0x67, 0xe6, 0x73, 0x47, 0xbe, - 0x4a, 0xfd, 0x27, 0xb1, 0x88, 0xef, 0xe6, 0xca, 0xcf, 0x8d, 0x0e, 0x26, - 0x9f, 0xfa, 0x5f, 0x57, 0x78, 0xad, 0x6d, 0xfe, 0xae, 0x9b, 0x35, 0x08, - 0xb1, 0xc3, 0xba, 0xc1, 0x00, 0x4a, 0x4b, 0x7d, 0x14, 0xbd, 0xf7, 0xf1, - 0xd3, 0x55, 0x18, 0xac, 0xd0, 0x33, 0x70, 0x88, 0x6d, 0xc4, 0x09, 0x71, - 0x14, 0xa6, 0x2b, 0x4f, 0x88, 0x81, 0xe7, 0x0b, 0x00, 0x37, 0xa9, 0x15, - 0x7d, 0x7e, 0xd7, 0x01, 0x96, 0x3f, 0x2f, 0xaf, 0x7b, 0x62, 0xae, 0x0a, - 0x4a, 0xbf, 0x4b, 0x39, 0x2e, 0x35, 0x10, 0x8b, 0xfe, 0x04, 0x39, 0xe4, - 0x3c, 0x3a, 0x0c, 0x09, 0x56, 0x40, 0x3a, 0xb5, 0xf4, 0xc2, 0x68, 0x0c, - 0xb5, 0xf9, 0x52, 0xcd, 0xee, 0x9d, 0xf8, 0x98, 0xfc, 0x78, 0xe7, 0x58, - 0x47, 0x8f, 0x1c, 0x73, 0x58, 0x69, 0x33, 0xab, 0xff, 0xdd, 0xdf, 0x8e, - 0x24, 0x01, 0x77, 0x98, 0x19, 0x3a, 0xb0, 0x66, 0x79, 0xbc, 0xe1, 0x08, - 0xa3, 0x0e, 0x4f, 0xc1, 0x04, 0xb3, 0xf3, 0x01, 0xc8, 0xeb, 0xd3, 0x59, - 0x1c, 0x35, 0xd2, 0x93, 0x1e, 0x70, 0x65, 0x82, 0x7f, 0xdb, 0xcf, 0xfb, - 0xc8, 0x99, 0x12, 0x60, 0xc3, 0x44, 0x6f, 0x3a, 0x80, 0x4b, 0xd7, 0xbe, - 0x21, 0xaa, 0x14, 0x7a, 0x64, 0xcb, 0xdd, 0x37, 0x43, 0x45, 0x5b, 0x32, - 0x2e, 0x45, 0xf0, 0xd9, 0x59, 0x1f, 0x6b, 0x18, 0xf0, 0x7c, 0xe9, 0x55, - 0x36, 0x19, 0x61, 0x5f, 0xb5, 0x7d, 0xf1, 0x8d, 0xbd, 0x88, 0xe4, 0x75, - 0x4b, 0x98, 0xdd, 0x27, 0xb0, 0xe4, 0x84, 0x44, 0x2a, 0x61, 0x84, 0x57, - 0x05, 0x82, 0x11, 0x1f, 0xaa, 0x35, 0x58, 0xf3, 0x20, 0x0e, 0xaf, 0x59, - 0xef, 0xfa, 0x55, 0x72, 0x72, 0x0d, 0x26, 0xd0, 0x9b, 0x53, 0x49, 0xac, - 0xce, 0x37, 0x2e, 0x65, 0x61, 0xff, 0xf6, 0xec, 0x1b, 0xea, 0xf6, 0xf1, - 0xa6, 0xd3, 0xd1, 0xb5, 0x7b, 0xbe, 0x35, 0xf4, 0x22, 0xc1, 0xbc, 0x8d, - 0x01, 0xbd, 0x68, 0x5e, 0x83, 0x0d, 0x2f, 0xec, 0xd6, 0xda, 0x63, 0x0c, - 0x27, 0xd1, 0x54, 0x3e, 0xe4, 0xa8, 0xd3, 0xce, 0x4b, 0x32, 0xb8, 0x91, - 0x94, 0xff, 0xfb, 0x5b, 0x49, 0x2d, 0x75, 0x18, 0xa8, 0xba, 0x71, 0x9a, - 0x3b, 0xae, 0xd9, 0xc0, 0xa9, 0x4f, 0x87, 0x91, 0xed, 0x8b, 0x7b, 0x6b, - 0x20, 0x98, 0x89, 0x39, 0x83, 0x4f, 0x80, 0xc4, 0x69, 0xcc, 0x17, 0xc9, - 0xc8, 0x4e, 0xbe, 0xe4, 0xa9, 0xa5, 0x81, 0x76, 0x70, 0x06, 0x04, 0x32, - 0xcd, 0x83, 0x65, 0xf4, 0xbc, 0x7d, 0x3e, 0x13, 0xbc, 0xd2, 0xe8, 0x6f, - 0x63, 0xaa, 0xb5, 0x3b, 0xda, 0x8d, 0x86, 0x32, 0x82, 0x78, 0x9d, 0xd9, - 0xcc, 0xff, 0xbf, 0x57, 0x64, 0x74, 0xed, 0x28, 0x3d, 0x44, 0x62, 0x15, - 0x61, 0x4b, 0xf7, 0x94, 0xb0, 0x0d, 0x2a, 0x67, 0x1c, 0xf0, 0xcb, 0x9b, - 0xa5, 0x92, 0xbf, 0xf8, 0x41, 0x5a, 0xc1, 0x3d, 0x60, 0xed, 0x9f, 0xbb, - 0xb8, 0x6d, 0x9b, 0xce, 0xa9, 0x6a, 0x16, 0x3f, 0x7e, 0xea, 0x06, 0xf1, -} - -var certSet2Cert53 = []byte{ - 0x30, 0x82, 0x06, 0xe6, 0x30, 0x82, 0x05, 0xce, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x03, 0x37, 0xb9, 0x28, 0x34, 0x7c, 0x60, 0xa6, 0xae, - 0xc5, 0xad, 0xb1, 0x21, 0x7f, 0x38, 0x60, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6c, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, - 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, - 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, - 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, - 0x63, 0x6f, 0x6d, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03, - 0x13, 0x22, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, - 0x69, 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, - 0x65, 0x20, 0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, - 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x37, 0x31, 0x31, 0x30, 0x39, 0x31, 0x32, - 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x31, 0x31, - 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x69, 0x31, 0x0b, - 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, - 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69, - 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x19, - 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, 0x77, 0x77, - 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, - 0x6d, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1f, - 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, 0x69, 0x67, - 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x20, - 0x45, 0x56, 0x20, 0x43, 0x41, 0x2d, 0x31, 0x30, 0x82, 0x01, 0x22, 0x30, - 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, - 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, - 0x82, 0x01, 0x01, 0x00, 0xf3, 0x96, 0x62, 0xd8, 0x75, 0x6e, 0x19, 0xff, - 0x3f, 0x34, 0x7c, 0x49, 0x4f, 0x31, 0x7e, 0x0d, 0x04, 0x4e, 0x99, 0x81, - 0xe2, 0xb3, 0x85, 0x55, 0x91, 0x30, 0xb1, 0xc0, 0xaf, 0x70, 0xbb, 0x2c, - 0xa8, 0xe7, 0x18, 0xaa, 0x3f, 0x78, 0xf7, 0x90, 0x68, 0x52, 0x86, 0x01, - 0x88, 0x97, 0xe2, 0x3b, 0x06, 0x65, 0x90, 0xaa, 0xbd, 0x65, 0x76, 0xc2, - 0xec, 0xbe, 0x10, 0x5b, 0x37, 0x78, 0x83, 0x60, 0x75, 0x45, 0xc6, 0xbd, - 0x74, 0xaa, 0xb6, 0x9f, 0xa4, 0x3a, 0x01, 0x50, 0x17, 0xc4, 0x39, 0x69, - 0xb9, 0xf1, 0x4f, 0xef, 0x82, 0xc1, 0xca, 0xf3, 0x4a, 0xdb, 0xcc, 0x9e, - 0x50, 0x4f, 0x4d, 0x40, 0xa3, 0x3a, 0x90, 0xe7, 0x86, 0x66, 0xbc, 0xf0, - 0x3e, 0x76, 0x28, 0x4c, 0xd1, 0x75, 0x80, 0x9e, 0x6a, 0x35, 0x14, 0x35, - 0x03, 0x9e, 0xdb, 0x0c, 0x8c, 0xc2, 0x28, 0xad, 0x50, 0xb2, 0xce, 0xf6, - 0x91, 0xa3, 0xc3, 0xa5, 0x0a, 0x58, 0x49, 0xf6, 0x75, 0x44, 0x6c, 0xba, - 0xf9, 0xce, 0xe9, 0xab, 0x3a, 0x02, 0xe0, 0x4d, 0xf3, 0xac, 0xe2, 0x7a, - 0xe0, 0x60, 0x22, 0x05, 0x3c, 0x82, 0xd3, 0x52, 0xe2, 0xf3, 0x9c, 0x47, - 0xf8, 0x3b, 0xd8, 0xb2, 0x4b, 0x93, 0x56, 0x4a, 0xbf, 0x70, 0xab, 0x3e, - 0xe9, 0x68, 0xc8, 0x1d, 0x8f, 0x58, 0x1d, 0x2a, 0x4d, 0x5e, 0x27, 0x3d, - 0xad, 0x0a, 0x59, 0x2f, 0x5a, 0x11, 0x20, 0x40, 0xd9, 0x68, 0x04, 0x68, - 0x2d, 0xf4, 0xc0, 0x84, 0x0b, 0x0a, 0x1b, 0x78, 0xdf, 0xed, 0x1a, 0x58, - 0xdc, 0xfb, 0x41, 0x5a, 0x6d, 0x6b, 0xf2, 0xed, 0x1c, 0xee, 0x5c, 0x32, - 0xb6, 0x5c, 0xec, 0xd7, 0xa6, 0x03, 0x32, 0xa6, 0xe8, 0xde, 0xb7, 0x28, - 0x27, 0x59, 0x88, 0x80, 0xff, 0x7b, 0xad, 0x89, 0x58, 0xd5, 0x1e, 0x14, - 0xa4, 0xf2, 0xb0, 0x70, 0xd4, 0xa0, 0x3e, 0xa7, 0x02, 0x03, 0x01, 0x00, - 0x01, 0xa3, 0x82, 0x03, 0x85, 0x30, 0x82, 0x03, 0x81, 0x30, 0x0e, 0x06, - 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, - 0x86, 0x30, 0x3b, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x34, 0x30, 0x32, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x08, 0x2b, 0x06, - 0x01, 0x05, 0x05, 0x07, 0x03, 0x03, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x03, 0x04, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x03, 0x08, 0x30, 0x82, 0x01, 0xc4, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, - 0x82, 0x01, 0xbb, 0x30, 0x82, 0x01, 0xb7, 0x30, 0x82, 0x01, 0xb3, 0x06, - 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xfd, 0x6c, 0x02, 0x01, 0x30, 0x82, - 0x01, 0xa4, 0x30, 0x3a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x02, 0x01, 0x16, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, - 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x73, 0x6c, 0x2d, 0x63, 0x70, 0x73, 0x2d, - 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x68, - 0x74, 0x6d, 0x30, 0x82, 0x01, 0x64, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x02, 0x02, 0x30, 0x82, 0x01, 0x56, 0x1e, 0x82, 0x01, 0x52, - 0x00, 0x41, 0x00, 0x6e, 0x00, 0x79, 0x00, 0x20, 0x00, 0x75, 0x00, 0x73, - 0x00, 0x65, 0x00, 0x20, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x20, 0x00, 0x74, - 0x00, 0x68, 0x00, 0x69, 0x00, 0x73, 0x00, 0x20, 0x00, 0x43, 0x00, 0x65, - 0x00, 0x72, 0x00, 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, 0x00, 0x63, - 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x20, 0x00, 0x63, 0x00, 0x6f, - 0x00, 0x6e, 0x00, 0x73, 0x00, 0x74, 0x00, 0x69, 0x00, 0x74, 0x00, 0x75, - 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x20, 0x00, 0x61, 0x00, 0x63, - 0x00, 0x63, 0x00, 0x65, 0x00, 0x70, 0x00, 0x74, 0x00, 0x61, 0x00, 0x6e, - 0x00, 0x63, 0x00, 0x65, 0x00, 0x20, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x20, - 0x00, 0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x44, 0x00, 0x69, - 0x00, 0x67, 0x00, 0x69, 0x00, 0x43, 0x00, 0x65, 0x00, 0x72, 0x00, 0x74, - 0x00, 0x20, 0x00, 0x45, 0x00, 0x56, 0x00, 0x20, 0x00, 0x43, 0x00, 0x50, - 0x00, 0x53, 0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x20, - 0x00, 0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x52, 0x00, 0x65, - 0x00, 0x6c, 0x00, 0x79, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x20, - 0x00, 0x50, 0x00, 0x61, 0x00, 0x72, 0x00, 0x74, 0x00, 0x79, 0x00, 0x20, - 0x00, 0x41, 0x00, 0x67, 0x00, 0x72, 0x00, 0x65, 0x00, 0x65, 0x00, 0x6d, - 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x20, 0x00, 0x77, 0x00, 0x68, - 0x00, 0x69, 0x00, 0x63, 0x00, 0x68, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x69, - 0x00, 0x6d, 0x00, 0x69, 0x00, 0x74, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x69, - 0x00, 0x61, 0x00, 0x62, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x74, - 0x00, 0x79, 0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x20, - 0x00, 0x61, 0x00, 0x72, 0x00, 0x65, 0x00, 0x20, 0x00, 0x69, 0x00, 0x6e, - 0x00, 0x63, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x70, 0x00, 0x6f, 0x00, 0x72, - 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x64, 0x00, 0x20, 0x00, 0x68, - 0x00, 0x65, 0x00, 0x72, 0x00, 0x65, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x20, - 0x00, 0x62, 0x00, 0x79, 0x00, 0x20, 0x00, 0x72, 0x00, 0x65, 0x00, 0x66, - 0x00, 0x65, 0x00, 0x72, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x63, 0x00, 0x65, - 0x00, 0x2e, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, - 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x81, - 0x83, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, - 0x77, 0x30, 0x75, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, - 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x4d, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x41, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, - 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x41, 0x43, 0x65, 0x72, - 0x74, 0x73, 0x2f, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x48, - 0x69, 0x67, 0x68, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, - 0x45, 0x56, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74, - 0x30, 0x81, 0x8f, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x81, 0x87, 0x30, - 0x81, 0x84, 0x30, 0x40, 0xa0, 0x3e, 0xa0, 0x3c, 0x86, 0x3a, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x33, 0x2e, 0x64, 0x69, - 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, - 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x48, 0x69, 0x67, 0x68, 0x41, - 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x45, 0x56, 0x52, 0x6f, - 0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x40, 0xa0, 0x3e, - 0xa0, 0x3c, 0x86, 0x3a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, - 0x72, 0x6c, 0x34, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, - 0x74, 0x48, 0x69, 0x67, 0x68, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, - 0x63, 0x65, 0x45, 0x56, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63, - 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, - 0x14, 0x4c, 0x58, 0xcb, 0x25, 0xf0, 0x41, 0x4f, 0x52, 0xf4, 0x28, 0xc8, - 0x81, 0x43, 0x9b, 0xa6, 0xa8, 0xa0, 0xe6, 0x92, 0xe5, 0x30, 0x1f, 0x06, - 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xb1, 0x3e, - 0xc3, 0x69, 0x03, 0xf8, 0xbf, 0x47, 0x01, 0xd4, 0x98, 0x26, 0x1a, 0x08, - 0x02, 0xef, 0x63, 0x64, 0x2b, 0xc3, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, - 0x01, 0x00, 0x4c, 0x7a, 0x17, 0x87, 0x28, 0x5d, 0x17, 0xbc, 0xb2, 0x32, - 0x73, 0xbf, 0xcd, 0x2e, 0xf5, 0x58, 0x31, 0x1d, 0xf0, 0xb1, 0x71, 0x54, - 0x9c, 0xd6, 0x9b, 0x67, 0x93, 0xdb, 0x2f, 0x03, 0x3e, 0x16, 0x6f, 0x1e, - 0x03, 0xc9, 0x53, 0x84, 0xa3, 0x56, 0x60, 0x1e, 0x78, 0x94, 0x1b, 0xa2, - 0xa8, 0x6f, 0xa3, 0xa4, 0x8b, 0x52, 0x91, 0xd7, 0xdd, 0x5c, 0x95, 0xbb, - 0xef, 0xb5, 0x16, 0x49, 0xe9, 0xa5, 0x42, 0x4f, 0x34, 0xf2, 0x47, 0xff, - 0xae, 0x81, 0x7f, 0x13, 0x54, 0xb7, 0x20, 0xc4, 0x70, 0x15, 0xcb, 0x81, - 0x0a, 0x81, 0xcb, 0x74, 0x57, 0xdc, 0x9c, 0xdf, 0x24, 0xa4, 0x29, 0x0c, - 0x18, 0xf0, 0x1c, 0xe4, 0xae, 0x07, 0x33, 0xec, 0xf1, 0x49, 0x3e, 0x55, - 0xcf, 0x6e, 0x4f, 0x0d, 0x54, 0x7b, 0xd3, 0xc9, 0xe8, 0x15, 0x48, 0xd4, - 0xc5, 0xbb, 0xdc, 0x35, 0x1c, 0x77, 0x45, 0x07, 0x48, 0x45, 0x85, 0xbd, - 0xd7, 0x7e, 0x53, 0xb8, 0xc0, 0x16, 0xd9, 0x95, 0xcd, 0x8b, 0x8d, 0x7d, - 0xc9, 0x60, 0x4f, 0xd1, 0xa2, 0x9b, 0xe3, 0xd0, 0x30, 0xd6, 0xb4, 0x73, - 0x36, 0xe6, 0xd2, 0xf9, 0x03, 0xb2, 0xe3, 0xa4, 0xf5, 0xe5, 0xb8, 0x3e, - 0x04, 0x49, 0x00, 0xba, 0x2e, 0xa6, 0x4a, 0x72, 0x83, 0x72, 0x9d, 0xf7, - 0x0b, 0x8c, 0xa9, 0x89, 0xe7, 0xb3, 0xd7, 0x64, 0x1f, 0xd6, 0xe3, 0x60, - 0xcb, 0x03, 0xc4, 0xdc, 0x88, 0xe9, 0x9d, 0x25, 0x01, 0x00, 0x71, 0xcb, - 0x03, 0xb4, 0x29, 0x60, 0x25, 0x8f, 0xf9, 0x46, 0xd1, 0x7b, 0x71, 0xae, - 0xcd, 0x53, 0x12, 0x5b, 0x84, 0x8e, 0xc2, 0x0f, 0xc7, 0xed, 0x93, 0x19, - 0xd9, 0xc9, 0xfa, 0x8f, 0x58, 0x34, 0x76, 0x32, 0x2f, 0xae, 0xe1, 0x50, - 0x14, 0x61, 0xd4, 0xa8, 0x58, 0xa3, 0xc8, 0x30, 0x13, 0x23, 0xef, 0xc6, - 0x25, 0x8c, 0x36, 0x8f, 0x1c, 0x80, -} diff --git a/vendor/github.com/lucas-clemente/quic-go-certificates/cert_set_3.go b/vendor/github.com/lucas-clemente/quic-go-certificates/cert_set_3.go deleted file mode 100644 index e3dfed9c63a..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go-certificates/cert_set_3.go +++ /dev/null @@ -1,5456 +0,0 @@ -package certsets - -var CertSet3 = [][]byte{ - certSet3Cert0, - certSet3Cert1, - certSet3Cert2, - certSet3Cert3, - certSet3Cert4, - certSet3Cert5, - certSet3Cert6, - certSet3Cert7, - certSet3Cert8, - certSet3Cert9, - certSet3Cert10, - certSet3Cert11, - certSet3Cert12, - certSet3Cert13, - certSet3Cert14, - certSet3Cert15, - certSet3Cert16, - certSet3Cert17, - certSet3Cert18, - certSet3Cert19, - certSet3Cert20, - certSet3Cert21, - certSet3Cert22, - certSet3Cert23, - certSet3Cert24, - certSet3Cert25, - certSet3Cert26, - certSet3Cert27, - certSet3Cert28, - certSet3Cert29, - certSet3Cert30, - certSet3Cert31, - certSet3Cert32, - certSet3Cert33, - certSet3Cert34, - certSet3Cert35, - certSet3Cert36, - certSet3Cert37, - certSet3Cert38, - certSet3Cert39, - certSet3Cert40, - certSet3Cert41, - certSet3Cert42, - certSet3Cert43, - certSet3Cert44, - certSet3Cert45, - certSet3Cert46, - certSet3Cert47, - certSet3Cert48, - certSet3Cert49, - certSet3Cert50, - certSet3Cert51, -} - -const CertSet3Hash uint64 = (0x918215a28680ed7e) - -var certSet3Cert0 = []byte{ - 0x30, 0x82, 0x03, 0x7d, 0x30, 0x82, 0x02, 0xe6, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x03, 0x12, 0xbb, 0xe6, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x4e, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x07, 0x45, - 0x71, 0x75, 0x69, 0x66, 0x61, 0x78, 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03, - 0x55, 0x04, 0x0b, 0x13, 0x24, 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, 0x78, - 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x32, 0x30, - 0x35, 0x32, 0x31, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, - 0x31, 0x38, 0x30, 0x38, 0x32, 0x31, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, - 0x5a, 0x30, 0x42, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, - 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, - 0x03, 0x13, 0x12, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, - 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, - 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xda, 0xcc, 0x18, 0x63, 0x30, 0xfd, - 0xf4, 0x17, 0x23, 0x1a, 0x56, 0x7e, 0x5b, 0xdf, 0x3c, 0x6c, 0x38, 0xe4, - 0x71, 0xb7, 0x78, 0x91, 0xd4, 0xbc, 0xa1, 0xd8, 0x4c, 0xf8, 0xa8, 0x43, - 0xb6, 0x03, 0xe9, 0x4d, 0x21, 0x07, 0x08, 0x88, 0xda, 0x58, 0x2f, 0x66, - 0x39, 0x29, 0xbd, 0x05, 0x78, 0x8b, 0x9d, 0x38, 0xe8, 0x05, 0xb7, 0x6a, - 0x7e, 0x71, 0xa4, 0xe6, 0xc4, 0x60, 0xa6, 0xb0, 0xef, 0x80, 0xe4, 0x89, - 0x28, 0x0f, 0x9e, 0x25, 0xd6, 0xed, 0x83, 0xf3, 0xad, 0xa6, 0x91, 0xc7, - 0x98, 0xc9, 0x42, 0x18, 0x35, 0x14, 0x9d, 0xad, 0x98, 0x46, 0x92, 0x2e, - 0x4f, 0xca, 0xf1, 0x87, 0x43, 0xc1, 0x16, 0x95, 0x57, 0x2d, 0x50, 0xef, - 0x89, 0x2d, 0x80, 0x7a, 0x57, 0xad, 0xf2, 0xee, 0x5f, 0x6b, 0xd2, 0x00, - 0x8d, 0xb9, 0x14, 0xf8, 0x14, 0x15, 0x35, 0xd9, 0xc0, 0x46, 0xa3, 0x7b, - 0x72, 0xc8, 0x91, 0xbf, 0xc9, 0x55, 0x2b, 0xcd, 0xd0, 0x97, 0x3e, 0x9c, - 0x26, 0x64, 0xcc, 0xdf, 0xce, 0x83, 0x19, 0x71, 0xca, 0x4e, 0xe6, 0xd4, - 0xd5, 0x7b, 0xa9, 0x19, 0xcd, 0x55, 0xde, 0xc8, 0xec, 0xd2, 0x5e, 0x38, - 0x53, 0xe5, 0x5c, 0x4f, 0x8c, 0x2d, 0xfe, 0x50, 0x23, 0x36, 0xfc, 0x66, - 0xe6, 0xcb, 0x8e, 0xa4, 0x39, 0x19, 0x00, 0xb7, 0x95, 0x02, 0x39, 0x91, - 0x0b, 0x0e, 0xfe, 0x38, 0x2e, 0xd1, 0x1d, 0x05, 0x9a, 0xf6, 0x4d, 0x3e, - 0x6f, 0x0f, 0x07, 0x1d, 0xaf, 0x2c, 0x1e, 0x8f, 0x60, 0x39, 0xe2, 0xfa, - 0x36, 0x53, 0x13, 0x39, 0xd4, 0x5e, 0x26, 0x2b, 0xdb, 0x3d, 0xa8, 0x14, - 0xbd, 0x32, 0xeb, 0x18, 0x03, 0x28, 0x52, 0x04, 0x71, 0xe5, 0xab, 0x33, - 0x3d, 0xe1, 0x38, 0xbb, 0x07, 0x36, 0x84, 0x62, 0x9c, 0x79, 0xea, 0x16, - 0x30, 0xf4, 0x5f, 0xc0, 0x2b, 0xe8, 0x71, 0x6b, 0xe4, 0xf9, 0x02, 0x03, - 0x01, 0x00, 0x01, 0xa3, 0x81, 0xf0, 0x30, 0x81, 0xed, 0x30, 0x1f, 0x06, - 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x48, 0xe6, - 0x68, 0xf9, 0x2b, 0xd2, 0xb2, 0x95, 0xd7, 0x47, 0xd8, 0x23, 0x20, 0x10, - 0x4f, 0x33, 0x98, 0x90, 0x9f, 0xd4, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, - 0x0e, 0x04, 0x16, 0x04, 0x14, 0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb, - 0xab, 0x05, 0x64, 0x0c, 0x11, 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc, - 0x4e, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, - 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, - 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x3a, - 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x33, 0x30, 0x31, 0x30, 0x2f, 0xa0, - 0x2d, 0xa0, 0x2b, 0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x63, 0x72, 0x6c, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x73, 0x65, - 0x63, 0x75, 0x72, 0x65, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x4e, - 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x47, 0x30, 0x45, 0x30, 0x43, 0x06, - 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x3b, 0x30, 0x39, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x2d, 0x68, 0x74, 0x74, - 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f, - 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x72, 0x65, 0x70, 0x6f, - 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, - 0x00, 0x76, 0xe1, 0x12, 0x6e, 0x4e, 0x4b, 0x16, 0x12, 0x86, 0x30, 0x06, - 0xb2, 0x81, 0x08, 0xcf, 0xf0, 0x08, 0xc7, 0xc7, 0x71, 0x7e, 0x66, 0xee, - 0xc2, 0xed, 0xd4, 0x3b, 0x1f, 0xff, 0xf0, 0xf0, 0xc8, 0x4e, 0xd6, 0x43, - 0x38, 0xb0, 0xb9, 0x30, 0x7d, 0x18, 0xd0, 0x55, 0x83, 0xa2, 0x6a, 0xcb, - 0x36, 0x11, 0x9c, 0xe8, 0x48, 0x66, 0xa3, 0x6d, 0x7f, 0xb8, 0x13, 0xd4, - 0x47, 0xfe, 0x8b, 0x5a, 0x5c, 0x73, 0xfc, 0xae, 0xd9, 0x1b, 0x32, 0x19, - 0x38, 0xab, 0x97, 0x34, 0x14, 0xaa, 0x96, 0xd2, 0xeb, 0xa3, 0x1c, 0x14, - 0x08, 0x49, 0xb6, 0xbb, 0xe5, 0x91, 0xef, 0x83, 0x36, 0xeb, 0x1d, 0x56, - 0x6f, 0xca, 0xda, 0xbc, 0x73, 0x63, 0x90, 0xe4, 0x7f, 0x7b, 0x3e, 0x22, - 0xcb, 0x3d, 0x07, 0xed, 0x5f, 0x38, 0x74, 0x9c, 0xe3, 0x03, 0x50, 0x4e, - 0xa1, 0xaf, 0x98, 0xee, 0x61, 0xf2, 0x84, 0x3f, 0x12, -} - -var certSet3Cert1 = []byte{ - 0x30, 0x82, 0x03, 0x8b, 0x30, 0x82, 0x02, 0xf4, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x03, 0x0d, 0x6e, 0x62, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x4e, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x07, 0x45, - 0x71, 0x75, 0x69, 0x66, 0x61, 0x78, 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03, - 0x55, 0x04, 0x0b, 0x13, 0x24, 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, 0x78, - 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, - 0x31, 0x32, 0x37, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, - 0x31, 0x38, 0x30, 0x38, 0x32, 0x31, 0x31, 0x36, 0x31, 0x35, 0x30, 0x30, - 0x5a, 0x30, 0x58, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, - 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, - 0x03, 0x13, 0x28, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x82, 0x01, 0x22, 0x30, - 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, - 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, - 0x82, 0x01, 0x01, 0x00, 0xbe, 0xb8, 0x15, 0x7b, 0xff, 0xd4, 0x7c, 0x7d, - 0x67, 0xad, 0x83, 0x64, 0x7b, 0xc8, 0x42, 0x53, 0x2d, 0xdf, 0xf6, 0x84, - 0x08, 0x20, 0x61, 0xd6, 0x01, 0x59, 0x6a, 0x9c, 0x44, 0x11, 0xaf, 0xef, - 0x76, 0xfd, 0x95, 0x7e, 0xce, 0x61, 0x30, 0xbb, 0x7a, 0x83, 0x5f, 0x02, - 0xbd, 0x01, 0x66, 0xca, 0xee, 0x15, 0x8d, 0x6f, 0xa1, 0x30, 0x9c, 0xbd, - 0xa1, 0x85, 0x9e, 0x94, 0x3a, 0xf3, 0x56, 0x88, 0x00, 0x31, 0xcf, 0xd8, - 0xee, 0x6a, 0x96, 0x02, 0xd9, 0xed, 0x03, 0x8c, 0xfb, 0x75, 0x6d, 0xe7, - 0xea, 0xb8, 0x55, 0x16, 0x05, 0x16, 0x9a, 0xf4, 0xe0, 0x5e, 0xb1, 0x88, - 0xc0, 0x64, 0x85, 0x5c, 0x15, 0x4d, 0x88, 0xc7, 0xb7, 0xba, 0xe0, 0x75, - 0xe9, 0xad, 0x05, 0x3d, 0x9d, 0xc7, 0x89, 0x48, 0xe0, 0xbb, 0x28, 0xc8, - 0x03, 0xe1, 0x30, 0x93, 0x64, 0x5e, 0x52, 0xc0, 0x59, 0x70, 0x22, 0x35, - 0x57, 0x88, 0x8a, 0xf1, 0x95, 0x0a, 0x83, 0xd7, 0xbc, 0x31, 0x73, 0x01, - 0x34, 0xed, 0xef, 0x46, 0x71, 0xe0, 0x6b, 0x02, 0xa8, 0x35, 0x72, 0x6b, - 0x97, 0x9b, 0x66, 0xe0, 0xcb, 0x1c, 0x79, 0x5f, 0xd8, 0x1a, 0x04, 0x68, - 0x1e, 0x47, 0x02, 0xe6, 0x9d, 0x60, 0xe2, 0x36, 0x97, 0x01, 0xdf, 0xce, - 0x35, 0x92, 0xdf, 0xbe, 0x67, 0xc7, 0x6d, 0x77, 0x59, 0x3b, 0x8f, 0x9d, - 0xd6, 0x90, 0x15, 0x94, 0xbc, 0x42, 0x34, 0x10, 0xc1, 0x39, 0xf9, 0xb1, - 0x27, 0x3e, 0x7e, 0xd6, 0x8a, 0x75, 0xc5, 0xb2, 0xaf, 0x96, 0xd3, 0xa2, - 0xde, 0x9b, 0xe4, 0x98, 0xbe, 0x7d, 0xe1, 0xe9, 0x81, 0xad, 0xb6, 0x6f, - 0xfc, 0xd7, 0x0e, 0xda, 0xe0, 0x34, 0xb0, 0x0d, 0x1a, 0x77, 0xe7, 0xe3, - 0x08, 0x98, 0xef, 0x58, 0xfa, 0x9c, 0x84, 0xb7, 0x36, 0xaf, 0xc2, 0xdf, - 0xac, 0xd2, 0xf4, 0x10, 0x06, 0x70, 0x71, 0x35, 0x02, 0x03, 0x01, 0x00, - 0x01, 0xa3, 0x81, 0xe8, 0x30, 0x81, 0xe5, 0x30, 0x0e, 0x06, 0x03, 0x55, - 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, - 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x2c, 0xd5, - 0x50, 0x41, 0x97, 0x15, 0x8b, 0xf0, 0x8f, 0x36, 0x61, 0x5b, 0x4a, 0xfb, - 0x6b, 0xd9, 0x99, 0xc9, 0x33, 0x92, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, - 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x48, 0xe6, 0x68, 0xf9, 0x2b, - 0xd2, 0xb2, 0x95, 0xd7, 0x47, 0xd8, 0x23, 0x20, 0x10, 0x4f, 0x33, 0x98, - 0x90, 0x9f, 0xd4, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, - 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x3a, 0x06, 0x03, - 0x55, 0x1d, 0x1f, 0x04, 0x33, 0x30, 0x31, 0x30, 0x2f, 0xa0, 0x2d, 0xa0, - 0x2b, 0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, - 0x6c, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x73, 0x65, 0x63, 0x75, - 0x72, 0x65, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x46, 0x06, 0x03, - 0x55, 0x1d, 0x20, 0x04, 0x3f, 0x30, 0x3d, 0x30, 0x3b, 0x06, 0x04, 0x55, - 0x1d, 0x20, 0x00, 0x30, 0x33, 0x30, 0x31, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, - 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, - 0x81, 0x81, 0x00, 0xaf, 0xf3, 0x0e, 0xd6, 0x72, 0xab, 0xc7, 0xa9, 0x97, - 0xca, 0x2a, 0x6b, 0x84, 0x39, 0xde, 0x79, 0xa9, 0xf0, 0x81, 0xe5, 0x08, - 0x67, 0xab, 0xd7, 0x2f, 0x20, 0x02, 0x01, 0x71, 0x0c, 0x04, 0x22, 0xc9, - 0x1e, 0x88, 0x95, 0x03, 0xc9, 0x49, 0x3a, 0xaf, 0x67, 0x08, 0x49, 0xb0, - 0xd5, 0x08, 0xf5, 0x20, 0x3d, 0x80, 0x91, 0xa0, 0xc5, 0x87, 0xa3, 0xfb, - 0xc9, 0xa3, 0x17, 0x91, 0xf9, 0xa8, 0x2f, 0xae, 0xe9, 0x0f, 0xdf, 0x96, - 0x72, 0x0f, 0x75, 0x17, 0x80, 0x5d, 0x78, 0x01, 0x4d, 0x9f, 0x1f, 0x6d, - 0x7b, 0xd8, 0xf5, 0x42, 0x38, 0x23, 0x1a, 0x99, 0x93, 0xf4, 0x83, 0xbe, - 0x3b, 0x35, 0x74, 0xe7, 0x37, 0x13, 0x35, 0x7a, 0xac, 0xb4, 0xb6, 0x90, - 0x82, 0x6c, 0x27, 0xa4, 0xe0, 0xec, 0x9e, 0x35, 0xbd, 0xbf, 0xe5, 0x29, - 0xa1, 0x47, 0x9f, 0x5b, 0x32, 0xfc, 0xe9, 0x99, 0x7d, 0x2b, 0x39, -} - -var certSet3Cert2 = []byte{ - 0x30, 0x82, 0x03, 0xf0, 0x30, 0x82, 0x02, 0xd8, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x03, 0x02, 0x3a, 0x92, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x42, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x35, 0x30, - 0x34, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, - 0x31, 0x37, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, - 0x5a, 0x30, 0x49, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, - 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x13, 0x0a, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x49, 0x6e, - 0x63, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1c, - 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, - 0x6e, 0x65, 0x74, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, - 0x79, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, - 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, - 0x00, 0x9c, 0x2a, 0x04, 0x77, 0x5c, 0xd8, 0x50, 0x91, 0x3a, 0x06, 0xa3, - 0x82, 0xe0, 0xd8, 0x50, 0x48, 0xbc, 0x89, 0x3f, 0xf1, 0x19, 0x70, 0x1a, - 0x88, 0x46, 0x7e, 0xe0, 0x8f, 0xc5, 0xf1, 0x89, 0xce, 0x21, 0xee, 0x5a, - 0xfe, 0x61, 0x0d, 0xb7, 0x32, 0x44, 0x89, 0xa0, 0x74, 0x0b, 0x53, 0x4f, - 0x55, 0xa4, 0xce, 0x82, 0x62, 0x95, 0xee, 0xeb, 0x59, 0x5f, 0xc6, 0xe1, - 0x05, 0x80, 0x12, 0xc4, 0x5e, 0x94, 0x3f, 0xbc, 0x5b, 0x48, 0x38, 0xf4, - 0x53, 0xf7, 0x24, 0xe6, 0xfb, 0x91, 0xe9, 0x15, 0xc4, 0xcf, 0xf4, 0x53, - 0x0d, 0xf4, 0x4a, 0xfc, 0x9f, 0x54, 0xde, 0x7d, 0xbe, 0xa0, 0x6b, 0x6f, - 0x87, 0xc0, 0xd0, 0x50, 0x1f, 0x28, 0x30, 0x03, 0x40, 0xda, 0x08, 0x73, - 0x51, 0x6c, 0x7f, 0xff, 0x3a, 0x3c, 0xa7, 0x37, 0x06, 0x8e, 0xbd, 0x4b, - 0x11, 0x04, 0xeb, 0x7d, 0x24, 0xde, 0xe6, 0xf9, 0xfc, 0x31, 0x71, 0xfb, - 0x94, 0xd5, 0x60, 0xf3, 0x2e, 0x4a, 0xaf, 0x42, 0xd2, 0xcb, 0xea, 0xc4, - 0x6a, 0x1a, 0xb2, 0xcc, 0x53, 0xdd, 0x15, 0x4b, 0x8b, 0x1f, 0xc8, 0x19, - 0x61, 0x1f, 0xcd, 0x9d, 0xa8, 0x3e, 0x63, 0x2b, 0x84, 0x35, 0x69, 0x65, - 0x84, 0xc8, 0x19, 0xc5, 0x46, 0x22, 0xf8, 0x53, 0x95, 0xbe, 0xe3, 0x80, - 0x4a, 0x10, 0xc6, 0x2a, 0xec, 0xba, 0x97, 0x20, 0x11, 0xc7, 0x39, 0x99, - 0x10, 0x04, 0xa0, 0xf0, 0x61, 0x7a, 0x95, 0x25, 0x8c, 0x4e, 0x52, 0x75, - 0xe2, 0xb6, 0xed, 0x08, 0xca, 0x14, 0xfc, 0xce, 0x22, 0x6a, 0xb3, 0x4e, - 0xcf, 0x46, 0x03, 0x97, 0x97, 0x03, 0x7e, 0xc0, 0xb1, 0xde, 0x7b, 0xaf, - 0x45, 0x33, 0xcf, 0xba, 0x3e, 0x71, 0xb7, 0xde, 0xf4, 0x25, 0x25, 0xc2, - 0x0d, 0x35, 0x89, 0x9d, 0x9d, 0xfb, 0x0e, 0x11, 0x79, 0x89, 0x1e, 0x37, - 0xc5, 0xaf, 0x8e, 0x72, 0x69, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, - 0xe7, 0x30, 0x81, 0xe4, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, - 0x18, 0x30, 0x16, 0x80, 0x14, 0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb, - 0xab, 0x05, 0x64, 0x0c, 0x11, 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc, - 0x4e, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, - 0x4a, 0xdd, 0x06, 0x16, 0x1b, 0xbc, 0xf6, 0x68, 0xb5, 0x76, 0xf5, 0x81, - 0xb6, 0xbb, 0x62, 0x1a, 0xba, 0x5a, 0x81, 0x2f, 0x30, 0x0e, 0x06, 0x03, - 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, - 0x30, 0x2e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, - 0x04, 0x22, 0x30, 0x20, 0x30, 0x1e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x30, 0x01, 0x86, 0x12, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, - 0x2f, 0x67, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x64, 0x2e, 0x63, 0x6f, 0x6d, - 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, - 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x35, 0x06, 0x03, - 0x55, 0x1d, 0x1f, 0x04, 0x2e, 0x30, 0x2c, 0x30, 0x2a, 0xa0, 0x28, 0xa0, - 0x26, 0x86, 0x24, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x67, 0x2e, - 0x73, 0x79, 0x6d, 0x63, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x72, - 0x6c, 0x73, 0x2f, 0x67, 0x74, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x2e, - 0x63, 0x72, 0x6c, 0x30, 0x17, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x10, - 0x30, 0x0e, 0x30, 0x0c, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, - 0x79, 0x02, 0x05, 0x01, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, - 0x08, 0x4e, 0x04, 0xa7, 0x80, 0x7f, 0x10, 0x16, 0x43, 0x5e, 0x02, 0xad, - 0xd7, 0x42, 0x80, 0xf4, 0xb0, 0x8e, 0xd2, 0xae, 0xb3, 0xeb, 0x11, 0x7d, - 0x90, 0x84, 0x18, 0x7d, 0xe7, 0x90, 0x15, 0xfb, 0x49, 0x7f, 0xa8, 0x99, - 0x05, 0x91, 0xbb, 0x7a, 0xc9, 0xd6, 0x3c, 0x37, 0x18, 0x09, 0x9a, 0xb6, - 0xc7, 0x92, 0x20, 0x07, 0x35, 0x33, 0x09, 0xe4, 0x28, 0x63, 0x72, 0x0d, - 0xb4, 0xe0, 0x32, 0x9c, 0x87, 0x98, 0xc4, 0x1b, 0x76, 0x89, 0x67, 0xc1, - 0x50, 0x58, 0xb0, 0x13, 0xaa, 0x13, 0x1a, 0x1b, 0x32, 0xa5, 0xbe, 0xea, - 0x11, 0x95, 0x4c, 0x48, 0x63, 0x49, 0xe9, 0x99, 0x5d, 0x20, 0x37, 0xcc, - 0xfe, 0x2a, 0x69, 0x51, 0x16, 0x95, 0x4b, 0xa9, 0xde, 0x49, 0x82, 0xc0, - 0x10, 0x70, 0xf4, 0x2c, 0xf3, 0xec, 0xbc, 0x24, 0x24, 0xd0, 0x4e, 0xac, - 0xa5, 0xd9, 0x5e, 0x1e, 0x6d, 0x92, 0xc1, 0xa7, 0xac, 0x48, 0x35, 0x81, - 0xf9, 0xe5, 0xe4, 0x9c, 0x65, 0x69, 0xcd, 0x87, 0xa4, 0x41, 0x50, 0x3f, - 0x2e, 0x57, 0xa5, 0x91, 0x51, 0x12, 0x58, 0x0e, 0x8c, 0x09, 0xa1, 0xac, - 0x7a, 0xa4, 0x12, 0xa5, 0x27, 0xf3, 0x9a, 0x10, 0x97, 0x7d, 0x55, 0x03, - 0x06, 0xf7, 0x66, 0x58, 0x5f, 0x5f, 0x64, 0xe1, 0xab, 0x5d, 0x6d, 0xa5, - 0x39, 0x48, 0x75, 0x98, 0x4c, 0x29, 0x5a, 0x3a, 0x8d, 0xd3, 0x2b, 0xca, - 0x9c, 0x55, 0x04, 0xbf, 0xf4, 0xe6, 0x14, 0xd5, 0x80, 0xac, 0x26, 0xed, - 0x17, 0x89, 0xa6, 0x93, 0x6c, 0x5c, 0xa4, 0xcc, 0xb8, 0xf0, 0x66, 0x8e, - 0x64, 0xe3, 0x7d, 0x9a, 0xe2, 0x00, 0xb3, 0x49, 0xc7, 0xe4, 0x0a, 0xaa, - 0xdd, 0x5b, 0x83, 0xc7, 0x70, 0x90, 0x46, 0x4e, 0xbe, 0xd0, 0xdb, 0x59, - 0x96, 0x6c, 0x2e, 0xf5, 0x16, 0x36, 0xde, 0x71, 0xcc, 0x01, 0xc2, 0x12, - 0xc1, 0x21, 0xc6, 0x16, -} - -var certSet3Cert3 = []byte{ - 0x30, 0x82, 0x04, 0x15, 0x30, 0x82, 0x03, 0x7e, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x04, 0x07, 0x27, 0x8e, 0xed, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x75, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f, - 0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b, - 0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, - 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21, - 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43, - 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, - 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, - 0x0d, 0x31, 0x32, 0x30, 0x34, 0x31, 0x38, 0x31, 0x36, 0x33, 0x36, 0x31, - 0x38, 0x5a, 0x17, 0x0d, 0x31, 0x38, 0x30, 0x38, 0x31, 0x33, 0x31, 0x36, - 0x33, 0x35, 0x31, 0x37, 0x5a, 0x30, 0x5a, 0x31, 0x0b, 0x30, 0x09, 0x06, - 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, 0x45, 0x31, 0x12, 0x30, 0x10, - 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09, 0x42, 0x61, 0x6c, 0x74, 0x69, - 0x6d, 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, - 0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, - 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x19, - 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x43, 0x79, - 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x52, 0x6f, 0x6f, - 0x74, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, - 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, - 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa3, 0x04, - 0xbb, 0x22, 0xab, 0x98, 0x3d, 0x57, 0xe8, 0x26, 0x72, 0x9a, 0xb5, 0x79, - 0xd4, 0x29, 0xe2, 0xe1, 0xe8, 0x95, 0x80, 0xb1, 0xb0, 0xe3, 0x5b, 0x8e, - 0x2b, 0x29, 0x9a, 0x64, 0xdf, 0xa1, 0x5d, 0xed, 0xb0, 0x09, 0x05, 0x6d, - 0xdb, 0x28, 0x2e, 0xce, 0x62, 0xa2, 0x62, 0xfe, 0xb4, 0x88, 0xda, 0x12, - 0xeb, 0x38, 0xeb, 0x21, 0x9d, 0xc0, 0x41, 0x2b, 0x01, 0x52, 0x7b, 0x88, - 0x77, 0xd3, 0x1c, 0x8f, 0xc7, 0xba, 0xb9, 0x88, 0xb5, 0x6a, 0x09, 0xe7, - 0x73, 0xe8, 0x11, 0x40, 0xa7, 0xd1, 0xcc, 0xca, 0x62, 0x8d, 0x2d, 0xe5, - 0x8f, 0x0b, 0xa6, 0x50, 0xd2, 0xa8, 0x50, 0xc3, 0x28, 0xea, 0xf5, 0xab, - 0x25, 0x87, 0x8a, 0x9a, 0x96, 0x1c, 0xa9, 0x67, 0xb8, 0x3f, 0x0c, 0xd5, - 0xf7, 0xf9, 0x52, 0x13, 0x2f, 0xc2, 0x1b, 0xd5, 0x70, 0x70, 0xf0, 0x8f, - 0xc0, 0x12, 0xca, 0x06, 0xcb, 0x9a, 0xe1, 0xd9, 0xca, 0x33, 0x7a, 0x77, - 0xd6, 0xf8, 0xec, 0xb9, 0xf1, 0x68, 0x44, 0x42, 0x48, 0x13, 0xd2, 0xc0, - 0xc2, 0xa4, 0xae, 0x5e, 0x60, 0xfe, 0xb6, 0xa6, 0x05, 0xfc, 0xb4, 0xdd, - 0x07, 0x59, 0x02, 0xd4, 0x59, 0x18, 0x98, 0x63, 0xf5, 0xa5, 0x63, 0xe0, - 0x90, 0x0c, 0x7d, 0x5d, 0xb2, 0x06, 0x7a, 0xf3, 0x85, 0xea, 0xeb, 0xd4, - 0x03, 0xae, 0x5e, 0x84, 0x3e, 0x5f, 0xff, 0x15, 0xed, 0x69, 0xbc, 0xf9, - 0x39, 0x36, 0x72, 0x75, 0xcf, 0x77, 0x52, 0x4d, 0xf3, 0xc9, 0x90, 0x2c, - 0xb9, 0x3d, 0xe5, 0xc9, 0x23, 0x53, 0x3f, 0x1f, 0x24, 0x98, 0x21, 0x5c, - 0x07, 0x99, 0x29, 0xbd, 0xc6, 0x3a, 0xec, 0xe7, 0x6e, 0x86, 0x3a, 0x6b, - 0x97, 0x74, 0x63, 0x33, 0xbd, 0x68, 0x18, 0x31, 0xf0, 0x78, 0x8d, 0x76, - 0xbf, 0xfc, 0x9e, 0x8e, 0x5d, 0x2a, 0x86, 0xa7, 0x4d, 0x90, 0xdc, 0x27, - 0x1a, 0x39, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x47, 0x30, - 0x82, 0x01, 0x43, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, - 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x03, 0x30, - 0x4a, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x43, 0x30, 0x41, 0x30, 0x3f, - 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x37, 0x30, 0x35, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x29, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x2e, 0x6f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, - 0x6f, 0x72, 0x79, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, - 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x81, 0x89, 0x06, 0x03, - 0x55, 0x1d, 0x23, 0x04, 0x81, 0x81, 0x30, 0x7f, 0xa1, 0x79, 0xa4, 0x77, - 0x30, 0x75, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, - 0x02, 0x55, 0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, - 0x13, 0x0f, 0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, - 0x04, 0x0b, 0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, - 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, - 0x30, 0x21, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, - 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x82, - 0x02, 0x01, 0xa5, 0x30, 0x45, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3e, - 0x30, 0x3c, 0x30, 0x3a, 0xa0, 0x38, 0xa0, 0x36, 0x86, 0x34, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x70, 0x75, 0x62, - 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x63, 0x67, 0x69, 0x2d, 0x62, 0x69, 0x6e, 0x2f, 0x43, 0x52, - 0x4c, 0x2f, 0x32, 0x30, 0x31, 0x38, 0x2f, 0x63, 0x64, 0x70, 0x2e, 0x63, - 0x72, 0x6c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, - 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x93, 0x1d, 0xfe, - 0x8b, 0xae, 0x46, 0xec, 0xcb, 0xa9, 0x0f, 0xab, 0xe5, 0xef, 0xca, 0xb2, - 0x68, 0x16, 0x68, 0xd8, 0x8f, 0xfa, 0x13, 0xa9, 0xaf, 0xb3, 0xcb, 0x2d, - 0xe7, 0x4b, 0x6e, 0x8e, 0x69, 0x2a, 0xc2, 0x2b, 0x10, 0x0a, 0x8d, 0xf6, - 0xae, 0x73, 0xb6, 0xb9, 0xfb, 0x14, 0xfd, 0x5f, 0x6d, 0xb8, 0x50, 0xb6, - 0xc4, 0x8a, 0xd6, 0x40, 0x7e, 0xd7, 0xc3, 0xcb, 0x73, 0xdc, 0xc9, 0x5d, - 0x5b, 0xaf, 0xb0, 0x41, 0xb5, 0x37, 0xeb, 0xea, 0xdc, 0x20, 0x91, 0xc4, - 0x34, 0x6a, 0xf4, 0xa1, 0xf3, 0x96, 0x9d, 0x37, 0x86, 0x97, 0xe1, 0x71, - 0xa4, 0xdd, 0x7d, 0xfa, 0x44, 0x84, 0x94, 0xae, 0xd7, 0x09, 0x04, 0x22, - 0x76, 0x0f, 0x64, 0x51, 0x35, 0xa9, 0x24, 0x0f, 0xf9, 0x0b, 0xdb, 0x32, - 0xda, 0xc2, 0xfe, 0xc1, 0xb9, 0x2a, 0x5c, 0x7a, 0x27, 0x13, 0xca, 0xb1, - 0x48, 0x3a, 0x71, 0xd0, 0x43, -} - -var certSet3Cert4 = []byte{ - 0x30, 0x82, 0x04, 0x25, 0x30, 0x82, 0x03, 0x0d, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x03, 0x02, 0x3a, 0x77, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x42, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x34, 0x30, - 0x38, 0x32, 0x39, 0x32, 0x31, 0x33, 0x39, 0x33, 0x32, 0x5a, 0x17, 0x0d, - 0x32, 0x32, 0x30, 0x35, 0x32, 0x30, 0x32, 0x31, 0x33, 0x39, 0x33, 0x32, - 0x5a, 0x30, 0x47, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, - 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x03, 0x55, 0x04, - 0x03, 0x13, 0x17, 0x52, 0x61, 0x70, 0x69, 0x64, 0x53, 0x53, 0x4c, 0x20, - 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, - 0x47, 0x33, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, - 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xaf, - 0x54, 0x9b, 0xd9, 0x58, 0x5d, 0x1e, 0x2c, 0x56, 0xc6, 0xd5, 0xe8, 0x7f, - 0xf4, 0x7d, 0x16, 0x03, 0xff, 0xd0, 0x8b, 0x5a, 0xe4, 0x8e, 0xa7, 0xdd, - 0x54, 0x2e, 0xd4, 0x04, 0xc0, 0x5d, 0x98, 0x9c, 0x8d, 0x90, 0x0f, 0xbc, - 0x10, 0x65, 0x5f, 0xda, 0x9a, 0xd6, 0x44, 0x7c, 0xc0, 0x9f, 0xb5, 0xe9, - 0x4a, 0x8c, 0x0b, 0x06, 0x43, 0x04, 0xbb, 0xf4, 0x96, 0xe2, 0x26, 0xf6, - 0x61, 0x01, 0x91, 0x66, 0x31, 0x22, 0xc3, 0x34, 0x34, 0x5f, 0x3f, 0x3f, - 0x91, 0x2f, 0x44, 0x5f, 0xdc, 0xc7, 0x14, 0xb6, 0x03, 0x9f, 0x86, 0x4b, - 0x0e, 0xa3, 0xff, 0xa0, 0x80, 0x02, 0x83, 0xc3, 0xd3, 0x1f, 0x69, 0x52, - 0xd6, 0x9d, 0x64, 0x0f, 0xc9, 0x83, 0xe7, 0x1b, 0xc4, 0x70, 0xac, 0x94, - 0xe7, 0xc3, 0xa4, 0x6a, 0x2c, 0xbd, 0xb8, 0x9e, 0x69, 0xd8, 0xbe, 0x0a, - 0x8f, 0x16, 0x63, 0x5a, 0x68, 0x71, 0x80, 0x7b, 0x30, 0xde, 0x15, 0x04, - 0xbf, 0xcc, 0xd3, 0xbf, 0x3e, 0x48, 0x05, 0x55, 0x7a, 0xb3, 0xd7, 0x10, - 0x0c, 0x03, 0xfc, 0x9b, 0xfd, 0x08, 0xa7, 0x8c, 0x8c, 0xdb, 0xa7, 0x8e, - 0xf1, 0x1e, 0x63, 0xdc, 0xb3, 0x01, 0x2f, 0x7f, 0xaf, 0x57, 0xc3, 0x3c, - 0x48, 0xa7, 0x83, 0x68, 0x21, 0xa7, 0x2f, 0xe7, 0xa7, 0x3f, 0xf0, 0xb5, - 0x0c, 0xfc, 0xf5, 0x84, 0xd1, 0x53, 0xbc, 0x0e, 0x72, 0x4f, 0x60, 0x0c, - 0x42, 0xb8, 0x98, 0xad, 0x19, 0x88, 0x57, 0xd7, 0x04, 0xec, 0x87, 0xbf, - 0x7e, 0x87, 0x4e, 0xa3, 0x21, 0xf9, 0x53, 0xfd, 0x36, 0x98, 0x48, 0x8d, - 0xd6, 0xf8, 0xbb, 0x48, 0xf2, 0x29, 0xc8, 0x64, 0xd1, 0xcc, 0x54, 0x48, - 0x53, 0x8b, 0xaf, 0xb7, 0x65, 0x1e, 0xbf, 0x29, 0x33, 0x29, 0xd9, 0x29, - 0x60, 0x48, 0xf8, 0xff, 0x91, 0xbc, 0x57, 0x58, 0xe5, 0x35, 0x2e, 0xbb, - 0x69, 0xb6, 0x59, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x1d, - 0x30, 0x82, 0x01, 0x19, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, - 0x18, 0x30, 0x16, 0x80, 0x14, 0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb, - 0xab, 0x05, 0x64, 0x0c, 0x11, 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc, - 0x4e, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, - 0xc3, 0x9c, 0xf3, 0xfc, 0xd3, 0x46, 0x08, 0x34, 0xbb, 0xce, 0x46, 0x7f, - 0xa0, 0x7c, 0x5b, 0xf3, 0xe2, 0x08, 0xcb, 0x59, 0x30, 0x12, 0x06, 0x03, - 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, - 0xff, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, - 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x35, 0x06, 0x03, - 0x55, 0x1d, 0x1f, 0x04, 0x2e, 0x30, 0x2c, 0x30, 0x2a, 0xa0, 0x28, 0xa0, - 0x26, 0x86, 0x24, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x67, 0x2e, - 0x73, 0x79, 0x6d, 0x63, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x72, - 0x6c, 0x73, 0x2f, 0x67, 0x74, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x2e, - 0x63, 0x72, 0x6c, 0x30, 0x2e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x01, 0x01, 0x04, 0x22, 0x30, 0x20, 0x30, 0x1e, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x12, 0x68, 0x74, 0x74, - 0x70, 0x3a, 0x2f, 0x2f, 0x67, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x64, 0x2e, - 0x63, 0x6f, 0x6d, 0x30, 0x4c, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x45, - 0x30, 0x43, 0x30, 0x41, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, - 0x45, 0x01, 0x07, 0x36, 0x30, 0x33, 0x30, 0x31, 0x06, 0x08, 0x2b, 0x06, - 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x25, 0x68, 0x74, 0x74, 0x70, - 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x0d, 0x06, - 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, - 0x03, 0x82, 0x01, 0x01, 0x00, 0xa3, 0x58, 0x1e, 0xc6, 0x43, 0x32, 0xac, - 0xac, 0x2f, 0x93, 0x78, 0xb7, 0xea, 0xae, 0x54, 0x40, 0x47, 0x2d, 0x7e, - 0x78, 0x8d, 0x50, 0xf6, 0xf8, 0x66, 0xac, 0xd6, 0x4f, 0x73, 0xd6, 0x44, - 0xef, 0xaf, 0x0b, 0xcc, 0x5b, 0xc1, 0xf4, 0x4f, 0x9a, 0x8f, 0x49, 0x7e, - 0x60, 0xaf, 0xc2, 0x27, 0xc7, 0x16, 0xf1, 0xfb, 0x93, 0x81, 0x90, 0xa9, - 0x7c, 0xef, 0x6f, 0x7e, 0x6e, 0x45, 0x94, 0x16, 0x84, 0xbd, 0xec, 0x49, - 0xf1, 0xc4, 0x0e, 0xf4, 0xaf, 0x04, 0x59, 0x83, 0x87, 0x0f, 0x2c, 0x3b, - 0x97, 0xc3, 0x5a, 0x12, 0x9b, 0x7b, 0x04, 0x35, 0x7b, 0xa3, 0x95, 0x33, - 0x08, 0x7b, 0x93, 0x71, 0x22, 0x42, 0xb3, 0xa9, 0xd9, 0x6f, 0x4f, 0x81, - 0x92, 0xfc, 0x07, 0xb6, 0x79, 0xbc, 0x84, 0x4a, 0x9d, 0x77, 0x09, 0xf1, - 0xc5, 0x89, 0xf2, 0xf0, 0xb4, 0x9c, 0x54, 0xaa, 0x12, 0x7b, 0x0d, 0xba, - 0x4f, 0xef, 0x93, 0x19, 0xec, 0xef, 0x7d, 0x4e, 0x61, 0xa3, 0x8e, 0x76, - 0x9c, 0x59, 0xcf, 0x8c, 0x94, 0xb1, 0x84, 0x97, 0xf7, 0x1a, 0xb9, 0x07, - 0xb8, 0xb2, 0xc6, 0x4f, 0x13, 0x79, 0xdb, 0xbf, 0x4f, 0x51, 0x1b, 0x7f, - 0x69, 0x0d, 0x51, 0x2a, 0xc1, 0xd6, 0x15, 0xff, 0x37, 0x51, 0x34, 0x65, - 0x51, 0xf4, 0x1e, 0xbe, 0x38, 0x6a, 0xec, 0x0e, 0xab, 0xbf, 0x3d, 0x7b, - 0x39, 0x05, 0x7b, 0xf4, 0xf3, 0xfb, 0x1a, 0xa1, 0xd0, 0xc8, 0x7e, 0x4e, - 0x64, 0x8d, 0xcd, 0x8c, 0x61, 0x55, 0x90, 0xfe, 0x3a, 0xca, 0x5d, 0x25, - 0x0f, 0xf8, 0x1d, 0xa3, 0x4a, 0x74, 0x56, 0x4f, 0x1a, 0x55, 0x40, 0x70, - 0x75, 0x25, 0xa6, 0x33, 0x2e, 0xba, 0x4b, 0xa5, 0x5d, 0x53, 0x9a, 0x0d, - 0x30, 0xe1, 0x8d, 0x5f, 0x61, 0x2c, 0xaf, 0xcc, 0xef, 0xb0, 0x99, 0xa1, - 0x80, 0xff, 0x0b, 0xf2, 0x62, 0x4c, 0x70, 0x26, 0x98, -} - -var certSet3Cert5 = []byte{ - 0x30, 0x82, 0x04, 0x44, 0x30, 0x82, 0x03, 0x2c, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x03, 0x02, 0x3a, 0x78, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x42, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x34, 0x30, - 0x38, 0x32, 0x39, 0x32, 0x32, 0x32, 0x34, 0x35, 0x38, 0x5a, 0x17, 0x0d, - 0x32, 0x32, 0x30, 0x35, 0x32, 0x30, 0x32, 0x32, 0x32, 0x34, 0x35, 0x38, - 0x5a, 0x30, 0x66, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, - 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x1d, 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, - 0x0b, 0x13, 0x14, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x20, 0x56, 0x61, - 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x64, 0x20, 0x53, 0x53, 0x4c, 0x31, - 0x20, 0x30, 0x1e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x17, 0x47, 0x65, - 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x44, 0x56, 0x20, 0x53, 0x53, - 0x4c, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, 0x34, 0x30, 0x82, 0x01, - 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, - 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xdf, 0x41, 0x94, 0x7a, 0xda, 0xf7, - 0xe4, 0x31, 0x43, 0xb6, 0xea, 0x01, 0x1b, 0x5c, 0xce, 0x63, 0xea, 0xfa, - 0x6d, 0xa3, 0xd9, 0x6a, 0xee, 0x2d, 0x9a, 0x75, 0xf9, 0xd5, 0x9c, 0x5b, - 0xbd, 0x34, 0xdf, 0xd8, 0x1c, 0xc9, 0x6d, 0xd8, 0x04, 0x88, 0xda, 0x6e, - 0xb5, 0xb7, 0xb5, 0xf0, 0x30, 0xae, 0x40, 0xd6, 0x5d, 0xfa, 0xc4, 0x53, - 0xc1, 0xd4, 0x22, 0x9d, 0x04, 0x4e, 0x11, 0xa6, 0x95, 0xd5, 0x45, 0x7c, - 0x41, 0x05, 0x58, 0xe0, 0x4c, 0xdd, 0xf9, 0xee, 0x55, 0xbd, 0x5f, 0x46, - 0xdc, 0xad, 0x13, 0x08, 0x9d, 0x2c, 0xe4, 0xf7, 0x82, 0xe6, 0x07, 0x2b, - 0x9e, 0x0e, 0x8c, 0x34, 0xa1, 0xce, 0xc4, 0xa1, 0xe0, 0x81, 0x70, 0x86, - 0x00, 0x06, 0x3f, 0x2d, 0xea, 0x7c, 0x9b, 0x28, 0xae, 0x1b, 0x28, 0x8b, - 0x39, 0x09, 0xd3, 0xe7, 0xf0, 0x45, 0xa4, 0xb1, 0xba, 0x11, 0x67, 0x90, - 0x55, 0x7b, 0x8f, 0xde, 0xed, 0x38, 0x5c, 0xa1, 0xe1, 0xe3, 0x83, 0xc4, - 0xc3, 0x72, 0x91, 0x4f, 0x98, 0xee, 0x1c, 0xc2, 0x80, 0xaa, 0x64, 0xa5, - 0x3e, 0x83, 0x62, 0x1c, 0xcc, 0xe0, 0x9e, 0xf8, 0x5a, 0xc0, 0x13, 0x12, - 0x7d, 0xa2, 0xa7, 0x8b, 0xa3, 0xe7, 0x9f, 0x2a, 0xd7, 0x9b, 0xca, 0xcb, - 0xed, 0x97, 0x01, 0x9c, 0x28, 0x84, 0x51, 0x04, 0x50, 0x41, 0xbc, 0xb4, - 0xfc, 0x78, 0xe9, 0x1b, 0xcf, 0x14, 0xea, 0x1f, 0x0f, 0xfc, 0x2e, 0x01, - 0x32, 0x8d, 0xb6, 0x35, 0xcb, 0x0a, 0x18, 0x3b, 0xec, 0x5a, 0x3e, 0x3c, - 0x1b, 0xd3, 0x99, 0x43, 0x1e, 0x2f, 0xf7, 0xbd, 0xf3, 0x5b, 0x12, 0xb9, - 0x07, 0x5e, 0xed, 0x3e, 0xd1, 0xa9, 0x87, 0xcc, 0x77, 0x72, 0x27, 0xd4, - 0xd9, 0x75, 0xa2, 0x63, 0x4b, 0x93, 0x36, 0xbd, 0xe5, 0x5c, 0xd7, 0xbf, - 0x5f, 0x79, 0x0d, 0xb3, 0x32, 0xa7, 0x0b, 0xb2, 0x63, 0x23, 0x02, 0x03, - 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x1d, 0x30, 0x82, 0x01, 0x19, 0x30, - 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, - 0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb, 0xab, 0x05, 0x64, 0x0c, 0x11, - 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc, 0x4e, 0x30, 0x1d, 0x06, 0x03, - 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x0b, 0x50, 0xec, 0x77, 0xef, - 0x2a, 0x9b, 0xff, 0xec, 0x03, 0xa1, 0x0a, 0xff, 0xad, 0xc6, 0xe4, 0x2a, - 0x18, 0xc7, 0x3e, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, - 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, - 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, - 0x02, 0x01, 0x06, 0x30, 0x35, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2e, - 0x30, 0x2c, 0x30, 0x2a, 0xa0, 0x28, 0xa0, 0x26, 0x86, 0x24, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x67, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x67, 0x74, - 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x2e, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x22, - 0x30, 0x20, 0x30, 0x1e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x30, 0x01, 0x86, 0x12, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x67, - 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x64, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x4c, - 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x45, 0x30, 0x43, 0x30, 0x41, 0x06, - 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x07, 0x36, 0x30, - 0x33, 0x30, 0x31, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, - 0x01, 0x16, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, - 0x77, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x2f, 0x63, 0x70, 0x73, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, - 0x33, 0x24, 0xd5, 0x90, 0xaa, 0x29, 0x0c, 0x35, 0xb9, 0x2f, 0xc3, 0xc7, - 0x42, 0x93, 0xc0, 0xc6, 0x10, 0x4b, 0x03, 0x08, 0x76, 0x84, 0x10, 0xa2, - 0xe0, 0xe7, 0x53, 0x12, 0x27, 0xf2, 0x0a, 0xda, 0x7f, 0x3a, 0xdc, 0xfd, - 0x5c, 0x79, 0x5a, 0x8f, 0x17, 0x74, 0x43, 0x53, 0xb1, 0xd5, 0xd1, 0x5d, - 0x59, 0xb9, 0xa6, 0x84, 0x64, 0xca, 0xf1, 0x3a, 0x0a, 0x59, 0x96, 0x10, - 0xbf, 0xa9, 0x81, 0x57, 0x8b, 0x5c, 0x87, 0xdc, 0x7f, 0xe3, 0xe4, 0xbb, - 0x05, 0x7a, 0xa0, 0x32, 0x09, 0x13, 0x4e, 0x10, 0x81, 0x28, 0x1f, 0x9c, - 0x03, 0x62, 0xbc, 0xf4, 0x01, 0xb5, 0x29, 0x83, 0x46, 0x07, 0xb9, 0xe7, - 0xb8, 0x5d, 0xc8, 0xe9, 0xd1, 0xdd, 0xad, 0x3b, 0xf8, 0x34, 0xdb, 0xc1, - 0xd1, 0x95, 0xa9, 0x91, 0x18, 0xed, 0x3c, 0x2c, 0x37, 0x11, 0x4d, 0xcc, - 0xfe, 0x53, 0x3e, 0x50, 0x43, 0xf9, 0xc3, 0x56, 0x41, 0xac, 0x53, 0x9b, - 0x6c, 0x05, 0xb2, 0x9a, 0xe2, 0xe0, 0x59, 0x57, 0x30, 0x32, 0xb6, 0x26, - 0x4e, 0x13, 0x25, 0xcd, 0xfa, 0x48, 0x70, 0x0f, 0x75, 0x55, 0x60, 0x11, - 0xf5, 0x3b, 0xd5, 0x5e, 0x5a, 0x3c, 0x8b, 0x5b, 0x0f, 0x0f, 0x62, 0x42, - 0x48, 0x61, 0x85, 0x8b, 0x10, 0xf4, 0xc1, 0x88, 0xbf, 0x7f, 0x5f, 0x8a, - 0xc2, 0xd7, 0xcd, 0x2b, 0x94, 0x5c, 0x1f, 0x34, 0x4a, 0x08, 0xaf, 0xeb, - 0xae, 0x89, 0xa8, 0x48, 0x75, 0x55, 0x95, 0x1d, 0xbb, 0xc0, 0x9a, 0x01, - 0xb9, 0xf4, 0x03, 0x22, 0x3e, 0xd4, 0xe6, 0x52, 0x30, 0x0d, 0x67, 0xb9, - 0xc0, 0x91, 0xfd, 0x2d, 0x4c, 0x30, 0x8e, 0xbd, 0x8c, 0xa5, 0x04, 0x91, - 0xbb, 0xa4, 0xab, 0x7f, 0x0f, 0xd8, 0x6f, 0xf0, 0x66, 0x00, 0xc9, 0xa3, - 0x5c, 0xf5, 0xb0, 0x8f, 0x83, 0xe6, 0x9c, 0x5a, 0xe6, 0xb6, 0xb9, 0xc5, - 0xbc, 0xbe, 0xe4, 0x02, -} - -var certSet3Cert6 = []byte{ - 0x30, 0x82, 0x04, 0x45, 0x30, 0x82, 0x03, 0xae, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x33, 0x65, 0x50, 0x08, 0x79, 0xad, 0x73, 0xe2, 0x30, - 0xb9, 0xe0, 0x1d, 0x0d, 0x7f, 0xac, 0x91, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81, - 0xce, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x5a, 0x41, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, - 0x0c, 0x57, 0x65, 0x73, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x43, 0x61, 0x70, - 0x65, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x09, - 0x43, 0x61, 0x70, 0x65, 0x20, 0x54, 0x6f, 0x77, 0x6e, 0x31, 0x1d, 0x30, - 0x1b, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x14, 0x54, 0x68, 0x61, 0x77, - 0x74, 0x65, 0x20, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x74, 0x69, 0x6e, - 0x67, 0x20, 0x63, 0x63, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, - 0x0b, 0x13, 0x1f, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x73, 0x20, 0x44, 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x21, - 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x18, 0x54, 0x68, 0x61, - 0x77, 0x74, 0x65, 0x20, 0x50, 0x72, 0x65, 0x6d, 0x69, 0x75, 0x6d, 0x20, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x31, 0x28, 0x30, - 0x26, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, - 0x16, 0x19, 0x70, 0x72, 0x65, 0x6d, 0x69, 0x75, 0x6d, 0x2d, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x40, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, - 0x63, 0x6f, 0x6d, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x31, - 0x37, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30, - 0x31, 0x32, 0x33, 0x30, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, - 0x81, 0xa9, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, - 0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, - 0x13, 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, - 0x63, 0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, - 0x1f, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, - 0x44, 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x38, 0x30, 0x36, - 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, - 0x30, 0x30, 0x36, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, - 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, - 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, - 0x55, 0x04, 0x03, 0x13, 0x16, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, - 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, - 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, - 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, - 0xac, 0xa0, 0xf0, 0xfb, 0x80, 0x59, 0xd4, 0x9c, 0xc7, 0xa4, 0xcf, 0x9d, - 0xa1, 0x59, 0x73, 0x09, 0x10, 0x45, 0x0c, 0x0d, 0x2c, 0x6e, 0x68, 0xf1, - 0x6c, 0x5b, 0x48, 0x68, 0x49, 0x59, 0x37, 0xfc, 0x0b, 0x33, 0x19, 0xc2, - 0x77, 0x7f, 0xcc, 0x10, 0x2d, 0x95, 0x34, 0x1c, 0xe6, 0xeb, 0x4d, 0x09, - 0xa7, 0x1c, 0xd2, 0xb8, 0xc9, 0x97, 0x36, 0x02, 0xb7, 0x89, 0xd4, 0x24, - 0x5f, 0x06, 0xc0, 0xcc, 0x44, 0x94, 0x94, 0x8d, 0x02, 0x62, 0x6f, 0xeb, - 0x5a, 0xdd, 0x11, 0x8d, 0x28, 0x9a, 0x5c, 0x84, 0x90, 0x10, 0x7a, 0x0d, - 0xbd, 0x74, 0x66, 0x2f, 0x6a, 0x38, 0xa0, 0xe2, 0xd5, 0x54, 0x44, 0xeb, - 0x1d, 0x07, 0x9f, 0x07, 0xba, 0x6f, 0xee, 0xe9, 0xfd, 0x4e, 0x0b, 0x29, - 0xf5, 0x3e, 0x84, 0xa0, 0x01, 0xf1, 0x9c, 0xab, 0xf8, 0x1c, 0x7e, 0x89, - 0xa4, 0xe8, 0xa1, 0xd8, 0x71, 0x65, 0x0d, 0xa3, 0x51, 0x7b, 0xee, 0xbc, - 0xd2, 0x22, 0x60, 0x0d, 0xb9, 0x5b, 0x9d, 0xdf, 0xba, 0xfc, 0x51, 0x5b, - 0x0b, 0xaf, 0x98, 0xb2, 0xe9, 0x2e, 0xe9, 0x04, 0xe8, 0x62, 0x87, 0xde, - 0x2b, 0xc8, 0xd7, 0x4e, 0xc1, 0x4c, 0x64, 0x1e, 0xdd, 0xcf, 0x87, 0x58, - 0xba, 0x4a, 0x4f, 0xca, 0x68, 0x07, 0x1d, 0x1c, 0x9d, 0x4a, 0xc6, 0xd5, - 0x2f, 0x91, 0xcc, 0x7c, 0x71, 0x72, 0x1c, 0xc5, 0xc0, 0x67, 0xeb, 0x32, - 0xfd, 0xc9, 0x92, 0x5c, 0x94, 0xda, 0x85, 0xc0, 0x9b, 0xbf, 0x53, 0x7d, - 0x2b, 0x09, 0xf4, 0x8c, 0x9d, 0x91, 0x1f, 0x97, 0x6a, 0x52, 0xcb, 0xde, - 0x09, 0x36, 0xa4, 0x77, 0xd8, 0x7b, 0x87, 0x50, 0x44, 0xd5, 0x3e, 0x6e, - 0x29, 0x69, 0xfb, 0x39, 0x49, 0x26, 0x1e, 0x09, 0xa5, 0x80, 0x7b, 0x40, - 0x2d, 0xeb, 0xe8, 0x27, 0x85, 0xc9, 0xfe, 0x61, 0xfd, 0x7e, 0xe6, 0x7c, - 0x97, 0x1d, 0xd5, 0x9d, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xc2, - 0x30, 0x81, 0xbf, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, - 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x3b, 0x06, 0x03, - 0x55, 0x1d, 0x20, 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0x06, 0x04, 0x55, - 0x1d, 0x20, 0x00, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x73, - 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, - 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x0e, 0x06, - 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, - 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, - 0x7b, 0x5b, 0x45, 0xcf, 0xaf, 0xce, 0xcb, 0x7a, 0xfd, 0x31, 0x92, 0x1a, - 0x6a, 0xb6, 0xf3, 0x46, 0xeb, 0x57, 0x48, 0x50, 0x30, 0x40, 0x06, 0x03, - 0x55, 0x1d, 0x1f, 0x04, 0x39, 0x30, 0x37, 0x30, 0x35, 0xa0, 0x33, 0xa0, - 0x31, 0x86, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, - 0x6c, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x50, 0x72, 0x65, 0x6d, 0x69, - 0x75, 0x6d, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x41, 0x2e, 0x63, - 0x72, 0x6c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, - 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x84, 0xa8, 0x4c, - 0xc9, 0x3e, 0x2a, 0xbc, 0x9a, 0xe2, 0xcc, 0x8f, 0x0b, 0xb2, 0x25, 0x77, - 0xc4, 0x61, 0x89, 0x89, 0x63, 0x5a, 0xd4, 0xa3, 0x15, 0x40, 0xd4, 0xfb, - 0x5e, 0x3f, 0xb4, 0x43, 0xea, 0x63, 0x17, 0x2b, 0x6b, 0x99, 0x74, 0x9e, - 0x09, 0xa8, 0xdd, 0xd4, 0x56, 0x15, 0x2e, 0x7a, 0x79, 0x31, 0x5f, 0x63, - 0x96, 0x53, 0x1b, 0x34, 0xd9, 0x15, 0xea, 0x4f, 0x6d, 0x70, 0xca, 0xbe, - 0xf6, 0x82, 0xa9, 0xed, 0xda, 0x85, 0x77, 0xcc, 0x76, 0x1c, 0x6a, 0x81, - 0x0a, 0x21, 0xd8, 0x41, 0x99, 0x7f, 0x5e, 0x2e, 0x82, 0xc1, 0xe8, 0xaa, - 0xf7, 0x93, 0x81, 0x05, 0xaa, 0x92, 0xb4, 0x1f, 0xb7, 0x9a, 0xc0, 0x07, - 0x17, 0xf5, 0xcb, 0xc6, 0xb4, 0x4c, 0x0e, 0xd7, 0x56, 0xdc, 0x71, 0x20, - 0x74, 0x38, 0xd6, 0x74, 0xc6, 0xd6, 0x8f, 0x6b, 0xaf, 0x8b, 0x8d, 0xa0, - 0x6c, 0x29, 0x0b, 0x61, 0xe0, -} - -var certSet3Cert7 = []byte{ - 0x30, 0x82, 0x04, 0x49, 0x30, 0x82, 0x03, 0x31, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x13, 0x06, 0x7f, 0x94, 0x57, 0x85, 0x87, 0xe8, 0xac, 0x77, - 0xde, 0xb2, 0x53, 0x32, 0x5b, 0xbc, 0x99, 0x8b, 0x56, 0x0d, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, - 0x00, 0x30, 0x39, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, - 0x13, 0x02, 0x55, 0x53, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x13, 0x06, 0x41, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x31, 0x19, 0x30, - 0x17, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x10, 0x41, 0x6d, 0x61, 0x7a, - 0x6f, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x31, - 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x35, 0x31, 0x30, 0x32, 0x32, 0x30, 0x30, - 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x35, 0x31, 0x30, 0x31, - 0x39, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x46, 0x31, 0x0b, - 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, - 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x06, 0x41, 0x6d, - 0x61, 0x7a, 0x6f, 0x6e, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, - 0x0b, 0x13, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, - 0x20, 0x31, 0x42, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x03, - 0x13, 0x06, 0x41, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x30, 0x82, 0x01, 0x22, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, - 0x02, 0x82, 0x01, 0x01, 0x00, 0xc2, 0x4e, 0x16, 0x67, 0xdd, 0xce, 0xbc, - 0x6a, 0xc8, 0x37, 0x5a, 0xec, 0x3a, 0x30, 0xb0, 0x1d, 0xe6, 0xd1, 0x12, - 0xe8, 0x12, 0x28, 0x48, 0xcc, 0xe8, 0x29, 0xc1, 0xb9, 0x6e, 0x53, 0xd5, - 0xa3, 0xeb, 0x03, 0x39, 0x1a, 0xcc, 0x77, 0x87, 0xf6, 0x01, 0xb9, 0xd9, - 0x70, 0xcc, 0xcf, 0x6b, 0x8d, 0xe3, 0xe3, 0x03, 0x71, 0x86, 0x99, 0x6d, - 0xcb, 0xa6, 0x94, 0x2a, 0x4e, 0x13, 0xd6, 0xa7, 0xbd, 0x04, 0xec, 0x0a, - 0x16, 0x3c, 0x0a, 0xeb, 0x39, 0xb1, 0xc4, 0xb5, 0x58, 0xa3, 0xb6, 0xc7, - 0x56, 0x25, 0xec, 0x3e, 0x52, 0x7a, 0xa8, 0xe3, 0x29, 0x16, 0x07, 0xb9, - 0x6e, 0x50, 0xcf, 0xfb, 0x5f, 0x31, 0xf8, 0x1d, 0xba, 0x03, 0x4a, 0x62, - 0x89, 0x03, 0xae, 0x3e, 0x47, 0xf2, 0x0f, 0x27, 0x91, 0xe3, 0x14, 0x20, - 0x85, 0xf8, 0xfa, 0xe9, 0x8a, 0x35, 0xf5, 0x5f, 0x9e, 0x99, 0x4d, 0xe7, - 0x6b, 0x37, 0xef, 0xa4, 0x50, 0x3e, 0x44, 0xec, 0xfa, 0x5a, 0x85, 0x66, - 0x07, 0x9c, 0x7e, 0x17, 0x6a, 0x55, 0xf3, 0x17, 0x8a, 0x35, 0x1e, 0xee, - 0xe9, 0xac, 0xc3, 0x75, 0x4e, 0x58, 0x55, 0x7d, 0x53, 0x6b, 0x0a, 0x6b, - 0x9b, 0x14, 0x42, 0xd7, 0xe5, 0xac, 0x01, 0x89, 0xb3, 0xea, 0xa3, 0xfe, - 0xcf, 0xc0, 0x2b, 0x0c, 0x84, 0xc2, 0xd8, 0x53, 0x15, 0xcb, 0x67, 0xf0, - 0xd0, 0x88, 0xca, 0x3a, 0xd1, 0x17, 0x73, 0xf5, 0x5f, 0x9a, 0xd4, 0xc5, - 0x72, 0x1e, 0x7e, 0x01, 0xf1, 0x98, 0x30, 0x63, 0x2a, 0xaa, 0xf2, 0x7a, - 0x2d, 0xc5, 0xe2, 0x02, 0x1a, 0x86, 0xe5, 0x32, 0x3e, 0x0e, 0xbd, 0x11, - 0xb4, 0xcf, 0x3c, 0x93, 0xef, 0x17, 0x50, 0x10, 0x9e, 0x43, 0xc2, 0x06, - 0x2a, 0xe0, 0x0d, 0x68, 0xbe, 0xd3, 0x88, 0x8b, 0x4a, 0x65, 0x8c, 0x4a, - 0xd4, 0xc3, 0x2e, 0x4c, 0x9b, 0x55, 0xf4, 0x86, 0xe5, 0x02, 0x03, 0x01, - 0x00, 0x01, 0xa3, 0x82, 0x01, 0x3b, 0x30, 0x82, 0x01, 0x37, 0x30, 0x12, - 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, - 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, - 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x1d, - 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x59, 0xa4, 0x66, - 0x06, 0x52, 0xa0, 0x7b, 0x95, 0x92, 0x3c, 0xa3, 0x94, 0x07, 0x27, 0x96, - 0x74, 0x5b, 0xf9, 0x3d, 0xd0, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, - 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x84, 0x18, 0xcc, 0x85, 0x34, 0xec, - 0xbc, 0x0c, 0x94, 0x94, 0x2e, 0x08, 0x59, 0x9c, 0xc7, 0xb2, 0x10, 0x4e, - 0x0a, 0x08, 0x30, 0x7b, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x01, 0x01, 0x04, 0x6f, 0x30, 0x6d, 0x30, 0x2f, 0x06, 0x08, 0x2b, 0x06, - 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x23, 0x68, 0x74, 0x74, 0x70, - 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x74, - 0x63, 0x61, 0x31, 0x2e, 0x61, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x3a, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2e, 0x68, 0x74, 0x74, - 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x72, 0x6f, 0x6f, 0x74, - 0x63, 0x61, 0x31, 0x2e, 0x61, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x6f, 0x6f, 0x74, - 0x63, 0x61, 0x31, 0x2e, 0x63, 0x65, 0x72, 0x30, 0x3f, 0x06, 0x03, 0x55, - 0x1d, 0x1f, 0x04, 0x38, 0x30, 0x36, 0x30, 0x34, 0xa0, 0x32, 0xa0, 0x30, - 0x86, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, - 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x63, 0x61, 0x31, 0x2e, 0x61, 0x6d, 0x61, - 0x7a, 0x6f, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x63, 0x61, 0x31, 0x2e, 0x63, 0x72, 0x6c, - 0x30, 0x13, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x0c, 0x30, 0x0a, 0x30, - 0x08, 0x06, 0x06, 0x67, 0x81, 0x0c, 0x01, 0x02, 0x01, 0x30, 0x0d, 0x06, - 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, - 0x03, 0x82, 0x01, 0x01, 0x00, 0x85, 0x92, 0xbe, 0x35, 0xbb, 0x79, 0xcf, - 0xa3, 0x81, 0x42, 0x1c, 0xe4, 0xe3, 0x63, 0x73, 0x53, 0x39, 0x52, 0x35, - 0xe7, 0xd1, 0xad, 0xfd, 0xae, 0x99, 0x8a, 0xac, 0x89, 0x12, 0x2f, 0xbb, - 0xe7, 0x6f, 0x9a, 0xd5, 0x4e, 0x72, 0xea, 0x20, 0x30, 0x61, 0xf9, 0x97, - 0xb2, 0xcd, 0xa5, 0x27, 0x02, 0x45, 0xa8, 0xca, 0x76, 0x3e, 0x98, 0x4a, - 0x83, 0x9e, 0xb6, 0xe6, 0x45, 0xe0, 0xf2, 0x43, 0xf6, 0x08, 0xde, 0x6d, - 0xe8, 0x6e, 0xdb, 0x31, 0x07, 0x13, 0xf0, 0x2f, 0x31, 0x0d, 0x93, 0x6d, - 0x61, 0x37, 0x7b, 0x58, 0xf0, 0xfc, 0x51, 0x98, 0x91, 0x28, 0x02, 0x4f, - 0x05, 0x76, 0xb7, 0xd3, 0xf0, 0x1b, 0xc2, 0xe6, 0x5e, 0xd0, 0x66, 0x85, - 0x11, 0x0f, 0x2e, 0x81, 0xc6, 0x10, 0x81, 0x29, 0xfe, 0x20, 0x60, 0x48, - 0xf3, 0xf2, 0xf0, 0x84, 0x13, 0x53, 0x65, 0x35, 0x15, 0x11, 0x6b, 0x82, - 0x51, 0x40, 0x55, 0x57, 0x5f, 0x18, 0xb5, 0xb0, 0x22, 0x3e, 0xad, 0xf2, - 0x5e, 0xa3, 0x01, 0xe3, 0xc3, 0xb3, 0xf9, 0xcb, 0x41, 0x5a, 0xe6, 0x52, - 0x91, 0xbb, 0xe4, 0x36, 0x87, 0x4f, 0x2d, 0xa9, 0xa4, 0x07, 0x68, 0x35, - 0xba, 0x94, 0x72, 0xcd, 0x0e, 0xea, 0x0e, 0x7d, 0x57, 0xf2, 0x79, 0xfc, - 0x37, 0xc5, 0x7b, 0x60, 0x9e, 0xb2, 0xeb, 0xc0, 0x2d, 0x90, 0x77, 0x0d, - 0x49, 0x10, 0x27, 0xa5, 0x38, 0xad, 0xc4, 0x12, 0xa3, 0xb4, 0xa3, 0xc8, - 0x48, 0xb3, 0x15, 0x0b, 0x1e, 0xe2, 0xe2, 0x19, 0xdc, 0xc4, 0x76, 0x52, - 0xc8, 0xbc, 0x8a, 0x41, 0x78, 0x70, 0xd9, 0x6d, 0x97, 0xb3, 0x4a, 0x8b, - 0x78, 0x2d, 0x5e, 0xb4, 0x0f, 0xa3, 0x4c, 0x60, 0xca, 0xe1, 0x47, 0xcb, - 0x78, 0x2d, 0x12, 0x17, 0xb1, 0x52, 0x8b, 0xca, 0x39, 0x2c, 0xbd, 0xb5, - 0x2f, 0xc2, 0x33, 0x02, 0x96, 0xab, 0xda, 0x94, 0x7f, -} - -var certSet3Cert8 = []byte{ - 0x30, 0x82, 0x04, 0x4d, 0x30, 0x82, 0x03, 0x35, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x03, 0x02, 0x3a, 0x71, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x42, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x33, 0x31, - 0x32, 0x31, 0x31, 0x32, 0x33, 0x34, 0x35, 0x35, 0x31, 0x5a, 0x17, 0x0d, - 0x32, 0x32, 0x30, 0x35, 0x32, 0x30, 0x32, 0x33, 0x34, 0x35, 0x35, 0x31, - 0x5a, 0x30, 0x42, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, - 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, - 0x03, 0x13, 0x12, 0x52, 0x61, 0x70, 0x69, 0x64, 0x53, 0x53, 0x4c, 0x20, - 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, - 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, - 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xbb, 0x58, 0xc1, 0x12, 0x01, 0x2e, - 0x97, 0xd8, 0x7d, 0x18, 0xaa, 0xc8, 0xc2, 0xe5, 0x85, 0xe2, 0x17, 0x6c, - 0x60, 0x2e, 0xc9, 0x8d, 0x31, 0x05, 0x39, 0x1a, 0x06, 0x98, 0x56, 0xdd, - 0x54, 0xd7, 0x11, 0x8c, 0x59, 0x5b, 0x3d, 0xb1, 0x54, 0xae, 0x4b, 0x21, - 0x85, 0x32, 0x16, 0x5f, 0x54, 0x86, 0xe6, 0xd9, 0xb1, 0xd8, 0x60, 0x89, - 0x6b, 0x58, 0xbe, 0x72, 0xda, 0xa0, 0x00, 0x42, 0x76, 0xb1, 0x27, 0x59, - 0x4c, 0xcd, 0xe3, 0xba, 0xd4, 0x5c, 0xd9, 0xa6, 0x7f, 0xbb, 0x2b, 0x75, - 0xd5, 0x46, 0x44, 0xbd, 0xec, 0x40, 0x5c, 0x59, 0xb7, 0xdd, 0x59, 0x9f, - 0xf1, 0x6a, 0xf7, 0x06, 0xfc, 0xd6, 0x2f, 0x19, 0x8a, 0x95, 0x12, 0xba, - 0x9a, 0xca, 0xd5, 0x30, 0xd2, 0x38, 0xfc, 0x19, 0x3b, 0x5b, 0x15, 0x3b, - 0x36, 0xd0, 0x43, 0x4d, 0xd1, 0x65, 0xa1, 0xd4, 0x8b, 0xc1, 0x60, 0x41, - 0xb3, 0xd6, 0x70, 0x17, 0xcc, 0x39, 0xc0, 0x9c, 0x0c, 0xa0, 0x3d, 0xb7, - 0x11, 0x22, 0x4e, 0xce, 0xd9, 0xa9, 0x7a, 0xd2, 0x2a, 0x62, 0x9c, 0xa0, - 0x0b, 0x4e, 0x2a, 0xd7, 0xc3, 0x61, 0x5a, 0x85, 0xdd, 0x5c, 0x10, 0xb9, - 0x54, 0x3d, 0x2d, 0x03, 0xf8, 0x49, 0xf0, 0xbc, 0x92, 0xb7, 0xb7, 0x9c, - 0x31, 0xc7, 0xe9, 0xb8, 0xaa, 0x82, 0x0b, 0x05, 0xb9, 0x31, 0xcd, 0x08, - 0x5b, 0xbb, 0x22, 0x0b, 0xf6, 0x9c, 0x8e, 0x8a, 0x55, 0x1c, 0x76, 0x43, - 0x76, 0xf0, 0xe2, 0x6e, 0xf0, 0xdf, 0xa8, 0x29, 0x75, 0xe7, 0xc8, 0xa4, - 0x87, 0x8b, 0x6a, 0xf1, 0xbb, 0x08, 0xc9, 0x36, 0x18, 0x65, 0xee, 0x50, - 0x43, 0xb8, 0x5d, 0x72, 0xd5, 0x28, 0x39, 0xe1, 0x53, 0x3e, 0x25, 0x2c, - 0xda, 0x2b, 0x4f, 0xdd, 0x8a, 0x9e, 0x50, 0x50, 0xe0, 0x6f, 0x9a, 0xc4, - 0xd5, 0x19, 0x26, 0x89, 0x01, 0x75, 0x73, 0x09, 0x9b, 0x3b, 0x02, 0x03, - 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x4a, 0x30, 0x82, 0x01, 0x46, 0x30, - 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, - 0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb, 0xab, 0x05, 0x64, 0x0c, 0x11, - 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc, 0x4e, 0x30, 0x1d, 0x06, 0x03, - 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x97, 0xc2, 0x27, 0x50, 0x9e, - 0xc2, 0xc9, 0xec, 0x0c, 0x88, 0x32, 0xc8, 0x7c, 0xad, 0xe2, 0xa6, 0x01, - 0x4f, 0xda, 0x6f, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, - 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, - 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, - 0x02, 0x01, 0x06, 0x30, 0x36, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2f, - 0x30, 0x2d, 0x30, 0x2b, 0xa0, 0x29, 0xa0, 0x27, 0x86, 0x25, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x67, 0x31, 0x2e, 0x73, 0x79, 0x6d, 0x63, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x67, - 0x74, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x2e, 0x63, 0x72, 0x6c, 0x30, - 0x2f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, - 0x23, 0x30, 0x21, 0x30, 0x1f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x30, 0x01, 0x86, 0x13, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x67, 0x32, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x30, 0x4c, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x45, 0x30, 0x43, 0x30, - 0x41, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x07, - 0x36, 0x30, 0x33, 0x30, 0x31, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x02, 0x01, 0x16, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x73, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x29, 0x06, 0x03, 0x55, 0x1d, - 0x11, 0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, 0x31, 0x1a, 0x30, - 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x53, 0x79, 0x6d, 0x61, - 0x6e, 0x74, 0x65, 0x63, 0x50, 0x4b, 0x49, 0x2d, 0x31, 0x2d, 0x35, 0x36, - 0x39, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x35, 0xeb, 0xe1, - 0x8b, 0x20, 0x56, 0x94, 0xba, 0x7a, 0xbd, 0x79, 0xa9, 0xf6, 0xe3, 0xfe, - 0x6e, 0x38, 0xb4, 0x32, 0xc1, 0xa3, 0xdb, 0x58, 0x56, 0x20, 0x3e, 0x7d, - 0xc7, 0x3a, 0xb1, 0x67, 0x69, 0xd5, 0x79, 0x14, 0x1b, 0xf6, 0xfa, 0xec, - 0x60, 0xf2, 0x79, 0xcd, 0x0a, 0x0c, 0x60, 0x8a, 0x74, 0x4c, 0xa3, 0x93, - 0x2a, 0xa0, 0xf0, 0x51, 0x7f, 0xcd, 0xe9, 0xf9, 0x92, 0xfd, 0x96, 0xab, - 0x45, 0xf5, 0x62, 0x3d, 0x3f, 0x60, 0x46, 0x50, 0x13, 0x3d, 0x20, 0x13, - 0x18, 0x2e, 0x94, 0x46, 0xae, 0xd5, 0x21, 0xfe, 0x43, 0xa1, 0xc9, 0x23, - 0xfe, 0x53, 0xc4, 0xbf, 0x1a, 0xd8, 0xac, 0x3a, 0xca, 0xde, 0x66, 0x97, - 0x23, 0xae, 0xd3, 0xdf, 0x4a, 0x4d, 0x73, 0x1f, 0x6f, 0x31, 0xa2, 0x51, - 0x04, 0x16, 0x6a, 0x00, 0xeb, 0xf9, 0x8d, 0x43, 0x81, 0xf0, 0x50, 0xa1, - 0x1f, 0xa6, 0xca, 0x3a, 0xf3, 0x28, 0x3c, 0x5f, 0x51, 0xac, 0xd7, 0x0a, - 0x45, 0x77, 0x4b, 0x0e, 0x52, 0x62, 0x1b, 0xd8, 0x38, 0x51, 0xa0, 0x92, - 0x2d, 0x3f, 0x90, 0x6e, 0xc8, 0x7e, 0x40, 0x9f, 0x20, 0x46, 0x15, 0x5d, - 0xe0, 0x50, 0x7c, 0xe1, 0x76, 0xaf, 0x5e, 0xed, 0x11, 0xd3, 0x2f, 0x13, - 0xb9, 0xb8, 0x25, 0xa4, 0xaf, 0x58, 0x09, 0xaf, 0x35, 0xb4, 0x62, 0x54, - 0x85, 0xe3, 0x48, 0xde, 0xbc, 0xd2, 0x90, 0x7a, 0x7a, 0xa4, 0x84, 0x0d, - 0xa3, 0x42, 0xf2, 0x51, 0xc0, 0xd4, 0xad, 0x53, 0x65, 0x5d, 0x6c, 0xf8, - 0x3f, 0x1f, 0x06, 0xf2, 0x4f, 0xcb, 0x97, 0xa0, 0x4a, 0x59, 0xc6, 0x78, - 0xd1, 0xe8, 0x03, 0xb9, 0x85, 0x6d, 0x2c, 0xba, 0xe1, 0x5f, 0xb6, 0xad, - 0x2b, 0x3e, 0x25, 0x79, 0xc5, 0x8b, 0x56, 0xd5, 0xe3, 0x09, 0x80, 0xea, - 0xc1, 0x27, 0xc2, 0xd9, 0x0e, 0xec, 0x47, 0x0a, 0xe9, 0xd0, 0xca, 0xfc, - 0xd8, -} - -var certSet3Cert9 = []byte{ - 0x30, 0x82, 0x04, 0x4d, 0x30, 0x82, 0x03, 0x35, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x44, 0x4e, 0xf0, - 0x36, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, - 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09, 0x06, - 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17, - 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61, - 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31, - 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x52, 0x6f, - 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, - 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x1e, - 0x17, 0x0d, 0x31, 0x34, 0x30, 0x32, 0x32, 0x30, 0x31, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x34, 0x30, 0x32, 0x32, 0x30, 0x31, - 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x4c, 0x31, 0x0b, 0x30, 0x09, - 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, - 0x17, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, - 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x19, 0x41, - 0x6c, 0x70, 0x68, 0x61, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x20, 0x2d, - 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x2d, 0x20, 0x47, 0x32, - 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, - 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xda, 0x01, 0xec, - 0xe4, 0xec, 0x73, 0x60, 0xfb, 0x7e, 0x8f, 0x6a, 0xb7, 0xc6, 0x17, 0xe3, - 0x92, 0x64, 0x32, 0xd4, 0xac, 0x00, 0xd9, 0xa2, 0x0f, 0xb9, 0xed, 0xee, - 0x6b, 0x8a, 0x86, 0xca, 0x92, 0x67, 0xd9, 0x74, 0xd7, 0x5d, 0x47, 0x02, - 0x3c, 0x8f, 0x40, 0xd6, 0x9e, 0x6d, 0x14, 0xcd, 0xc3, 0xda, 0x29, 0x39, - 0xa7, 0x0f, 0x05, 0x0a, 0x68, 0xa2, 0x66, 0x1a, 0x1e, 0xc4, 0xb2, 0x8b, - 0x76, 0x58, 0xe5, 0xab, 0x5d, 0x1d, 0x8f, 0x40, 0xb3, 0x39, 0x8b, 0xef, - 0x1e, 0x83, 0x7d, 0x22, 0xd0, 0xe3, 0xa9, 0x00, 0x2e, 0xec, 0x53, 0xcf, - 0x62, 0x19, 0x85, 0x44, 0x28, 0x4c, 0xc0, 0x27, 0xcb, 0x7b, 0x0e, 0xec, - 0x10, 0x64, 0x00, 0x10, 0xa4, 0x05, 0xcc, 0xa0, 0x72, 0xbe, 0x41, 0x6c, - 0x31, 0x5b, 0x48, 0xe4, 0xb1, 0xec, 0xb9, 0x23, 0xeb, 0x55, 0x4d, 0xd0, - 0x7d, 0x62, 0x4a, 0xa5, 0xb4, 0xa5, 0xa4, 0x59, 0x85, 0xc5, 0x25, 0x91, - 0xa6, 0xfe, 0xa6, 0x09, 0x9f, 0x06, 0x10, 0x6d, 0x8f, 0x81, 0x0c, 0x64, - 0x40, 0x5e, 0x73, 0x00, 0x9a, 0xe0, 0x2e, 0x65, 0x98, 0x54, 0x10, 0x00, - 0x70, 0x98, 0xc8, 0xe1, 0xed, 0x34, 0x5f, 0xd8, 0x9c, 0xc7, 0x0d, 0xc0, - 0xd6, 0x23, 0x59, 0x45, 0xfc, 0xfe, 0x55, 0x7a, 0x86, 0xee, 0x94, 0x60, - 0x22, 0xf1, 0xae, 0xd1, 0xe6, 0x55, 0x46, 0xf6, 0x99, 0xc5, 0x1b, 0x08, - 0x74, 0x5f, 0xac, 0xb0, 0x64, 0x84, 0x8f, 0x89, 0x38, 0x1c, 0xa1, 0xa7, - 0x90, 0x21, 0x4f, 0x02, 0x6e, 0xbd, 0xe0, 0x61, 0x67, 0xd4, 0xf8, 0x42, - 0x87, 0x0f, 0x0a, 0xf7, 0xc9, 0x04, 0x6d, 0x2a, 0xa9, 0x2f, 0xef, 0x42, - 0xa5, 0xdf, 0xdd, 0xa3, 0x53, 0xdb, 0x98, 0x1e, 0x81, 0xf9, 0x9a, 0x72, - 0x7b, 0x5a, 0xde, 0x4f, 0x3e, 0x7f, 0xa2, 0x58, 0xa0, 0xe2, 0x17, 0xad, - 0x67, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x23, 0x30, 0x82, - 0x01, 0x1f, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, - 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, - 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, - 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, - 0x14, 0xf5, 0xcd, 0xd5, 0x3c, 0x08, 0x50, 0xf9, 0x6a, 0x4f, 0x3a, 0xb7, - 0x97, 0xda, 0x56, 0x83, 0xe6, 0x69, 0xd2, 0x68, 0xf7, 0x30, 0x45, 0x06, - 0x03, 0x55, 0x1d, 0x20, 0x04, 0x3e, 0x30, 0x3c, 0x30, 0x3a, 0x06, 0x04, - 0x55, 0x1d, 0x20, 0x00, 0x30, 0x32, 0x30, 0x30, 0x06, 0x08, 0x2b, 0x06, - 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x24, 0x68, 0x74, 0x74, 0x70, - 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, - 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x30, 0x33, 0x06, 0x03, - 0x55, 0x1d, 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, - 0x24, 0x86, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, - 0x6c, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, - 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, - 0x6c, 0x30, 0x3d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, - 0x01, 0x04, 0x31, 0x30, 0x2f, 0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, - 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x6f, - 0x6f, 0x74, 0x72, 0x31, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, - 0x18, 0x30, 0x16, 0x80, 0x14, 0x60, 0x7b, 0x66, 0x1a, 0x45, 0x0d, 0x97, - 0xca, 0x89, 0x50, 0x2f, 0x7d, 0x04, 0xcd, 0x34, 0xa8, 0xff, 0xfc, 0xfd, - 0x4b, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x60, 0x40, 0x68, - 0x16, 0x47, 0xe7, 0x16, 0x8d, 0xdb, 0x5c, 0xa1, 0x56, 0x2a, 0xcb, 0xf4, - 0x5c, 0x9b, 0xb0, 0x1e, 0xa2, 0x4b, 0xf5, 0xcb, 0x02, 0x3f, 0xf8, 0x0b, - 0xa1, 0xf2, 0xa7, 0x42, 0xd4, 0xb7, 0x4c, 0xeb, 0xe3, 0x66, 0x80, 0xf3, - 0x25, 0x43, 0x78, 0x2e, 0x1b, 0x17, 0x56, 0x07, 0x52, 0x18, 0xcb, 0xd1, - 0xa8, 0xec, 0xe6, 0xfb, 0x73, 0x3e, 0xa4, 0x62, 0x8c, 0x80, 0xb4, 0xd2, - 0xc5, 0x12, 0x73, 0xa3, 0xd3, 0xfa, 0x02, 0x38, 0xbe, 0x63, 0x3d, 0x84, - 0xb8, 0x99, 0xc1, 0xf1, 0xba, 0xf7, 0x9f, 0xc3, 0x40, 0xd1, 0x58, 0x18, - 0x53, 0xc1, 0x62, 0xdd, 0xaf, 0x18, 0x42, 0x7f, 0x34, 0x4e, 0xc5, 0x43, - 0xd5, 0x71, 0xb0, 0x30, 0x00, 0xc7, 0xe3, 0x90, 0xae, 0x3f, 0x57, 0x86, - 0x97, 0xce, 0xea, 0x0c, 0x12, 0x8e, 0x22, 0x70, 0xe3, 0x66, 0xa7, 0x54, - 0x7f, 0x2e, 0x28, 0xcb, 0xd4, 0x54, 0xd0, 0xb3, 0x1e, 0x62, 0x67, 0x08, - 0xf9, 0x27, 0xe1, 0xcb, 0xe3, 0x66, 0xb8, 0x24, 0x1b, 0x89, 0x6a, 0x89, - 0x44, 0x65, 0xf2, 0xd9, 0x4c, 0xd2, 0x58, 0x1c, 0x8c, 0x4e, 0xc0, 0x95, - 0xa1, 0xd4, 0xef, 0x67, 0x2f, 0x38, 0x20, 0xe8, 0x2e, 0xff, 0x96, 0x51, - 0xf0, 0xba, 0xd8, 0x3d, 0x92, 0x70, 0x47, 0x65, 0x1c, 0x9e, 0x73, 0x72, - 0xb4, 0x60, 0x0c, 0x5c, 0xe2, 0xd1, 0x73, 0x76, 0xe0, 0xaf, 0x4e, 0xe2, - 0xe5, 0x37, 0xa5, 0x45, 0x2f, 0x8a, 0x23, 0x3e, 0x87, 0xc7, 0x30, 0xe6, - 0x31, 0x38, 0x7c, 0xf4, 0xdd, 0x52, 0xca, 0xf3, 0x53, 0x04, 0x25, 0x57, - 0x56, 0x66, 0x94, 0xe8, 0x0b, 0xee, 0xe6, 0x03, 0x14, 0x4e, 0xee, 0xfd, - 0x6d, 0x94, 0x64, 0x9e, 0x5e, 0xce, 0x79, 0xd4, 0xb2, 0xa6, 0xcf, 0x40, - 0xb1, 0x44, 0xa8, 0x3e, 0x87, 0x19, 0x5e, 0xe9, 0xf8, 0x21, 0x16, 0x59, - 0x53, -} - -var certSet3Cert10 = []byte{ - 0x30, 0x82, 0x04, 0x4f, 0x30, 0x82, 0x03, 0x37, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x03, 0x02, 0x3a, 0x6f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x42, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x33, 0x31, - 0x31, 0x30, 0x35, 0x32, 0x31, 0x33, 0x36, 0x35, 0x30, 0x5a, 0x17, 0x0d, - 0x32, 0x32, 0x30, 0x35, 0x32, 0x30, 0x32, 0x31, 0x33, 0x36, 0x35, 0x30, - 0x5a, 0x30, 0x44, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, - 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x1d, 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, - 0x03, 0x13, 0x14, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x30, - 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, - 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, - 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xe3, 0xbe, 0x7e, 0x0a, - 0x86, 0xa3, 0xcf, 0x6b, 0x6d, 0x3d, 0x2b, 0xa1, 0x97, 0xad, 0x49, 0x24, - 0x4d, 0xd7, 0x77, 0xb9, 0x34, 0x79, 0x08, 0xa5, 0x9e, 0xa2, 0x9e, 0xde, - 0x47, 0x12, 0x92, 0x3d, 0x7e, 0xea, 0x19, 0x86, 0xb1, 0xe8, 0x4f, 0x3d, - 0x5f, 0xf7, 0xd0, 0xa7, 0x77, 0x9a, 0x5b, 0x1f, 0x0a, 0x03, 0xb5, 0x19, - 0x53, 0xdb, 0xa5, 0x21, 0x94, 0x69, 0x63, 0x9d, 0x6a, 0x4c, 0x91, 0x0c, - 0x10, 0x47, 0xbe, 0x11, 0xfa, 0x6c, 0x86, 0x25, 0xb7, 0xab, 0x04, 0x68, - 0x42, 0x38, 0x09, 0x65, 0xf0, 0x14, 0xda, 0x19, 0x9e, 0xfa, 0x6b, 0x0b, - 0xab, 0x62, 0xef, 0x8d, 0xa7, 0xef, 0x63, 0x70, 0x23, 0xa8, 0xaf, 0x81, - 0xf3, 0xd1, 0x6e, 0x88, 0x67, 0x53, 0xec, 0x12, 0xa4, 0x29, 0x75, 0x8a, - 0xa7, 0xf2, 0x57, 0x3d, 0xa2, 0x83, 0x98, 0x97, 0xf2, 0x0a, 0x7d, 0xd4, - 0xe7, 0x43, 0x6e, 0x30, 0x78, 0x62, 0x22, 0x59, 0x59, 0xb8, 0x71, 0x27, - 0x45, 0xaa, 0x0f, 0x66, 0xc6, 0x55, 0x3f, 0xfa, 0x32, 0x17, 0x2b, 0x31, - 0x8f, 0x46, 0xa0, 0xfa, 0x69, 0x14, 0x7c, 0x9d, 0x9f, 0x5a, 0xe2, 0xeb, - 0x33, 0x4e, 0x10, 0xa6, 0xb3, 0xed, 0x77, 0x63, 0xd8, 0xc3, 0x9e, 0xf4, - 0xdd, 0xdf, 0x79, 0x9a, 0x7a, 0xd4, 0xee, 0xde, 0xdd, 0x9a, 0xcc, 0xc3, - 0xb7, 0xa9, 0x5d, 0xcc, 0x11, 0x3a, 0x07, 0xbb, 0x6f, 0x97, 0xa4, 0x01, - 0x23, 0x47, 0x95, 0x1f, 0xa3, 0x77, 0xfa, 0x58, 0x92, 0xc6, 0xc7, 0xd0, - 0xbd, 0xcf, 0x93, 0x18, 0x42, 0xb7, 0x7e, 0xf7, 0x9e, 0x65, 0xea, 0xd5, - 0x3b, 0xca, 0xed, 0xac, 0xc5, 0x70, 0xa1, 0xfe, 0xd4, 0x10, 0x9a, 0xf0, - 0x12, 0x04, 0x44, 0xac, 0x1a, 0x5b, 0x78, 0x50, 0x45, 0x57, 0x4c, 0x6f, - 0xbd, 0x80, 0xcb, 0x81, 0x5c, 0x2d, 0xb3, 0xbc, 0x76, 0xa1, 0x1e, 0x65, - 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x4a, 0x30, 0x82, 0x01, - 0x46, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, - 0x80, 0x14, 0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb, 0xab, 0x05, 0x64, - 0x0c, 0x11, 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc, 0x4e, 0x30, 0x1d, - 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xd2, 0x6f, 0xf7, - 0x96, 0xf4, 0x85, 0x3f, 0x72, 0x3c, 0x30, 0x7d, 0x23, 0xda, 0x85, 0x78, - 0x9b, 0xa3, 0x7c, 0x5a, 0x7c, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, - 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, - 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, - 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x36, 0x06, 0x03, 0x55, 0x1d, 0x1f, - 0x04, 0x2f, 0x30, 0x2d, 0x30, 0x2b, 0xa0, 0x29, 0xa0, 0x27, 0x86, 0x25, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x67, 0x31, 0x2e, 0x73, 0x79, - 0x6d, 0x63, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, - 0x2f, 0x67, 0x74, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x2e, 0x63, 0x72, - 0x6c, 0x30, 0x2f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, - 0x01, 0x04, 0x23, 0x30, 0x21, 0x30, 0x1f, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x13, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x67, 0x32, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x30, 0x4c, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x45, 0x30, - 0x43, 0x30, 0x41, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, - 0x01, 0x07, 0x36, 0x30, 0x33, 0x30, 0x31, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, - 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x29, 0x06, 0x03, - 0x55, 0x1d, 0x11, 0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, 0x31, - 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x53, 0x79, - 0x6d, 0x61, 0x6e, 0x74, 0x65, 0x63, 0x50, 0x4b, 0x49, 0x2d, 0x31, 0x2d, - 0x35, 0x33, 0x39, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, - 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0xa0, - 0xd4, 0xf7, 0x2c, 0xfb, 0x74, 0x0b, 0x7f, 0x64, 0xf1, 0xcd, 0x43, 0x6a, - 0x9f, 0x62, 0x53, 0x1c, 0x02, 0x7c, 0x98, 0x90, 0xa2, 0xee, 0x4f, 0x68, - 0xd4, 0x20, 0x1a, 0x73, 0x12, 0x3e, 0x77, 0xb3, 0x50, 0xeb, 0x72, 0xbc, - 0xee, 0x88, 0xbe, 0x7f, 0x17, 0xea, 0x77, 0x8f, 0x83, 0x61, 0x95, 0x4f, - 0x84, 0xa1, 0xcb, 0x32, 0x4f, 0x6c, 0x21, 0xbe, 0xd2, 0x69, 0x96, 0x7d, - 0x63, 0xbd, 0xdc, 0x2b, 0xa8, 0x1f, 0xd0, 0x13, 0x84, 0x70, 0xfe, 0xf6, - 0x35, 0x95, 0x89, 0xf9, 0xa6, 0x77, 0xb0, 0x46, 0xc8, 0xbb, 0xb7, 0x13, - 0xf5, 0xc9, 0x60, 0x69, 0xd6, 0x4c, 0xfe, 0xd2, 0x8e, 0xef, 0xd3, 0x60, - 0xc1, 0x80, 0x80, 0xe1, 0xe7, 0xfb, 0x8b, 0x6f, 0x21, 0x79, 0x4a, 0xe0, - 0xdc, 0xa9, 0x1b, 0xc1, 0xb7, 0xfb, 0xc3, 0x49, 0x59, 0x5c, 0xb5, 0x77, - 0x07, 0x44, 0xd4, 0x97, 0xfc, 0x49, 0x00, 0x89, 0x6f, 0x06, 0x4e, 0x01, - 0x70, 0x19, 0xac, 0x2f, 0x11, 0xc0, 0xe2, 0xe6, 0x0f, 0x2f, 0x86, 0x4b, - 0x8d, 0x7b, 0xc3, 0xb9, 0xa7, 0x2e, 0xf4, 0xf1, 0xac, 0x16, 0x3e, 0x39, - 0x49, 0x51, 0x9e, 0x17, 0x4b, 0x4f, 0x10, 0x3a, 0x5b, 0xa5, 0xa8, 0x92, - 0x6f, 0xfd, 0xfa, 0xd6, 0x0b, 0x03, 0x4d, 0x47, 0x56, 0x57, 0x19, 0xf3, - 0xcb, 0x6b, 0xf5, 0xf3, 0xd6, 0xcf, 0xb0, 0xf5, 0xf5, 0xa3, 0x11, 0xd2, - 0x20, 0x53, 0x13, 0x34, 0x37, 0x05, 0x2c, 0x43, 0x5a, 0x63, 0xdf, 0x8d, - 0x40, 0xd6, 0x85, 0x1e, 0x51, 0xe9, 0x51, 0x17, 0x1e, 0x03, 0x56, 0xc9, - 0xf1, 0x30, 0xad, 0xe7, 0x9b, 0x11, 0xa2, 0xb9, 0xd0, 0x31, 0x81, 0x9b, - 0x68, 0xb1, 0xd9, 0xe8, 0xf3, 0xe6, 0x94, 0x7e, 0xc7, 0xae, 0x13, 0x2f, - 0x87, 0xed, 0xd0, 0x25, 0xb0, 0x68, 0xf9, 0xde, 0x08, 0x5a, 0xf3, 0x29, - 0xcc, 0xd4, 0x92, -} - -var certSet3Cert11 = []byte{ - 0x30, 0x82, 0x04, 0x59, 0x30, 0x82, 0x03, 0x41, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x03, 0x02, 0x3a, 0x63, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x42, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x32, 0x30, - 0x38, 0x32, 0x37, 0x32, 0x30, 0x34, 0x30, 0x34, 0x30, 0x5a, 0x17, 0x0d, - 0x32, 0x32, 0x30, 0x35, 0x32, 0x30, 0x32, 0x30, 0x34, 0x30, 0x34, 0x30, - 0x5a, 0x30, 0x44, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, - 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x1d, 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, - 0x03, 0x13, 0x14, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, - 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, - 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, - 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb9, 0x27, 0xf9, 0x4f, - 0xd8, 0xf6, 0xb7, 0x15, 0x3f, 0x8f, 0xcd, 0xce, 0xd6, 0x8d, 0x1c, 0x6b, - 0xfd, 0x7f, 0xda, 0x54, 0x21, 0x4e, 0x03, 0xd8, 0xca, 0xd0, 0x72, 0x52, - 0x15, 0xb8, 0xc9, 0x82, 0x5b, 0x58, 0x79, 0x84, 0xff, 0x24, 0x72, 0x6f, - 0xf2, 0x69, 0x7f, 0xbc, 0x96, 0xd9, 0x9a, 0x7a, 0xc3, 0x3e, 0xa9, 0xcf, - 0x50, 0x22, 0x13, 0x0e, 0x86, 0x19, 0xdb, 0xe8, 0x49, 0xef, 0x8b, 0xe6, - 0xd6, 0x47, 0xf2, 0xfd, 0x73, 0x45, 0x08, 0xae, 0x8f, 0xac, 0x5e, 0xb6, - 0xf8, 0x9e, 0x7c, 0xf7, 0x10, 0xff, 0x92, 0x43, 0x66, 0xef, 0x1c, 0xd4, - 0xee, 0xa1, 0x46, 0x88, 0x11, 0x89, 0x49, 0x79, 0x7a, 0x25, 0xce, 0x4b, - 0x6a, 0xf0, 0xd7, 0x1c, 0x76, 0x1a, 0x29, 0x3c, 0xc9, 0xe4, 0xfd, 0x1e, - 0x85, 0xdc, 0xe0, 0x31, 0x65, 0x05, 0x47, 0x16, 0xac, 0x0a, 0x07, 0x4b, - 0x2e, 0x70, 0x5e, 0x6b, 0x06, 0xa7, 0x6b, 0x3a, 0x6c, 0xaf, 0x05, 0x12, - 0xc4, 0xb2, 0x11, 0x25, 0xd6, 0x3e, 0x97, 0x29, 0xf0, 0x83, 0x6c, 0x57, - 0x1c, 0xd8, 0xa5, 0xef, 0xcc, 0xec, 0xfd, 0xd6, 0x12, 0xf1, 0x3f, 0xdb, - 0x40, 0xb4, 0xae, 0x0f, 0x18, 0xd3, 0xc5, 0xaf, 0x40, 0x92, 0x5d, 0x07, - 0x5e, 0x4e, 0xfe, 0x62, 0x17, 0x37, 0x89, 0xe9, 0x8b, 0x74, 0x26, 0xa2, - 0xed, 0xb8, 0x0a, 0xe7, 0x6c, 0x15, 0x5b, 0x35, 0x90, 0x72, 0xdd, 0xd8, - 0x4d, 0x21, 0xd4, 0x40, 0x23, 0x5c, 0x8f, 0xee, 0x80, 0x31, 0x16, 0xab, - 0x68, 0x55, 0xf4, 0x0e, 0x3b, 0x54, 0xe9, 0x04, 0x4d, 0xf0, 0xcc, 0x4e, - 0x81, 0x5e, 0xe9, 0x6f, 0x52, 0x69, 0x4e, 0xbe, 0xa6, 0x16, 0x6d, 0x42, - 0xf5, 0x51, 0xff, 0xe0, 0x0b, 0x56, 0x3c, 0x98, 0x4f, 0x73, 0x8f, 0x0e, - 0x6f, 0x1a, 0x23, 0xf1, 0xc9, 0xc8, 0xd9, 0xdf, 0xbc, 0xec, 0x52, 0xd7, - 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x54, 0x30, 0x82, 0x01, - 0x50, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, - 0x80, 0x14, 0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb, 0xab, 0x05, 0x64, - 0x0c, 0x11, 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc, 0x4e, 0x30, 0x1d, - 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x11, 0x4a, 0xd0, - 0x73, 0x39, 0xd5, 0x5b, 0x69, 0x08, 0x5c, 0xba, 0x3d, 0xbf, 0x64, 0x9a, - 0xa8, 0x8b, 0x1c, 0x55, 0xbc, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, - 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, - 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, - 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x3a, 0x06, 0x03, 0x55, 0x1d, 0x1f, - 0x04, 0x33, 0x30, 0x31, 0x30, 0x2f, 0xa0, 0x2d, 0xa0, 0x2b, 0x86, 0x29, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x67, - 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x67, 0x74, 0x67, 0x6c, 0x6f, 0x62, 0x61, - 0x6c, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, - 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, - 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, - 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, - 0x4c, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x45, 0x30, 0x43, 0x30, 0x41, - 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x07, 0x36, - 0x30, 0x33, 0x30, 0x31, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x02, 0x01, 0x16, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, - 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x73, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x2a, 0x06, 0x03, 0x55, 0x1d, 0x11, - 0x04, 0x23, 0x30, 0x21, 0xa4, 0x1f, 0x30, 0x1d, 0x31, 0x1b, 0x30, 0x19, - 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x56, 0x65, 0x72, 0x69, 0x53, - 0x69, 0x67, 0x6e, 0x4d, 0x50, 0x4b, 0x49, 0x2d, 0x32, 0x2d, 0x32, 0x35, - 0x34, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x3c, 0xe5, 0x3d, - 0x5a, 0x1b, 0xa2, 0x37, 0x2a, 0xe3, 0x46, 0xcf, 0x36, 0x96, 0x18, 0x3c, - 0x7b, 0xf1, 0x84, 0xc5, 0x57, 0x86, 0x77, 0x40, 0x9d, 0x35, 0xf0, 0x12, - 0xf0, 0x78, 0x18, 0xfb, 0x22, 0xa4, 0xde, 0x98, 0x4b, 0x78, 0x81, 0xe6, - 0x4d, 0x86, 0xe3, 0x91, 0x0f, 0x42, 0xe3, 0xb9, 0xdc, 0xa0, 0xd6, 0xff, - 0xa9, 0xf8, 0xb1, 0x79, 0x97, 0x99, 0xd1, 0xc3, 0x6c, 0x42, 0xa5, 0x92, - 0x94, 0xe0, 0x5d, 0x0c, 0x33, 0x18, 0x25, 0xc9, 0x2b, 0x95, 0x53, 0xe0, - 0xe5, 0xa9, 0x0c, 0x7d, 0x47, 0xfe, 0x7f, 0x51, 0x31, 0x44, 0x5e, 0xf7, - 0x2a, 0x1e, 0x35, 0xa2, 0x94, 0x32, 0xf7, 0xc9, 0xee, 0xc0, 0xb6, 0xc6, - 0x9a, 0xac, 0xde, 0x99, 0x21, 0x6a, 0x23, 0xa0, 0x38, 0x64, 0xee, 0xa3, - 0xc4, 0x88, 0x73, 0x32, 0x3b, 0x50, 0xce, 0xbf, 0xad, 0xd3, 0x75, 0x1e, - 0xa6, 0xf4, 0xe9, 0xf9, 0x42, 0x6b, 0x60, 0xb2, 0xdd, 0x45, 0xfd, 0x5d, - 0x57, 0x08, 0xce, 0x2d, 0x50, 0xe6, 0x12, 0x32, 0x16, 0x13, 0x8a, 0xf2, - 0x94, 0xa2, 0x9b, 0x47, 0xa8, 0x86, 0x7f, 0xd9, 0x98, 0xe5, 0xf7, 0xe5, - 0x76, 0x74, 0x64, 0xd8, 0x91, 0xbc, 0x84, 0x16, 0x28, 0xd8, 0x25, 0x44, - 0x30, 0x7e, 0x82, 0xd8, 0xac, 0xb1, 0xe4, 0xc0, 0xe4, 0x15, 0x6c, 0xdb, - 0xb6, 0x24, 0x27, 0x02, 0x2a, 0x01, 0x12, 0x85, 0xba, 0x31, 0x88, 0x58, - 0x47, 0x74, 0xe3, 0xb8, 0xd2, 0x64, 0xa6, 0xc3, 0x32, 0x59, 0x2e, 0x29, - 0x4b, 0x45, 0xf1, 0x5b, 0x89, 0x49, 0x2e, 0x82, 0x9a, 0xc6, 0x18, 0x15, - 0x44, 0xd0, 0x2e, 0x64, 0x01, 0x15, 0x68, 0x38, 0xf9, 0xf6, 0xf9, 0x66, - 0x03, 0x0c, 0x55, 0x1b, 0x9d, 0xbf, 0x00, 0x40, 0xae, 0xf0, 0x48, 0x27, - 0x4c, 0xe0, 0x80, 0x5e, 0x2d, 0xb9, 0x2a, 0x15, 0x7a, 0xbc, 0x66, 0xf8, - 0x35, -} - -var certSet3Cert12 = []byte{ - 0x30, 0x82, 0x04, 0x63, 0x30, 0x82, 0x03, 0x4b, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x44, 0x4e, 0xf0, - 0x3e, 0x20, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, - 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09, 0x06, - 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17, - 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61, - 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31, - 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x52, 0x6f, - 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, - 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x1e, - 0x17, 0x0d, 0x31, 0x34, 0x30, 0x32, 0x32, 0x30, 0x31, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x34, 0x30, 0x32, 0x32, 0x30, 0x31, - 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x60, 0x31, 0x0b, 0x30, 0x09, - 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, - 0x17, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, - 0x31, 0x36, 0x30, 0x34, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2d, 0x47, - 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x44, 0x6f, - 0x6d, 0x61, 0x69, 0x6e, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x53, 0x48, 0x41, - 0x32, 0x35, 0x36, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, - 0x02, 0x82, 0x01, 0x01, 0x00, 0xa9, 0xdd, 0xcc, 0x0e, 0xb3, 0xe2, 0x32, - 0x39, 0xdd, 0x49, 0x22, 0xa8, 0x13, 0x69, 0x93, 0x87, 0x88, 0xe1, 0x0c, - 0xee, 0x71, 0x7d, 0xbd, 0x90, 0x87, 0x96, 0x5d, 0x59, 0xf2, 0xcc, 0xb3, - 0xd2, 0x58, 0x57, 0x57, 0xf9, 0x46, 0xef, 0x6c, 0x26, 0xd8, 0x36, 0x42, - 0x8e, 0x7e, 0x30, 0xb3, 0x2f, 0x9a, 0x3e, 0x53, 0x7b, 0x1f, 0x6e, 0xb6, - 0xa2, 0x4c, 0x45, 0x1f, 0x3c, 0xd3, 0x15, 0x93, 0x1c, 0x89, 0xed, 0x3c, - 0xf4, 0x57, 0xde, 0xca, 0xbd, 0xec, 0x06, 0x9a, 0x6a, 0x2a, 0xa0, 0x19, - 0x52, 0x7f, 0x51, 0xd1, 0x74, 0x39, 0x08, 0x9f, 0xab, 0xeb, 0xd7, 0x86, - 0x13, 0x15, 0x97, 0xae, 0x36, 0xc3, 0x54, 0x66, 0x0e, 0x5a, 0xf2, 0xa0, - 0x73, 0x85, 0x31, 0xe3, 0xb2, 0x64, 0x14, 0x6a, 0xff, 0xa5, 0xa2, 0x8e, - 0x24, 0xbb, 0xbd, 0x85, 0x52, 0x15, 0xa2, 0x79, 0xee, 0xf0, 0xb5, 0xee, - 0x3d, 0xb8, 0xf4, 0x7d, 0x80, 0xbc, 0xd9, 0x90, 0x35, 0x65, 0xb8, 0x17, - 0xa9, 0xad, 0xb3, 0x98, 0x9f, 0xa0, 0x7e, 0x7d, 0x6e, 0xfb, 0x3f, 0xad, - 0x7c, 0xc2, 0x1b, 0x59, 0x36, 0x96, 0xda, 0x37, 0x32, 0x4b, 0x4b, 0x5d, - 0x35, 0x02, 0x63, 0x8e, 0xdb, 0xa7, 0xcf, 0x62, 0xee, 0xcc, 0x2e, 0xd4, - 0x8d, 0xc9, 0xbd, 0x3c, 0x6a, 0x91, 0x72, 0xa2, 0x22, 0xa7, 0x72, 0x2d, - 0x20, 0xd1, 0xfa, 0xca, 0x37, 0xda, 0x18, 0x98, 0xe6, 0x16, 0x24, 0x71, - 0x25, 0x4b, 0xc4, 0xe5, 0x7b, 0x89, 0x52, 0x09, 0x02, 0xfd, 0x59, 0x2b, - 0x04, 0x6e, 0xca, 0x07, 0x81, 0xd4, 0xb3, 0xda, 0xda, 0xdb, 0xe3, 0xcc, - 0x80, 0xa8, 0x56, 0x07, 0x06, 0x7c, 0x96, 0x08, 0x37, 0x9d, 0xdb, 0x38, - 0xb6, 0x62, 0x34, 0x91, 0x62, 0x07, 0x74, 0x01, 0x38, 0xd8, 0x72, 0x30, - 0xe2, 0xeb, 0x90, 0x71, 0x26, 0x62, 0xc0, 0x57, 0xf3, 0x02, 0x03, 0x01, - 0x00, 0x01, 0xa3, 0x82, 0x01, 0x25, 0x30, 0x82, 0x01, 0x21, 0x30, 0x0e, - 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, - 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, - 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x1d, - 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xea, 0x4e, 0x7c, - 0xd4, 0x80, 0x2d, 0xe5, 0x15, 0x81, 0x86, 0x26, 0x8c, 0x82, 0x6d, 0xc0, - 0x98, 0xa4, 0xcf, 0x97, 0x0f, 0x30, 0x47, 0x06, 0x03, 0x55, 0x1d, 0x20, - 0x04, 0x40, 0x30, 0x3e, 0x30, 0x3c, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, - 0x30, 0x34, 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x02, 0x01, 0x16, 0x26, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, - 0x77, 0x77, 0x77, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, - 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, - 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x30, 0x33, 0x06, 0x03, 0x55, 0x1d, - 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86, - 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, - 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x6e, - 0x65, 0x74, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, - 0x3d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, - 0x31, 0x30, 0x2f, 0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x30, 0x01, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, - 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x6f, 0x6f, 0x74, - 0x72, 0x31, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, - 0x16, 0x80, 0x14, 0x60, 0x7b, 0x66, 0x1a, 0x45, 0x0d, 0x97, 0xca, 0x89, - 0x50, 0x2f, 0x7d, 0x04, 0xcd, 0x34, 0xa8, 0xff, 0xfc, 0xfd, 0x4b, 0x30, - 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, - 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0xd7, 0x45, 0x9e, 0xa0, 0xdc, - 0xe0, 0xe3, 0x61, 0x5a, 0x0b, 0x7d, 0x77, 0x84, 0x17, 0x2d, 0x65, 0x5a, - 0x82, 0x9a, 0x8d, 0xa3, 0x27, 0x2a, 0x85, 0xf7, 0xc9, 0xef, 0xe9, 0x86, - 0xfd, 0xd4, 0x47, 0xcd, 0x01, 0x52, 0x96, 0xc5, 0x43, 0xbd, 0x37, 0xb1, - 0xe1, 0xb8, 0xf2, 0xa9, 0xd2, 0x8a, 0x11, 0x84, 0x71, 0x91, 0x15, 0x89, - 0xdc, 0x02, 0x9d, 0x0b, 0xcb, 0x6c, 0x33, 0x85, 0x34, 0x28, 0x9e, 0x20, - 0xb2, 0xb1, 0x97, 0xdc, 0x6d, 0x0b, 0x10, 0xc1, 0x3c, 0xcd, 0x5f, 0xea, - 0x5d, 0xd7, 0x98, 0x31, 0xc5, 0x34, 0x99, 0x5c, 0x00, 0x61, 0x55, 0xc4, - 0x1b, 0x02, 0x5b, 0xc5, 0xe3, 0x89, 0xc8, 0xb4, 0xb8, 0x6f, 0x1e, 0x38, - 0xf2, 0x56, 0x26, 0xe9, 0x41, 0xef, 0x3d, 0xcd, 0xac, 0x99, 0x4f, 0x59, - 0x4a, 0x57, 0x2d, 0x4b, 0x7d, 0xae, 0xc7, 0x88, 0xfb, 0xd6, 0x98, 0x3b, - 0xf5, 0xe5, 0xf0, 0xe8, 0x89, 0x89, 0xb9, 0x8b, 0x03, 0xcb, 0x5a, 0x23, - 0x1f, 0xa4, 0xfd, 0xb8, 0xea, 0xfb, 0x2e, 0x9d, 0xae, 0x6a, 0x73, 0x09, - 0xbc, 0xfc, 0xd5, 0xa0, 0xb5, 0x44, 0x82, 0xab, 0x44, 0x91, 0x2e, 0x50, - 0x2e, 0x57, 0xc1, 0x43, 0xd8, 0x91, 0x04, 0x8b, 0xe9, 0x11, 0x2e, 0x5f, - 0xb4, 0x3f, 0x79, 0xdf, 0x1e, 0xfb, 0x3f, 0x30, 0x00, 0x8b, 0x53, 0xe3, - 0xb7, 0x2c, 0x1d, 0x3b, 0x4d, 0x8b, 0xdc, 0xe4, 0x64, 0x1d, 0x04, 0x58, - 0x33, 0xaf, 0x1b, 0x55, 0xe7, 0xab, 0x0c, 0xbf, 0x30, 0x04, 0x74, 0xe4, - 0xf3, 0x0e, 0x2f, 0x30, 0x39, 0x8d, 0x4b, 0x04, 0x8c, 0x1e, 0x75, 0x66, - 0x66, 0x49, 0xe0, 0xbe, 0x40, 0x34, 0xc7, 0x5c, 0x5a, 0x51, 0x92, 0xba, - 0x12, 0x3c, 0x52, 0xd5, 0x04, 0x82, 0x55, 0x2d, 0x67, 0xa5, 0xdf, 0xb7, - 0x95, 0x7c, 0xee, 0x3f, 0xc3, 0x08, 0xba, 0x04, 0xbe, 0xc0, 0x46, -} - -var certSet3Cert13 = []byte{ - 0x30, 0x82, 0x04, 0x69, 0x30, 0x82, 0x03, 0x51, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x44, 0x4e, 0xf0, - 0x42, 0x47, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, - 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09, 0x06, - 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17, - 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61, - 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31, - 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x52, 0x6f, - 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, - 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x1e, - 0x17, 0x0d, 0x31, 0x34, 0x30, 0x32, 0x32, 0x30, 0x31, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x34, 0x30, 0x32, 0x32, 0x30, 0x31, - 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x66, 0x31, 0x0b, 0x30, 0x09, - 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, - 0x17, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, - 0x31, 0x3c, 0x30, 0x3a, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x33, 0x47, - 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x4f, 0x72, - 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x56, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x43, 0x41, - 0x20, 0x2d, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x2d, 0x20, - 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, - 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xc7, - 0x0e, 0x6c, 0x3f, 0x23, 0x93, 0x7f, 0xcc, 0x70, 0xa5, 0x9d, 0x20, 0xc3, - 0x0e, 0x53, 0x3f, 0x7e, 0xc0, 0x4e, 0xc2, 0x98, 0x49, 0xca, 0x47, 0xd5, - 0x23, 0xef, 0x03, 0x34, 0x85, 0x74, 0xc8, 0xa3, 0x02, 0x2e, 0x46, 0x5c, - 0x0b, 0x7d, 0xc9, 0x88, 0x9d, 0x4f, 0x8b, 0xf0, 0xf8, 0x9c, 0x6c, 0x8c, - 0x55, 0x35, 0xdb, 0xbf, 0xf2, 0xb3, 0xea, 0xfb, 0xe3, 0x56, 0xe7, 0x4a, - 0x46, 0xd9, 0x13, 0x22, 0xca, 0x36, 0xd5, 0x9b, 0xc1, 0xa8, 0xe3, 0x96, - 0x43, 0x93, 0xf2, 0x0c, 0xbc, 0xe6, 0xf9, 0xe6, 0xe8, 0x99, 0xc8, 0x63, - 0x48, 0x78, 0x7f, 0x57, 0x36, 0x69, 0x1a, 0x19, 0x1d, 0x5a, 0xd1, 0xd4, - 0x7d, 0xc2, 0x9c, 0xd4, 0x7f, 0xe1, 0x80, 0x12, 0xae, 0x7a, 0xea, 0x88, - 0xea, 0x57, 0xd8, 0xca, 0x0a, 0x0a, 0x3a, 0x12, 0x49, 0xa2, 0x62, 0x19, - 0x7a, 0x0d, 0x24, 0xf7, 0x37, 0xeb, 0xb4, 0x73, 0x92, 0x7b, 0x05, 0x23, - 0x9b, 0x12, 0xb5, 0xce, 0xeb, 0x29, 0xdf, 0xa4, 0x14, 0x02, 0xb9, 0x01, - 0xa5, 0xd4, 0xa6, 0x9c, 0x43, 0x64, 0x88, 0xde, 0xf8, 0x7e, 0xfe, 0xe3, - 0xf5, 0x1e, 0xe5, 0xfe, 0xdc, 0xa3, 0xa8, 0xe4, 0x66, 0x31, 0xd9, 0x4c, - 0x25, 0xe9, 0x18, 0xb9, 0x89, 0x59, 0x09, 0xae, 0xe9, 0x9d, 0x1c, 0x6d, - 0x37, 0x0f, 0x4a, 0x1e, 0x35, 0x20, 0x28, 0xe2, 0xaf, 0xd4, 0x21, 0x8b, - 0x01, 0xc4, 0x45, 0xad, 0x6e, 0x2b, 0x63, 0xab, 0x92, 0x6b, 0x61, 0x0a, - 0x4d, 0x20, 0xed, 0x73, 0xba, 0x7c, 0xce, 0xfe, 0x16, 0xb5, 0xdb, 0x9f, - 0x80, 0xf0, 0xd6, 0x8b, 0x6c, 0xd9, 0x08, 0x79, 0x4a, 0x4f, 0x78, 0x65, - 0xda, 0x92, 0xbc, 0xbe, 0x35, 0xf9, 0xb3, 0xc4, 0xf9, 0x27, 0x80, 0x4e, - 0xff, 0x96, 0x52, 0xe6, 0x02, 0x20, 0xe1, 0x07, 0x73, 0xe9, 0x5d, 0x2b, - 0xbd, 0xb2, 0xf1, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x25, - 0x30, 0x82, 0x01, 0x21, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, - 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, - 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, - 0xff, 0x02, 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, - 0x16, 0x04, 0x14, 0x96, 0xde, 0x61, 0xf1, 0xbd, 0x1c, 0x16, 0x29, 0x53, - 0x1c, 0xc0, 0xcc, 0x7d, 0x3b, 0x83, 0x00, 0x40, 0xe6, 0x1a, 0x7c, 0x30, - 0x47, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x40, 0x30, 0x3e, 0x30, 0x3c, - 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x34, 0x30, 0x32, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x26, 0x68, 0x74, - 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x6c, - 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, - 0x30, 0x33, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, - 0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, - 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x6f, 0x6f, - 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3d, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x31, 0x30, 0x2f, 0x30, 0x2d, 0x06, - 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x21, 0x68, - 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, - 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x72, 0x31, 0x30, 0x1f, 0x06, 0x03, - 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x60, 0x7b, 0x66, - 0x1a, 0x45, 0x0d, 0x97, 0xca, 0x89, 0x50, 0x2f, 0x7d, 0x04, 0xcd, 0x34, - 0xa8, 0xff, 0xfc, 0xfd, 0x4b, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, - 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, - 0x00, 0x46, 0x2a, 0xee, 0x5e, 0xbd, 0xae, 0x01, 0x60, 0x37, 0x31, 0x11, - 0x86, 0x71, 0x74, 0xb6, 0x46, 0x49, 0xc8, 0x10, 0x16, 0xfe, 0x2f, 0x62, - 0x23, 0x17, 0xab, 0x1f, 0x87, 0xf8, 0x82, 0xed, 0xca, 0xdf, 0x0e, 0x2c, - 0xdf, 0x64, 0x75, 0x8e, 0xe5, 0x18, 0x72, 0xa7, 0x8c, 0x3a, 0x8b, 0xc9, - 0xac, 0xa5, 0x77, 0x50, 0xf7, 0xef, 0x9e, 0xa4, 0xe0, 0xa0, 0x8f, 0x14, - 0x57, 0xa3, 0x2a, 0x5f, 0xec, 0x7e, 0x6d, 0x10, 0xe6, 0xba, 0x8d, 0xb0, - 0x08, 0x87, 0x76, 0x0e, 0x4c, 0xb2, 0xd9, 0x51, 0xbb, 0x11, 0x02, 0xf2, - 0x5c, 0xdd, 0x1c, 0xbd, 0xf3, 0x55, 0x96, 0x0f, 0xd4, 0x06, 0xc0, 0xfc, - 0xe2, 0x23, 0x8a, 0x24, 0x70, 0xd3, 0xbb, 0xf0, 0x79, 0x1a, 0xa7, 0x61, - 0x70, 0x83, 0x8a, 0xaf, 0x06, 0xc5, 0x20, 0xd8, 0xa1, 0x63, 0xd0, 0x6c, - 0xae, 0x4f, 0x32, 0xd7, 0xae, 0x7c, 0x18, 0x45, 0x75, 0x05, 0x29, 0x77, - 0xdf, 0x42, 0x40, 0x64, 0x64, 0x86, 0xbe, 0x2a, 0x76, 0x09, 0x31, 0x6f, - 0x1d, 0x24, 0xf4, 0x99, 0xd0, 0x85, 0xfe, 0xf2, 0x21, 0x08, 0xf9, 0xc6, - 0xf6, 0xf1, 0xd0, 0x59, 0xed, 0xd6, 0x56, 0x3c, 0x08, 0x28, 0x03, 0x67, - 0xba, 0xf0, 0xf9, 0xf1, 0x90, 0x16, 0x47, 0xae, 0x67, 0xe6, 0xbc, 0x80, - 0x48, 0xe9, 0x42, 0x76, 0x34, 0x97, 0x55, 0x69, 0x24, 0x0e, 0x83, 0xd6, - 0xa0, 0x2d, 0xb4, 0xf5, 0xf3, 0x79, 0x8a, 0x49, 0x28, 0x74, 0x1a, 0x41, - 0xa1, 0xc2, 0xd3, 0x24, 0x88, 0x35, 0x30, 0x60, 0x94, 0x17, 0xb4, 0xe1, - 0x04, 0x22, 0x31, 0x3d, 0x3b, 0x2f, 0x17, 0x06, 0xb2, 0xb8, 0x9d, 0x86, - 0x2b, 0x5a, 0x69, 0xef, 0x83, 0xf5, 0x4b, 0xc4, 0xaa, 0xb4, 0x2a, 0xf8, - 0x7c, 0xa1, 0xb1, 0x85, 0x94, 0x8c, 0xf4, 0x0c, 0x87, 0x0c, 0xf4, 0xac, - 0x40, 0xf8, 0x59, 0x49, 0x98, -} - -var certSet3Cert14 = []byte{ - 0x30, 0x82, 0x04, 0x6c, 0x30, 0x82, 0x03, 0x54, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x4d, 0x5f, 0x2c, 0x34, 0x08, 0xb2, 0x4c, 0x20, 0xcd, - 0x6d, 0x50, 0x7e, 0x24, 0x4d, 0xc9, 0xec, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81, - 0xa9, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, - 0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1f, - 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x44, - 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x38, 0x30, 0x36, 0x06, - 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, - 0x30, 0x36, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, - 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x16, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50, - 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, - 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x32, 0x30, 0x38, - 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30, - 0x32, 0x30, 0x37, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x3c, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, - 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0d, 0x54, - 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, - 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, - 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0x99, 0xe4, 0x85, - 0x5b, 0x76, 0x49, 0x7d, 0x2f, 0x05, 0xd8, 0xc5, 0xac, 0xc8, 0xc8, 0xa9, - 0xd3, 0xdc, 0x98, 0xe6, 0xd7, 0x34, 0xa6, 0x2f, 0x0c, 0xf2, 0x22, 0x26, - 0xd8, 0xa3, 0xc9, 0x14, 0x4c, 0x8f, 0x05, 0xa4, 0x45, 0xe8, 0x14, 0x0c, - 0x58, 0x90, 0x05, 0x1a, 0xb7, 0xc5, 0xc1, 0x06, 0xa5, 0x80, 0xaf, 0xbb, - 0x1d, 0x49, 0x6b, 0x52, 0x34, 0x88, 0xc3, 0x59, 0xe7, 0xef, 0x6b, 0xc4, - 0x27, 0x41, 0x8c, 0x2b, 0x66, 0x1d, 0xd0, 0xe0, 0xa3, 0x97, 0x98, 0x19, - 0x34, 0x4b, 0x41, 0xd5, 0x98, 0xd5, 0xc7, 0x05, 0xad, 0xa2, 0xe4, 0xd7, - 0xed, 0x0c, 0xad, 0x4f, 0xc1, 0xb5, 0xb0, 0x21, 0xfd, 0x3e, 0x50, 0x53, - 0xb2, 0xc4, 0x90, 0xd0, 0xd4, 0x30, 0x67, 0x6c, 0x9a, 0xf1, 0x0e, 0x74, - 0xc4, 0xc2, 0xdc, 0x8a, 0xe8, 0x97, 0xff, 0xc9, 0x92, 0xae, 0x01, 0x8a, - 0x56, 0x0a, 0x98, 0x32, 0xb0, 0x00, 0x23, 0xec, 0x90, 0x1a, 0x60, 0xc3, - 0xed, 0xbb, 0x3a, 0xcb, 0x0f, 0x63, 0x9f, 0x0d, 0x44, 0xc9, 0x52, 0xe1, - 0x25, 0x96, 0xbf, 0xed, 0x50, 0x95, 0x89, 0x7f, 0x56, 0x14, 0xb1, 0xb7, - 0x61, 0x1d, 0x1c, 0x07, 0x8c, 0x3a, 0x2c, 0xf7, 0xff, 0x80, 0xde, 0x39, - 0x45, 0xd5, 0xaf, 0x1a, 0xd1, 0x78, 0xd8, 0xc7, 0x71, 0x6a, 0xa3, 0x19, - 0xa7, 0x32, 0x50, 0x21, 0xe9, 0xf2, 0x0e, 0xa1, 0xc6, 0x13, 0x03, 0x44, - 0x48, 0xd1, 0x66, 0xa8, 0x52, 0x57, 0xd7, 0x11, 0xb4, 0x93, 0x8b, 0xe5, - 0x99, 0x9f, 0x5d, 0xe7, 0x78, 0x51, 0xe5, 0x4d, 0xf6, 0xb7, 0x59, 0xb4, - 0x76, 0xb5, 0x09, 0x37, 0x4d, 0x06, 0x38, 0x13, 0x7a, 0x1c, 0x08, 0x98, - 0x5c, 0xc4, 0x48, 0x4a, 0xcb, 0x52, 0xa0, 0xa9, 0xf8, 0xb1, 0x9d, 0x8e, - 0x7b, 0x79, 0xb0, 0x20, 0x2f, 0x3c, 0x96, 0xa8, 0x11, 0x62, 0x47, 0xbb, - 0x11, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xfb, 0x30, 0x81, 0xf8, - 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, - 0x04, 0x26, 0x30, 0x24, 0x30, 0x22, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x30, 0x01, 0x86, 0x16, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, - 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, - 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, - 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, - 0x30, 0x34, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2d, 0x30, 0x2b, 0x30, - 0x29, 0xa0, 0x27, 0xa0, 0x25, 0x86, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x50, - 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, - 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x28, - 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x21, 0x30, 0x1f, 0xa4, 0x1d, 0x30, - 0x1b, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x10, - 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x50, 0x4b, 0x49, - 0x2d, 0x32, 0x2d, 0x39, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, - 0x16, 0x04, 0x14, 0xa7, 0xa2, 0x83, 0xbb, 0x34, 0x45, 0x40, 0x3d, 0xfc, - 0xd5, 0x30, 0x4f, 0x12, 0xb9, 0x3e, 0xa1, 0x01, 0x9f, 0xf6, 0xdb, 0x30, - 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, - 0x7b, 0x5b, 0x45, 0xcf, 0xaf, 0xce, 0xcb, 0x7a, 0xfd, 0x31, 0x92, 0x1a, - 0x6a, 0xb6, 0xf3, 0x46, 0xeb, 0x57, 0x48, 0x50, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, - 0x82, 0x01, 0x01, 0x00, 0x80, 0x22, 0x80, 0xe0, 0x6c, 0xc8, 0x95, 0x16, - 0xd7, 0x57, 0x26, 0x87, 0xf3, 0x72, 0x34, 0xdb, 0xc6, 0x72, 0x56, 0x27, - 0x3e, 0xd3, 0x96, 0xf6, 0x2e, 0x25, 0x91, 0xa5, 0x3e, 0x33, 0x97, 0xa7, - 0x4b, 0xe5, 0x2f, 0xfb, 0x25, 0x7d, 0x2f, 0x07, 0x61, 0xfa, 0x6f, 0x83, - 0x74, 0x4c, 0x4c, 0x53, 0x72, 0x20, 0xa4, 0x7a, 0xcf, 0x51, 0x51, 0x56, - 0x81, 0x88, 0xb0, 0x6d, 0x1f, 0x36, 0x2c, 0xc8, 0x2b, 0xb1, 0x88, 0x99, - 0xc1, 0xfe, 0x44, 0xab, 0x48, 0x51, 0x7c, 0xd8, 0xf2, 0x44, 0x64, 0x2a, - 0xd8, 0x71, 0xa7, 0xfb, 0x1a, 0x2f, 0xf9, 0x19, 0x8d, 0x34, 0xb2, 0x23, - 0xbf, 0xc4, 0x4c, 0x55, 0x1d, 0x8e, 0x44, 0xe8, 0xaa, 0x5d, 0x9a, 0xdd, - 0x9f, 0xfd, 0x03, 0xc7, 0xba, 0x24, 0x43, 0x8d, 0x2d, 0x47, 0x44, 0xdb, - 0xf6, 0xd8, 0x98, 0xc8, 0xb2, 0xf9, 0xda, 0xef, 0xed, 0x29, 0x5c, 0x69, - 0x12, 0xfa, 0xd1, 0x23, 0x96, 0x0f, 0xbf, 0x9c, 0x0d, 0xf2, 0x79, 0x45, - 0x53, 0x37, 0x9a, 0x56, 0x2f, 0xe8, 0x57, 0x10, 0x70, 0xf6, 0xee, 0x89, - 0x0c, 0x49, 0x89, 0x9a, 0xc1, 0x23, 0xf5, 0xc2, 0x2a, 0xcc, 0x41, 0xcf, - 0x22, 0xab, 0x65, 0x6e, 0xb7, 0x94, 0x82, 0x6d, 0x2f, 0x40, 0x5f, 0x58, - 0xde, 0xeb, 0x95, 0x2b, 0xa6, 0x72, 0x68, 0x52, 0x19, 0x91, 0x2a, 0xae, - 0x75, 0x9d, 0x4e, 0x92, 0xe6, 0xca, 0xde, 0x54, 0xea, 0x18, 0xab, 0x25, - 0x3c, 0xe6, 0x64, 0xa6, 0x79, 0x1f, 0x26, 0x7d, 0x61, 0xed, 0x7d, 0xd2, - 0xe5, 0x71, 0x55, 0xd8, 0x93, 0x17, 0x7c, 0x14, 0x38, 0x30, 0x3c, 0xdf, - 0x86, 0xe3, 0x4c, 0xad, 0x49, 0xe3, 0x97, 0x59, 0xce, 0x1b, 0x9b, 0x2b, - 0xce, 0xdc, 0x65, 0xd4, 0x0b, 0x28, 0x6b, 0x4e, 0x84, 0x46, 0x51, 0x44, - 0xf7, 0x33, 0x08, 0x2d, 0x58, 0x97, 0x21, 0xae, -} - -var certSet3Cert15 = []byte{ - 0x30, 0x82, 0x04, 0x6e, 0x30, 0x82, 0x03, 0x56, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x6e, 0x8a, 0x90, 0xeb, 0xcf, 0xf0, 0x44, 0x8a, 0x72, - 0x0d, 0x08, 0x05, 0xd0, 0x82, 0xa5, 0x44, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x58, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, - 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, - 0x2e, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x28, - 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x50, 0x72, 0x69, - 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x33, 0x31, 0x30, - 0x33, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, - 0x33, 0x31, 0x30, 0x33, 0x30, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, - 0x30, 0x47, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, - 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, - 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x03, 0x55, 0x04, 0x03, - 0x13, 0x17, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, - 0x56, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, - 0x34, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, - 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, - 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd9, 0xb4, - 0x05, 0xf2, 0x38, 0x67, 0x0f, 0x09, 0xe7, 0x7c, 0xf5, 0x63, 0x2a, 0xe5, - 0xb9, 0x5e, 0xa8, 0x11, 0xae, 0x75, 0x71, 0xd9, 0x4c, 0x84, 0x67, 0xad, - 0x89, 0x5d, 0xfc, 0x28, 0x3d, 0x2a, 0xb0, 0xa5, 0xd5, 0xd4, 0xe6, 0x30, - 0x0a, 0x84, 0xd4, 0xe4, 0x18, 0xcb, 0x85, 0x37, 0xc5, 0x46, 0x71, 0xeb, - 0x1c, 0x7b, 0x69, 0xdb, 0x65, 0x69, 0x8c, 0x30, 0x05, 0x3e, 0x07, 0xe1, - 0x6f, 0x3c, 0xc1, 0x0b, 0x61, 0xe6, 0x38, 0x44, 0xfc, 0xbc, 0x8c, 0x2f, - 0x4e, 0x75, 0x57, 0xf5, 0x96, 0x99, 0x7c, 0x3e, 0x87, 0x1f, 0x0f, 0x90, - 0x4b, 0x70, 0xc3, 0x3f, 0x39, 0x45, 0x3b, 0x3a, 0x6b, 0xcb, 0xbb, 0x7b, - 0x40, 0x54, 0xd1, 0x8b, 0x4b, 0xa1, 0x72, 0xd2, 0x04, 0xe9, 0xe0, 0x72, - 0x1a, 0x93, 0x11, 0x7a, 0x2f, 0xf1, 0xab, 0x9d, 0x9c, 0x98, 0x58, 0xae, - 0x2c, 0xea, 0x77, 0x5f, 0x2f, 0x2e, 0x87, 0xaf, 0xb8, 0x6b, 0xe3, 0xe2, - 0xe2, 0x3f, 0xd6, 0x3d, 0xe0, 0x96, 0x44, 0xdf, 0x11, 0x55, 0x63, 0x52, - 0x2f, 0xf4, 0x26, 0x78, 0xc4, 0x0f, 0x20, 0x4d, 0x0a, 0xc0, 0x68, 0x70, - 0x15, 0x86, 0x38, 0xee, 0xb7, 0x76, 0x88, 0xab, 0x18, 0x8f, 0x4f, 0x35, - 0x1e, 0xd4, 0x8c, 0xc9, 0xdb, 0x7e, 0x3d, 0x44, 0xd4, 0x36, 0x8c, 0xc1, - 0x37, 0xb5, 0x59, 0x5b, 0x87, 0xf9, 0xe9, 0xf1, 0xd4, 0xc5, 0x28, 0xbd, - 0x1d, 0xdc, 0xcc, 0x96, 0x72, 0xd1, 0x7a, 0xa1, 0xa7, 0x20, 0xb5, 0xb8, - 0xaf, 0xf8, 0x6e, 0xa5, 0x60, 0x7b, 0x2b, 0x8d, 0x1f, 0xee, 0xf4, 0x2b, - 0xd6, 0x69, 0xcd, 0xaf, 0xca, 0x80, 0x58, 0x29, 0xe8, 0x4c, 0x00, 0x20, - 0x8a, 0x49, 0x0a, 0x6e, 0x8e, 0x8c, 0xa8, 0xd1, 0x00, 0x12, 0x84, 0xb6, - 0xc5, 0xe2, 0x95, 0xa2, 0xc0, 0x3b, 0xa4, 0x6b, 0xf0, 0x82, 0xd0, 0x96, - 0x5d, 0x25, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x43, 0x30, - 0x82, 0x01, 0x3f, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, - 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, - 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, - 0x02, 0x01, 0x06, 0x30, 0x2f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x01, 0x01, 0x04, 0x23, 0x30, 0x21, 0x30, 0x1f, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x13, 0x68, 0x74, 0x74, - 0x70, 0x3a, 0x2f, 0x2f, 0x67, 0x32, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x47, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, - 0x40, 0x30, 0x3e, 0x30, 0x3c, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, - 0x34, 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, - 0x01, 0x16, 0x26, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, - 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x73, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x34, 0x06, 0x03, 0x55, 0x1d, 0x1f, - 0x04, 0x2d, 0x30, 0x2b, 0x30, 0x29, 0xa0, 0x27, 0xa0, 0x25, 0x86, 0x23, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x67, 0x31, 0x2e, 0x73, 0x79, - 0x6d, 0x63, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x47, 0x65, 0x6f, 0x54, - 0x72, 0x75, 0x73, 0x74, 0x50, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, - 0x29, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, - 0x30, 0x1c, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, - 0x11, 0x53, 0x79, 0x6d, 0x61, 0x6e, 0x74, 0x65, 0x63, 0x50, 0x4b, 0x49, - 0x2d, 0x31, 0x2d, 0x35, 0x33, 0x38, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, - 0x0e, 0x04, 0x16, 0x04, 0x14, 0xde, 0xcf, 0x5c, 0x50, 0xb7, 0xae, 0x02, - 0x1f, 0x15, 0x17, 0xaa, 0x16, 0xe8, 0x0d, 0xb5, 0x28, 0x9d, 0x6a, 0x5a, - 0xf3, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, - 0x80, 0x14, 0x2c, 0xd5, 0x50, 0x41, 0x97, 0x15, 0x8b, 0xf0, 0x8f, 0x36, - 0x61, 0x5b, 0x4a, 0xfb, 0x6b, 0xd9, 0x99, 0xc9, 0x33, 0x92, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, - 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0xb4, 0x8e, 0xbd, 0x07, 0xb9, 0x9a, - 0x85, 0xec, 0x3b, 0x67, 0xbd, 0x07, 0x60, 0x61, 0xe6, 0x84, 0xd1, 0xd4, - 0xef, 0xeb, 0x1b, 0xba, 0x0b, 0x82, 0x4b, 0x95, 0x64, 0xb6, 0x66, 0x53, - 0x23, 0xbd, 0xb7, 0x84, 0xdd, 0xe4, 0x7b, 0x8d, 0x09, 0xda, 0xcf, 0xb2, - 0xf5, 0xf1, 0xc3, 0xbf, 0x87, 0x84, 0xbe, 0x4e, 0xa6, 0xa8, 0xc2, 0xe7, - 0x12, 0x39, 0x28, 0x34, 0xe0, 0xa4, 0x56, 0x44, 0x40, 0x0c, 0x9f, 0x88, - 0xa3, 0x15, 0xd3, 0xe8, 0xd3, 0x5e, 0xe3, 0x1c, 0x04, 0x60, 0xfb, 0x69, - 0x36, 0x4f, 0x6a, 0x7e, 0x0c, 0x2a, 0x28, 0xc1, 0xf3, 0xaa, 0x58, 0x0e, - 0x6c, 0xce, 0x1d, 0x07, 0xc3, 0x4a, 0xc0, 0x9c, 0x8d, 0xc3, 0x74, 0xb1, - 0xae, 0x82, 0xf0, 0x1a, 0xe1, 0xf9, 0x4e, 0x29, 0xbd, 0x46, 0xde, 0xb7, - 0x1d, 0xf9, 0x7d, 0xdb, 0xd9, 0x0f, 0x84, 0xcb, 0x92, 0x45, 0xcc, 0x1c, - 0xb3, 0x18, 0xf6, 0xa0, 0xcf, 0x71, 0x6f, 0x0c, 0x2e, 0x9b, 0xd2, 0x2d, - 0xb3, 0x99, 0x93, 0x83, 0x44, 0xac, 0x15, 0xaa, 0x9b, 0x2e, 0x67, 0xec, - 0x4f, 0x88, 0x69, 0x05, 0x56, 0x7b, 0x8b, 0xb2, 0x43, 0xa9, 0x3a, 0x6c, - 0x1c, 0x13, 0x33, 0x25, 0x1b, 0xfd, 0xa8, 0xc8, 0x57, 0x02, 0xfb, 0x1c, - 0xe0, 0xd1, 0xbd, 0x3b, 0x56, 0x44, 0x65, 0xc3, 0x63, 0xf5, 0x1b, 0xef, - 0xec, 0x30, 0xd9, 0xe3, 0x6e, 0x2e, 0x13, 0xe9, 0x39, 0x08, 0x2a, 0x0c, - 0x72, 0xf3, 0x9a, 0xcc, 0xf6, 0x27, 0x29, 0x84, 0xd3, 0xef, 0x4c, 0xc7, - 0x84, 0x11, 0x65, 0x1f, 0xc6, 0xe3, 0x81, 0x03, 0xdb, 0x87, 0xcc, 0x78, - 0xf7, 0xb5, 0x9d, 0x96, 0x3e, 0x6a, 0x7f, 0xbc, 0x11, 0x85, 0x7a, 0x75, - 0xe6, 0x41, 0x7d, 0x0d, 0xcf, 0xf9, 0xe5, 0x85, 0x69, 0x25, 0x8f, 0xc7, - 0x8d, 0x07, 0x2d, 0xf8, 0x69, 0x0f, 0xcb, 0x41, 0x53, 0x00, -} - -var certSet3Cert16 = []byte{ - 0x30, 0x82, 0x04, 0x6f, 0x30, 0x82, 0x03, 0x57, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x03, 0x02, 0x3a, 0x73, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x42, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47, - 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x34, 0x30, - 0x36, 0x31, 0x31, 0x32, 0x32, 0x30, 0x32, 0x35, 0x39, 0x5a, 0x17, 0x0d, - 0x32, 0x32, 0x30, 0x35, 0x32, 0x30, 0x32, 0x32, 0x30, 0x32, 0x35, 0x39, - 0x5a, 0x30, 0x66, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, - 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, - 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x1d, 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, - 0x0b, 0x13, 0x14, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x20, 0x56, 0x61, - 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x64, 0x20, 0x53, 0x53, 0x4c, 0x31, - 0x20, 0x30, 0x1e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x17, 0x47, 0x65, - 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x44, 0x56, 0x20, 0x53, 0x53, - 0x4c, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x30, 0x82, 0x01, - 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, - 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb3, 0x44, 0x3a, 0x6c, 0xb0, 0xae, - 0xcb, 0x14, 0xf9, 0x8c, 0x19, 0x74, 0x34, 0x5c, 0xa9, 0x69, 0xe3, 0x88, - 0x53, 0x77, 0xa5, 0xa7, 0xff, 0xbd, 0xd1, 0x3c, 0x0d, 0x27, 0xe4, 0xde, - 0xad, 0x7f, 0xbc, 0xd1, 0x90, 0x58, 0x93, 0xd6, 0xa6, 0xda, 0x39, 0x9c, - 0xad, 0xe1, 0x0e, 0x56, 0x46, 0xee, 0x95, 0x9e, 0x10, 0x68, 0x4c, 0x9c, - 0x2b, 0xf6, 0x6a, 0x3a, 0x8b, 0x80, 0x81, 0x87, 0x06, 0x57, 0x25, 0x1a, - 0x56, 0x52, 0x94, 0xdd, 0x90, 0xeb, 0x67, 0x3b, 0xde, 0xfa, 0xae, 0x36, - 0x68, 0xd3, 0x62, 0x69, 0xf6, 0x6c, 0x82, 0x24, 0x44, 0x4f, 0x87, 0x5c, - 0x98, 0x11, 0x95, 0x64, 0x6b, 0xe8, 0x0c, 0xd1, 0xdd, 0xe6, 0x27, 0x97, - 0xae, 0xcc, 0xe2, 0x91, 0x6a, 0x41, 0x12, 0xb6, 0xab, 0xe5, 0xcc, 0x6e, - 0xcc, 0x23, 0xb8, 0x63, 0x8a, 0x1f, 0x31, 0x93, 0x2d, 0x06, 0xc4, 0xf7, - 0xe8, 0x3d, 0x58, 0xcd, 0x97, 0x08, 0x46, 0x6c, 0x7b, 0x74, 0xc0, 0xf8, - 0xfc, 0x31, 0x3b, 0xa7, 0x7f, 0xd7, 0x8f, 0xb0, 0xc9, 0x15, 0x63, 0x50, - 0x7a, 0x12, 0x4d, 0xf5, 0x12, 0x1e, 0xa3, 0x7e, 0x55, 0xe3, 0x75, 0xb7, - 0xea, 0x1e, 0xea, 0x31, 0x2c, 0x08, 0x4e, 0xd8, 0xcb, 0x43, 0x74, 0x89, - 0x24, 0xbc, 0xd2, 0x0e, 0x1e, 0xf0, 0xdb, 0x05, 0x24, 0xf6, 0x8a, 0xbf, - 0x10, 0x27, 0x84, 0x41, 0x1a, 0xf6, 0x18, 0x53, 0xee, 0x91, 0xd0, 0x54, - 0x17, 0xd3, 0x7d, 0x3e, 0x7e, 0xb2, 0x7d, 0xa8, 0xbf, 0xdb, 0xb9, 0x21, - 0x2a, 0xf0, 0x89, 0xb9, 0x08, 0x6e, 0x5a, 0xb3, 0x5e, 0xea, 0x82, 0xb8, - 0x7e, 0x27, 0x0b, 0xcc, 0x56, 0x73, 0x81, 0x05, 0x4f, 0xe3, 0x96, 0x2d, - 0x71, 0xd5, 0x78, 0xa7, 0x60, 0xc3, 0xd7, 0xec, 0xaa, 0x39, 0x1a, 0x05, - 0x39, 0x82, 0x81, 0xe0, 0x15, 0x2c, 0x35, 0xd1, 0xee, 0x25, 0x02, 0x03, - 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x48, 0x30, 0x82, 0x01, 0x44, 0x30, - 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, - 0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb, 0xab, 0x05, 0x64, 0x0c, 0x11, - 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc, 0x4e, 0x30, 0x1d, 0x06, 0x03, - 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xad, 0x65, 0x22, 0x85, 0x90, - 0xd0, 0x3b, 0xe3, 0xa1, 0x49, 0x8b, 0x37, 0xf9, 0xf1, 0x0b, 0x1d, 0x5f, - 0x17, 0xa0, 0x77, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, - 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, - 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, - 0x02, 0x01, 0x06, 0x30, 0x35, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2e, - 0x30, 0x2c, 0x30, 0x2a, 0xa0, 0x28, 0xa0, 0x26, 0x86, 0x24, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x67, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x67, 0x74, - 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x2e, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x22, - 0x30, 0x20, 0x30, 0x1e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x30, 0x01, 0x86, 0x12, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x67, - 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x64, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x4c, - 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x45, 0x30, 0x43, 0x30, 0x41, 0x06, - 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x07, 0x36, 0x30, - 0x33, 0x30, 0x31, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, - 0x01, 0x16, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, - 0x77, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x2f, 0x63, 0x70, 0x73, 0x30, 0x29, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, - 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, 0x31, 0x1a, 0x30, 0x18, 0x06, - 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x53, 0x79, 0x6d, 0x61, 0x6e, 0x74, - 0x65, 0x63, 0x50, 0x4b, 0x49, 0x2d, 0x31, 0x2d, 0x36, 0x39, 0x39, 0x30, - 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, - 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x4e, 0x27, 0xb8, 0x1a, 0xc7, - 0x3b, 0xdc, 0x5d, 0xbb, 0x9e, 0x1a, 0x35, 0x23, 0x1e, 0x88, 0x55, 0x90, - 0xd1, 0xec, 0x86, 0x9c, 0x88, 0xb7, 0xe0, 0x1f, 0x67, 0x87, 0xe2, 0x7c, - 0xb5, 0x43, 0x03, 0x0e, 0xb6, 0x02, 0xe8, 0xe0, 0xff, 0x86, 0x84, 0x19, - 0x71, 0xe9, 0xf2, 0x4b, 0xf5, 0x9e, 0x2e, 0x2e, 0x5e, 0xdb, 0xab, 0xd6, - 0x1c, 0x4e, 0xc4, 0x3e, 0xb8, 0x2c, 0x78, 0x86, 0x71, 0x10, 0xae, 0x8d, - 0xc5, 0x70, 0xbf, 0xa4, 0xf9, 0x89, 0xe6, 0xb4, 0xed, 0xe8, 0x4b, 0xed, - 0x7c, 0x09, 0x2a, 0x09, 0x08, 0x06, 0x3e, 0xd4, 0xe1, 0xde, 0x82, 0x92, - 0x0c, 0x34, 0x30, 0x35, 0x0a, 0xc1, 0x60, 0x75, 0xca, 0xb6, 0x55, 0x6b, - 0xaa, 0x00, 0x42, 0xcb, 0x3f, 0xfb, 0x10, 0xe1, 0xfb, 0x85, 0xc1, 0x21, - 0x90, 0x72, 0x2b, 0x6e, 0xc0, 0xe8, 0x9d, 0xd9, 0xb5, 0x5a, 0x50, 0x8e, - 0x34, 0x1e, 0xbb, 0x38, 0xa7, 0x3c, 0x31, 0xbd, 0x7a, 0xf2, 0x43, 0x8b, - 0xeb, 0x16, 0xca, 0xad, 0x9b, 0xde, 0x6b, 0x1e, 0xf8, 0x4f, 0xb6, 0x5e, - 0x4a, 0x29, 0x1f, 0x7a, 0x14, 0xee, 0x91, 0xf4, 0x94, 0x4f, 0xa4, 0xbd, - 0x9b, 0x76, 0x7a, 0xbc, 0xf1, 0x51, 0x7a, 0x96, 0xa8, 0x81, 0x0e, 0x83, - 0x87, 0x3f, 0x8b, 0xae, 0x5e, 0x32, 0x9b, 0x34, 0x9e, 0xb2, 0xe7, 0xdb, - 0x2f, 0xec, 0x02, 0xa0, 0xe1, 0xfd, 0x51, 0x52, 0xfe, 0x2c, 0xdb, 0x36, - 0xba, 0xc1, 0xd6, 0x5e, 0x4b, 0x58, 0x6d, 0xde, 0xc6, 0xe1, 0xe1, 0xfa, - 0x9a, 0x03, 0x2c, 0x5b, 0xa2, 0xe1, 0xb3, 0x9b, 0xf9, 0x36, 0xec, 0xc1, - 0x73, 0xfa, 0x33, 0x12, 0x66, 0x95, 0xe3, 0x69, 0x10, 0xb6, 0xd7, 0xaa, - 0x33, 0xfa, 0xf6, 0x9d, 0x41, 0x6d, 0x96, 0x2a, 0xba, 0xbe, 0x83, 0x31, - 0x41, 0x7f, 0x0c, 0x0a, 0xd2, 0x69, 0xd6, 0xfc, 0x35, 0x4c, 0xc3, -} - -var certSet3Cert17 = []byte{ - 0x30, 0x82, 0x04, 0x75, 0x30, 0x82, 0x03, 0x5d, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x09, 0x00, 0xa7, 0x0e, 0x4a, 0x4c, 0x34, 0x82, 0xb7, 0x7f, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x0b, 0x05, 0x00, 0x30, 0x68, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, - 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, - 0x55, 0x04, 0x0a, 0x13, 0x1c, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, - 0x6c, 0x64, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, - 0x69, 0x65, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x32, 0x30, - 0x30, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x29, 0x53, 0x74, 0x61, 0x72, - 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, - 0x32, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, - 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x39, 0x30, 0x39, 0x30, 0x32, 0x30, - 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x33, 0x34, 0x30, 0x36, - 0x32, 0x38, 0x31, 0x37, 0x33, 0x39, 0x31, 0x36, 0x5a, 0x30, 0x81, 0x98, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x07, - 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31, 0x13, 0x30, 0x11, 0x06, - 0x03, 0x55, 0x04, 0x07, 0x13, 0x0a, 0x53, 0x63, 0x6f, 0x74, 0x74, 0x73, - 0x64, 0x61, 0x6c, 0x65, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x13, 0x1c, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, - 0x20, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x65, - 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x3b, 0x30, 0x39, 0x06, - 0x03, 0x55, 0x04, 0x03, 0x13, 0x32, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, - 0x65, 0x6c, 0x64, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, - 0x02, 0x82, 0x01, 0x01, 0x00, 0xd5, 0x0c, 0x3a, 0xc4, 0x2a, 0xf9, 0x4e, - 0xe2, 0xf5, 0xbe, 0x19, 0x97, 0x5f, 0x8e, 0x88, 0x53, 0xb1, 0x1f, 0x3f, - 0xcb, 0xcf, 0x9f, 0x20, 0x13, 0x6d, 0x29, 0x3a, 0xc8, 0x0f, 0x7d, 0x3c, - 0xf7, 0x6b, 0x76, 0x38, 0x63, 0xd9, 0x36, 0x60, 0xa8, 0x9b, 0x5e, 0x5c, - 0x00, 0x80, 0xb2, 0x2f, 0x59, 0x7f, 0xf6, 0x87, 0xf9, 0x25, 0x43, 0x86, - 0xe7, 0x69, 0x1b, 0x52, 0x9a, 0x90, 0xe1, 0x71, 0xe3, 0xd8, 0x2d, 0x0d, - 0x4e, 0x6f, 0xf6, 0xc8, 0x49, 0xd9, 0xb6, 0xf3, 0x1a, 0x56, 0xae, 0x2b, - 0xb6, 0x74, 0x14, 0xeb, 0xcf, 0xfb, 0x26, 0xe3, 0x1a, 0xba, 0x1d, 0x96, - 0x2e, 0x6a, 0x3b, 0x58, 0x94, 0x89, 0x47, 0x56, 0xff, 0x25, 0xa0, 0x93, - 0x70, 0x53, 0x83, 0xda, 0x84, 0x74, 0x14, 0xc3, 0x67, 0x9e, 0x04, 0x68, - 0x3a, 0xdf, 0x8e, 0x40, 0x5a, 0x1d, 0x4a, 0x4e, 0xcf, 0x43, 0x91, 0x3b, - 0xe7, 0x56, 0xd6, 0x00, 0x70, 0xcb, 0x52, 0xee, 0x7b, 0x7d, 0xae, 0x3a, - 0xe7, 0xbc, 0x31, 0xf9, 0x45, 0xf6, 0xc2, 0x60, 0xcf, 0x13, 0x59, 0x02, - 0x2b, 0x80, 0xcc, 0x34, 0x47, 0xdf, 0xb9, 0xde, 0x90, 0x65, 0x6d, 0x02, - 0xcf, 0x2c, 0x91, 0xa6, 0xa6, 0xe7, 0xde, 0x85, 0x18, 0x49, 0x7c, 0x66, - 0x4e, 0xa3, 0x3a, 0x6d, 0xa9, 0xb5, 0xee, 0x34, 0x2e, 0xba, 0x0d, 0x03, - 0xb8, 0x33, 0xdf, 0x47, 0xeb, 0xb1, 0x6b, 0x8d, 0x25, 0xd9, 0x9b, 0xce, - 0x81, 0xd1, 0x45, 0x46, 0x32, 0x96, 0x70, 0x87, 0xde, 0x02, 0x0e, 0x49, - 0x43, 0x85, 0xb6, 0x6c, 0x73, 0xbb, 0x64, 0xea, 0x61, 0x41, 0xac, 0xc9, - 0xd4, 0x54, 0xdf, 0x87, 0x2f, 0xc7, 0x22, 0xb2, 0x26, 0xcc, 0x9f, 0x59, - 0x54, 0x68, 0x9f, 0xfc, 0xbe, 0x2a, 0x2f, 0xc4, 0x55, 0x1c, 0x75, 0x40, - 0x60, 0x17, 0x85, 0x02, 0x55, 0x39, 0x8b, 0x7f, 0x05, 0x02, 0x03, 0x01, - 0x00, 0x01, 0xa3, 0x81, 0xf0, 0x30, 0x81, 0xed, 0x30, 0x0f, 0x06, 0x03, - 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, - 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, - 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, - 0x04, 0x16, 0x04, 0x14, 0x9c, 0x5f, 0x00, 0xdf, 0xaa, 0x01, 0xd7, 0x30, - 0x2b, 0x38, 0x88, 0xa2, 0xb8, 0x6d, 0x4a, 0x9c, 0xf2, 0x11, 0x91, 0x83, - 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, - 0x14, 0xbf, 0x5f, 0xb7, 0xd1, 0xce, 0xdd, 0x1f, 0x86, 0xf4, 0x5b, 0x55, - 0xac, 0xdc, 0xd7, 0x10, 0xc2, 0x0e, 0xa9, 0x88, 0xe7, 0x30, 0x4f, 0x06, - 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x43, 0x30, - 0x41, 0x30, 0x1c, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, - 0x01, 0x86, 0x10, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x2e, - 0x73, 0x73, 0x32, 0x2e, 0x75, 0x73, 0x2f, 0x30, 0x21, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x15, 0x68, 0x74, 0x74, - 0x70, 0x3a, 0x2f, 0x2f, 0x78, 0x2e, 0x73, 0x73, 0x32, 0x2e, 0x75, 0x73, - 0x2f, 0x78, 0x2e, 0x63, 0x65, 0x72, 0x30, 0x26, 0x06, 0x03, 0x55, 0x1d, - 0x1f, 0x04, 0x1f, 0x30, 0x1d, 0x30, 0x1b, 0xa0, 0x19, 0xa0, 0x17, 0x86, - 0x15, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x73, 0x2e, 0x73, 0x73, - 0x32, 0x2e, 0x75, 0x73, 0x2f, 0x72, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x11, - 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x0a, 0x30, 0x08, 0x30, 0x06, 0x06, - 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, - 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, - 0x00, 0x23, 0x1d, 0xe3, 0x8a, 0x57, 0xca, 0x7d, 0xe9, 0x17, 0x79, 0x4c, - 0xf1, 0x1e, 0x55, 0xfd, 0xcc, 0x53, 0x6e, 0x3e, 0x47, 0x0f, 0xdf, 0xc6, - 0x55, 0xf2, 0xb2, 0x04, 0x36, 0xed, 0x80, 0x1f, 0x53, 0xc4, 0x5d, 0x34, - 0x28, 0x6b, 0xbe, 0xc7, 0x55, 0xfc, 0x67, 0xea, 0xcb, 0x3f, 0x7f, 0x90, - 0xb2, 0x33, 0xcd, 0x1b, 0x58, 0x10, 0x82, 0x02, 0xf8, 0xf8, 0x2f, 0xf5, - 0x13, 0x60, 0xd4, 0x05, 0xce, 0xf1, 0x81, 0x08, 0xc1, 0xdd, 0xa7, 0x75, - 0x97, 0x4f, 0x18, 0xb9, 0x6d, 0xde, 0xf7, 0x93, 0x91, 0x08, 0xba, 0x7e, - 0x40, 0x2c, 0xed, 0xc1, 0xea, 0xbb, 0x76, 0x9e, 0x33, 0x06, 0x77, 0x1d, - 0x0d, 0x08, 0x7f, 0x53, 0xdd, 0x1b, 0x64, 0xab, 0x82, 0x27, 0xf1, 0x69, - 0xd5, 0x4d, 0x5e, 0xae, 0xf4, 0xa1, 0xc3, 0x75, 0xa7, 0x58, 0x44, 0x2d, - 0xf2, 0x3c, 0x70, 0x98, 0xac, 0xba, 0x69, 0xb6, 0x95, 0x77, 0x7f, 0x0f, - 0x31, 0x5e, 0x2c, 0xfc, 0xa0, 0x87, 0x3a, 0x47, 0x69, 0xf0, 0x79, 0x5f, - 0xf4, 0x14, 0x54, 0xa4, 0x95, 0x5e, 0x11, 0x78, 0x12, 0x60, 0x27, 0xce, - 0x9f, 0xc2, 0x77, 0xff, 0x23, 0x53, 0x77, 0x5d, 0xba, 0xff, 0xea, 0x59, - 0xe7, 0xdb, 0xcf, 0xaf, 0x92, 0x96, 0xef, 0x24, 0x9a, 0x35, 0x10, 0x7a, - 0x9c, 0x91, 0xc6, 0x0e, 0x7d, 0x99, 0xf6, 0x3f, 0x19, 0xdf, 0xf5, 0x72, - 0x54, 0xe1, 0x15, 0xa9, 0x07, 0x59, 0x7b, 0x83, 0xbf, 0x52, 0x2e, 0x46, - 0x8c, 0xb2, 0x00, 0x64, 0x76, 0x1c, 0x48, 0xd3, 0xd8, 0x79, 0xe8, 0x6e, - 0x56, 0xcc, 0xae, 0x2c, 0x03, 0x90, 0xd7, 0x19, 0x38, 0x99, 0xe4, 0xca, - 0x09, 0x19, 0x5b, 0xff, 0x07, 0x96, 0xb0, 0xa8, 0x7f, 0x34, 0x49, 0xdf, - 0x56, 0xa9, 0xf7, 0xb0, 0x5f, 0xed, 0x33, 0xed, 0x8c, 0x47, 0xb7, 0x30, - 0x03, 0x5d, 0xf4, 0x03, 0x8c, -} - -var certSet3Cert18 = []byte{ - 0x30, 0x82, 0x04, 0x79, 0x30, 0x82, 0x03, 0x61, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x04, 0x07, 0x27, 0xa2, 0x76, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x5a, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, - 0x45, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09, - 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30, - 0x11, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65, - 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, - 0x55, 0x04, 0x03, 0x13, 0x19, 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, - 0x72, 0x65, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, - 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x34, - 0x30, 0x32, 0x32, 0x37, 0x31, 0x38, 0x30, 0x39, 0x32, 0x37, 0x5a, 0x17, - 0x0d, 0x32, 0x30, 0x30, 0x36, 0x30, 0x39, 0x31, 0x37, 0x30, 0x37, 0x32, - 0x39, 0x5a, 0x30, 0x5a, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, - 0x06, 0x13, 0x02, 0x4a, 0x50, 0x31, 0x23, 0x30, 0x21, 0x06, 0x03, 0x55, - 0x04, 0x0a, 0x13, 0x1a, 0x43, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, 0x75, - 0x73, 0x74, 0x20, 0x4a, 0x61, 0x70, 0x61, 0x6e, 0x20, 0x43, 0x6f, 0x2e, - 0x2c, 0x20, 0x4c, 0x74, 0x64, 0x2e, 0x31, 0x26, 0x30, 0x24, 0x06, 0x03, - 0x55, 0x04, 0x03, 0x13, 0x1d, 0x43, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x20, 0x4a, 0x61, 0x70, 0x61, 0x6e, 0x20, 0x50, 0x75, - 0x62, 0x6c, 0x69, 0x63, 0x20, 0x43, 0x41, 0x20, 0x47, 0x33, 0x30, 0x82, - 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, - 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, - 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0x94, 0x56, 0xa3, 0x45, 0x44, - 0x54, 0xaa, 0x60, 0x64, 0xbf, 0xb8, 0x57, 0x9f, 0x4e, 0xdb, 0xd4, 0x79, - 0x68, 0x5f, 0x13, 0x05, 0xf4, 0x3f, 0xcd, 0x25, 0xdd, 0x3c, 0x5e, 0x58, - 0x77, 0x1c, 0x9d, 0xe6, 0x9f, 0xe3, 0x32, 0x49, 0xef, 0x02, 0x3a, 0x34, - 0x53, 0x8d, 0x52, 0xe5, 0xe3, 0x39, 0x66, 0x1f, 0xe7, 0x33, 0x61, 0xb6, - 0x27, 0xc6, 0x24, 0x55, 0x50, 0x27, 0x02, 0x65, 0xf0, 0xb0, 0x8c, 0x41, - 0x8d, 0x30, 0x5e, 0x47, 0x5b, 0x82, 0x6f, 0xc7, 0x9c, 0xa3, 0x28, 0x43, - 0x6d, 0x58, 0x7b, 0xc8, 0x15, 0x98, 0x4e, 0x25, 0x6f, 0xcb, 0x76, 0x27, - 0x5b, 0x0b, 0x2c, 0x2c, 0xb5, 0x98, 0x23, 0xe7, 0x8b, 0x7c, 0xfd, 0x77, - 0x1a, 0xc4, 0x52, 0xba, 0x5d, 0x19, 0xee, 0x78, 0x21, 0x4d, 0x21, 0x9a, - 0xd9, 0x12, 0x7c, 0x33, 0x15, 0x6b, 0x1a, 0xc9, 0x81, 0xea, 0xda, 0xda, - 0x57, 0xb7, 0xd5, 0x2f, 0xce, 0x1f, 0x4b, 0xfc, 0xb4, 0x33, 0xe0, 0xa0, - 0xc9, 0x94, 0x27, 0xbb, 0x27, 0x40, 0xb6, 0x90, 0xdb, 0xac, 0x9e, 0x75, - 0xa6, 0x11, 0x2b, 0x49, 0x19, 0x2d, 0xc3, 0xc2, 0x43, 0x07, 0x09, 0xbb, - 0x3d, 0x6e, 0x88, 0xa3, 0xe3, 0x8a, 0xc5, 0xd2, 0x86, 0xf6, 0x65, 0x5b, - 0x34, 0xc3, 0x9f, 0x4c, 0x02, 0xe5, 0x09, 0xba, 0x2c, 0xc6, 0x76, 0x66, - 0xeb, 0xd1, 0x76, 0x25, 0xf4, 0x30, 0x13, 0xfb, 0x58, 0x60, 0xa8, 0x58, - 0xe3, 0x51, 0x6f, 0x4b, 0x08, 0x04, 0x61, 0x8d, 0xac, 0xa9, 0x30, 0x2f, - 0x52, 0x41, 0xa3, 0x22, 0xc1, 0x33, 0x59, 0xab, 0x7b, 0x59, 0xf9, 0x93, - 0x67, 0x4b, 0xc9, 0x89, 0x75, 0x52, 0xef, 0x29, 0x49, 0x34, 0x93, 0x1c, - 0x9c, 0x93, 0x73, 0x9c, 0x19, 0xce, 0x5c, 0x18, 0xcd, 0x4c, 0x09, 0x27, - 0xc1, 0x3f, 0xf5, 0x49, 0xec, 0xf4, 0xe2, 0xdf, 0x4b, 0xaf, 0x8f, 0x02, - 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x45, 0x30, 0x82, 0x01, 0x41, - 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, - 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x53, 0x06, 0x03, - 0x55, 0x1d, 0x20, 0x04, 0x4c, 0x30, 0x4a, 0x30, 0x48, 0x06, 0x09, 0x2b, - 0x06, 0x01, 0x04, 0x01, 0xb1, 0x3e, 0x01, 0x00, 0x30, 0x3b, 0x30, 0x39, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x2d, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x79, 0x62, 0x65, 0x72, - 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, - 0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, - 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x63, 0x66, 0x6d, 0x30, 0x42, 0x06, - 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x36, 0x30, - 0x34, 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, - 0x01, 0x86, 0x26, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, - 0x73, 0x70, 0x2e, 0x6f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, - 0x65, 0x72, 0x6f, 0x6f, 0x74, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, - 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1f, 0x06, - 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xe5, 0x9d, - 0x59, 0x30, 0x82, 0x47, 0x58, 0xcc, 0xac, 0xfa, 0x08, 0x54, 0x36, 0x86, - 0x7b, 0x3a, 0xb5, 0x04, 0x4d, 0xf0, 0x30, 0x42, 0x06, 0x03, 0x55, 0x1d, - 0x1f, 0x04, 0x3b, 0x30, 0x39, 0x30, 0x37, 0xa0, 0x35, 0xa0, 0x33, 0x86, - 0x31, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x64, 0x70, 0x31, - 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72, 0x75, 0x73, - 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x4f, 0x6d, - 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x32, 0x30, 0x32, 0x35, 0x2e, 0x63, - 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, - 0x14, 0x73, 0xa8, 0x08, 0x53, 0x29, 0xb8, 0x15, 0xfb, 0x99, 0x80, 0xe5, - 0xc5, 0x37, 0xd8, 0xf8, 0x39, 0x7b, 0xa4, 0x13, 0x06, 0x30, 0x0d, 0x06, - 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, - 0x03, 0x82, 0x01, 0x01, 0x00, 0x68, 0xdf, 0xfe, 0x72, 0x54, 0x4e, 0x1b, - 0xfb, 0x5c, 0x6e, 0x5a, 0x45, 0x46, 0xcf, 0x42, 0xbe, 0xb2, 0x02, 0x9c, - 0x9d, 0x90, 0x6a, 0x09, 0x2e, 0xb7, 0x36, 0x64, 0x24, 0xb6, 0xb1, 0xe2, - 0x48, 0x67, 0xce, 0x17, 0x46, 0x9b, 0x23, 0x75, 0x78, 0x11, 0xf6, 0xc6, - 0x09, 0x38, 0x42, 0x62, 0x96, 0x97, 0x30, 0x7b, 0x51, 0x77, 0xdf, 0x33, - 0xb5, 0x00, 0x51, 0x29, 0xd5, 0x24, 0xfe, 0xb7, 0x98, 0xa2, 0xac, 0x6c, - 0xa1, 0x13, 0x7f, 0xca, 0xf3, 0xb7, 0xa6, 0x52, 0xc2, 0x16, 0x0d, 0xec, - 0x3a, 0xbf, 0xa3, 0x37, 0x77, 0x4f, 0xae, 0x7b, 0x55, 0x1d, 0x46, 0xe9, - 0x10, 0xda, 0xc3, 0xb4, 0x05, 0x5c, 0x5b, 0xf6, 0x48, 0x21, 0x00, 0x89, - 0xf4, 0xbb, 0x38, 0x8e, 0x1e, 0x33, 0xf3, 0x49, 0x97, 0x81, 0x31, 0x6c, - 0x16, 0x74, 0x08, 0x91, 0x17, 0xc0, 0xd3, 0x25, 0xb3, 0xbc, 0xc1, 0x15, - 0xb5, 0xa4, 0xcd, 0x84, 0x4d, 0xb9, 0xc8, 0xeb, 0xc5, 0x59, 0x42, 0x10, - 0x14, 0x25, 0x79, 0xf8, 0xdb, 0xb6, 0xd0, 0xe6, 0xd3, 0xa0, 0x14, 0x7c, - 0x17, 0x1c, 0x20, 0x1e, 0xed, 0x99, 0x90, 0x65, 0xc0, 0x41, 0x71, 0xc3, - 0xab, 0x3f, 0x29, 0x41, 0x67, 0xf9, 0xe2, 0xd1, 0x98, 0xe3, 0xf8, 0xdf, - 0x3a, 0xb8, 0xca, 0xa3, 0x6f, 0x68, 0x8b, 0x6c, 0x9f, 0x6e, 0x88, 0x7c, - 0x9d, 0x41, 0x5c, 0xba, 0xcb, 0x19, 0x05, 0x83, 0x9c, 0x99, 0xf4, 0x1a, - 0xd2, 0x24, 0x69, 0x57, 0x0a, 0x0f, 0x7a, 0xc3, 0x1b, 0x2c, 0x4b, 0x06, - 0xd3, 0x2a, 0x97, 0x7e, 0x07, 0xb0, 0xf9, 0x20, 0x5a, 0xb5, 0x92, 0x4b, - 0x5b, 0xa8, 0xeb, 0xeb, 0x36, 0x33, 0x47, 0x36, 0xda, 0x72, 0x9c, 0xbf, - 0x68, 0x45, 0x81, 0x31, 0xbe, 0xd2, 0xfd, 0x3b, 0xe9, 0x72, 0xd5, 0x70, - 0xdd, 0xa6, 0xde, 0x5f, 0x0d, 0xb6, 0x5e, 0x00, 0x49, -} - -var certSet3Cert19 = []byte{ - 0x30, 0x82, 0x04, 0x7d, 0x30, 0x82, 0x03, 0x65, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x03, 0x1b, 0xe7, 0x15, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x63, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x18, 0x54, - 0x68, 0x65, 0x20, 0x47, 0x6f, 0x20, 0x44, 0x61, 0x64, 0x64, 0x79, 0x20, - 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, - 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x28, 0x47, 0x6f, - 0x20, 0x44, 0x61, 0x64, 0x64, 0x79, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, - 0x20, 0x32, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x34, 0x30, 0x31, 0x30, 0x31, - 0x30, 0x37, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x33, 0x31, 0x30, - 0x35, 0x33, 0x30, 0x30, 0x37, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x81, - 0x83, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, - 0x07, 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31, 0x13, 0x30, 0x11, - 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0a, 0x53, 0x63, 0x6f, 0x74, 0x74, - 0x73, 0x64, 0x61, 0x6c, 0x65, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, - 0x04, 0x0a, 0x13, 0x11, 0x47, 0x6f, 0x44, 0x61, 0x64, 0x64, 0x79, 0x2e, - 0x63, 0x6f, 0x6d, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x31, 0x30, - 0x2f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x28, 0x47, 0x6f, 0x20, 0x44, - 0x61, 0x64, 0x64, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, - 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, - 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, - 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xbf, 0x71, 0x62, - 0x08, 0xf1, 0xfa, 0x59, 0x34, 0xf7, 0x1b, 0xc9, 0x18, 0xa3, 0xf7, 0x80, - 0x49, 0x58, 0xe9, 0x22, 0x83, 0x13, 0xa6, 0xc5, 0x20, 0x43, 0x01, 0x3b, - 0x84, 0xf1, 0xe6, 0x85, 0x49, 0x9f, 0x27, 0xea, 0xf6, 0x84, 0x1b, 0x4e, - 0xa0, 0xb4, 0xdb, 0x70, 0x98, 0xc7, 0x32, 0x01, 0xb1, 0x05, 0x3e, 0x07, - 0x4e, 0xee, 0xf4, 0xfa, 0x4f, 0x2f, 0x59, 0x30, 0x22, 0xe7, 0xab, 0x19, - 0x56, 0x6b, 0xe2, 0x80, 0x07, 0xfc, 0xf3, 0x16, 0x75, 0x80, 0x39, 0x51, - 0x7b, 0xe5, 0xf9, 0x35, 0xb6, 0x74, 0x4e, 0xa9, 0x8d, 0x82, 0x13, 0xe4, - 0xb6, 0x3f, 0xa9, 0x03, 0x83, 0xfa, 0xa2, 0xbe, 0x8a, 0x15, 0x6a, 0x7f, - 0xde, 0x0b, 0xc3, 0xb6, 0x19, 0x14, 0x05, 0xca, 0xea, 0xc3, 0xa8, 0x04, - 0x94, 0x3b, 0x46, 0x7c, 0x32, 0x0d, 0xf3, 0x00, 0x66, 0x22, 0xc8, 0x8d, - 0x69, 0x6d, 0x36, 0x8c, 0x11, 0x18, 0xb7, 0xd3, 0xb2, 0x1c, 0x60, 0xb4, - 0x38, 0xfa, 0x02, 0x8c, 0xce, 0xd3, 0xdd, 0x46, 0x07, 0xde, 0x0a, 0x3e, - 0xeb, 0x5d, 0x7c, 0xc8, 0x7c, 0xfb, 0xb0, 0x2b, 0x53, 0xa4, 0x92, 0x62, - 0x69, 0x51, 0x25, 0x05, 0x61, 0x1a, 0x44, 0x81, 0x8c, 0x2c, 0xa9, 0x43, - 0x96, 0x23, 0xdf, 0xac, 0x3a, 0x81, 0x9a, 0x0e, 0x29, 0xc5, 0x1c, 0xa9, - 0xe9, 0x5d, 0x1e, 0xb6, 0x9e, 0x9e, 0x30, 0x0a, 0x39, 0xce, 0xf1, 0x88, - 0x80, 0xfb, 0x4b, 0x5d, 0xcc, 0x32, 0xec, 0x85, 0x62, 0x43, 0x25, 0x34, - 0x02, 0x56, 0x27, 0x01, 0x91, 0xb4, 0x3b, 0x70, 0x2a, 0x3f, 0x6e, 0xb1, - 0xe8, 0x9c, 0x88, 0x01, 0x7d, 0x9f, 0xd4, 0xf9, 0xdb, 0x53, 0x6d, 0x60, - 0x9d, 0xbf, 0x2c, 0xe7, 0x58, 0xab, 0xb8, 0x5f, 0x46, 0xfc, 0xce, 0xc4, - 0x1b, 0x03, 0x3c, 0x09, 0xeb, 0x49, 0x31, 0x5c, 0x69, 0x46, 0xb3, 0xe0, - 0x47, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x17, 0x30, 0x82, - 0x01, 0x13, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, - 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, - 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, - 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x3a, 0x9a, - 0x85, 0x07, 0x10, 0x67, 0x28, 0xb6, 0xef, 0xf6, 0xbd, 0x05, 0x41, 0x6e, - 0x20, 0xc1, 0x94, 0xda, 0x0f, 0xde, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, - 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xd2, 0xc4, 0xb0, 0xd2, 0x91, - 0xd4, 0x4c, 0x11, 0x71, 0xb3, 0x61, 0xcb, 0x3d, 0xa1, 0xfe, 0xdd, 0xa8, - 0x6a, 0xd4, 0xe3, 0x30, 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x01, 0x01, 0x04, 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, - 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6f, 0x64, - 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x32, 0x06, - 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2b, 0x30, 0x29, 0x30, 0x27, 0xa0, 0x25, - 0xa0, 0x23, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, - 0x72, 0x6c, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x67, 0x64, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, - 0x6c, 0x30, 0x46, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x3f, 0x30, 0x3d, - 0x30, 0x3b, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x33, 0x30, 0x31, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x25, - 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, - 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, - 0x2f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x59, 0x0b, 0x53, - 0xbd, 0x92, 0x86, 0x11, 0xa7, 0x24, 0x7b, 0xed, 0x5b, 0x31, 0xcf, 0x1d, - 0x1f, 0x6c, 0x70, 0xc5, 0xb8, 0x6e, 0xbe, 0x4e, 0xbb, 0xf6, 0xbe, 0x97, - 0x50, 0xe1, 0x30, 0x7f, 0xba, 0x28, 0x5c, 0x62, 0x94, 0xc2, 0xe3, 0x7e, - 0x33, 0xf7, 0xfb, 0x42, 0x76, 0x85, 0xdb, 0x95, 0x1c, 0x8c, 0x22, 0x58, - 0x75, 0x09, 0x0c, 0x88, 0x65, 0x67, 0x39, 0x0a, 0x16, 0x09, 0xc5, 0xa0, - 0x38, 0x97, 0xa4, 0xc5, 0x23, 0x93, 0x3f, 0xb4, 0x18, 0xa6, 0x01, 0x06, - 0x44, 0x91, 0xe3, 0xa7, 0x69, 0x27, 0xb4, 0x5a, 0x25, 0x7f, 0x3a, 0xb7, - 0x32, 0xcd, 0xdd, 0x84, 0xff, 0x2a, 0x38, 0x29, 0x33, 0xa4, 0xdd, 0x67, - 0xb2, 0x85, 0xfe, 0xa1, 0x88, 0x20, 0x1c, 0x50, 0x89, 0xc8, 0xdc, 0x2a, - 0xf6, 0x42, 0x03, 0x37, 0x4c, 0xe6, 0x88, 0xdf, 0xd5, 0xaf, 0x24, 0xf2, - 0xb1, 0xc3, 0xdf, 0xcc, 0xb5, 0xec, 0xe0, 0x99, 0x5e, 0xb7, 0x49, 0x54, - 0x20, 0x3c, 0x94, 0x18, 0x0c, 0xc7, 0x1c, 0x52, 0x18, 0x49, 0xa4, 0x6d, - 0xe1, 0xb3, 0x58, 0x0b, 0xc9, 0xd8, 0xec, 0xd9, 0xae, 0x1c, 0x32, 0x8e, - 0x28, 0x70, 0x0d, 0xe2, 0xfe, 0xa6, 0x17, 0x9e, 0x84, 0x0f, 0xbd, 0x57, - 0x70, 0xb3, 0x5a, 0xe9, 0x1f, 0xa0, 0x86, 0x53, 0xbb, 0xef, 0x7c, 0xff, - 0x69, 0x0b, 0xe0, 0x48, 0xc3, 0xb7, 0x93, 0x0b, 0xc8, 0x0a, 0x54, 0xc4, - 0xac, 0x5d, 0x14, 0x67, 0x37, 0x6c, 0xca, 0xa5, 0x2f, 0x31, 0x08, 0x37, - 0xaa, 0x6e, 0x6f, 0x8c, 0xbc, 0x9b, 0xe2, 0x57, 0x5d, 0x24, 0x81, 0xaf, - 0x97, 0x97, 0x9c, 0x84, 0xad, 0x6c, 0xac, 0x37, 0x4c, 0x66, 0xf3, 0x61, - 0x91, 0x11, 0x20, 0xe4, 0xbe, 0x30, 0x9f, 0x7a, 0xa4, 0x29, 0x09, 0xb0, - 0xe1, 0x34, 0x5f, 0x64, 0x77, 0x18, 0x40, 0x51, 0xdf, 0x8c, 0x30, 0xa6, - 0xaf, -} - -var certSet3Cert20 = []byte{ - 0x30, 0x82, 0x04, 0x8b, 0x30, 0x82, 0x03, 0x73, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x0e, 0x46, 0xf0, 0x8c, 0xdb, 0xcf, 0x2c, 0x54, 0x66, 0xef, - 0x33, 0x01, 0xdd, 0x5f, 0x34, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, - 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x57, 0x31, 0x0b, - 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, - 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, - 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, - 0x73, 0x61, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, - 0x07, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1b, 0x30, 0x19, - 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, - 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, - 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x35, 0x30, 0x38, 0x31, 0x39, 0x30, - 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x35, 0x30, 0x38, - 0x31, 0x39, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x57, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, - 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, - 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, - 0x2d, 0x73, 0x61, 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03, 0x55, 0x04, 0x03, - 0x13, 0x24, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, - 0x20, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, - 0x20, 0x2d, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x2d, 0x20, - 0x47, 0x33, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, - 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa3, - 0xc0, 0x75, 0xe1, 0x32, 0x98, 0xe5, 0xd9, 0xae, 0x84, 0x7c, 0x8d, 0xe8, - 0x23, 0x5f, 0x46, 0x95, 0x5b, 0x4c, 0xa2, 0x25, 0x70, 0xd7, 0x90, 0x04, - 0x85, 0x80, 0xc9, 0xb5, 0xf4, 0x8a, 0x65, 0x4d, 0x92, 0xcb, 0xa5, 0xc4, - 0x42, 0xa0, 0xb6, 0x79, 0x25, 0x31, 0xed, 0xf1, 0x85, 0x20, 0xcd, 0x13, - 0x51, 0x3d, 0x67, 0xac, 0x97, 0x4d, 0x68, 0x9b, 0x33, 0x86, 0x5c, 0xb3, - 0x7b, 0x2d, 0xaa, 0xdf, 0x77, 0xa0, 0x61, 0xd1, 0xf5, 0x3c, 0xfb, 0x9a, - 0xfc, 0xd3, 0xd5, 0x94, 0xca, 0xc9, 0x1e, 0x80, 0x1b, 0x90, 0x90, 0xc8, - 0xac, 0x8d, 0xf6, 0x60, 0x17, 0x9c, 0x31, 0xb8, 0xc5, 0x61, 0xa2, 0xe2, - 0x6e, 0x57, 0x25, 0x08, 0x6f, 0x24, 0x99, 0x99, 0xcf, 0x94, 0xbf, 0xc7, - 0x8b, 0x6b, 0xb0, 0x1f, 0xca, 0x14, 0xfa, 0x18, 0x9b, 0x6c, 0x10, 0x7c, - 0x99, 0x2b, 0xda, 0x4a, 0x63, 0xe5, 0xb2, 0x4e, 0xc2, 0xfd, 0x3e, 0x10, - 0x0b, 0x48, 0xf4, 0x77, 0x0b, 0x2f, 0xf0, 0x96, 0x4b, 0x3a, 0xee, 0xbd, - 0x35, 0xde, 0x85, 0x8d, 0xda, 0x13, 0x0e, 0xce, 0x01, 0xc4, 0x71, 0xd3, - 0xd3, 0x77, 0xc5, 0x08, 0xa6, 0x60, 0x39, 0x25, 0xa7, 0x27, 0x69, 0x5c, - 0x83, 0xd1, 0x6f, 0x76, 0x78, 0xee, 0xc5, 0x44, 0x5b, 0x45, 0xbd, 0x29, - 0x3b, 0xe2, 0xc6, 0x09, 0x0f, 0xa2, 0xbe, 0x2b, 0xdc, 0xe3, 0x5c, 0xda, - 0x5a, 0x6f, 0x8e, 0xe7, 0xc9, 0x07, 0x6b, 0x7e, 0xa1, 0xc0, 0x53, 0x95, - 0x82, 0x89, 0xe0, 0x78, 0x5c, 0x72, 0xa8, 0x6c, 0xbe, 0x67, 0x6b, 0xab, - 0xe7, 0x33, 0xd9, 0x87, 0xf2, 0xf8, 0x5c, 0x27, 0xf4, 0xf6, 0x2a, 0x3b, - 0x87, 0xef, 0xda, 0xc2, 0x47, 0xda, 0xbf, 0xac, 0xeb, 0x27, 0x64, 0x7b, - 0x4c, 0x53, 0xeb, 0x34, 0xe1, 0x2f, 0x9b, 0x20, 0x4d, 0x54, 0x12, 0x6b, - 0x7d, 0x28, 0xbd, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x53, - 0x30, 0x82, 0x01, 0x4f, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, - 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03, - 0x55, 0x1d, 0x25, 0x04, 0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x03, 0x02, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, - 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, - 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xa9, 0x2b, - 0x87, 0xe1, 0xce, 0x24, 0x47, 0x3b, 0x1b, 0xbf, 0xcf, 0x85, 0x37, 0x02, - 0x55, 0x9d, 0x0d, 0x94, 0x58, 0xe6, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, - 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x60, 0x7b, 0x66, 0x1a, 0x45, - 0x0d, 0x97, 0xca, 0x89, 0x50, 0x2f, 0x7d, 0x04, 0xcd, 0x34, 0xa8, 0xff, - 0xfc, 0xfd, 0x4b, 0x30, 0x3d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x01, 0x01, 0x04, 0x31, 0x30, 0x2f, 0x30, 0x2d, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x21, 0x68, 0x74, 0x74, - 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6c, 0x6f, - 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x72, 0x6f, 0x6f, 0x74, 0x72, 0x31, 0x30, 0x33, 0x06, 0x03, 0x55, 0x1d, - 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86, - 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, - 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, - 0x56, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x4f, 0x30, 0x4d, 0x30, 0x0b, - 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xa0, 0x32, 0x01, 0x14, 0x30, - 0x3e, 0x06, 0x06, 0x67, 0x81, 0x0c, 0x01, 0x02, 0x02, 0x30, 0x34, 0x30, - 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, - 0x26, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, - 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, - 0x72, 0x79, 0x2f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, - 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0xa2, - 0x1d, 0x69, 0x8a, 0x0a, 0x8e, 0xc4, 0x14, 0x83, 0x2a, 0x2a, 0x12, 0x4d, - 0x39, 0x27, 0x90, 0x4e, 0xf0, 0x8d, 0xac, 0xd2, 0x96, 0x62, 0x47, 0x36, - 0x5e, 0x92, 0xd1, 0xfa, 0xc5, 0x93, 0xb5, 0x37, 0x07, 0x65, 0x29, 0xd2, - 0xf4, 0x53, 0x50, 0x6b, 0xc9, 0xf4, 0xfe, 0x34, 0xf5, 0xdd, 0xb8, 0x1d, - 0xfa, 0xfc, 0xdc, 0x14, 0xac, 0x56, 0x94, 0x27, 0x9c, 0x42, 0xaa, 0x04, - 0x4d, 0xb7, 0xed, 0x58, 0xd9, 0x99, 0xd2, 0x49, 0xe6, 0x20, 0x2f, 0xd3, - 0xa7, 0x77, 0xb8, 0x2a, 0x89, 0x1a, 0xef, 0xa7, 0xcf, 0x86, 0x2d, 0xd6, - 0x53, 0xe9, 0x0b, 0x93, 0x9c, 0x4e, 0xab, 0xd9, 0x45, 0xee, 0xa4, 0x84, - 0x85, 0xff, 0x34, 0xe4, 0x0e, 0xc0, 0xbb, 0xa5, 0xce, 0x5f, 0x95, 0x89, - 0x85, 0x70, 0xaa, 0xc1, 0x5d, 0xec, 0xcf, 0x2b, 0xd3, 0xd9, 0x83, 0xdf, - 0x03, 0xca, 0x81, 0xa7, 0x02, 0x32, 0xb7, 0x77, 0x61, 0x10, 0x25, 0x4e, - 0xd9, 0x74, 0xf3, 0xd9, 0x79, 0x82, 0xb5, 0x26, 0x70, 0xb4, 0x52, 0xbc, - 0x8f, 0x33, 0xd7, 0x8a, 0xae, 0x19, 0xd0, 0xfc, 0x92, 0xad, 0x2f, 0xba, - 0x3c, 0xa0, 0x48, 0x58, 0x47, 0x5e, 0xfd, 0x20, 0x56, 0x95, 0x20, 0xc1, - 0x72, 0x1d, 0xab, 0x66, 0x99, 0xa4, 0xd5, 0x78, 0x37, 0x48, 0x1b, 0x9f, - 0xb2, 0x4c, 0x37, 0x67, 0x7a, 0xfd, 0x42, 0xd2, 0xd3, 0x56, 0x9e, 0xd3, - 0x1d, 0x8e, 0xc4, 0x0c, 0x68, 0x96, 0xb6, 0x47, 0x51, 0x10, 0xf7, 0x7b, - 0xeb, 0x15, 0x09, 0x64, 0xf5, 0xf9, 0xf0, 0x63, 0x16, 0x2d, 0x3d, 0xdf, - 0x23, 0x42, 0x3a, 0x93, 0x63, 0xcc, 0xab, 0xaf, 0x4f, 0x57, 0x06, 0xc7, - 0xfe, 0x14, 0x55, 0x62, 0xce, 0x27, 0x11, 0x19, 0xe1, 0xf4, 0x42, 0xed, - 0x22, 0x30, 0x6b, 0x35, 0x1a, 0x4a, 0x05, 0x80, 0xa4, 0x65, 0xdf, 0xcc, - 0xcb, 0x6f, 0xd0, -} - -var certSet3Cert21 = []byte{ - 0x30, 0x82, 0x04, 0x90, 0x30, 0x82, 0x03, 0xf9, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x1b, 0x09, 0x3b, 0x78, 0x60, 0x96, 0xda, 0x37, 0xbb, - 0xa4, 0x51, 0x94, 0x46, 0xc8, 0x96, 0x78, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5f, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, - 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, - 0x63, 0x2e, 0x31, 0x37, 0x30, 0x35, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, - 0x2e, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, - 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, - 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, - 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x30, 0x38, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x31, 0x30, 0x37, - 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xca, 0x31, 0x0b, - 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, - 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65, - 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56, - 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, - 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3a, 0x30, - 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28, 0x63, 0x29, 0x20, - 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, - 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, - 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, - 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x45, 0x30, - 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56, 0x65, 0x72, 0x69, - 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, - 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, - 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30, 0x82, 0x01, 0x22, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, - 0x02, 0x82, 0x01, 0x01, 0x00, 0xaf, 0x24, 0x08, 0x08, 0x29, 0x7a, 0x35, - 0x9e, 0x60, 0x0c, 0xaa, 0xe7, 0x4b, 0x3b, 0x4e, 0xdc, 0x7c, 0xbc, 0x3c, - 0x45, 0x1c, 0xbb, 0x2b, 0xe0, 0xfe, 0x29, 0x02, 0xf9, 0x57, 0x08, 0xa3, - 0x64, 0x85, 0x15, 0x27, 0xf5, 0xf1, 0xad, 0xc8, 0x31, 0x89, 0x5d, 0x22, - 0xe8, 0x2a, 0xaa, 0xa6, 0x42, 0xb3, 0x8f, 0xf8, 0xb9, 0x55, 0xb7, 0xb1, - 0xb7, 0x4b, 0xb3, 0xfe, 0x8f, 0x7e, 0x07, 0x57, 0xec, 0xef, 0x43, 0xdb, - 0x66, 0x62, 0x15, 0x61, 0xcf, 0x60, 0x0d, 0xa4, 0xd8, 0xde, 0xf8, 0xe0, - 0xc3, 0x62, 0x08, 0x3d, 0x54, 0x13, 0xeb, 0x49, 0xca, 0x59, 0x54, 0x85, - 0x26, 0xe5, 0x2b, 0x8f, 0x1b, 0x9f, 0xeb, 0xf5, 0xa1, 0x91, 0xc2, 0x33, - 0x49, 0xd8, 0x43, 0x63, 0x6a, 0x52, 0x4b, 0xd2, 0x8f, 0xe8, 0x70, 0x51, - 0x4d, 0xd1, 0x89, 0x69, 0x7b, 0xc7, 0x70, 0xf6, 0xb3, 0xdc, 0x12, 0x74, - 0xdb, 0x7b, 0x5d, 0x4b, 0x56, 0xd3, 0x96, 0xbf, 0x15, 0x77, 0xa1, 0xb0, - 0xf4, 0xa2, 0x25, 0xf2, 0xaf, 0x1c, 0x92, 0x67, 0x18, 0xe5, 0xf4, 0x06, - 0x04, 0xef, 0x90, 0xb9, 0xe4, 0x00, 0xe4, 0xdd, 0x3a, 0xb5, 0x19, 0xff, - 0x02, 0xba, 0xf4, 0x3c, 0xee, 0xe0, 0x8b, 0xeb, 0x37, 0x8b, 0xec, 0xf4, - 0xd7, 0xac, 0xf2, 0xf6, 0xf0, 0x3d, 0xaf, 0xdd, 0x75, 0x91, 0x33, 0x19, - 0x1d, 0x1c, 0x40, 0xcb, 0x74, 0x24, 0x19, 0x21, 0x93, 0xd9, 0x14, 0xfe, - 0xac, 0x2a, 0x52, 0xc7, 0x8f, 0xd5, 0x04, 0x49, 0xe4, 0x8d, 0x63, 0x47, - 0x88, 0x3c, 0x69, 0x83, 0xcb, 0xfe, 0x47, 0xbd, 0x2b, 0x7e, 0x4f, 0xc5, - 0x95, 0xae, 0x0e, 0x9d, 0xd4, 0xd1, 0x43, 0xc0, 0x67, 0x73, 0xe3, 0x14, - 0x08, 0x7e, 0xe5, 0x3f, 0x9f, 0x73, 0xb8, 0x33, 0x0a, 0xcf, 0x5d, 0x3f, - 0x34, 0x87, 0x96, 0x8a, 0xee, 0x53, 0xe8, 0x25, 0x15, 0x02, 0x03, 0x01, - 0x00, 0x01, 0xa3, 0x82, 0x01, 0x5b, 0x30, 0x82, 0x01, 0x57, 0x30, 0x0f, - 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, - 0x01, 0x01, 0xff, 0x30, 0x31, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2a, - 0x30, 0x28, 0x30, 0x26, 0xa0, 0x24, 0xa0, 0x22, 0x86, 0x20, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, - 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, - 0x61, 0x33, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, - 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x3d, - 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, 0x30, 0x34, 0x30, 0x32, 0x06, - 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, 0x30, 0x28, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, 0x74, - 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72, - 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, - 0x73, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, - 0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0, 0x30, 0x09, 0xf3, - 0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30, 0x6d, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x0c, 0x04, 0x61, 0x30, 0x5f, - 0xa1, 0x5d, 0xa0, 0x5b, 0x30, 0x59, 0x30, 0x57, 0x30, 0x55, 0x16, 0x09, - 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, 0x69, 0x66, 0x30, 0x21, 0x30, - 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14, - 0x8f, 0xe5, 0xd3, 0x1a, 0x86, 0xac, 0x8d, 0x8e, 0x6b, 0xc3, 0xcf, 0x80, - 0x6a, 0xd4, 0x48, 0x18, 0x2c, 0x7b, 0x19, 0x2e, 0x30, 0x25, 0x16, 0x23, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, - 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x76, 0x73, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x67, 0x69, 0x66, 0x30, - 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, - 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, - 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, - 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, - 0xa3, 0xcd, 0x7d, 0x1e, 0xf7, 0xc7, 0x75, 0x8d, 0x48, 0xe7, 0x56, 0x34, - 0x4c, 0x00, 0x90, 0x75, 0xa9, 0x51, 0xa5, 0x56, 0xc1, 0x6d, 0xbc, 0xfe, - 0xf5, 0x53, 0x22, 0xe9, 0x98, 0xa2, 0xac, 0x9a, 0x7e, 0x70, 0x1e, 0xb3, - 0x8e, 0x3b, 0x45, 0xe3, 0x86, 0x95, 0x31, 0xda, 0x6d, 0x4c, 0xfb, 0x34, - 0x50, 0x80, 0x96, 0xcd, 0x24, 0xf2, 0x40, 0xdf, 0x04, 0x3f, 0xe2, 0x65, - 0xce, 0x34, 0x22, 0x61, 0x15, 0xea, 0x66, 0x70, 0x64, 0xd2, 0xf1, 0x6e, - 0xf3, 0xca, 0x18, 0x59, 0x6a, 0x41, 0x46, 0x7e, 0x82, 0xde, 0x19, 0xb0, - 0x70, 0x31, 0x56, 0x69, 0x0d, 0x0c, 0xe6, 0x1d, 0x9d, 0x71, 0x58, 0xdc, - 0xcc, 0xde, 0x62, 0xf5, 0xe1, 0x7a, 0x10, 0x02, 0xd8, 0x7a, 0xdc, 0x3b, - 0xfa, 0x57, 0xbd, 0xc9, 0xe9, 0x8f, 0x46, 0x21, 0x39, 0x9f, 0x51, 0x65, - 0x4c, 0x8e, 0x3a, 0xbe, 0x28, 0x41, 0x70, 0x1d, -} - -var certSet3Cert22 = []byte{ - 0x30, 0x82, 0x04, 0x92, 0x30, 0x82, 0x03, 0x7a, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x0a, 0x01, 0x41, 0x42, 0x00, 0x00, 0x01, 0x53, 0x85, - 0x73, 0x6a, 0x0b, 0x85, 0xec, 0xa7, 0x08, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x3f, - 0x31, 0x24, 0x30, 0x22, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1b, 0x44, - 0x69, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x43, - 0x6f, 0x2e, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, - 0x0e, 0x44, 0x53, 0x54, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, - 0x20, 0x58, 0x33, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x36, 0x30, 0x33, 0x31, - 0x37, 0x31, 0x36, 0x34, 0x30, 0x34, 0x36, 0x5a, 0x17, 0x0d, 0x32, 0x31, - 0x30, 0x33, 0x31, 0x37, 0x31, 0x36, 0x34, 0x30, 0x34, 0x36, 0x5a, 0x30, - 0x4a, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0d, 0x4c, 0x65, 0x74, 0x27, 0x73, 0x20, 0x45, 0x6e, 0x63, 0x72, 0x79, - 0x70, 0x74, 0x31, 0x23, 0x30, 0x21, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, - 0x1a, 0x4c, 0x65, 0x74, 0x27, 0x73, 0x20, 0x45, 0x6e, 0x63, 0x72, 0x79, - 0x70, 0x74, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, - 0x20, 0x58, 0x33, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, - 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, - 0x9c, 0xd3, 0x0c, 0xf0, 0x5a, 0xe5, 0x2e, 0x47, 0xb7, 0x72, 0x5d, 0x37, - 0x83, 0xb3, 0x68, 0x63, 0x30, 0xea, 0xd7, 0x35, 0x26, 0x19, 0x25, 0xe1, - 0xbd, 0xbe, 0x35, 0xf1, 0x70, 0x92, 0x2f, 0xb7, 0xb8, 0x4b, 0x41, 0x05, - 0xab, 0xa9, 0x9e, 0x35, 0x08, 0x58, 0xec, 0xb1, 0x2a, 0xc4, 0x68, 0x87, - 0x0b, 0xa3, 0xe3, 0x75, 0xe4, 0xe6, 0xf3, 0xa7, 0x62, 0x71, 0xba, 0x79, - 0x81, 0x60, 0x1f, 0xd7, 0x91, 0x9a, 0x9f, 0xf3, 0xd0, 0x78, 0x67, 0x71, - 0xc8, 0x69, 0x0e, 0x95, 0x91, 0xcf, 0xfe, 0xe6, 0x99, 0xe9, 0x60, 0x3c, - 0x48, 0xcc, 0x7e, 0xca, 0x4d, 0x77, 0x12, 0x24, 0x9d, 0x47, 0x1b, 0x5a, - 0xeb, 0xb9, 0xec, 0x1e, 0x37, 0x00, 0x1c, 0x9c, 0xac, 0x7b, 0xa7, 0x05, - 0xea, 0xce, 0x4a, 0xeb, 0xbd, 0x41, 0xe5, 0x36, 0x98, 0xb9, 0xcb, 0xfd, - 0x6d, 0x3c, 0x96, 0x68, 0xdf, 0x23, 0x2a, 0x42, 0x90, 0x0c, 0x86, 0x74, - 0x67, 0xc8, 0x7f, 0xa5, 0x9a, 0xb8, 0x52, 0x61, 0x14, 0x13, 0x3f, 0x65, - 0xe9, 0x82, 0x87, 0xcb, 0xdb, 0xfa, 0x0e, 0x56, 0xf6, 0x86, 0x89, 0xf3, - 0x85, 0x3f, 0x97, 0x86, 0xaf, 0xb0, 0xdc, 0x1a, 0xef, 0x6b, 0x0d, 0x95, - 0x16, 0x7d, 0xc4, 0x2b, 0xa0, 0x65, 0xb2, 0x99, 0x04, 0x36, 0x75, 0x80, - 0x6b, 0xac, 0x4a, 0xf3, 0x1b, 0x90, 0x49, 0x78, 0x2f, 0xa2, 0x96, 0x4f, - 0x2a, 0x20, 0x25, 0x29, 0x04, 0xc6, 0x74, 0xc0, 0xd0, 0x31, 0xcd, 0x8f, - 0x31, 0x38, 0x95, 0x16, 0xba, 0xa8, 0x33, 0xb8, 0x43, 0xf1, 0xb1, 0x1f, - 0xc3, 0x30, 0x7f, 0xa2, 0x79, 0x31, 0x13, 0x3d, 0x2d, 0x36, 0xf8, 0xe3, - 0xfc, 0xf2, 0x33, 0x6a, 0xb9, 0x39, 0x31, 0xc5, 0xaf, 0xc4, 0x8d, 0x0d, - 0x1d, 0x64, 0x16, 0x33, 0xaa, 0xfa, 0x84, 0x29, 0xb6, 0xd4, 0x0b, 0xc0, - 0xd8, 0x7d, 0xc3, 0x93, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, - 0x7d, 0x30, 0x82, 0x01, 0x79, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, - 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, - 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, - 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x7f, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x73, 0x30, 0x71, 0x30, 0x32, 0x06, - 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x26, 0x68, - 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x69, 0x73, 0x72, 0x67, 0x2e, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x69, 0x64, 0x2e, 0x6f, 0x63, 0x73, 0x70, 0x2e, - 0x69, 0x64, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, - 0x6d, 0x30, 0x3b, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, - 0x02, 0x86, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x61, 0x70, - 0x70, 0x73, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x2f, 0x64, - 0x73, 0x74, 0x72, 0x6f, 0x6f, 0x74, 0x63, 0x61, 0x78, 0x33, 0x2e, 0x70, - 0x37, 0x63, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, - 0x16, 0x80, 0x14, 0xc4, 0xa7, 0xb1, 0xa4, 0x7b, 0x2c, 0x71, 0xfa, 0xdb, - 0xe1, 0x4b, 0x90, 0x75, 0xff, 0xc4, 0x15, 0x60, 0x85, 0x89, 0x10, 0x30, - 0x54, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x4d, 0x30, 0x4b, 0x30, 0x08, - 0x06, 0x06, 0x67, 0x81, 0x0c, 0x01, 0x02, 0x01, 0x30, 0x3f, 0x06, 0x0b, - 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xdf, 0x13, 0x01, 0x01, 0x01, 0x30, - 0x30, 0x30, 0x2e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, - 0x01, 0x16, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x70, - 0x73, 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x78, 0x31, 0x2e, 0x6c, 0x65, - 0x74, 0x73, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2e, 0x6f, 0x72, - 0x67, 0x30, 0x3c, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x35, 0x30, 0x33, - 0x30, 0x31, 0xa0, 0x2f, 0xa0, 0x2d, 0x86, 0x2b, 0x68, 0x74, 0x74, 0x70, - 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x53, 0x54, - 0x52, 0x4f, 0x4f, 0x54, 0x43, 0x41, 0x58, 0x33, 0x43, 0x52, 0x4c, 0x2e, - 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, - 0x04, 0x14, 0xa8, 0x4a, 0x6a, 0x63, 0x04, 0x7d, 0xdd, 0xba, 0xe6, 0xd1, - 0x39, 0xb7, 0xa6, 0x45, 0x65, 0xef, 0xf3, 0xa8, 0xec, 0xa1, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, - 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0xdd, 0x33, 0xd7, 0x11, 0xf3, 0x63, - 0x58, 0x38, 0xdd, 0x18, 0x15, 0xfb, 0x09, 0x55, 0xbe, 0x76, 0x56, 0xb9, - 0x70, 0x48, 0xa5, 0x69, 0x47, 0x27, 0x7b, 0xc2, 0x24, 0x08, 0x92, 0xf1, - 0x5a, 0x1f, 0x4a, 0x12, 0x29, 0x37, 0x24, 0x74, 0x51, 0x1c, 0x62, 0x68, - 0xb8, 0xcd, 0x95, 0x70, 0x67, 0xe5, 0xf7, 0xa4, 0xbc, 0x4e, 0x28, 0x51, - 0xcd, 0x9b, 0xe8, 0xae, 0x87, 0x9d, 0xea, 0xd8, 0xba, 0x5a, 0xa1, 0x01, - 0x9a, 0xdc, 0xf0, 0xdd, 0x6a, 0x1d, 0x6a, 0xd8, 0x3e, 0x57, 0x23, 0x9e, - 0xa6, 0x1e, 0x04, 0x62, 0x9a, 0xff, 0xd7, 0x05, 0xca, 0xb7, 0x1f, 0x3f, - 0xc0, 0x0a, 0x48, 0xbc, 0x94, 0xb0, 0xb6, 0x65, 0x62, 0xe0, 0xc1, 0x54, - 0xe5, 0xa3, 0x2a, 0xad, 0x20, 0xc4, 0xe9, 0xe6, 0xbb, 0xdc, 0xc8, 0xf6, - 0xb5, 0xc3, 0x32, 0xa3, 0x98, 0xcc, 0x77, 0xa8, 0xe6, 0x79, 0x65, 0x07, - 0x2b, 0xcb, 0x28, 0xfe, 0x3a, 0x16, 0x52, 0x81, 0xce, 0x52, 0x0c, 0x2e, - 0x5f, 0x83, 0xe8, 0xd5, 0x06, 0x33, 0xfb, 0x77, 0x6c, 0xce, 0x40, 0xea, - 0x32, 0x9e, 0x1f, 0x92, 0x5c, 0x41, 0xc1, 0x74, 0x6c, 0x5b, 0x5d, 0x0a, - 0x5f, 0x33, 0xcc, 0x4d, 0x9f, 0xac, 0x38, 0xf0, 0x2f, 0x7b, 0x2c, 0x62, - 0x9d, 0xd9, 0xa3, 0x91, 0x6f, 0x25, 0x1b, 0x2f, 0x90, 0xb1, 0x19, 0x46, - 0x3d, 0xf6, 0x7e, 0x1b, 0xa6, 0x7a, 0x87, 0xb9, 0xa3, 0x7a, 0x6d, 0x18, - 0xfa, 0x25, 0xa5, 0x91, 0x87, 0x15, 0xe0, 0xf2, 0x16, 0x2f, 0x58, 0xb0, - 0x06, 0x2f, 0x2c, 0x68, 0x26, 0xc6, 0x4b, 0x98, 0xcd, 0xda, 0x9f, 0x0c, - 0xf9, 0x7f, 0x90, 0xed, 0x43, 0x4a, 0x12, 0x44, 0x4e, 0x6f, 0x73, 0x7a, - 0x28, 0xea, 0xa4, 0xaa, 0x6e, 0x7b, 0x4c, 0x7d, 0x87, 0xdd, 0xe0, 0xc9, - 0x02, 0x44, 0xa7, 0x87, 0xaf, 0xc3, 0x34, 0x5b, 0xb4, 0x42, -} - -var certSet3Cert23 = []byte{ - 0x30, 0x82, 0x04, 0x92, 0x30, 0x82, 0x03, 0x7a, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x13, 0x06, 0x7f, 0x94, 0x4a, 0x2a, 0x27, 0xcd, 0xf3, 0xfa, - 0xc2, 0xae, 0x2b, 0x01, 0xf9, 0x08, 0xee, 0xb9, 0xc4, 0xc6, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, - 0x00, 0x30, 0x81, 0x98, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, - 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, - 0x04, 0x08, 0x13, 0x07, 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31, - 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0a, 0x53, 0x63, - 0x6f, 0x74, 0x74, 0x73, 0x64, 0x61, 0x6c, 0x65, 0x31, 0x25, 0x30, 0x23, - 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c, 0x53, 0x74, 0x61, 0x72, 0x66, - 0x69, 0x65, 0x6c, 0x64, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x6f, 0x6c, - 0x6f, 0x67, 0x69, 0x65, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, - 0x3b, 0x30, 0x39, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x32, 0x53, 0x74, - 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, - 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, - 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x35, 0x30, 0x35, 0x32, 0x35, 0x31, 0x32, - 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x33, 0x37, 0x31, 0x32, 0x33, - 0x31, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x39, 0x31, 0x0b, - 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, - 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x06, 0x41, 0x6d, - 0x61, 0x7a, 0x6f, 0x6e, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, - 0x03, 0x13, 0x10, 0x41, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x20, 0x52, 0x6f, - 0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x31, 0x30, 0x82, 0x01, 0x22, 0x30, - 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, - 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, - 0x82, 0x01, 0x01, 0x00, 0xb2, 0x78, 0x80, 0x71, 0xca, 0x78, 0xd5, 0xe3, - 0x71, 0xaf, 0x47, 0x80, 0x50, 0x74, 0x7d, 0x6e, 0xd8, 0xd7, 0x88, 0x76, - 0xf4, 0x99, 0x68, 0xf7, 0x58, 0x21, 0x60, 0xf9, 0x74, 0x84, 0x01, 0x2f, - 0xac, 0x02, 0x2d, 0x86, 0xd3, 0xa0, 0x43, 0x7a, 0x4e, 0xb2, 0xa4, 0xd0, - 0x36, 0xba, 0x01, 0xbe, 0x8d, 0xdb, 0x48, 0xc8, 0x07, 0x17, 0x36, 0x4c, - 0xf4, 0xee, 0x88, 0x23, 0xc7, 0x3e, 0xeb, 0x37, 0xf5, 0xb5, 0x19, 0xf8, - 0x49, 0x68, 0xb0, 0xde, 0xd7, 0xb9, 0x76, 0x38, 0x1d, 0x61, 0x9e, 0xa4, - 0xfe, 0x82, 0x36, 0xa5, 0xe5, 0x4a, 0x56, 0xe4, 0x45, 0xe1, 0xf9, 0xfd, - 0xb4, 0x16, 0xfa, 0x74, 0xda, 0x9c, 0x9b, 0x35, 0x39, 0x2f, 0xfa, 0xb0, - 0x20, 0x50, 0x06, 0x6c, 0x7a, 0xd0, 0x80, 0xb2, 0xa6, 0xf9, 0xaf, 0xec, - 0x47, 0x19, 0x8f, 0x50, 0x38, 0x07, 0xdc, 0xa2, 0x87, 0x39, 0x58, 0xf8, - 0xba, 0xd5, 0xa9, 0xf9, 0x48, 0x67, 0x30, 0x96, 0xee, 0x94, 0x78, 0x5e, - 0x6f, 0x89, 0xa3, 0x51, 0xc0, 0x30, 0x86, 0x66, 0xa1, 0x45, 0x66, 0xba, - 0x54, 0xeb, 0xa3, 0xc3, 0x91, 0xf9, 0x48, 0xdc, 0xff, 0xd1, 0xe8, 0x30, - 0x2d, 0x7d, 0x2d, 0x74, 0x70, 0x35, 0xd7, 0x88, 0x24, 0xf7, 0x9e, 0xc4, - 0x59, 0x6e, 0xbb, 0x73, 0x87, 0x17, 0xf2, 0x32, 0x46, 0x28, 0xb8, 0x43, - 0xfa, 0xb7, 0x1d, 0xaa, 0xca, 0xb4, 0xf2, 0x9f, 0x24, 0x0e, 0x2d, 0x4b, - 0xf7, 0x71, 0x5c, 0x5e, 0x69, 0xff, 0xea, 0x95, 0x02, 0xcb, 0x38, 0x8a, - 0xae, 0x50, 0x38, 0x6f, 0xdb, 0xfb, 0x2d, 0x62, 0x1b, 0xc5, 0xc7, 0x1e, - 0x54, 0xe1, 0x77, 0xe0, 0x67, 0xc8, 0x0f, 0x9c, 0x87, 0x23, 0xd6, 0x3f, - 0x40, 0x20, 0x7f, 0x20, 0x80, 0xc4, 0x80, 0x4c, 0x3e, 0x3b, 0x24, 0x26, - 0x8e, 0x04, 0xae, 0x6c, 0x9a, 0xc8, 0xaa, 0x0d, 0x02, 0x03, 0x01, 0x00, - 0x01, 0xa3, 0x82, 0x01, 0x31, 0x30, 0x82, 0x01, 0x2d, 0x30, 0x0f, 0x06, - 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, - 0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, - 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, - 0x0e, 0x04, 0x16, 0x04, 0x14, 0x84, 0x18, 0xcc, 0x85, 0x34, 0xec, 0xbc, - 0x0c, 0x94, 0x94, 0x2e, 0x08, 0x59, 0x9c, 0xc7, 0xb2, 0x10, 0x4e, 0x0a, - 0x08, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, - 0x80, 0x14, 0x9c, 0x5f, 0x00, 0xdf, 0xaa, 0x01, 0xd7, 0x30, 0x2b, 0x38, - 0x88, 0xa2, 0xb8, 0x6d, 0x4a, 0x9c, 0xf2, 0x11, 0x91, 0x83, 0x30, 0x78, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x6c, - 0x30, 0x6a, 0x30, 0x2e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x30, 0x01, 0x86, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, - 0x63, 0x73, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x67, 0x32, 0x2e, 0x61, - 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, - 0x6f, 0x6d, 0x30, 0x38, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x30, 0x02, 0x86, 0x2c, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, - 0x72, 0x74, 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x67, 0x32, 0x2e, 0x61, 0x6d, - 0x61, 0x7a, 0x6f, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x67, 0x32, 0x2e, 0x63, 0x65, 0x72, - 0x30, 0x3d, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x36, 0x30, 0x34, 0x30, - 0x32, 0xa0, 0x30, 0xa0, 0x2e, 0x86, 0x2c, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x67, 0x32, - 0x2e, 0x61, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x67, 0x32, 0x2e, - 0x63, 0x72, 0x6c, 0x30, 0x11, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x0a, - 0x30, 0x08, 0x30, 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, - 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x62, 0x37, 0x42, 0x5c, 0xbc, 0x10, - 0xb5, 0x3e, 0x8b, 0x2c, 0xe9, 0x0c, 0x9b, 0x6c, 0x45, 0xe2, 0x07, 0x00, - 0x7a, 0xf9, 0xc5, 0x58, 0x0b, 0xb9, 0x08, 0x8c, 0x3e, 0xed, 0xb3, 0x25, - 0x3c, 0xb5, 0x6f, 0x50, 0xe4, 0xcd, 0x35, 0x6a, 0xa7, 0x93, 0x34, 0x96, - 0x32, 0x21, 0xa9, 0x48, 0x44, 0xab, 0x9c, 0xed, 0x3d, 0xb4, 0xaa, 0x73, - 0x6d, 0xe4, 0x7f, 0x16, 0x80, 0x89, 0x6c, 0xcf, 0x28, 0x03, 0x18, 0x83, - 0x47, 0x79, 0xa3, 0x10, 0x7e, 0x30, 0x5b, 0xac, 0x3b, 0xb0, 0x60, 0xe0, - 0x77, 0xd4, 0x08, 0xa6, 0xe1, 0x1d, 0x7c, 0x5e, 0xc0, 0xbb, 0xf9, 0x9a, - 0x7b, 0x22, 0x9d, 0xa7, 0x00, 0x09, 0x7e, 0xac, 0x46, 0x17, 0x83, 0xdc, - 0x9c, 0x26, 0x57, 0x99, 0x30, 0x39, 0x62, 0x96, 0x8f, 0xed, 0xda, 0xde, - 0xaa, 0xc5, 0xcc, 0x1b, 0x3e, 0xca, 0x43, 0x68, 0x6c, 0x57, 0x16, 0xbc, - 0xd5, 0x0e, 0x20, 0x2e, 0xfe, 0xff, 0xc2, 0x6a, 0x5d, 0x2e, 0xa0, 0x4a, - 0x6d, 0x14, 0x58, 0x87, 0x94, 0xe6, 0x39, 0x31, 0x5f, 0x7c, 0x73, 0xcb, - 0x90, 0x88, 0x6a, 0x84, 0x11, 0x96, 0x27, 0xa6, 0xed, 0xd9, 0x81, 0x46, - 0xa6, 0x7e, 0xa3, 0x72, 0x00, 0x0a, 0x52, 0x3e, 0x83, 0x88, 0x07, 0x63, - 0x77, 0x89, 0x69, 0x17, 0x0f, 0x39, 0x85, 0xd2, 0xab, 0x08, 0x45, 0x4d, - 0xd0, 0x51, 0x3a, 0xfd, 0x5d, 0x5d, 0x37, 0x64, 0x4c, 0x7e, 0x30, 0xb2, - 0x55, 0x24, 0x42, 0x9d, 0x36, 0xb0, 0x5d, 0x9c, 0x17, 0x81, 0x61, 0xf1, - 0xca, 0xf9, 0x10, 0x02, 0x24, 0xab, 0xeb, 0x0d, 0x74, 0x91, 0x8d, 0x7b, - 0x45, 0x29, 0x50, 0x39, 0x88, 0xb2, 0xa6, 0x89, 0x35, 0x25, 0x1e, 0x14, - 0x6a, 0x47, 0x23, 0x31, 0x2f, 0x5c, 0x9a, 0xfa, 0xad, 0x9a, 0x0e, 0x62, - 0x51, 0xa4, 0x2a, 0xa9, 0xc4, 0xf9, 0x34, 0x9d, 0x21, 0x18, -} - -var certSet3Cert24 = []byte{ - 0x30, 0x82, 0x04, 0x94, 0x30, 0x82, 0x03, 0x7c, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x01, 0xfd, 0xa3, 0xeb, 0x6e, 0xca, 0x75, 0xc8, 0x88, - 0x43, 0x8b, 0x72, 0x4b, 0xcf, 0xbc, 0x91, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x61, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, - 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, - 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, - 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, - 0x63, 0x6f, 0x6d, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x03, 0x55, 0x04, 0x03, - 0x13, 0x17, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x47, - 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, - 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x33, 0x30, 0x33, 0x30, 0x38, 0x31, - 0x32, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x33, 0x30, 0x33, - 0x30, 0x38, 0x31, 0x32, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x4d, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, - 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, - 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1e, 0x44, 0x69, - 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x53, 0x48, 0x41, 0x32, 0x20, - 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, - 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, - 0x00, 0xdc, 0xae, 0x58, 0x90, 0x4d, 0xc1, 0xc4, 0x30, 0x15, 0x90, 0x35, - 0x5b, 0x6e, 0x3c, 0x82, 0x15, 0xf5, 0x2c, 0x5c, 0xbd, 0xe3, 0xdb, 0xff, - 0x71, 0x43, 0xfa, 0x64, 0x25, 0x80, 0xd4, 0xee, 0x18, 0xa2, 0x4d, 0xf0, - 0x66, 0xd0, 0x0a, 0x73, 0x6e, 0x11, 0x98, 0x36, 0x17, 0x64, 0xaf, 0x37, - 0x9d, 0xfd, 0xfa, 0x41, 0x84, 0xaf, 0xc7, 0xaf, 0x8c, 0xfe, 0x1a, 0x73, - 0x4d, 0xcf, 0x33, 0x97, 0x90, 0xa2, 0x96, 0x87, 0x53, 0x83, 0x2b, 0xb9, - 0xa6, 0x75, 0x48, 0x2d, 0x1d, 0x56, 0x37, 0x7b, 0xda, 0x31, 0x32, 0x1a, - 0xd7, 0xac, 0xab, 0x06, 0xf4, 0xaa, 0x5d, 0x4b, 0xb7, 0x47, 0x46, 0xdd, - 0x2a, 0x93, 0xc3, 0x90, 0x2e, 0x79, 0x80, 0x80, 0xef, 0x13, 0x04, 0x6a, - 0x14, 0x3b, 0xb5, 0x9b, 0x92, 0xbe, 0xc2, 0x07, 0x65, 0x4e, 0xfc, 0xda, - 0xfc, 0xff, 0x7a, 0xae, 0xdc, 0x5c, 0x7e, 0x55, 0x31, 0x0c, 0xe8, 0x39, - 0x07, 0xa4, 0xd7, 0xbe, 0x2f, 0xd3, 0x0b, 0x6a, 0xd2, 0xb1, 0xdf, 0x5f, - 0xfe, 0x57, 0x74, 0x53, 0x3b, 0x35, 0x80, 0xdd, 0xae, 0x8e, 0x44, 0x98, - 0xb3, 0x9f, 0x0e, 0xd3, 0xda, 0xe0, 0xd7, 0xf4, 0x6b, 0x29, 0xab, 0x44, - 0xa7, 0x4b, 0x58, 0x84, 0x6d, 0x92, 0x4b, 0x81, 0xc3, 0xda, 0x73, 0x8b, - 0x12, 0x97, 0x48, 0x90, 0x04, 0x45, 0x75, 0x1a, 0xdd, 0x37, 0x31, 0x97, - 0x92, 0xe8, 0xcd, 0x54, 0x0d, 0x3b, 0xe4, 0xc1, 0x3f, 0x39, 0x5e, 0x2e, - 0xb8, 0xf3, 0x5c, 0x7e, 0x10, 0x8e, 0x86, 0x41, 0x00, 0x8d, 0x45, 0x66, - 0x47, 0xb0, 0xa1, 0x65, 0xce, 0xa0, 0xaa, 0x29, 0x09, 0x4e, 0xf3, 0x97, - 0xeb, 0xe8, 0x2e, 0xab, 0x0f, 0x72, 0xa7, 0x30, 0x0e, 0xfa, 0xc7, 0xf4, - 0xfd, 0x14, 0x77, 0xc3, 0xa4, 0x5b, 0x28, 0x57, 0xc2, 0xb3, 0xf9, 0x82, - 0xfd, 0xb7, 0x45, 0x58, 0x9b, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, - 0x01, 0x5a, 0x30, 0x82, 0x01, 0x56, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, - 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, - 0x01, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, - 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x34, 0x06, 0x08, 0x2b, 0x06, - 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x28, 0x30, 0x26, 0x30, 0x24, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, - 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, - 0x30, 0x7b, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x74, 0x30, 0x72, 0x30, - 0x37, 0xa0, 0x35, 0xa0, 0x33, 0x86, 0x31, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x33, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, - 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x69, 0x67, 0x69, - 0x43, 0x65, 0x72, 0x74, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x52, 0x6f, - 0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x37, 0xa0, 0x35, - 0xa0, 0x33, 0x86, 0x31, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, - 0x72, 0x6c, 0x34, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, - 0x74, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x52, 0x6f, 0x6f, 0x74, 0x43, - 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x1d, 0x20, - 0x04, 0x36, 0x30, 0x34, 0x30, 0x32, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, - 0x30, 0x2a, 0x30, 0x28, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, - 0x77, 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x50, 0x53, 0x30, 0x1d, 0x06, 0x03, - 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x0f, 0x80, 0x61, 0x1c, 0x82, - 0x31, 0x61, 0xd5, 0x2f, 0x28, 0xe7, 0x8d, 0x46, 0x38, 0xb4, 0x2c, 0xe1, - 0xc6, 0xd9, 0xe2, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, - 0x30, 0x16, 0x80, 0x14, 0x03, 0xde, 0x50, 0x35, 0x56, 0xd1, 0x4c, 0xbb, - 0x66, 0xf0, 0xa3, 0xe2, 0x1b, 0x1b, 0xc3, 0x97, 0xb2, 0x3d, 0xd1, 0x55, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x23, 0x3e, 0xdf, 0x4b, - 0xd2, 0x31, 0x42, 0xa5, 0xb6, 0x7e, 0x42, 0x5c, 0x1a, 0x44, 0xcc, 0x69, - 0xd1, 0x68, 0xb4, 0x5d, 0x4b, 0xe0, 0x04, 0x21, 0x6c, 0x4b, 0xe2, 0x6d, - 0xcc, 0xb1, 0xe0, 0x97, 0x8f, 0xa6, 0x53, 0x09, 0xcd, 0xaa, 0x2a, 0x65, - 0xe5, 0x39, 0x4f, 0x1e, 0x83, 0xa5, 0x6e, 0x5c, 0x98, 0xa2, 0x24, 0x26, - 0xe6, 0xfb, 0xa1, 0xed, 0x93, 0xc7, 0x2e, 0x02, 0xc6, 0x4d, 0x4a, 0xbf, - 0xb0, 0x42, 0xdf, 0x78, 0xda, 0xb3, 0xa8, 0xf9, 0x6d, 0xff, 0x21, 0x85, - 0x53, 0x36, 0x60, 0x4c, 0x76, 0xce, 0xec, 0x38, 0xdc, 0xd6, 0x51, 0x80, - 0xf0, 0xc5, 0xd6, 0xe5, 0xd4, 0x4d, 0x27, 0x64, 0xab, 0x9b, 0xc7, 0x3e, - 0x71, 0xfb, 0x48, 0x97, 0xb8, 0x33, 0x6d, 0xc9, 0x13, 0x07, 0xee, 0x96, - 0xa2, 0x1b, 0x18, 0x15, 0xf6, 0x5c, 0x4c, 0x40, 0xed, 0xb3, 0xc2, 0xec, - 0xff, 0x71, 0xc1, 0xe3, 0x47, 0xff, 0xd4, 0xb9, 0x00, 0xb4, 0x37, 0x42, - 0xda, 0x20, 0xc9, 0xea, 0x6e, 0x8a, 0xee, 0x14, 0x06, 0xae, 0x7d, 0xa2, - 0x59, 0x98, 0x88, 0xa8, 0x1b, 0x6f, 0x2d, 0xf4, 0xf2, 0xc9, 0x14, 0x5f, - 0x26, 0xcf, 0x2c, 0x8d, 0x7e, 0xed, 0x37, 0xc0, 0xa9, 0xd5, 0x39, 0xb9, - 0x82, 0xbf, 0x19, 0x0c, 0xea, 0x34, 0xaf, 0x00, 0x21, 0x68, 0xf8, 0xad, - 0x73, 0xe2, 0xc9, 0x32, 0xda, 0x38, 0x25, 0x0b, 0x55, 0xd3, 0x9a, 0x1d, - 0xf0, 0x68, 0x86, 0xed, 0x2e, 0x41, 0x34, 0xef, 0x7c, 0xa5, 0x50, 0x1d, - 0xbf, 0x3a, 0xf9, 0xd3, 0xc1, 0x08, 0x0c, 0xe6, 0xed, 0x1e, 0x8a, 0x58, - 0x25, 0xe4, 0xb8, 0x77, 0xad, 0x2d, 0x6e, 0xf5, 0x52, 0xdd, 0xb4, 0x74, - 0x8f, 0xab, 0x49, 0x2e, 0x9d, 0x3b, 0x93, 0x34, 0x28, 0x1f, 0x78, 0xce, - 0x94, 0xea, 0xc7, 0xbd, 0xd3, 0xc9, 0x6d, 0x1c, 0xde, 0x5c, 0x32, 0xf3, -} - -var certSet3Cert25 = []byte{ - 0x30, 0x82, 0x04, 0xa0, 0x30, 0x82, 0x03, 0x88, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x03, 0x39, 0x14, 0x84, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x68, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c, 0x53, - 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x54, 0x65, 0x63, - 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x65, 0x73, 0x2c, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x31, 0x32, 0x30, 0x30, 0x06, 0x03, 0x55, 0x04, 0x0b, - 0x13, 0x29, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, - 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x32, 0x20, 0x43, 0x65, 0x72, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x31, - 0x34, 0x30, 0x31, 0x30, 0x31, 0x30, 0x37, 0x30, 0x30, 0x30, 0x30, 0x5a, - 0x17, 0x0d, 0x33, 0x31, 0x30, 0x35, 0x33, 0x30, 0x30, 0x37, 0x30, 0x30, - 0x30, 0x30, 0x5a, 0x30, 0x81, 0x8f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, - 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, - 0x03, 0x55, 0x04, 0x08, 0x13, 0x07, 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, - 0x61, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0a, - 0x53, 0x63, 0x6f, 0x74, 0x74, 0x73, 0x64, 0x61, 0x6c, 0x65, 0x31, 0x25, - 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c, 0x53, 0x74, 0x61, - 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6e, - 0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x65, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, - 0x2e, 0x31, 0x32, 0x30, 0x30, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x29, - 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x52, 0x6f, - 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, - 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, - 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, - 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, - 0x01, 0x00, 0xbd, 0xed, 0xc1, 0x03, 0xfc, 0xf6, 0x8f, 0xfc, 0x02, 0xb1, - 0x6f, 0x5b, 0x9f, 0x48, 0xd9, 0x9d, 0x79, 0xe2, 0xa2, 0xb7, 0x03, 0x61, - 0x56, 0x18, 0xc3, 0x47, 0xb6, 0xd7, 0xca, 0x3d, 0x35, 0x2e, 0x89, 0x43, - 0xf7, 0xa1, 0x69, 0x9b, 0xde, 0x8a, 0x1a, 0xfd, 0x13, 0x20, 0x9c, 0xb4, - 0x49, 0x77, 0x32, 0x29, 0x56, 0xfd, 0xb9, 0xec, 0x8c, 0xdd, 0x22, 0xfa, - 0x72, 0xdc, 0x27, 0x61, 0x97, 0xee, 0xf6, 0x5a, 0x84, 0xec, 0x6e, 0x19, - 0xb9, 0x89, 0x2c, 0xdc, 0x84, 0x5b, 0xd5, 0x74, 0xfb, 0x6b, 0x5f, 0xc5, - 0x89, 0xa5, 0x10, 0x52, 0x89, 0x46, 0x55, 0xf4, 0xb8, 0x75, 0x1c, 0xe6, - 0x7f, 0xe4, 0x54, 0xae, 0x4b, 0xf8, 0x55, 0x72, 0x57, 0x02, 0x19, 0xf8, - 0x17, 0x71, 0x59, 0xeb, 0x1e, 0x28, 0x07, 0x74, 0xc5, 0x9d, 0x48, 0xbe, - 0x6c, 0xb4, 0xf4, 0xa4, 0xb0, 0xf3, 0x64, 0x37, 0x79, 0x92, 0xc0, 0xec, - 0x46, 0x5e, 0x7f, 0xe1, 0x6d, 0x53, 0x4c, 0x62, 0xaf, 0xcd, 0x1f, 0x0b, - 0x63, 0xbb, 0x3a, 0x9d, 0xfb, 0xfc, 0x79, 0x00, 0x98, 0x61, 0x74, 0xcf, - 0x26, 0x82, 0x40, 0x63, 0xf3, 0xb2, 0x72, 0x6a, 0x19, 0x0d, 0x99, 0xca, - 0xd4, 0x0e, 0x75, 0xcc, 0x37, 0xfb, 0x8b, 0x89, 0xc1, 0x59, 0xf1, 0x62, - 0x7f, 0x5f, 0xb3, 0x5f, 0x65, 0x30, 0xf8, 0xa7, 0xb7, 0x4d, 0x76, 0x5a, - 0x1e, 0x76, 0x5e, 0x34, 0xc0, 0xe8, 0x96, 0x56, 0x99, 0x8a, 0xb3, 0xf0, - 0x7f, 0xa4, 0xcd, 0xbd, 0xdc, 0x32, 0x31, 0x7c, 0x91, 0xcf, 0xe0, 0x5f, - 0x11, 0xf8, 0x6b, 0xaa, 0x49, 0x5c, 0xd1, 0x99, 0x94, 0xd1, 0xa2, 0xe3, - 0x63, 0x5b, 0x09, 0x76, 0xb5, 0x56, 0x62, 0xe1, 0x4b, 0x74, 0x1d, 0x96, - 0xd4, 0x26, 0xd4, 0x08, 0x04, 0x59, 0xd0, 0x98, 0x0e, 0x0e, 0xe6, 0xde, - 0xfc, 0xc3, 0xec, 0x1f, 0x90, 0xf1, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, - 0x82, 0x01, 0x29, 0x30, 0x82, 0x01, 0x25, 0x30, 0x0f, 0x06, 0x03, 0x55, - 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, - 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, - 0x03, 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, - 0x16, 0x04, 0x14, 0x7c, 0x0c, 0x32, 0x1f, 0xa7, 0xd9, 0x30, 0x7f, 0xc4, - 0x7d, 0x68, 0xa3, 0x62, 0xa8, 0xa1, 0xce, 0xab, 0x07, 0x5b, 0x27, 0x30, - 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, - 0xbf, 0x5f, 0xb7, 0xd1, 0xce, 0xdd, 0x1f, 0x86, 0xf4, 0x5b, 0x55, 0xac, - 0xdc, 0xd7, 0x10, 0xc2, 0x0e, 0xa9, 0x88, 0xe7, 0x30, 0x3a, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x2e, 0x30, 0x2c, - 0x30, 0x2a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, - 0x86, 0x1e, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, - 0x70, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x74, - 0x65, 0x63, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x38, 0x06, 0x03, - 0x55, 0x1d, 0x1f, 0x04, 0x31, 0x30, 0x2f, 0x30, 0x2d, 0xa0, 0x2b, 0xa0, - 0x29, 0x86, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, - 0x6c, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x74, - 0x65, 0x63, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x66, 0x72, 0x6f, - 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x4c, 0x06, 0x03, 0x55, 0x1d, - 0x20, 0x04, 0x45, 0x30, 0x43, 0x30, 0x41, 0x06, 0x04, 0x55, 0x1d, 0x20, - 0x00, 0x30, 0x39, 0x30, 0x37, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x02, 0x01, 0x16, 0x2b, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, - 0x2f, 0x63, 0x65, 0x72, 0x74, 0x73, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x66, - 0x69, 0x65, 0x6c, 0x64, 0x74, 0x65, 0x63, 0x68, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x85, 0x63, 0xc1, 0xd9, - 0xdd, 0xb9, 0xff, 0xa9, 0xbd, 0xa6, 0x19, 0xdc, 0xbf, 0x13, 0x3a, 0x11, - 0x38, 0x22, 0x54, 0xb1, 0xac, 0x05, 0x10, 0xfb, 0x7c, 0xb3, 0x96, 0x3f, - 0x31, 0x8b, 0x66, 0xff, 0x88, 0xf3, 0xe1, 0xbf, 0xfb, 0xc7, 0x1f, 0x00, - 0xff, 0x46, 0x6a, 0x8b, 0x61, 0x32, 0xc9, 0x01, 0x51, 0x76, 0xfb, 0x9a, - 0xc6, 0xfa, 0x20, 0x51, 0xc8, 0x46, 0xc4, 0x98, 0xd7, 0x79, 0xa3, 0xe3, - 0x04, 0x72, 0x3f, 0x8b, 0x4d, 0x34, 0x53, 0x67, 0xec, 0x33, 0x2c, 0x7b, - 0xe8, 0x94, 0x01, 0x28, 0x7c, 0x3a, 0x34, 0x5b, 0x02, 0x77, 0x16, 0x8d, - 0x40, 0x25, 0x33, 0xb0, 0xbc, 0x6c, 0x97, 0xd7, 0x05, 0x7a, 0xff, 0x8c, - 0x85, 0xce, 0x6f, 0xa0, 0x53, 0x00, 0x17, 0x6e, 0x1e, 0x6c, 0xbd, 0x22, - 0xd7, 0x0a, 0x88, 0x37, 0xf6, 0x7d, 0xeb, 0x99, 0x41, 0xef, 0x27, 0xcb, - 0x8c, 0x60, 0x6b, 0x4c, 0x01, 0x7e, 0x65, 0x50, 0x0b, 0x4f, 0xb8, 0x95, - 0x9a, 0x9a, 0x6e, 0x34, 0xfd, 0x73, 0x3a, 0x33, 0xf1, 0x91, 0xd5, 0xf3, - 0x4e, 0x2d, 0x74, 0xe8, 0xef, 0xd3, 0x90, 0x35, 0xf1, 0x06, 0x68, 0x64, - 0xd4, 0xd0, 0x13, 0xfd, 0x52, 0xd3, 0xc6, 0x6d, 0xc1, 0x3a, 0x8a, 0x31, - 0xdd, 0x05, 0x26, 0x35, 0x4a, 0x8c, 0x65, 0xb8, 0x52, 0x6b, 0x81, 0xec, - 0xd2, 0x9c, 0xb5, 0x34, 0x10, 0x97, 0x9c, 0x3e, 0xc6, 0x2f, 0xed, 0x8e, - 0x42, 0x42, 0x24, 0x2e, 0xe9, 0x73, 0x9a, 0x25, 0xf9, 0x11, 0xf1, 0xf2, - 0x23, 0x69, 0xcb, 0xe5, 0x94, 0x69, 0xa0, 0xd2, 0xdc, 0xb0, 0xfc, 0x44, - 0x89, 0xac, 0x17, 0xa8, 0xcc, 0xd5, 0x37, 0x77, 0x16, 0xc5, 0x80, 0xb9, - 0x0c, 0x8f, 0x57, 0x02, 0x55, 0x99, 0x85, 0x7b, 0x49, 0xf0, 0x2e, 0x5b, - 0xa0, 0xc2, 0x57, 0x53, 0x5d, 0xa2, 0xe8, 0xa6, 0x37, 0xc3, 0x01, 0xfa, -} - -var certSet3Cert26 = []byte{ - 0x30, 0x82, 0x04, 0xa6, 0x30, 0x82, 0x03, 0x8e, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x28, 0x1c, 0x89, 0x29, 0x66, 0x14, 0x43, 0x80, 0x42, - 0x63, 0x55, 0x3a, 0x32, 0x40, 0xae, 0xb3, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, - 0x98, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, - 0x63, 0x2e, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, - 0x30, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x38, 0x20, 0x47, 0x65, - 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, - 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, - 0x79, 0x31, 0x36, 0x30, 0x34, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2d, - 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x50, 0x72, 0x69, - 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x30, 0x1e, 0x17, - 0x0d, 0x31, 0x35, 0x30, 0x36, 0x33, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, - 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x35, 0x30, 0x36, 0x32, 0x39, 0x32, 0x33, - 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x47, 0x31, 0x0b, 0x30, 0x09, 0x06, - 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, - 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, - 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x20, 0x30, 0x1e, - 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x17, 0x52, 0x61, 0x70, 0x69, 0x64, - 0x53, 0x53, 0x4c, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x43, - 0x41, 0x20, 0x2d, 0x20, 0x47, 0x34, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, - 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, - 0x01, 0x01, 0x00, 0xc0, 0x9e, 0x3a, 0x0f, 0x9a, 0xb2, 0xba, 0xd3, 0xd2, - 0xdc, 0x15, 0xec, 0xd0, 0x30, 0x54, 0x59, 0x30, 0x4d, 0x40, 0x51, 0xae, - 0x42, 0x71, 0x71, 0xd2, 0x8d, 0x53, 0x73, 0x81, 0xfe, 0xb8, 0xe0, 0xc4, - 0x96, 0xc5, 0x8e, 0x7e, 0xc2, 0xf1, 0xb7, 0x63, 0x4a, 0xcf, 0xa7, 0x1e, - 0x3f, 0xa8, 0xe7, 0xce, 0x53, 0xa0, 0xfa, 0x2d, 0xf7, 0xd6, 0xe6, 0xce, - 0x70, 0x11, 0xa6, 0xee, 0xe1, 0x03, 0x52, 0xd2, 0x68, 0xde, 0x3d, 0x08, - 0x0d, 0x87, 0xfd, 0x1c, 0xd7, 0x0b, 0x97, 0x62, 0x6d, 0x82, 0x30, 0x76, - 0x1b, 0x47, 0x3a, 0xc4, 0xf7, 0xce, 0xed, 0x1d, 0x7c, 0x8c, 0xb7, 0x17, - 0x8e, 0x53, 0x80, 0x1e, 0x1d, 0x0f, 0x5d, 0x8c, 0xf9, 0x90, 0xe4, 0x04, - 0x1e, 0x02, 0x7e, 0xcb, 0xb0, 0x49, 0xef, 0xda, 0x52, 0x25, 0xfb, 0xfb, - 0x67, 0xed, 0xdd, 0x84, 0x74, 0x59, 0x84, 0x0e, 0xf3, 0xde, 0x70, 0x66, - 0x8d, 0xe4, 0x52, 0x38, 0xf7, 0x53, 0x5a, 0x37, 0x13, 0x67, 0x0b, 0x3e, - 0xbb, 0xa8, 0x58, 0xb7, 0x2e, 0xed, 0xff, 0xb7, 0x5e, 0x11, 0x73, 0xb9, - 0x77, 0x45, 0x52, 0x67, 0x46, 0xae, 0xc4, 0xdc, 0x24, 0x81, 0x89, 0x76, - 0x0a, 0xca, 0xa1, 0x6c, 0x66, 0x73, 0x04, 0x82, 0xaa, 0xf5, 0x70, 0x6c, - 0x5f, 0x1b, 0x9a, 0x00, 0x79, 0x46, 0xd6, 0x7f, 0x7a, 0x26, 0x17, 0x30, - 0xcf, 0x39, 0x4b, 0x2c, 0x74, 0xd9, 0x89, 0x44, 0x76, 0x10, 0xd0, 0xed, - 0xf7, 0x8b, 0xbb, 0x89, 0x05, 0x75, 0x4d, 0x0b, 0x0d, 0xb3, 0xda, 0xe9, - 0xbf, 0xf1, 0x6a, 0x7d, 0x2a, 0x11, 0xdb, 0x1e, 0x9f, 0x8c, 0xe3, 0xc4, - 0x06, 0x69, 0xe1, 0x1d, 0x88, 0x45, 0x39, 0xd1, 0x6e, 0x55, 0xd8, 0xaa, - 0xb7, 0x9b, 0x6f, 0xea, 0xf4, 0xde, 0xac, 0x17, 0x11, 0x92, 0x5d, 0x40, - 0x9b, 0x83, 0x7b, 0x9a, 0xe2, 0xf7, 0xa9, 0x02, 0x03, 0x01, 0x00, 0x01, - 0xa3, 0x82, 0x01, 0x3a, 0x30, 0x82, 0x01, 0x36, 0x30, 0x2e, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x22, 0x30, 0x20, - 0x30, 0x1e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, - 0x86, 0x12, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x67, 0x2e, 0x73, - 0x79, 0x6d, 0x63, 0x64, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x12, 0x06, 0x03, - 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, - 0xff, 0x02, 0x01, 0x00, 0x30, 0x49, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, - 0x42, 0x30, 0x40, 0x30, 0x3e, 0x06, 0x06, 0x67, 0x81, 0x0c, 0x01, 0x02, - 0x01, 0x30, 0x34, 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x02, 0x01, 0x16, 0x26, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, - 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, - 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x36, 0x06, 0x03, 0x55, - 0x1d, 0x1f, 0x04, 0x2f, 0x30, 0x2d, 0x30, 0x2b, 0xa0, 0x29, 0xa0, 0x27, - 0x86, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x67, 0x2e, 0x73, - 0x79, 0x6d, 0x63, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x47, 0x65, 0x6f, - 0x54, 0x72, 0x75, 0x73, 0x74, 0x50, 0x43, 0x41, 0x2d, 0x47, 0x33, 0x2e, - 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x16, - 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x30, 0x0e, - 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, - 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, - 0x14, 0xf3, 0xb5, 0x56, 0x0c, 0xc4, 0x09, 0xb0, 0xb4, 0xcf, 0x1f, 0xaa, - 0xf9, 0xdd, 0x23, 0x56, 0xf0, 0x77, 0xe8, 0xa1, 0xf9, 0x30, 0x1f, 0x06, - 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xc4, 0x79, - 0xca, 0x8e, 0xa1, 0x4e, 0x03, 0x1d, 0x1c, 0xdc, 0x6b, 0xdb, 0x31, 0x5b, - 0x94, 0x3e, 0x3f, 0x30, 0x7f, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, - 0x01, 0x00, 0xc3, 0x7e, 0xd8, 0x83, 0x4b, 0x04, 0x4c, 0x55, 0x29, 0x2a, - 0x4f, 0x14, 0x9d, 0x9a, 0x6e, 0xde, 0x90, 0x70, 0xc1, 0xa4, 0x26, 0x4c, - 0x88, 0x8e, 0x78, 0x48, 0xef, 0xbd, 0x9c, 0xb0, 0xa0, 0xf5, 0xf0, 0x66, - 0xfc, 0xfe, 0x59, 0x26, 0xe1, 0x79, 0xef, 0xc8, 0xb7, 0x60, 0x64, 0xa8, - 0x8b, 0x47, 0xea, 0x2f, 0xe0, 0x83, 0x99, 0xda, 0x41, 0x19, 0xd7, 0xc5, - 0xbe, 0x05, 0xfa, 0xf2, 0x90, 0x11, 0xf0, 0x0a, 0xff, 0x6c, 0xdc, 0x05, - 0xb4, 0xd8, 0x06, 0x6f, 0xa4, 0x6f, 0x8d, 0xbe, 0x20, 0x2b, 0x54, 0xdb, - 0xf9, 0xa2, 0x45, 0x83, 0x9a, 0x1e, 0xa5, 0x21, 0x89, 0x35, 0x1d, 0x7c, - 0x20, 0x5c, 0x17, 0xfd, 0x04, 0x2e, 0x45, 0xd8, 0xb2, 0xc6, 0xf8, 0x42, - 0x99, 0xfc, 0x54, 0x08, 0x4e, 0x4b, 0x80, 0x5f, 0x39, 0x37, 0xba, 0x95, - 0x4e, 0xa6, 0x37, 0x0a, 0x9e, 0x93, 0x5e, 0x87, 0x5b, 0xe9, 0x90, 0xd6, - 0xa8, 0xb6, 0x65, 0x08, 0x8d, 0x61, 0x49, 0xeb, 0x83, 0x20, 0xa9, 0x5d, - 0x1b, 0x16, 0x60, 0x62, 0x6b, 0x2f, 0x54, 0xfb, 0x5a, 0x02, 0x0d, 0x7a, - 0x27, 0xe2, 0x4b, 0xe1, 0x05, 0x14, 0xc2, 0xe4, 0xe9, 0xf9, 0x70, 0xc0, - 0xd9, 0xf7, 0x34, 0x65, 0x0e, 0xa2, 0x91, 0x4b, 0xac, 0x28, 0xf2, 0xb7, - 0x08, 0x0f, 0x98, 0xca, 0xd7, 0x3e, 0x70, 0xb6, 0xc8, 0x0b, 0xf1, 0x8b, - 0x9c, 0x51, 0xf8, 0xc6, 0x10, 0x6c, 0xd2, 0x53, 0x4f, 0x62, 0x8c, 0x11, - 0x00, 0x3e, 0x88, 0xdf, 0xbf, 0xe6, 0xd2, 0xcc, 0x70, 0xbd, 0xed, 0x25, - 0x9c, 0xfb, 0xdd, 0x24, 0x0a, 0xbd, 0x59, 0x91, 0x4a, 0x42, 0x03, 0x38, - 0x12, 0x71, 0x32, 0x88, 0x76, 0xa0, 0x8e, 0x7c, 0xbb, 0x32, 0xef, 0x88, - 0x2a, 0x1b, 0xd4, 0x6a, 0x6f, 0x50, 0xb9, 0x52, 0x67, 0x8b, 0xab, 0x30, - 0xfa, 0x1f, 0xfd, 0xe3, 0x24, 0x9a, -} - -var certSet3Cert27 = []byte{ - 0x30, 0x82, 0x04, 0xa8, 0x30, 0x82, 0x03, 0x90, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x11, 0x00, 0xe4, 0x05, 0x47, 0x83, 0x0e, 0x0c, 0x64, 0x52, - 0x97, 0x6f, 0x7a, 0x35, 0x49, 0xc0, 0xdd, 0x48, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, - 0x7e, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x50, 0x4c, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x19, 0x55, 0x6e, 0x69, 0x7a, 0x65, 0x74, 0x6f, 0x20, 0x54, 0x65, 0x63, - 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x65, 0x73, 0x20, 0x53, 0x2e, - 0x41, 0x2e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, - 0x1e, 0x43, 0x65, 0x72, 0x74, 0x75, 0x6d, 0x20, 0x43, 0x65, 0x72, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31, 0x22, 0x30, 0x20, 0x06, - 0x03, 0x55, 0x04, 0x03, 0x13, 0x19, 0x43, 0x65, 0x72, 0x74, 0x75, 0x6d, - 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x20, 0x4e, 0x65, 0x74, - 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, - 0x35, 0x30, 0x31, 0x32, 0x31, 0x31, 0x32, 0x30, 0x30, 0x30, 0x30, 0x5a, - 0x17, 0x0d, 0x32, 0x35, 0x30, 0x31, 0x31, 0x38, 0x31, 0x32, 0x30, 0x30, - 0x30, 0x30, 0x5a, 0x30, 0x5f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, - 0x04, 0x06, 0x13, 0x02, 0x52, 0x55, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, - 0x55, 0x04, 0x0a, 0x13, 0x0a, 0x59, 0x61, 0x6e, 0x64, 0x65, 0x78, 0x20, - 0x4c, 0x4c, 0x43, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b, - 0x13, 0x1e, 0x59, 0x61, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x43, 0x65, 0x72, - 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31, 0x12, 0x30, 0x10, - 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x09, 0x59, 0x61, 0x6e, 0x64, 0x65, - 0x78, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, - 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, - 0x00, 0xa6, 0x05, 0x24, 0x76, 0x61, 0xb9, 0x9e, 0x42, 0x60, 0x22, 0x63, - 0x85, 0x59, 0xe5, 0x9d, 0x88, 0x0d, 0xdf, 0xef, 0x21, 0x64, 0x5a, 0x26, - 0x94, 0x71, 0x3a, 0xa4, 0x7f, 0x2b, 0x53, 0xc3, 0xac, 0x7b, 0xba, 0x95, - 0x42, 0x6d, 0x6a, 0x5b, 0xd6, 0x7e, 0x78, 0x0c, 0x67, 0x40, 0x98, 0x2f, - 0x6a, 0x2d, 0xd0, 0xb7, 0x18, 0x3a, 0x7e, 0x99, 0x60, 0x01, 0xe5, 0x27, - 0xbf, 0xff, 0x49, 0xf5, 0xcd, 0xc4, 0x58, 0xc3, 0x4c, 0xe1, 0x70, 0xd5, - 0xfd, 0x08, 0xa8, 0x79, 0x95, 0x76, 0x1c, 0x0e, 0x05, 0x41, 0xfa, 0xbd, - 0x80, 0x38, 0x2a, 0x87, 0x4f, 0xc1, 0x67, 0x42, 0xaa, 0x17, 0xa6, 0xee, - 0xa7, 0x8c, 0x8e, 0xef, 0x2d, 0x7f, 0x7a, 0x1d, 0x05, 0x17, 0x8f, 0x7e, - 0x3b, 0x92, 0x35, 0xf5, 0x68, 0xed, 0x93, 0x03, 0x55, 0x23, 0x4f, 0x4b, - 0xa2, 0x00, 0x86, 0x65, 0x91, 0x0f, 0xeb, 0xf6, 0x3c, 0xd5, 0xdb, 0x6d, - 0x0e, 0xed, 0xe8, 0x7c, 0x3a, 0xc8, 0xba, 0xb7, 0x53, 0xc1, 0xa4, 0xd8, - 0x40, 0x02, 0xe5, 0xb5, 0xa2, 0xca, 0xbf, 0xda, 0x9c, 0x94, 0x0d, 0xfc, - 0xc5, 0x1c, 0x2a, 0x59, 0x88, 0x62, 0x57, 0x93, 0x2e, 0x11, 0xf0, 0x38, - 0x2c, 0x7a, 0x81, 0x2a, 0xf2, 0x25, 0x15, 0x17, 0x35, 0x70, 0x2c, 0x4b, - 0xf7, 0x23, 0x4c, 0x82, 0xef, 0x33, 0x9f, 0xc2, 0x9a, 0x0b, 0xa3, 0xe2, - 0x5d, 0x6b, 0x38, 0x77, 0xf9, 0x60, 0x33, 0xcf, 0x2e, 0x7b, 0x56, 0xb7, - 0x13, 0x93, 0x1f, 0x34, 0x97, 0x71, 0x99, 0x76, 0x02, 0x46, 0x35, 0x14, - 0x7c, 0xdc, 0xca, 0x48, 0x8a, 0x0a, 0x72, 0x4b, 0x78, 0x6d, 0x82, 0x34, - 0x96, 0x13, 0x45, 0xcf, 0x02, 0x2f, 0x50, 0x13, 0x39, 0x43, 0x89, 0xc0, - 0xe1, 0x74, 0xd7, 0x28, 0x71, 0x21, 0xe5, 0xaa, 0x97, 0x0e, 0xee, 0x46, - 0xec, 0x93, 0xf7, 0x23, 0x7d, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, - 0x01, 0x3e, 0x30, 0x82, 0x01, 0x3a, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, - 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, - 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x37, 0x5c, - 0xe3, 0x19, 0xe0, 0xb2, 0x8e, 0xa1, 0xa8, 0x4e, 0xd2, 0xcf, 0xab, 0xd0, - 0xdc, 0xe3, 0x0b, 0x5c, 0x35, 0x4d, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, - 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x08, 0x76, 0xcd, 0xcb, 0x07, - 0xff, 0x24, 0xf6, 0xc5, 0xcd, 0xed, 0xbb, 0x90, 0xbc, 0xe2, 0x84, 0x37, - 0x46, 0x75, 0xf7, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, - 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x2f, 0x06, 0x03, 0x55, - 0x1d, 0x1f, 0x04, 0x28, 0x30, 0x26, 0x30, 0x24, 0xa0, 0x22, 0xa0, 0x20, - 0x86, 0x1e, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, - 0x2e, 0x63, 0x65, 0x72, 0x74, 0x75, 0x6d, 0x2e, 0x70, 0x6c, 0x2f, 0x63, - 0x74, 0x6e, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x6b, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x5f, 0x30, 0x5d, - 0x30, 0x28, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, - 0x86, 0x1c, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x73, 0x75, 0x62, - 0x63, 0x61, 0x2e, 0x6f, 0x63, 0x73, 0x70, 0x2d, 0x63, 0x65, 0x72, 0x74, - 0x75, 0x6d, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x31, 0x06, 0x08, 0x2b, 0x06, - 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x25, 0x68, 0x74, 0x74, 0x70, - 0x3a, 0x2f, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, - 0x79, 0x2e, 0x63, 0x65, 0x72, 0x74, 0x75, 0x6d, 0x2e, 0x70, 0x6c, 0x2f, - 0x63, 0x74, 0x6e, 0x63, 0x61, 0x2e, 0x63, 0x65, 0x72, 0x30, 0x39, 0x06, - 0x03, 0x55, 0x1d, 0x20, 0x04, 0x32, 0x30, 0x30, 0x30, 0x2e, 0x06, 0x04, - 0x55, 0x1d, 0x20, 0x00, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, - 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x18, 0x68, 0x74, 0x74, 0x70, - 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x63, 0x65, 0x72, 0x74, 0x75, - 0x6d, 0x2e, 0x70, 0x6c, 0x2f, 0x43, 0x50, 0x53, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, - 0x82, 0x01, 0x01, 0x00, 0x02, 0x5e, 0x8e, 0x7b, 0xe0, 0x66, 0xa1, 0xc6, - 0xab, 0x8b, 0x18, 0x1f, 0x0e, 0xb9, 0xc4, 0xcd, 0x71, 0xdb, 0x44, 0x5c, - 0x03, 0x7d, 0x65, 0xea, 0xb8, 0x47, 0xb5, 0x1e, 0xce, 0x24, 0x70, 0xa0, - 0x7f, 0xd3, 0xdf, 0x66, 0x4b, 0x8c, 0x90, 0xe2, 0xa5, 0xed, 0x9b, 0x94, - 0x36, 0xb4, 0xa8, 0xbe, 0xf0, 0x74, 0x8c, 0x26, 0x92, 0x75, 0x9d, 0x56, - 0x50, 0x9e, 0xad, 0xd0, 0x1a, 0xa0, 0xdf, 0xa4, 0x14, 0x56, 0x10, 0x75, - 0x93, 0x7a, 0xc1, 0xf4, 0x53, 0xa0, 0x76, 0x74, 0x2c, 0x72, 0xba, 0xb5, - 0xd1, 0xc9, 0xe2, 0xdc, 0x46, 0x86, 0x3f, 0x1d, 0xf6, 0x33, 0x87, 0x59, - 0xec, 0x9c, 0xdc, 0x2d, 0x1e, 0x4d, 0x43, 0x1a, 0xce, 0xba, 0xd9, 0x87, - 0x7e, 0xe2, 0x47, 0x45, 0x72, 0x3d, 0x28, 0x03, 0xc9, 0x0a, 0x4d, 0xe0, - 0x57, 0xa3, 0x5e, 0x6e, 0x7e, 0xcc, 0x5a, 0xc8, 0xc4, 0x78, 0x01, 0x57, - 0x68, 0x7a, 0x38, 0x3b, 0x53, 0x36, 0xe7, 0x92, 0x6d, 0x8a, 0x2c, 0x2f, - 0xd7, 0x8b, 0xb6, 0x34, 0xa8, 0xd1, 0xb6, 0xf8, 0x5e, 0x3b, 0xab, 0xed, - 0xa5, 0x8f, 0x39, 0x6f, 0x45, 0xad, 0xcb, 0x63, 0xed, 0x6a, 0x64, 0xc9, - 0x10, 0xa7, 0x03, 0x08, 0x12, 0x53, 0xb1, 0x1c, 0xaf, 0xca, 0xf7, 0x53, - 0xfc, 0xd8, 0x29, 0x4b, 0x1b, 0xfb, 0x38, 0xcd, 0xc0, 0x63, 0xff, 0x5f, - 0xe4, 0xb9, 0x8d, 0x5e, 0xaa, 0x2b, 0xd2, 0xc3, 0x22, 0x35, 0x31, 0xf6, - 0x30, 0x0e, 0x53, 0x32, 0xf4, 0x93, 0xc5, 0x43, 0xcb, 0xc8, 0xf0, 0x15, - 0x56, 0x8f, 0x00, 0x19, 0x87, 0xca, 0x78, 0x22, 0x8d, 0xa0, 0x2e, 0xdb, - 0x2f, 0xa0, 0xc3, 0x7e, 0x29, 0x5d, 0x91, 0x25, 0x84, 0x1d, 0x1d, 0x39, - 0xab, 0x1b, 0xc5, 0xd6, 0x91, 0xfe, 0x69, 0x0e, 0x46, 0x80, 0xbc, 0x45, - 0x7b, 0x35, 0x53, 0x2a, 0xdf, 0x00, 0xb6, 0x77, -} - -var certSet3Cert28 = []byte{ - 0x30, 0x82, 0x04, 0xaf, 0x30, 0x82, 0x03, 0x97, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x5d, 0x72, 0xfb, 0x33, 0x76, 0x20, 0xf6, 0x4c, 0x72, - 0x80, 0xdb, 0xe9, 0x12, 0x81, 0xff, 0x6a, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, - 0xa9, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, - 0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1f, - 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x44, - 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x38, 0x30, 0x36, 0x06, - 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, - 0x30, 0x36, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, - 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x16, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50, - 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, - 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x33, 0x31, 0x30, 0x33, 0x31, - 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x33, 0x31, - 0x30, 0x33, 0x30, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x44, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, - 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x15, 0x74, - 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x45, 0x56, 0x20, 0x53, 0x53, 0x4c, - 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x30, 0x82, 0x01, 0x22, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, - 0x02, 0x82, 0x01, 0x01, 0x00, 0xc4, 0xdd, 0xda, 0x94, 0x1e, 0x32, 0xb2, - 0x2e, 0xa0, 0x83, 0xc0, 0xa6, 0x7d, 0x5f, 0x65, 0x2d, 0xfd, 0x27, 0xb8, - 0x73, 0x0e, 0xf8, 0x0b, 0xa9, 0xd4, 0x56, 0x26, 0x69, 0x98, 0x67, 0x35, - 0x39, 0x64, 0x58, 0xce, 0x82, 0x6f, 0x98, 0x94, 0xd1, 0x8f, 0xe0, 0x90, - 0xd6, 0xed, 0x55, 0x4b, 0x98, 0x4b, 0xd7, 0x10, 0x59, 0x34, 0x02, 0x1b, - 0xe7, 0x51, 0x31, 0x51, 0xc4, 0x38, 0xc2, 0xbc, 0xdb, 0x03, 0x5c, 0xca, - 0xe1, 0x7c, 0xdc, 0x4f, 0x59, 0x97, 0xea, 0x07, 0x7f, 0x0f, 0x85, 0x3e, - 0x92, 0xea, 0xaa, 0xa7, 0xd9, 0xbe, 0x01, 0x41, 0xe4, 0x62, 0x56, 0x47, - 0x36, 0xbd, 0x57, 0x91, 0xe6, 0x21, 0xd3, 0xf8, 0x41, 0x0b, 0xd8, 0xba, - 0xe8, 0xed, 0x81, 0xad, 0x70, 0xc0, 0x8b, 0x6e, 0xf3, 0x89, 0x6e, 0x27, - 0x9e, 0xa6, 0xa6, 0x73, 0x59, 0xbb, 0x71, 0x00, 0xd4, 0x4f, 0x4b, 0x48, - 0xe9, 0xd5, 0xc9, 0x27, 0x36, 0x9c, 0x7c, 0x1c, 0x02, 0xaa, 0xac, 0xbd, - 0x3b, 0xd1, 0x53, 0x83, 0x6a, 0x1f, 0xe6, 0x08, 0x47, 0x33, 0xa7, 0xb1, - 0x9f, 0x02, 0xbe, 0x9b, 0x47, 0xed, 0x33, 0x04, 0xdc, 0x1c, 0x80, 0x27, - 0xd1, 0x4a, 0x33, 0xa0, 0x8c, 0xeb, 0x01, 0x47, 0xa1, 0x32, 0x90, 0x64, - 0x7b, 0xc4, 0xe0, 0x84, 0xc9, 0x32, 0xe9, 0xdd, 0x34, 0x1f, 0x8a, 0x68, - 0x67, 0xf3, 0xad, 0x10, 0x63, 0xeb, 0xee, 0x8a, 0x9a, 0xb1, 0x2a, 0x1b, - 0x26, 0x74, 0xa1, 0x2a, 0xb0, 0x8f, 0xfe, 0x52, 0x98, 0x46, 0x97, 0xcf, - 0xa3, 0x56, 0x1c, 0x6f, 0x6e, 0x99, 0x97, 0x8d, 0x26, 0x0e, 0xa9, 0xec, - 0xc2, 0x53, 0x70, 0xfc, 0x7a, 0xa5, 0x19, 0x49, 0xbd, 0xb5, 0x17, 0x82, - 0x55, 0xde, 0x97, 0xe0, 0x5d, 0x62, 0x84, 0x81, 0xf0, 0x70, 0xa8, 0x34, - 0x53, 0x4f, 0x14, 0xfd, 0x3d, 0x5d, 0x3d, 0x6f, 0xb9, 0x02, 0x03, 0x01, - 0x00, 0x01, 0xa3, 0x82, 0x01, 0x35, 0x30, 0x82, 0x01, 0x31, 0x30, 0x12, - 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, - 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, - 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x2f, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x23, - 0x30, 0x21, 0x30, 0x1f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x30, 0x01, 0x86, 0x13, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x74, - 0x32, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x30, - 0x3b, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, - 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, - 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x74, 0x68, - 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73, - 0x30, 0x32, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2b, 0x30, 0x29, 0x30, - 0x27, 0xa0, 0x25, 0xa0, 0x23, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x74, 0x31, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x50, 0x43, 0x41, - 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x29, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, - 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, 0x31, 0x1a, 0x30, 0x18, 0x06, - 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x53, 0x79, 0x6d, 0x61, 0x6e, 0x74, - 0x65, 0x63, 0x50, 0x4b, 0x49, 0x2d, 0x31, 0x2d, 0x35, 0x33, 0x36, 0x30, - 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xf0, 0x70, - 0x51, 0xda, 0xd3, 0x2a, 0x91, 0x4f, 0x52, 0x77, 0xd7, 0x86, 0x77, 0x74, - 0x0f, 0xce, 0x71, 0x1a, 0x6c, 0x22, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, - 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x7b, 0x5b, 0x45, 0xcf, 0xaf, - 0xce, 0xcb, 0x7a, 0xfd, 0x31, 0x92, 0x1a, 0x6a, 0xb6, 0xf3, 0x46, 0xeb, - 0x57, 0x48, 0x50, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, - 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0xa1, - 0x2e, 0x94, 0x3e, 0x9b, 0x16, 0xf4, 0x58, 0x1a, 0x6f, 0xc1, 0xfa, 0xc1, - 0x7e, 0x43, 0x93, 0xb2, 0xc3, 0xf7, 0x89, 0xeb, 0x13, 0x62, 0x5d, 0xdd, - 0xcc, 0x61, 0x13, 0x2b, 0x1d, 0x4e, 0x88, 0x79, 0x11, 0x62, 0x14, 0x37, - 0x30, 0x46, 0xff, 0x89, 0x62, 0x10, 0x85, 0x2a, 0x87, 0x1e, 0xf8, 0xe2, - 0xaf, 0xfe, 0x93, 0x02, 0x93, 0xca, 0xf2, 0xe9, 0x46, 0x03, 0x6b, 0xa1, - 0x1a, 0xac, 0xd5, 0xf0, 0x80, 0x1b, 0x98, 0x6f, 0xb8, 0x3a, 0x50, 0xf8, - 0x54, 0x71, 0x06, 0x03, 0xe7, 0x84, 0xcc, 0x8e, 0x61, 0xd2, 0x5f, 0x4d, - 0x0c, 0x97, 0x02, 0x65, 0xb5, 0x8c, 0x26, 0xbc, 0x05, 0x98, 0xf4, 0xdc, - 0xc6, 0xaf, 0xe4, 0x57, 0x7f, 0xe3, 0xdc, 0xa1, 0xd7, 0x27, 0x47, 0x2a, - 0xe0, 0x2c, 0x3f, 0x09, 0x74, 0xdc, 0x5a, 0xe5, 0xb5, 0x7c, 0xfa, 0x82, - 0x9a, 0x15, 0xfa, 0x74, 0x2b, 0x84, 0x2e, 0x6b, 0xac, 0xef, 0x35, 0xa6, - 0x30, 0xfa, 0x47, 0x4a, 0xaa, 0x36, 0x44, 0xf6, 0x5a, 0x91, 0x07, 0xd3, - 0xe4, 0x4e, 0x97, 0x3f, 0xa6, 0x53, 0xd8, 0x29, 0x33, 0x32, 0x6f, 0x8b, - 0x3d, 0xb5, 0xa5, 0x0d, 0xe5, 0xe4, 0x8a, 0xe8, 0xf5, 0xc0, 0xfa, 0xaf, - 0xd8, 0x37, 0x28, 0x27, 0xc3, 0xed, 0x34, 0x31, 0xd9, 0x7c, 0xa6, 0xaf, - 0x4d, 0x12, 0x4f, 0xd0, 0x2b, 0x92, 0x9c, 0x69, 0x95, 0xf2, 0x28, 0xa6, - 0xfe, 0xa8, 0xc6, 0xe0, 0x2c, 0x4d, 0x36, 0xeb, 0x11, 0x34, 0xd6, 0xe1, - 0x81, 0x99, 0x9d, 0x41, 0xf2, 0xe7, 0xc5, 0x57, 0x05, 0x0e, 0x19, 0xca, - 0xaf, 0x42, 0x39, 0x1f, 0xa7, 0x27, 0x5e, 0xe0, 0x0a, 0x17, 0xb8, 0xae, - 0x47, 0xab, 0x92, 0xf1, 0x8a, 0x04, 0xdf, 0x30, 0xe0, 0xbb, 0x4f, 0x8a, - 0xf9, 0x1b, 0x88, 0x4f, 0x03, 0xb4, 0x25, 0x7a, 0x78, 0xde, 0x2e, 0x7d, - 0x29, 0xd1, 0x31, -} - -var certSet3Cert29 = []byte{ - 0x30, 0x82, 0x04, 0xb1, 0x30, 0x82, 0x03, 0x99, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x04, 0xe1, 0xe7, 0xa4, 0xdc, 0x5c, 0xf2, 0xf3, 0x6d, - 0xc0, 0x2b, 0x42, 0xb8, 0x5d, 0x15, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x6c, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, - 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, - 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, - 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, - 0x63, 0x6f, 0x6d, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03, - 0x13, 0x22, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, - 0x69, 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, - 0x65, 0x20, 0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, - 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x33, 0x31, 0x30, 0x32, 0x32, 0x31, 0x32, - 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x38, 0x31, 0x30, 0x32, - 0x32, 0x31, 0x32, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x70, 0x31, 0x0b, - 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, - 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69, - 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x19, - 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, 0x77, 0x77, - 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, - 0x6d, 0x31, 0x2f, 0x30, 0x2d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x26, - 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x53, 0x48, 0x41, - 0x32, 0x20, 0x48, 0x69, 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, - 0x61, 0x6e, 0x63, 0x65, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, - 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, - 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb6, - 0xe0, 0x2f, 0xc2, 0x24, 0x06, 0xc8, 0x6d, 0x04, 0x5f, 0xd7, 0xef, 0x0a, - 0x64, 0x06, 0xb2, 0x7d, 0x22, 0x26, 0x65, 0x16, 0xae, 0x42, 0x40, 0x9b, - 0xce, 0xdc, 0x9f, 0x9f, 0x76, 0x07, 0x3e, 0xc3, 0x30, 0x55, 0x87, 0x19, - 0xb9, 0x4f, 0x94, 0x0e, 0x5a, 0x94, 0x1f, 0x55, 0x56, 0xb4, 0xc2, 0x02, - 0x2a, 0xaf, 0xd0, 0x98, 0xee, 0x0b, 0x40, 0xd7, 0xc4, 0xd0, 0x3b, 0x72, - 0xc8, 0x14, 0x9e, 0xef, 0x90, 0xb1, 0x11, 0xa9, 0xae, 0xd2, 0xc8, 0xb8, - 0x43, 0x3a, 0xd9, 0x0b, 0x0b, 0xd5, 0xd5, 0x95, 0xf5, 0x40, 0xaf, 0xc8, - 0x1d, 0xed, 0x4d, 0x9c, 0x5f, 0x57, 0xb7, 0x86, 0x50, 0x68, 0x99, 0xf5, - 0x8a, 0xda, 0xd2, 0xc7, 0x05, 0x1f, 0xa8, 0x97, 0xc9, 0xdc, 0xa4, 0xb1, - 0x82, 0x84, 0x2d, 0xc6, 0xad, 0xa5, 0x9c, 0xc7, 0x19, 0x82, 0xa6, 0x85, - 0x0f, 0x5e, 0x44, 0x58, 0x2a, 0x37, 0x8f, 0xfd, 0x35, 0xf1, 0x0b, 0x08, - 0x27, 0x32, 0x5a, 0xf5, 0xbb, 0x8b, 0x9e, 0xa4, 0xbd, 0x51, 0xd0, 0x27, - 0xe2, 0xdd, 0x3b, 0x42, 0x33, 0xa3, 0x05, 0x28, 0xc4, 0xbb, 0x28, 0xcc, - 0x9a, 0xac, 0x2b, 0x23, 0x0d, 0x78, 0xc6, 0x7b, 0xe6, 0x5e, 0x71, 0xb7, - 0x4a, 0x3e, 0x08, 0xfb, 0x81, 0xb7, 0x16, 0x16, 0xa1, 0x9d, 0x23, 0x12, - 0x4d, 0xe5, 0xd7, 0x92, 0x08, 0xac, 0x75, 0xa4, 0x9c, 0xba, 0xcd, 0x17, - 0xb2, 0x1e, 0x44, 0x35, 0x65, 0x7f, 0x53, 0x25, 0x39, 0xd1, 0x1c, 0x0a, - 0x9a, 0x63, 0x1b, 0x19, 0x92, 0x74, 0x68, 0x0a, 0x37, 0xc2, 0xc2, 0x52, - 0x48, 0xcb, 0x39, 0x5a, 0xa2, 0xb6, 0xe1, 0x5d, 0xc1, 0xdd, 0xa0, 0x20, - 0xb8, 0x21, 0xa2, 0x93, 0x26, 0x6f, 0x14, 0x4a, 0x21, 0x41, 0xc7, 0xed, - 0x6d, 0x9b, 0xf2, 0x48, 0x2f, 0xf3, 0x03, 0xf5, 0xa2, 0x68, 0x92, 0x53, - 0x2f, 0x5e, 0xe3, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x49, - 0x30, 0x82, 0x01, 0x45, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, - 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, - 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, - 0x03, 0x02, 0x01, 0x86, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, - 0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, - 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x30, - 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, - 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, - 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x4b, 0x06, 0x03, 0x55, 0x1d, 0x1f, - 0x04, 0x44, 0x30, 0x42, 0x30, 0x40, 0xa0, 0x3e, 0xa0, 0x3c, 0x86, 0x3a, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x34, 0x2e, - 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x48, 0x69, 0x67, - 0x68, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x45, 0x56, - 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3d, - 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, 0x30, 0x34, 0x30, 0x32, 0x06, - 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, 0x30, 0x28, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, 0x74, - 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, - 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x50, - 0x53, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, - 0x51, 0x68, 0xff, 0x90, 0xaf, 0x02, 0x07, 0x75, 0x3c, 0xcc, 0xd9, 0x65, - 0x64, 0x62, 0xa2, 0x12, 0xb8, 0x59, 0x72, 0x3b, 0x30, 0x1f, 0x06, 0x03, - 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xb1, 0x3e, 0xc3, - 0x69, 0x03, 0xf8, 0xbf, 0x47, 0x01, 0xd4, 0x98, 0x26, 0x1a, 0x08, 0x02, - 0xef, 0x63, 0x64, 0x2b, 0xc3, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, - 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, - 0x00, 0x18, 0x8a, 0x95, 0x89, 0x03, 0xe6, 0x6d, 0xdf, 0x5c, 0xfc, 0x1d, - 0x68, 0xea, 0x4a, 0x8f, 0x83, 0xd6, 0x51, 0x2f, 0x8d, 0x6b, 0x44, 0x16, - 0x9e, 0xac, 0x63, 0xf5, 0xd2, 0x6e, 0x6c, 0x84, 0x99, 0x8b, 0xaa, 0x81, - 0x71, 0x84, 0x5b, 0xed, 0x34, 0x4e, 0xb0, 0xb7, 0x79, 0x92, 0x29, 0xcc, - 0x2d, 0x80, 0x6a, 0xf0, 0x8e, 0x20, 0xe1, 0x79, 0xa4, 0xfe, 0x03, 0x47, - 0x13, 0xea, 0xf5, 0x86, 0xca, 0x59, 0x71, 0x7d, 0xf4, 0x04, 0x96, 0x6b, - 0xd3, 0x59, 0x58, 0x3d, 0xfe, 0xd3, 0x31, 0x25, 0x5c, 0x18, 0x38, 0x84, - 0xa3, 0xe6, 0x9f, 0x82, 0xfd, 0x8c, 0x5b, 0x98, 0x31, 0x4e, 0xcd, 0x78, - 0x9e, 0x1a, 0xfd, 0x85, 0xcb, 0x49, 0xaa, 0xf2, 0x27, 0x8b, 0x99, 0x72, - 0xfc, 0x3e, 0xaa, 0xd5, 0x41, 0x0b, 0xda, 0xd5, 0x36, 0xa1, 0xbf, 0x1c, - 0x6e, 0x47, 0x49, 0x7f, 0x5e, 0xd9, 0x48, 0x7c, 0x03, 0xd9, 0xfd, 0x8b, - 0x49, 0xa0, 0x98, 0x26, 0x42, 0x40, 0xeb, 0xd6, 0x92, 0x11, 0xa4, 0x64, - 0x0a, 0x57, 0x54, 0xc4, 0xf5, 0x1d, 0xd6, 0x02, 0x5e, 0x6b, 0xac, 0xee, - 0xc4, 0x80, 0x9a, 0x12, 0x72, 0xfa, 0x56, 0x93, 0xd7, 0xff, 0xbf, 0x30, - 0x85, 0x06, 0x30, 0xbf, 0x0b, 0x7f, 0x4e, 0xff, 0x57, 0x05, 0x9d, 0x24, - 0xed, 0x85, 0xc3, 0x2b, 0xfb, 0xa6, 0x75, 0xa8, 0xac, 0x2d, 0x16, 0xef, - 0x7d, 0x79, 0x27, 0xb2, 0xeb, 0xc2, 0x9d, 0x0b, 0x07, 0xea, 0xaa, 0x85, - 0xd3, 0x01, 0xa3, 0x20, 0x28, 0x41, 0x59, 0x43, 0x28, 0xd2, 0x81, 0xe3, - 0xaa, 0xf6, 0xec, 0x7b, 0x3b, 0x77, 0xb6, 0x40, 0x62, 0x80, 0x05, 0x41, - 0x45, 0x01, 0xef, 0x17, 0x06, 0x3e, 0xde, 0xc0, 0x33, 0x9b, 0x67, 0xd3, - 0x61, 0x2e, 0x72, 0x87, 0xe4, 0x69, 0xfc, 0x12, 0x00, 0x57, 0x40, 0x1e, - 0x70, 0xf5, 0x1e, 0xc9, 0xb4, -} - -var certSet3Cert30 = []byte{ - 0x30, 0x82, 0x04, 0xb2, 0x30, 0x82, 0x03, 0x9a, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x16, 0x87, 0xd6, 0x88, 0x6d, 0xe2, 0x30, 0x06, 0x85, - 0x23, 0x3d, 0xbf, 0x11, 0xbf, 0x65, 0x97, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, - 0xa9, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, - 0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1f, - 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x44, - 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x38, 0x30, 0x36, 0x06, - 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, - 0x30, 0x36, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, - 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x16, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50, - 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, - 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x33, 0x31, 0x30, 0x33, 0x31, - 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x33, 0x31, - 0x30, 0x33, 0x30, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x41, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, - 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x74, - 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, - 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, - 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, - 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, - 0x01, 0x00, 0xb2, 0xfc, 0x06, 0xfb, 0x04, 0x93, 0xd2, 0xea, 0x59, 0x20, - 0x3b, 0x44, 0x85, 0x97, 0x52, 0x39, 0xe7, 0x10, 0xf0, 0x7a, 0xe0, 0xb0, - 0x94, 0x40, 0xda, 0x46, 0xf8, 0x0c, 0x28, 0xbb, 0xb9, 0xce, 0x60, 0x38, - 0x3f, 0xd2, 0xd8, 0x11, 0x42, 0x1b, 0x91, 0xad, 0x49, 0xee, 0x8f, 0xc7, - 0xde, 0x6c, 0xde, 0x37, 0x6f, 0xfd, 0x8b, 0x20, 0x3c, 0x6d, 0xe7, 0x74, - 0xd3, 0xdc, 0xd5, 0x24, 0x88, 0x41, 0x80, 0x89, 0xee, 0x36, 0xbe, 0xc4, - 0xd5, 0xbe, 0x8d, 0x53, 0x13, 0xaa, 0xe4, 0xa5, 0xb8, 0x93, 0x0a, 0xbe, - 0xec, 0xda, 0xcd, 0x3c, 0xd4, 0x32, 0x56, 0xef, 0xd0, 0x4e, 0xa0, 0xb8, - 0x97, 0xbb, 0x39, 0x50, 0x1e, 0x6e, 0x65, 0xc3, 0xfd, 0xb2, 0xce, 0xe0, - 0x59, 0xa9, 0x48, 0x09, 0xc6, 0xfe, 0xbe, 0xae, 0xfc, 0x3e, 0x3b, 0x81, - 0x20, 0x97, 0x8b, 0x8f, 0x46, 0xdf, 0x60, 0x64, 0x07, 0x75, 0xbb, 0x1b, - 0x86, 0x38, 0x9f, 0x47, 0x7b, 0x34, 0xce, 0xa1, 0xd1, 0x97, 0xad, 0x76, - 0xd8, 0x9f, 0xb7, 0x26, 0xdb, 0x79, 0x80, 0x36, 0x48, 0xf2, 0xc5, 0x37, - 0xf8, 0xd9, 0x32, 0xae, 0x7c, 0xa4, 0x53, 0x81, 0xc7, 0x99, 0xa1, 0x54, - 0x38, 0x2f, 0x4f, 0x75, 0xa0, 0xbb, 0x5a, 0xa5, 0xbb, 0xcd, 0xac, 0x02, - 0x5b, 0x19, 0x02, 0xd5, 0x13, 0x18, 0xa7, 0xce, 0xac, 0x74, 0x55, 0x12, - 0x05, 0x8b, 0x9b, 0xa2, 0x95, 0x46, 0x64, 0x72, 0x38, 0xcd, 0x5a, 0x1b, - 0x3a, 0x16, 0xa7, 0xbe, 0x71, 0x99, 0x8c, 0x54, 0x03, 0xb8, 0x96, 0x6c, - 0x01, 0xd3, 0x3e, 0x06, 0x98, 0x3f, 0x21, 0x81, 0x3b, 0x02, 0x7e, 0x00, - 0x47, 0x53, 0x01, 0x1e, 0x0e, 0x46, 0x43, 0xfb, 0x4b, 0x2d, 0xdc, 0x0b, - 0x1a, 0xe8, 0x2f, 0x98, 0xf8, 0x7e, 0xd1, 0x99, 0xab, 0x13, 0x6c, 0xa4, - 0x17, 0xde, 0x6f, 0xf6, 0x15, 0xf5, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, - 0x82, 0x01, 0x3b, 0x30, 0x82, 0x01, 0x37, 0x30, 0x12, 0x06, 0x03, 0x55, - 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, - 0x02, 0x01, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, - 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x32, 0x06, 0x03, 0x55, - 0x1d, 0x1f, 0x04, 0x2b, 0x30, 0x29, 0x30, 0x27, 0xa0, 0x25, 0xa0, 0x23, - 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x74, 0x31, 0x2e, - 0x73, 0x79, 0x6d, 0x63, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x54, 0x68, - 0x61, 0x77, 0x74, 0x65, 0x50, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, - 0x2f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, - 0x23, 0x30, 0x21, 0x30, 0x1f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x30, 0x01, 0x86, 0x13, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x74, 0x32, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x30, 0x41, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x3a, 0x30, 0x38, 0x30, - 0x36, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x07, - 0x36, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, - 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x29, 0x06, 0x03, 0x55, - 0x1d, 0x11, 0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, 0x31, 0x1a, - 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x53, 0x79, 0x6d, - 0x61, 0x6e, 0x74, 0x65, 0x63, 0x50, 0x4b, 0x49, 0x2d, 0x31, 0x2d, 0x35, - 0x33, 0x37, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, - 0x14, 0xc2, 0x4f, 0x48, 0x57, 0xfc, 0xd1, 0x4f, 0x9a, 0xc0, 0x5d, 0x38, - 0x7d, 0x0e, 0x05, 0xdb, 0xd9, 0x2e, 0xb5, 0x52, 0x60, 0x30, 0x1f, 0x06, - 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x7b, 0x5b, - 0x45, 0xcf, 0xaf, 0xce, 0xcb, 0x7a, 0xfd, 0x31, 0x92, 0x1a, 0x6a, 0xb6, - 0xf3, 0x46, 0xeb, 0x57, 0x48, 0x50, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, - 0x01, 0x00, 0x8d, 0x06, 0xde, 0x43, 0xc9, 0x76, 0x02, 0xca, 0xd9, 0x23, - 0x97, 0x5e, 0xf3, 0x63, 0xd7, 0x7d, 0x44, 0xc2, 0x0f, 0x6b, 0x0a, 0xf5, - 0x07, 0xe5, 0x8b, 0xb8, 0xfa, 0xe0, 0xa3, 0xfa, 0x6b, 0x80, 0x92, 0xb5, - 0x03, 0x2c, 0xc5, 0x37, 0xe0, 0xc2, 0xe5, 0x95, 0xb5, 0x92, 0x70, 0x18, - 0x28, 0x42, 0x94, 0xee, 0x4b, 0x77, 0x6a, 0x01, 0x0f, 0x8b, 0x23, 0xec, - 0x56, 0x4d, 0xf4, 0x00, 0x69, 0xe5, 0x84, 0xc8, 0xe2, 0xea, 0xde, 0x5b, - 0x3e, 0xf6, 0x3c, 0x07, 0x3a, 0x94, 0xca, 0x6c, 0x27, 0xb1, 0xcc, 0x83, - 0x1a, 0x60, 0x71, 0x27, 0xd2, 0xbf, 0x02, 0xf5, 0x1e, 0x44, 0xd3, 0x48, - 0xd5, 0xa6, 0xd3, 0x76, 0x21, 0x00, 0x9c, 0xfa, 0x98, 0x64, 0xeb, 0x17, - 0x36, 0x3f, 0xeb, 0x1b, 0x3c, 0x3e, 0xa6, 0xb1, 0xd9, 0x58, 0x06, 0x0e, - 0x72, 0xd9, 0x68, 0xbe, 0xf1, 0xa7, 0x20, 0xd7, 0x52, 0xe4, 0xa4, 0x77, - 0x1f, 0x71, 0x70, 0x9d, 0x55, 0x35, 0x85, 0x37, 0xe1, 0x1d, 0x4d, 0x94, - 0xc2, 0x70, 0x7f, 0x95, 0x40, 0x6e, 0x4b, 0x7d, 0xb2, 0xb4, 0x29, 0x2a, - 0x03, 0x79, 0xc8, 0xb9, 0x4c, 0x67, 0x61, 0x04, 0xa0, 0x8b, 0x27, 0xff, - 0x59, 0x00, 0xeb, 0x55, 0x7f, 0xc6, 0xb7, 0x33, 0x35, 0x2d, 0x5e, 0x4e, - 0xac, 0xb8, 0xea, 0x12, 0xc5, 0xe8, 0xf7, 0xb9, 0xab, 0xbe, 0x74, 0x92, - 0x2c, 0xb7, 0xd9, 0x4d, 0xca, 0x84, 0x2f, 0x1c, 0xc2, 0xf0, 0x72, 0x7c, - 0xb2, 0x31, 0x6e, 0xcf, 0x80, 0xe5, 0x88, 0x07, 0x36, 0x51, 0x7b, 0xba, - 0x61, 0xaf, 0x6d, 0x8d, 0x23, 0x5b, 0x34, 0xa3, 0x95, 0xbc, 0xa2, 0x31, - 0x7f, 0xf2, 0xf5, 0xe7, 0xb7, 0xe8, 0xef, 0xc4, 0xb5, 0x27, 0x32, 0xe9, - 0xf7, 0x9e, 0x69, 0xc7, 0x2b, 0xe8, 0xbe, 0xbb, 0x0c, 0xaa, 0xe7, 0xea, - 0x60, 0x12, 0xea, 0x26, 0x8a, 0x78, -} - -var certSet3Cert31 = []byte{ - 0x30, 0x82, 0x04, 0xb4, 0x30, 0x82, 0x03, 0x9c, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x11, 0x00, 0x93, 0x92, 0x85, 0x40, 0x01, 0x65, 0x71, 0x5f, - 0x94, 0x7f, 0x28, 0x8f, 0xef, 0xc9, 0x9b, 0x28, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, - 0x3e, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x50, 0x4c, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x12, 0x55, 0x6e, 0x69, 0x7a, 0x65, 0x74, 0x6f, 0x20, 0x53, 0x70, 0x2e, - 0x20, 0x7a, 0x20, 0x6f, 0x2e, 0x6f, 0x2e, 0x31, 0x12, 0x30, 0x10, 0x06, - 0x03, 0x55, 0x04, 0x03, 0x13, 0x09, 0x43, 0x65, 0x72, 0x74, 0x75, 0x6d, - 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x38, 0x31, 0x30, 0x32, - 0x32, 0x31, 0x32, 0x30, 0x37, 0x33, 0x37, 0x5a, 0x17, 0x0d, 0x32, 0x37, - 0x30, 0x36, 0x31, 0x30, 0x31, 0x30, 0x34, 0x36, 0x33, 0x39, 0x5a, 0x30, - 0x7e, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x50, 0x4c, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x19, 0x55, 0x6e, 0x69, 0x7a, 0x65, 0x74, 0x6f, 0x20, 0x54, 0x65, 0x63, - 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x65, 0x73, 0x20, 0x53, 0x2e, - 0x41, 0x2e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, - 0x1e, 0x43, 0x65, 0x72, 0x74, 0x75, 0x6d, 0x20, 0x43, 0x65, 0x72, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31, 0x22, 0x30, 0x20, 0x06, - 0x03, 0x55, 0x04, 0x03, 0x13, 0x19, 0x43, 0x65, 0x72, 0x74, 0x75, 0x6d, - 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x20, 0x4e, 0x65, 0x74, - 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, - 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, - 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, - 0x82, 0x01, 0x01, 0x00, 0xe3, 0xfb, 0x7d, 0xa3, 0x72, 0xba, 0xc2, 0xf0, - 0xc9, 0x14, 0x87, 0xf5, 0x6b, 0x01, 0x4e, 0xe1, 0x6e, 0x40, 0x07, 0xba, - 0x6d, 0x27, 0x5d, 0x7f, 0xf7, 0x5b, 0x2d, 0xb3, 0x5a, 0xc7, 0x51, 0x5f, - 0xab, 0xa4, 0x32, 0xa6, 0x61, 0x87, 0xb6, 0x6e, 0x0f, 0x86, 0xd2, 0x30, - 0x02, 0x97, 0xf8, 0xd7, 0x69, 0x57, 0xa1, 0x18, 0x39, 0x5d, 0x6a, 0x64, - 0x79, 0xc6, 0x01, 0x59, 0xac, 0x3c, 0x31, 0x4a, 0x38, 0x7c, 0xd2, 0x04, - 0xd2, 0x4b, 0x28, 0xe8, 0x20, 0x5f, 0x3b, 0x07, 0xa2, 0xcc, 0x4d, 0x73, - 0xdb, 0xf3, 0xae, 0x4f, 0xc7, 0x56, 0xd5, 0x5a, 0xa7, 0x96, 0x89, 0xfa, - 0xf3, 0xab, 0x68, 0xd4, 0x23, 0x86, 0x59, 0x27, 0xcf, 0x09, 0x27, 0xbc, - 0xac, 0x6e, 0x72, 0x83, 0x1c, 0x30, 0x72, 0xdf, 0xe0, 0xa2, 0xe9, 0xd2, - 0xe1, 0x74, 0x75, 0x19, 0xbd, 0x2a, 0x9e, 0x7b, 0x15, 0x54, 0x04, 0x1b, - 0xd7, 0x43, 0x39, 0xad, 0x55, 0x28, 0xc5, 0xe2, 0x1a, 0xbb, 0xf4, 0xc0, - 0xe4, 0xae, 0x38, 0x49, 0x33, 0xcc, 0x76, 0x85, 0x9f, 0x39, 0x45, 0xd2, - 0xa4, 0x9e, 0xf2, 0x12, 0x8c, 0x51, 0xf8, 0x7c, 0xe4, 0x2d, 0x7f, 0xf5, - 0xac, 0x5f, 0xeb, 0x16, 0x9f, 0xb1, 0x2d, 0xd1, 0xba, 0xcc, 0x91, 0x42, - 0x77, 0x4c, 0x25, 0xc9, 0x90, 0x38, 0x6f, 0xdb, 0xf0, 0xcc, 0xfb, 0x8e, - 0x1e, 0x97, 0x59, 0x3e, 0xd5, 0x60, 0x4e, 0xe6, 0x05, 0x28, 0xed, 0x49, - 0x79, 0x13, 0x4b, 0xba, 0x48, 0xdb, 0x2f, 0xf9, 0x72, 0xd3, 0x39, 0xca, - 0xfe, 0x1f, 0xd8, 0x34, 0x72, 0xf5, 0xb4, 0x40, 0xcf, 0x31, 0x01, 0xc3, - 0xec, 0xde, 0x11, 0x2d, 0x17, 0x5d, 0x1f, 0xb8, 0x50, 0xd1, 0x5e, 0x19, - 0xa7, 0x69, 0xde, 0x07, 0x33, 0x28, 0xca, 0x50, 0x95, 0xf9, 0xa7, 0x54, - 0xcb, 0x54, 0x86, 0x50, 0x45, 0xa9, 0xf9, 0x49, 0x02, 0x03, 0x01, 0x00, - 0x01, 0xa3, 0x82, 0x01, 0x6b, 0x30, 0x82, 0x01, 0x67, 0x30, 0x0f, 0x06, - 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, - 0x01, 0xff, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, - 0x14, 0x08, 0x76, 0xcd, 0xcb, 0x07, 0xff, 0x24, 0xf6, 0xc5, 0xcd, 0xed, - 0xbb, 0x90, 0xbc, 0xe2, 0x84, 0x37, 0x46, 0x75, 0xf7, 0x30, 0x52, 0x06, - 0x03, 0x55, 0x1d, 0x23, 0x04, 0x4b, 0x30, 0x49, 0xa1, 0x42, 0xa4, 0x40, - 0x30, 0x3e, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, - 0x02, 0x50, 0x4c, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x0a, - 0x13, 0x12, 0x55, 0x6e, 0x69, 0x7a, 0x65, 0x74, 0x6f, 0x20, 0x53, 0x70, - 0x2e, 0x20, 0x7a, 0x20, 0x6f, 0x2e, 0x6f, 0x2e, 0x31, 0x12, 0x30, 0x10, - 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x09, 0x43, 0x65, 0x72, 0x74, 0x75, - 0x6d, 0x20, 0x43, 0x41, 0x82, 0x03, 0x01, 0x00, 0x20, 0x30, 0x0e, 0x06, - 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, - 0x06, 0x30, 0x2c, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x25, 0x30, 0x23, - 0x30, 0x21, 0xa0, 0x1f, 0xa0, 0x1d, 0x86, 0x1b, 0x68, 0x74, 0x74, 0x70, - 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63, 0x65, 0x72, 0x74, 0x75, - 0x6d, 0x2e, 0x70, 0x6c, 0x2f, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, - 0x68, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, - 0x5c, 0x30, 0x5a, 0x30, 0x28, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x30, 0x01, 0x86, 0x1c, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x73, 0x75, 0x62, 0x63, 0x61, 0x2e, 0x6f, 0x63, 0x73, 0x70, 0x2d, 0x63, - 0x65, 0x72, 0x74, 0x75, 0x6d, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x2e, 0x06, - 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x22, 0x68, - 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, - 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x63, 0x65, 0x72, 0x74, 0x75, 0x6d, 0x2e, - 0x70, 0x6c, 0x2f, 0x63, 0x61, 0x2e, 0x63, 0x65, 0x72, 0x30, 0x39, 0x06, - 0x03, 0x55, 0x1d, 0x20, 0x04, 0x32, 0x30, 0x30, 0x30, 0x2e, 0x06, 0x04, - 0x55, 0x1d, 0x20, 0x00, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, - 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x18, 0x68, 0x74, 0x74, 0x70, - 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x63, 0x65, 0x72, 0x74, 0x75, - 0x6d, 0x2e, 0x70, 0x6c, 0x2f, 0x43, 0x50, 0x53, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, - 0x82, 0x01, 0x01, 0x00, 0x8d, 0xe6, 0xfd, 0x40, 0x66, 0xa3, 0x4c, 0x9c, - 0xa7, 0xab, 0xa1, 0xda, 0x84, 0xdd, 0x1c, 0x30, 0x07, 0xe6, 0xdb, 0xc7, - 0x2d, 0xec, 0x83, 0xa1, 0x56, 0xe4, 0x1d, 0x3c, 0x26, 0xa1, 0xa5, 0x09, - 0x2b, 0xe8, 0x7d, 0x62, 0xbe, 0xb2, 0x75, 0x94, 0xdd, 0x08, 0xf2, 0x7f, - 0x28, 0x41, 0xe4, 0x80, 0x67, 0x02, 0x4e, 0x8a, 0x8f, 0xc3, 0x35, 0xd0, - 0xd5, 0xa9, 0x27, 0x28, 0xea, 0xd2, 0xf4, 0xab, 0x06, 0x86, 0x43, 0xae, - 0x8c, 0xe3, 0xf9, 0x88, 0x7d, 0xe0, 0xdb, 0xbd, 0x42, 0x81, 0x80, 0x02, - 0x12, 0x75, 0xb2, 0xe8, 0x17, 0x71, 0xab, 0x21, 0x95, 0x31, 0x46, 0x42, - 0x0d, 0x88, 0x10, 0x39, 0xd3, 0x6f, 0xec, 0x2f, 0x42, 0xea, 0x40, 0x53, - 0x62, 0xbf, 0xeb, 0xca, 0x78, 0x9e, 0xab, 0xa2, 0xd5, 0x2e, 0x05, 0xea, - 0x33, 0xab, 0xe9, 0xd6, 0x97, 0x94, 0x42, 0x5e, 0x04, 0xed, 0x2c, 0xed, - 0x6a, 0x9c, 0x7a, 0x95, 0x7d, 0x05, 0x2a, 0x05, 0x7f, 0x08, 0x5d, 0x66, - 0xad, 0x61, 0xd4, 0x76, 0xac, 0x75, 0x96, 0x97, 0x73, 0x63, 0xbd, 0x1a, - 0x41, 0x59, 0x29, 0xa5, 0x5e, 0x22, 0x83, 0xc3, 0x8b, 0x59, 0xfa, 0x9a, - 0xa2, 0xf6, 0xbd, 0x30, 0xbf, 0x72, 0x1d, 0x1c, 0x99, 0x86, 0x9c, 0xf2, - 0x85, 0x3c, 0x1d, 0xf7, 0x26, 0x96, 0x2f, 0x2e, 0xf9, 0x02, 0xb1, 0xb5, - 0xa9, 0x50, 0xe8, 0x38, 0xfa, 0x9b, 0x0a, 0x5e, 0xb4, 0x04, 0xc0, 0xce, - 0x4e, 0x39, 0x2c, 0xca, 0x0b, 0x5b, 0x62, 0xf0, 0x4d, 0x58, 0x50, 0x34, - 0x99, 0xe6, 0x9a, 0x2c, 0xd2, 0x90, 0xd7, 0x09, 0x81, 0xd6, 0xc0, 0xaa, - 0x5e, 0xce, 0xfe, 0xd2, 0xf7, 0xa1, 0xba, 0x4b, 0xd9, 0xd6, 0x86, 0x8e, - 0x19, 0x1f, 0xa6, 0x06, 0x47, 0x42, 0x72, 0xe0, 0x56, 0x0a, 0x00, 0x1c, - 0x78, 0xb9, 0x8d, 0xcc, 0x99, 0x04, 0x37, 0x49, -} - -var certSet3Cert32 = []byte{ - 0x30, 0x82, 0x04, 0xb5, 0x30, 0x82, 0x03, 0x9d, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x48, 0xe9, 0x94, 0x40, 0xd4, 0x36, 0x49, 0x1c, 0xb8, - 0xb8, 0x82, 0x3d, 0x09, 0x43, 0x94, 0xc7, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, - 0x98, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, - 0x63, 0x2e, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, - 0x30, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x38, 0x20, 0x47, 0x65, - 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, - 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, - 0x79, 0x31, 0x36, 0x30, 0x34, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2d, - 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x50, 0x72, 0x69, - 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x30, 0x1e, 0x17, - 0x0d, 0x31, 0x34, 0x30, 0x36, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, - 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x34, 0x30, 0x36, 0x30, 0x39, 0x32, 0x33, - 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x47, 0x31, 0x0b, 0x30, 0x09, 0x06, - 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, - 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, - 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x20, 0x30, 0x1e, - 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x17, 0x52, 0x61, 0x70, 0x69, 0x64, - 0x53, 0x53, 0x4c, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x43, - 0x41, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, - 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, - 0x01, 0x01, 0x00, 0xc4, 0x95, 0x63, 0x28, 0xd0, 0x4e, 0x30, 0x45, 0xaf, - 0x8b, 0x97, 0x34, 0x14, 0x45, 0xf8, 0x5c, 0x58, 0x4a, 0xfa, 0x33, 0x8e, - 0x6e, 0x9c, 0x60, 0xab, 0xf3, 0x86, 0xff, 0x34, 0x74, 0xb2, 0x2b, 0xbe, - 0xa1, 0x8c, 0xd5, 0xa2, 0xa3, 0x60, 0x7a, 0x40, 0xb9, 0xe1, 0xfc, 0x22, - 0xca, 0x67, 0xba, 0x60, 0xaa, 0xc7, 0x9a, 0xf9, 0x06, 0x7f, 0xee, 0xf7, - 0xba, 0x85, 0x05, 0xb0, 0x03, 0xff, 0x72, 0xae, 0x15, 0x41, 0x4a, 0x98, - 0x64, 0xd7, 0x17, 0x4b, 0x54, 0xef, 0x05, 0xc6, 0x98, 0x07, 0x93, 0x27, - 0x3e, 0x4f, 0xdc, 0x0f, 0xc6, 0x7b, 0x8b, 0xe7, 0xf3, 0x06, 0x5e, 0x8d, - 0xe8, 0xb4, 0xae, 0x29, 0xb4, 0x1e, 0x1e, 0x2d, 0x16, 0x90, 0xd3, 0xea, - 0xaa, 0xe7, 0x8c, 0x3b, 0x6d, 0xaf, 0x36, 0x59, 0xff, 0xc5, 0x0a, 0xfa, - 0xc7, 0x4c, 0xbd, 0x36, 0x8b, 0x64, 0xc4, 0x4a, 0xf5, 0xce, 0x33, 0xf9, - 0x07, 0xbe, 0x7f, 0x45, 0x90, 0xa8, 0x08, 0x14, 0xb0, 0xd0, 0xa5, 0x4f, - 0xdf, 0x82, 0x80, 0xda, 0x1b, 0xee, 0xc3, 0x13, 0xb0, 0x98, 0xf5, 0x0f, - 0xf9, 0x7e, 0x76, 0xb5, 0xe6, 0xb9, 0x5d, 0x68, 0xb9, 0x5c, 0x50, 0x90, - 0x89, 0xa4, 0x36, 0xb1, 0x70, 0x16, 0xea, 0xb1, 0x10, 0xb5, 0x6a, 0x76, - 0xdf, 0xe1, 0xbb, 0xfc, 0x78, 0xf2, 0x72, 0x99, 0xcf, 0xc9, 0xa2, 0xd4, - 0x73, 0x54, 0x77, 0xbf, 0xc0, 0x39, 0x77, 0xe5, 0xae, 0x12, 0xc5, 0x78, - 0x5a, 0x19, 0x45, 0xd4, 0x41, 0x19, 0xd3, 0x7c, 0xf5, 0x6f, 0x99, 0x6b, - 0xd7, 0x8b, 0xbc, 0x2d, 0x09, 0x9d, 0x4b, 0x10, 0x61, 0xc0, 0xda, 0x52, - 0xc3, 0xaf, 0x22, 0x43, 0xc6, 0xeb, 0x37, 0x7e, 0x63, 0x74, 0x30, 0x0d, - 0x6a, 0x71, 0x8e, 0xde, 0x5d, 0x5b, 0x8a, 0xc8, 0xc5, 0xd7, 0x9b, 0x29, - 0xe8, 0xae, 0xb6, 0x25, 0x61, 0x81, 0xeb, 0x02, 0x03, 0x01, 0x00, 0x01, - 0xa3, 0x82, 0x01, 0x49, 0x30, 0x82, 0x01, 0x45, 0x30, 0x2e, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x22, 0x30, 0x20, - 0x30, 0x1e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, - 0x86, 0x12, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x67, 0x2e, 0x73, - 0x79, 0x6d, 0x63, 0x64, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x12, 0x06, 0x03, - 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, - 0xff, 0x02, 0x01, 0x00, 0x30, 0x4c, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, - 0x45, 0x30, 0x43, 0x30, 0x41, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, - 0xf8, 0x45, 0x01, 0x07, 0x36, 0x30, 0x33, 0x30, 0x31, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x25, 0x68, 0x74, 0x74, - 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x36, - 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2f, 0x30, 0x2d, 0x30, 0x2b, 0xa0, - 0x29, 0xa0, 0x27, 0x86, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x67, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x50, 0x43, 0x41, 0x2d, - 0x47, 0x33, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, - 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x29, - 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, - 0x1c, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, - 0x53, 0x79, 0x6d, 0x61, 0x6e, 0x74, 0x65, 0x63, 0x50, 0x4b, 0x49, 0x2d, - 0x31, 0x2d, 0x36, 0x39, 0x37, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, - 0x04, 0x16, 0x04, 0x14, 0x4c, 0xf4, 0xbf, 0xe8, 0x3b, 0xbe, 0xc2, 0x24, - 0xf3, 0x1b, 0x47, 0x3b, 0xb5, 0x6e, 0x48, 0x8e, 0x16, 0xab, 0xaf, 0x12, - 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, - 0x14, 0xc4, 0x79, 0xca, 0x8e, 0xa1, 0x4e, 0x03, 0x1d, 0x1c, 0xdc, 0x6b, - 0xdb, 0x31, 0x5b, 0x94, 0x3e, 0x3f, 0x30, 0x7f, 0x2d, 0x30, 0x0d, 0x06, - 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, - 0x03, 0x82, 0x01, 0x01, 0x00, 0x7a, 0x53, 0xb5, 0xde, 0xb6, 0xef, 0x52, - 0xa3, 0x5f, 0x8a, 0xf5, 0x89, 0xf1, 0x42, 0xcc, 0x5e, 0x46, 0x88, 0xae, - 0xa5, 0x08, 0x87, 0x51, 0xde, 0x0f, 0x0f, 0x02, 0xeb, 0x0c, 0x82, 0x78, - 0xe3, 0x73, 0x7d, 0x71, 0xbd, 0x43, 0xe9, 0xca, 0x8a, 0x3f, 0xe0, 0x25, - 0x92, 0x9b, 0x33, 0x33, 0x74, 0x49, 0x5e, 0x00, 0xd9, 0x73, 0x14, 0x1c, - 0x0b, 0x46, 0x76, 0x1c, 0x8a, 0x0d, 0x4d, 0x8c, 0x6c, 0x7e, 0x4b, 0xf7, - 0x60, 0xd8, 0x81, 0x78, 0xa0, 0x78, 0xd0, 0x25, 0x62, 0xab, 0x10, 0xca, - 0x22, 0xe8, 0x1c, 0x19, 0xdd, 0x52, 0x83, 0x64, 0x05, 0xe5, 0x87, 0x66, - 0xae, 0xe7, 0x7a, 0xa4, 0x3b, 0x3e, 0xd8, 0x70, 0x7a, 0x76, 0xa2, 0x67, - 0x39, 0xd4, 0xc9, 0xfa, 0xe5, 0xb7, 0x1e, 0x41, 0xe2, 0x09, 0x39, 0x88, - 0x1c, 0x18, 0x55, 0x0a, 0xc4, 0x41, 0xaf, 0xb2, 0xf3, 0xf3, 0x0f, 0x42, - 0x14, 0x61, 0x74, 0x81, 0xe3, 0xda, 0x87, 0x5a, 0x9a, 0x4d, 0x8b, 0xd3, - 0xc9, 0x8f, 0x89, 0x66, 0x13, 0x29, 0x11, 0xe4, 0xff, 0xe2, 0xdf, 0x8e, - 0x96, 0x0c, 0x5a, 0xa1, 0xaa, 0x6b, 0x9b, 0xfd, 0xfc, 0x03, 0x3b, 0x55, - 0x0d, 0xa6, 0xa2, 0x25, 0x48, 0x17, 0x1f, 0x42, 0xa8, 0xda, 0x6c, 0x7e, - 0x69, 0x6e, 0xa0, 0xdf, 0x67, 0xd2, 0x6d, 0xf4, 0x0e, 0x6a, 0x12, 0x79, - 0xf5, 0x7c, 0xc8, 0xa5, 0x32, 0x1c, 0xc4, 0x31, 0xb2, 0xe6, 0xbb, 0xa8, - 0x6b, 0x6a, 0xa2, 0x8a, 0x60, 0x69, 0xc0, 0x57, 0x7d, 0xb2, 0xf2, 0x31, - 0x0c, 0x98, 0x65, 0x32, 0xec, 0x08, 0x5a, 0xce, 0xc6, 0x98, 0xe9, 0x21, - 0x97, 0x3f, 0x2c, 0x79, 0x29, 0x03, 0xf5, 0xf6, 0x94, 0x2b, 0x53, 0x31, - 0xf3, 0x93, 0x68, 0x57, 0xe1, 0xd7, 0x4f, 0x3a, 0xd1, 0x61, 0xa1, 0x60, - 0xce, 0xb9, 0xab, 0x98, 0xae, 0x35, 0x54, 0x63, 0x8b, -} - -var certSet3Cert33 = []byte{ - 0x30, 0x82, 0x04, 0xb6, 0x30, 0x82, 0x03, 0x9e, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x0c, 0x79, 0xa9, 0x44, 0xb0, 0x8c, 0x11, 0x95, 0x20, - 0x92, 0x61, 0x5f, 0xe2, 0x6b, 0x1d, 0x83, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x6c, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, - 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, - 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, - 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, - 0x63, 0x6f, 0x6d, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03, - 0x13, 0x22, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, - 0x69, 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, - 0x65, 0x20, 0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, - 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x33, 0x31, 0x30, 0x32, 0x32, 0x31, 0x32, - 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x38, 0x31, 0x30, 0x32, - 0x32, 0x31, 0x32, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x75, 0x31, 0x0b, - 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, - 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69, - 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x19, - 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, 0x77, 0x77, - 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, - 0x6d, 0x31, 0x34, 0x30, 0x32, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2b, - 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x53, 0x48, 0x41, - 0x32, 0x20, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x20, 0x56, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, - 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, - 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, - 0x82, 0x01, 0x01, 0x00, 0xd7, 0x53, 0xa4, 0x04, 0x51, 0xf8, 0x99, 0xa6, - 0x16, 0x48, 0x4b, 0x67, 0x27, 0xaa, 0x93, 0x49, 0xd0, 0x39, 0xed, 0x0c, - 0xb0, 0xb0, 0x00, 0x87, 0xf1, 0x67, 0x28, 0x86, 0x85, 0x8c, 0x8e, 0x63, - 0xda, 0xbc, 0xb1, 0x40, 0x38, 0xe2, 0xd3, 0xf5, 0xec, 0xa5, 0x05, 0x18, - 0xb8, 0x3d, 0x3e, 0xc5, 0x99, 0x17, 0x32, 0xec, 0x18, 0x8c, 0xfa, 0xf1, - 0x0c, 0xa6, 0x64, 0x21, 0x85, 0xcb, 0x07, 0x10, 0x34, 0xb0, 0x52, 0x88, - 0x2b, 0x1f, 0x68, 0x9b, 0xd2, 0xb1, 0x8f, 0x12, 0xb0, 0xb3, 0xd2, 0xe7, - 0x88, 0x1f, 0x1f, 0xef, 0x38, 0x77, 0x54, 0x53, 0x5f, 0x80, 0x79, 0x3f, - 0x2e, 0x1a, 0xaa, 0xa8, 0x1e, 0x4b, 0x2b, 0x0d, 0xab, 0xb7, 0x63, 0xb9, - 0x35, 0xb7, 0x7d, 0x14, 0xbc, 0x59, 0x4b, 0xdf, 0x51, 0x4a, 0xd2, 0xa1, - 0xe2, 0x0c, 0xe2, 0x90, 0x82, 0x87, 0x6a, 0xae, 0xea, 0xd7, 0x64, 0xd6, - 0x98, 0x55, 0xe8, 0xfd, 0xaf, 0x1a, 0x50, 0x6c, 0x54, 0xbc, 0x11, 0xf2, - 0xfd, 0x4a, 0xf2, 0x9d, 0xbb, 0x7f, 0x0e, 0xf4, 0xd5, 0xbe, 0x8e, 0x16, - 0x89, 0x12, 0x55, 0xd8, 0xc0, 0x71, 0x34, 0xee, 0xf6, 0xdc, 0x2d, 0xec, - 0xc4, 0x87, 0x25, 0x86, 0x8d, 0xd8, 0x21, 0xe4, 0xb0, 0x4d, 0x0c, 0x89, - 0xdc, 0x39, 0x26, 0x17, 0xdd, 0xf6, 0xd7, 0x94, 0x85, 0xd8, 0x04, 0x21, - 0x70, 0x9d, 0x6f, 0x6f, 0xff, 0x5c, 0xba, 0x19, 0xe1, 0x45, 0xcb, 0x56, - 0x57, 0x28, 0x7e, 0x1c, 0x0d, 0x41, 0x57, 0xaa, 0xb7, 0xb8, 0x27, 0xbb, - 0xb1, 0xe4, 0xfa, 0x2a, 0xef, 0x21, 0x23, 0x75, 0x1a, 0xad, 0x2d, 0x9b, - 0x86, 0x35, 0x8c, 0x9c, 0x77, 0xb5, 0x73, 0xad, 0xd8, 0x94, 0x2d, 0xe4, - 0xf3, 0x0c, 0x9d, 0xee, 0xc1, 0x4e, 0x62, 0x7e, 0x17, 0xc0, 0x71, 0x9e, - 0x2c, 0xde, 0xf1, 0xf9, 0x10, 0x28, 0x19, 0x33, 0x02, 0x03, 0x01, 0x00, - 0x01, 0xa3, 0x82, 0x01, 0x49, 0x30, 0x82, 0x01, 0x45, 0x30, 0x12, 0x06, - 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, - 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, - 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x1d, 0x06, - 0x03, 0x55, 0x1d, 0x25, 0x04, 0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, - 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x03, 0x02, 0x30, 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x01, 0x01, 0x04, 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x64, 0x69, - 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x4b, - 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x44, 0x30, 0x42, 0x30, 0x40, 0xa0, - 0x3e, 0xa0, 0x3c, 0x86, 0x3a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x63, 0x72, 0x6c, 0x34, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, - 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, - 0x72, 0x74, 0x48, 0x69, 0x67, 0x68, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, - 0x6e, 0x63, 0x65, 0x45, 0x56, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x2e, - 0x63, 0x72, 0x6c, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, - 0x30, 0x34, 0x30, 0x32, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, - 0x30, 0x28, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, - 0x16, 0x1c, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, - 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x43, 0x50, 0x53, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, - 0x0e, 0x04, 0x16, 0x04, 0x14, 0x3d, 0xd3, 0x50, 0xa5, 0xd6, 0xa0, 0xad, - 0xee, 0xf3, 0x4a, 0x60, 0x0a, 0x65, 0xd3, 0x21, 0xd4, 0xf8, 0xf8, 0xd6, - 0x0f, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, - 0x80, 0x14, 0xb1, 0x3e, 0xc3, 0x69, 0x03, 0xf8, 0xbf, 0x47, 0x01, 0xd4, - 0x98, 0x26, 0x1a, 0x08, 0x02, 0xef, 0x63, 0x64, 0x2b, 0xc3, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, - 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x9d, 0xb6, 0xd0, 0x90, 0x86, 0xe1, - 0x86, 0x02, 0xed, 0xc5, 0xa0, 0xf0, 0x34, 0x1c, 0x74, 0xc1, 0x8d, 0x76, - 0xcc, 0x86, 0x0a, 0xa8, 0xf0, 0x4a, 0x8a, 0x42, 0xd6, 0x3f, 0xc8, 0xa9, - 0x4d, 0xad, 0x7c, 0x08, 0xad, 0xe6, 0xb6, 0x50, 0xb8, 0xa2, 0x1a, 0x4d, - 0x88, 0x07, 0xb1, 0x29, 0x21, 0xdc, 0xe7, 0xda, 0xc6, 0x3c, 0x21, 0xe0, - 0xe3, 0x11, 0x49, 0x70, 0xac, 0x7a, 0x1d, 0x01, 0xa4, 0xca, 0x11, 0x3a, - 0x57, 0xab, 0x7d, 0x57, 0x2a, 0x40, 0x74, 0xfd, 0xd3, 0x1d, 0x85, 0x18, - 0x50, 0xdf, 0x57, 0x47, 0x75, 0xa1, 0x7d, 0x55, 0x20, 0x2e, 0x47, 0x37, - 0x50, 0x72, 0x8c, 0x7f, 0x82, 0x1b, 0xd2, 0x62, 0x8f, 0x2d, 0x03, 0x5a, - 0xda, 0xc3, 0xc8, 0xa1, 0xce, 0x2c, 0x52, 0xa2, 0x00, 0x63, 0xeb, 0x73, - 0xba, 0x71, 0xc8, 0x49, 0x27, 0x23, 0x97, 0x64, 0x85, 0x9e, 0x38, 0x0e, - 0xad, 0x63, 0x68, 0x3c, 0xba, 0x52, 0x81, 0x58, 0x79, 0xa3, 0x2c, 0x0c, - 0xdf, 0xde, 0x6d, 0xeb, 0x31, 0xf2, 0xba, 0xa0, 0x7c, 0x6c, 0xf1, 0x2c, - 0xd4, 0xe1, 0xbd, 0x77, 0x84, 0x37, 0x03, 0xce, 0x32, 0xb5, 0xc8, 0x9a, - 0x81, 0x1a, 0x4a, 0x92, 0x4e, 0x3b, 0x46, 0x9a, 0x85, 0xfe, 0x83, 0xa2, - 0xf9, 0x9e, 0x8c, 0xa3, 0xcc, 0x0d, 0x5e, 0xb3, 0x3d, 0xcf, 0x04, 0x78, - 0x8f, 0x14, 0x14, 0x7b, 0x32, 0x9c, 0xc7, 0x00, 0xa6, 0x5c, 0xc4, 0xb5, - 0xa1, 0x55, 0x8d, 0x5a, 0x56, 0x68, 0xa4, 0x22, 0x70, 0xaa, 0x3c, 0x81, - 0x71, 0xd9, 0x9d, 0xa8, 0x45, 0x3b, 0xf4, 0xe5, 0xf6, 0xa2, 0x51, 0xdd, - 0xc7, 0x7b, 0x62, 0xe8, 0x6f, 0x0c, 0x74, 0xeb, 0xb8, 0xda, 0xf8, 0xbf, - 0x87, 0x0d, 0x79, 0x50, 0x91, 0x90, 0x9b, 0x18, 0x3b, 0x91, 0x59, 0x27, - 0xf1, 0x35, 0x28, 0x13, 0xab, 0x26, 0x7e, 0xd5, 0xf7, 0x7a, -} - -var certSet3Cert34 = []byte{ - 0x30, 0x82, 0x04, 0xc2, 0x30, 0x82, 0x03, 0xaa, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x36, 0x34, 0x9e, 0x18, 0xc9, 0x9c, 0x26, 0x69, 0xb6, - 0x56, 0x2e, 0x6c, 0xe5, 0xad, 0x71, 0x32, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, - 0xae, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, - 0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1f, - 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x44, - 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x38, 0x30, 0x36, 0x06, - 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, - 0x30, 0x38, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, - 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x24, 0x30, 0x22, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x1b, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50, - 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, - 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x30, 0x1e, 0x17, 0x0d, 0x31, - 0x33, 0x30, 0x35, 0x32, 0x33, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, - 0x17, 0x0d, 0x32, 0x33, 0x30, 0x35, 0x32, 0x32, 0x32, 0x33, 0x35, 0x39, - 0x35, 0x39, 0x5a, 0x30, 0x43, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, - 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, - 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, - 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x1d, 0x30, 0x1b, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x14, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x53, - 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, - 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, - 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa3, 0x63, 0x2b, - 0xd4, 0xba, 0x5d, 0x38, 0xae, 0xb0, 0xcf, 0xb9, 0x4c, 0x38, 0xdf, 0x20, - 0x7d, 0xf1, 0x2b, 0x47, 0x71, 0x1d, 0x8b, 0x68, 0xf3, 0x56, 0xf9, 0x9c, - 0xda, 0xaa, 0xe5, 0x84, 0x26, 0xde, 0xa5, 0x71, 0x30, 0xbc, 0xf3, 0x31, - 0x23, 0x9d, 0xe8, 0x3b, 0x80, 0xc8, 0x66, 0x57, 0x75, 0xb6, 0x57, 0x0e, - 0xdb, 0x93, 0xf5, 0x26, 0x8e, 0x70, 0xba, 0x64, 0x52, 0x66, 0x8a, 0x2a, - 0x88, 0x5c, 0x44, 0x18, 0x4d, 0xa8, 0xa2, 0x7c, 0xbd, 0x56, 0x61, 0x32, - 0x90, 0x12, 0xf9, 0x35, 0x87, 0x48, 0x60, 0xb0, 0x6e, 0x90, 0x67, 0x44, - 0x01, 0x8d, 0xe7, 0xc9, 0x0d, 0x63, 0x68, 0x72, 0x72, 0xab, 0x63, 0x3c, - 0x86, 0xb8, 0x1f, 0x7d, 0xad, 0x88, 0x25, 0xa7, 0x6a, 0x88, 0x29, 0xfb, - 0x59, 0xc6, 0x78, 0x71, 0x5f, 0x2c, 0xba, 0x89, 0xe6, 0xd3, 0x80, 0xfd, - 0x57, 0xec, 0xb9, 0x51, 0x5f, 0x43, 0x33, 0x2e, 0x7e, 0x25, 0x3b, 0xa4, - 0x04, 0xd1, 0x60, 0x8c, 0xb3, 0x44, 0x33, 0x93, 0x0c, 0xad, 0x2a, 0xb6, - 0x44, 0xa2, 0x19, 0x3b, 0xaf, 0xc4, 0x90, 0x6f, 0x7b, 0x05, 0x87, 0x86, - 0x9b, 0x2c, 0x6a, 0x9d, 0x2b, 0x6c, 0x77, 0xc9, 0x00, 0x9f, 0xc9, 0xcf, - 0xac, 0xed, 0x3e, 0x1b, 0xf7, 0xc3, 0xf3, 0xd9, 0xf8, 0x6c, 0xd4, 0xa0, - 0x57, 0xc4, 0xfb, 0x28, 0x32, 0xaa, 0x33, 0xf0, 0xe6, 0xba, 0x98, 0xdf, - 0xe5, 0xc2, 0x4e, 0x9c, 0x74, 0xbf, 0x8a, 0x48, 0xc2, 0xf2, 0x1b, 0xf0, - 0x77, 0x40, 0x41, 0x07, 0x04, 0xb2, 0x3a, 0xd5, 0x4c, 0xc4, 0x29, 0xa9, - 0x11, 0x40, 0x3f, 0x02, 0x46, 0xf0, 0x91, 0xd5, 0xd2, 0x81, 0x83, 0x86, - 0x13, 0xb3, 0x31, 0xed, 0x46, 0xab, 0xa8, 0x87, 0x76, 0xa9, 0x99, 0x7d, - 0xbc, 0xcd, 0x31, 0x50, 0xf4, 0xa5, 0xb5, 0xdc, 0xa5, 0x32, 0xb3, 0x8b, - 0x8b, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x44, 0x30, 0x82, - 0x01, 0x40, 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x01, 0x01, 0x04, 0x26, 0x30, 0x24, 0x30, 0x22, 0x06, 0x08, 0x2b, 0x06, - 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x16, 0x68, 0x74, 0x74, 0x70, - 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x74, 0x68, 0x61, 0x77, - 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, - 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, - 0x01, 0x00, 0x30, 0x41, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x3a, 0x30, - 0x38, 0x30, 0x36, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, - 0x01, 0x07, 0x36, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x73, - 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, - 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x37, 0x06, - 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x30, 0x30, 0x2e, 0x30, 0x2c, 0xa0, 0x2a, - 0xa0, 0x28, 0x86, 0x26, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, - 0x72, 0x6c, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x50, 0x43, 0x41, 0x2d, - 0x47, 0x33, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, - 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x2a, - 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x23, 0x30, 0x21, 0xa4, 0x1f, 0x30, - 0x1d, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, - 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x50, 0x4b, 0x49, - 0x2d, 0x32, 0x2d, 0x34, 0x31, 0x35, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, - 0x0e, 0x04, 0x16, 0x04, 0x14, 0x2b, 0x9a, 0x35, 0xae, 0x01, 0x18, 0x38, - 0x30, 0xe1, 0x70, 0x7a, 0x05, 0xe0, 0x11, 0x76, 0xa3, 0xce, 0xbd, 0x90, - 0x14, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, - 0x80, 0x14, 0xad, 0x6c, 0xaa, 0x94, 0x60, 0x9c, 0xed, 0xe4, 0xff, 0xfa, - 0x3e, 0x0a, 0x74, 0x2b, 0x63, 0x03, 0xf7, 0xb6, 0x59, 0xbf, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, - 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x74, 0xa6, 0x56, 0xe8, 0xaf, 0x93, - 0x96, 0x19, 0xfb, 0x26, 0xf9, 0x0d, 0xb0, 0x44, 0xa5, 0xcd, 0xe9, 0x7a, - 0x48, 0x03, 0x74, 0x01, 0x6c, 0x13, 0x71, 0xb7, 0xe0, 0x82, 0x90, 0x99, - 0x62, 0x23, 0xe3, 0xd6, 0x99, 0xaf, 0xf0, 0xc7, 0x1e, 0x9e, 0xa8, 0x18, - 0x21, 0xdb, 0xb4, 0x94, 0x3f, 0x34, 0x56, 0x1b, 0x99, 0x55, 0x2f, 0x8e, - 0xf0, 0x45, 0x33, 0x32, 0xb7, 0x72, 0xc1, 0x13, 0x5b, 0x34, 0xd3, 0xf5, - 0x60, 0xe5, 0x2e, 0x18, 0xd1, 0x5c, 0xc5, 0x6a, 0xc1, 0xaa, 0x87, 0x50, - 0x0c, 0x1c, 0x9d, 0x64, 0x2b, 0xff, 0x1b, 0xdc, 0xd5, 0x2e, 0x61, 0x0b, - 0xe7, 0xb9, 0xb6, 0x91, 0x53, 0x86, 0xd9, 0x03, 0x2a, 0xd1, 0x3d, 0x7b, - 0x4a, 0xda, 0x2b, 0x07, 0xbe, 0x29, 0xf2, 0x60, 0x42, 0xa9, 0x91, 0x1a, - 0x0e, 0x2e, 0x3c, 0xd1, 0x7d, 0xa5, 0x13, 0x14, 0x02, 0xfa, 0xee, 0x8b, - 0x8d, 0xb6, 0xc8, 0xb8, 0x3e, 0x56, 0x81, 0x57, 0x21, 0x24, 0x3f, 0x65, - 0xc3, 0xb4, 0xc9, 0xce, 0x5c, 0x8d, 0x46, 0xac, 0x53, 0xf3, 0xf9, 0x55, - 0x74, 0xc8, 0x2b, 0xfd, 0xd2, 0x78, 0x70, 0xf5, 0xf8, 0x11, 0xe5, 0xf4, - 0xa7, 0xad, 0x20, 0xf5, 0x9d, 0xf1, 0xec, 0x70, 0xf6, 0x13, 0xac, 0xe6, - 0x8c, 0x8d, 0xdb, 0x3f, 0xc6, 0xf2, 0x79, 0x0e, 0xab, 0x52, 0xf2, 0xcc, - 0x1b, 0x79, 0x27, 0xcf, 0x16, 0xb3, 0xd6, 0xf3, 0xc6, 0x36, 0x80, 0x43, - 0xec, 0xc5, 0x94, 0xf0, 0xdd, 0x90, 0x8d, 0xf8, 0xc6, 0x52, 0x46, 0x56, - 0xeb, 0x74, 0x47, 0xbe, 0xa6, 0xf3, 0x19, 0xae, 0x71, 0x4c, 0xc0, 0xe1, - 0xe7, 0xd4, 0xcf, 0xed, 0xd4, 0x06, 0x28, 0x2a, 0x11, 0x3c, 0xba, 0xd9, - 0x41, 0x6e, 0x00, 0xe7, 0x81, 0x37, 0x93, 0xe4, 0xda, 0x62, 0xc6, 0x1d, - 0x67, 0x6f, 0x63, 0xb4, 0x14, 0x86, 0xd9, 0xa6, 0x62, 0xf0, -} - -var certSet3Cert35 = []byte{ - 0x30, 0x82, 0x04, 0xc7, 0x30, 0x82, 0x03, 0xaf, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x41, 0x82, 0x12, 0x7d, 0x12, 0xd9, 0xc6, 0xb3, 0x21, - 0x39, 0x43, 0x12, 0x56, 0x64, 0x00, 0xb8, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, - 0x98, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, - 0x63, 0x2e, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, - 0x30, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x38, 0x20, 0x47, 0x65, - 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, - 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, - 0x79, 0x31, 0x36, 0x30, 0x34, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2d, - 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x50, 0x72, 0x69, - 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x30, 0x1e, 0x17, - 0x0d, 0x31, 0x33, 0x30, 0x35, 0x32, 0x33, 0x30, 0x30, 0x30, 0x30, 0x30, - 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x33, 0x30, 0x35, 0x32, 0x32, 0x32, 0x33, - 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x46, 0x31, 0x0b, 0x30, 0x09, 0x06, - 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, - 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, - 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, - 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x16, 0x47, 0x65, 0x6f, 0x54, 0x72, - 0x75, 0x73, 0x74, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x53, - 0x53, 0x4c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, - 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, - 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, - 0x01, 0x00, 0xc6, 0xa9, 0x0b, 0x5d, 0x17, 0xa5, 0x7d, 0xc6, 0xcf, 0x2a, - 0xef, 0xc6, 0x66, 0xd1, 0x42, 0x1e, 0x5f, 0x83, 0x78, 0x68, 0x91, 0xaf, - 0xe6, 0xa7, 0x8b, 0xf0, 0x1d, 0x44, 0x01, 0x0a, 0x19, 0xca, 0x9c, 0xd4, - 0x8b, 0x1d, 0xe1, 0xa1, 0x90, 0xa3, 0xc1, 0x5b, 0xb4, 0xd7, 0x5b, 0x6a, - 0x8b, 0xfc, 0x0e, 0x49, 0x1e, 0xc2, 0x62, 0x29, 0xfe, 0x80, 0x15, 0x39, - 0x8b, 0x81, 0x2a, 0x27, 0xb5, 0xfb, 0x12, 0xa8, 0x05, 0x22, 0x0b, 0xc5, - 0x2c, 0xf5, 0xd9, 0x98, 0xdd, 0x16, 0x2f, 0x3b, 0x66, 0xe7, 0x62, 0xa2, - 0x43, 0x32, 0xac, 0x8f, 0xb5, 0x85, 0xc8, 0x52, 0x06, 0x2c, 0x5c, 0xc0, - 0x77, 0xfa, 0x67, 0xf7, 0x83, 0xe8, 0x5e, 0x05, 0x8d, 0xc8, 0xab, 0xa1, - 0x16, 0x32, 0x8a, 0xd2, 0x40, 0xec, 0x86, 0x3a, 0x1c, 0x23, 0xa9, 0x8d, - 0xb5, 0x00, 0xde, 0x72, 0xbd, 0x85, 0x55, 0xfe, 0x06, 0x01, 0x60, 0x5d, - 0xad, 0xb3, 0xe0, 0x65, 0x73, 0xa5, 0x92, 0x14, 0x9e, 0x94, 0x56, 0x6f, - 0x93, 0xee, 0xaf, 0xa9, 0x3a, 0x30, 0x25, 0x4a, 0x8e, 0x09, 0x84, 0xef, - 0xb7, 0xd2, 0xd5, 0xd7, 0x9b, 0x49, 0xcd, 0xe9, 0xc0, 0x5e, 0x67, 0x71, - 0x22, 0xac, 0x50, 0x90, 0x43, 0x20, 0x5d, 0xa1, 0xa3, 0x15, 0x83, 0xfd, - 0xfc, 0xa7, 0x39, 0xbc, 0x6b, 0x65, 0x48, 0x12, 0x60, 0xff, 0xdd, 0x23, - 0xb3, 0x3a, 0xaa, 0xf4, 0x9f, 0x9c, 0x37, 0x53, 0x41, 0xa2, 0x47, 0x93, - 0x81, 0x33, 0x09, 0xe5, 0x22, 0xc6, 0xc8, 0x1c, 0x49, 0xa1, 0x6e, 0x8d, - 0xcc, 0x83, 0xb3, 0x9a, 0xcd, 0xea, 0x43, 0xf2, 0x19, 0xd3, 0x24, 0xcb, - 0xa8, 0x29, 0xae, 0x52, 0xcc, 0xf4, 0x08, 0x27, 0xb0, 0x84, 0xea, 0xce, - 0x27, 0xb5, 0xe1, 0x34, 0x13, 0x73, 0x92, 0x5c, 0x87, 0x86, 0x2a, 0xc6, - 0xb0, 0x68, 0x36, 0xad, 0xcb, 0x09, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, - 0x82, 0x01, 0x5c, 0x30, 0x82, 0x01, 0x58, 0x30, 0x3b, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x2f, 0x30, 0x2d, 0x30, - 0x2b, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, - 0x1f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x70, 0x63, 0x61, 0x2d, - 0x67, 0x33, 0x2d, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x65, 0x6f, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x12, 0x06, 0x03, - 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, - 0xff, 0x02, 0x01, 0x00, 0x30, 0x4c, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, - 0x45, 0x30, 0x43, 0x30, 0x41, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, - 0xf8, 0x45, 0x01, 0x07, 0x36, 0x30, 0x33, 0x30, 0x31, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x25, 0x68, 0x74, 0x74, - 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x3b, - 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0xa0, - 0x2e, 0xa0, 0x2c, 0x86, 0x2a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x63, 0x72, 0x6c, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, - 0x74, 0x50, 0x43, 0x41, 0x2d, 0x47, 0x33, 0x2e, 0x63, 0x72, 0x6c, 0x30, - 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, - 0x02, 0x01, 0x06, 0x30, 0x2a, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x23, - 0x30, 0x21, 0xa4, 0x1f, 0x30, 0x1d, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, - 0x55, 0x04, 0x03, 0x13, 0x12, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, - 0x6e, 0x4d, 0x50, 0x4b, 0x49, 0x2d, 0x32, 0x2d, 0x34, 0x31, 0x36, 0x30, - 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x14, 0x67, - 0x8e, 0xed, 0x83, 0x4f, 0xd6, 0x1e, 0x9d, 0x40, 0x04, 0x0c, 0x04, 0x46, - 0xa1, 0x70, 0x34, 0xb2, 0x0f, 0x72, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, - 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xc4, 0x79, 0xca, 0x8e, 0xa1, - 0x4e, 0x03, 0x1d, 0x1c, 0xdc, 0x6b, 0xdb, 0x31, 0x5b, 0x94, 0x3e, 0x3f, - 0x30, 0x7f, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, - 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x10, - 0x10, 0xea, 0xf2, 0x10, 0xd6, 0x08, 0x46, 0xe2, 0xc1, 0x8f, 0x3e, 0x36, - 0x59, 0xc8, 0x2b, 0x0f, 0xfe, 0x4d, 0xec, 0xe3, 0xf8, 0xb6, 0x56, 0x31, - 0x78, 0x25, 0xd4, 0x76, 0xf2, 0x08, 0xdd, 0xef, 0x3f, 0xcd, 0x8b, 0x1c, - 0x7e, 0xaa, 0x7f, 0xfc, 0x0b, 0xa8, 0x23, 0x64, 0x51, 0xb3, 0x87, 0xd6, - 0x09, 0xfa, 0x22, 0xfa, 0xc7, 0x0a, 0x51, 0xe8, 0xce, 0xb8, 0xf6, 0x03, - 0x70, 0xe0, 0x1b, 0x5a, 0xb9, 0xb1, 0xb2, 0x93, 0x11, 0x10, 0xf9, 0x97, - 0x05, 0x07, 0x29, 0x6c, 0x6d, 0x57, 0x25, 0x54, 0xe8, 0xf9, 0x66, 0x9b, - 0x0e, 0xfb, 0xdb, 0x9f, 0xee, 0x96, 0x6f, 0x65, 0xcb, 0x1f, 0xd8, 0x55, - 0xce, 0x31, 0xfa, 0xcf, 0x02, 0xf4, 0xd0, 0x7f, 0x50, 0x66, 0xff, 0x2f, - 0x79, 0x9b, 0xa5, 0xc2, 0xdf, 0xd6, 0xcf, 0xc8, 0x15, 0x83, 0x96, 0x84, - 0x98, 0xb2, 0x46, 0xd4, 0x5f, 0x13, 0xa8, 0x3e, 0xa7, 0x34, 0x9c, 0x05, - 0x38, 0xda, 0xcf, 0xd6, 0x69, 0x95, 0xa9, 0x26, 0x87, 0x76, 0x01, 0xd7, - 0xb2, 0x51, 0x0f, 0x81, 0x69, 0x46, 0x26, 0x1c, 0x99, 0xb6, 0x83, 0x58, - 0xe3, 0x3b, 0x58, 0x8f, 0xdc, 0xb4, 0x71, 0xc0, 0xb9, 0xbf, 0x42, 0x9c, - 0x1c, 0x03, 0x9e, 0xe4, 0x46, 0xa8, 0xea, 0xb9, 0xc1, 0xcd, 0xf6, 0x5b, - 0xa9, 0x3c, 0x96, 0xfb, 0x79, 0xa4, 0x33, 0x73, 0xa7, 0x9e, 0x78, 0xb9, - 0x70, 0xdc, 0x72, 0x74, 0xc4, 0x32, 0xc8, 0x00, 0x1b, 0xc9, 0xef, 0x48, - 0xd3, 0xfb, 0x3a, 0x9b, 0xfa, 0xfe, 0x7a, 0x9a, 0x40, 0x69, 0x1c, 0xc8, - 0xda, 0x28, 0x37, 0x0b, 0xd3, 0xa3, 0xb9, 0x7e, 0x96, 0xcc, 0x2b, 0x28, - 0xc3, 0x56, 0x6c, 0x6f, 0xe9, 0xdb, 0x52, 0xb1, 0xfa, 0x9a, 0xfb, 0xe7, - 0xaf, 0xb5, 0x97, 0xa6, 0x22, 0xc3, 0xc5, 0xa8, 0x93, 0xb1, 0x00, 0xc9, - 0x07, 0xb2, 0x7d, -} - -var certSet3Cert36 = []byte{ - 0x30, 0x82, 0x04, 0xd0, 0x30, 0x82, 0x03, 0xb8, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x01, 0x07, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, 0x83, 0x31, 0x0b, - 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, - 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x07, 0x41, 0x72, - 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, - 0x04, 0x07, 0x13, 0x0a, 0x53, 0x63, 0x6f, 0x74, 0x74, 0x73, 0x64, 0x61, - 0x6c, 0x65, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x11, 0x47, 0x6f, 0x44, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, - 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, - 0x55, 0x04, 0x03, 0x13, 0x28, 0x47, 0x6f, 0x20, 0x44, 0x61, 0x64, 0x64, - 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x1e, 0x17, - 0x0d, 0x31, 0x31, 0x30, 0x35, 0x30, 0x33, 0x30, 0x37, 0x30, 0x30, 0x30, - 0x30, 0x5a, 0x17, 0x0d, 0x33, 0x31, 0x30, 0x35, 0x30, 0x33, 0x30, 0x37, - 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x81, 0xb4, 0x31, 0x0b, 0x30, 0x09, - 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, - 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x07, 0x41, 0x72, 0x69, 0x7a, - 0x6f, 0x6e, 0x61, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x07, - 0x13, 0x0a, 0x53, 0x63, 0x6f, 0x74, 0x74, 0x73, 0x64, 0x61, 0x6c, 0x65, - 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x47, - 0x6f, 0x44, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x20, - 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03, 0x55, 0x04, - 0x0b, 0x13, 0x24, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, - 0x72, 0x74, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, - 0x72, 0x79, 0x2f, 0x31, 0x33, 0x30, 0x31, 0x06, 0x03, 0x55, 0x04, 0x03, - 0x13, 0x2a, 0x47, 0x6f, 0x20, 0x44, 0x61, 0x64, 0x64, 0x79, 0x20, 0x53, - 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, - 0x02, 0x82, 0x01, 0x01, 0x00, 0xb9, 0xe0, 0xcb, 0x10, 0xd4, 0xaf, 0x76, - 0xbd, 0xd4, 0x93, 0x62, 0xeb, 0x30, 0x64, 0xb8, 0x81, 0x08, 0x6c, 0xc3, - 0x04, 0xd9, 0x62, 0x17, 0x8e, 0x2f, 0xff, 0x3e, 0x65, 0xcf, 0x8f, 0xce, - 0x62, 0xe6, 0x3c, 0x52, 0x1c, 0xda, 0x16, 0x45, 0x4b, 0x55, 0xab, 0x78, - 0x6b, 0x63, 0x83, 0x62, 0x90, 0xce, 0x0f, 0x69, 0x6c, 0x99, 0xc8, 0x1a, - 0x14, 0x8b, 0x4c, 0xcc, 0x45, 0x33, 0xea, 0x88, 0xdc, 0x9e, 0xa3, 0xaf, - 0x2b, 0xfe, 0x80, 0x61, 0x9d, 0x79, 0x57, 0xc4, 0xcf, 0x2e, 0xf4, 0x3f, - 0x30, 0x3c, 0x5d, 0x47, 0xfc, 0x9a, 0x16, 0xbc, 0xc3, 0x37, 0x96, 0x41, - 0x51, 0x8e, 0x11, 0x4b, 0x54, 0xf8, 0x28, 0xbe, 0xd0, 0x8c, 0xbe, 0xf0, - 0x30, 0x38, 0x1e, 0xf3, 0xb0, 0x26, 0xf8, 0x66, 0x47, 0x63, 0x6d, 0xde, - 0x71, 0x26, 0x47, 0x8f, 0x38, 0x47, 0x53, 0xd1, 0x46, 0x1d, 0xb4, 0xe3, - 0xdc, 0x00, 0xea, 0x45, 0xac, 0xbd, 0xbc, 0x71, 0xd9, 0xaa, 0x6f, 0x00, - 0xdb, 0xdb, 0xcd, 0x30, 0x3a, 0x79, 0x4f, 0x5f, 0x4c, 0x47, 0xf8, 0x1d, - 0xef, 0x5b, 0xc2, 0xc4, 0x9d, 0x60, 0x3b, 0xb1, 0xb2, 0x43, 0x91, 0xd8, - 0xa4, 0x33, 0x4e, 0xea, 0xb3, 0xd6, 0x27, 0x4f, 0xad, 0x25, 0x8a, 0xa5, - 0xc6, 0xf4, 0xd5, 0xd0, 0xa6, 0xae, 0x74, 0x05, 0x64, 0x57, 0x88, 0xb5, - 0x44, 0x55, 0xd4, 0x2d, 0x2a, 0x3a, 0x3e, 0xf8, 0xb8, 0xbd, 0xe9, 0x32, - 0x0a, 0x02, 0x94, 0x64, 0xc4, 0x16, 0x3a, 0x50, 0xf1, 0x4a, 0xae, 0xe7, - 0x79, 0x33, 0xaf, 0x0c, 0x20, 0x07, 0x7f, 0xe8, 0xdf, 0x04, 0x39, 0xc2, - 0x69, 0x02, 0x6c, 0x63, 0x52, 0xfa, 0x77, 0xc1, 0x1b, 0xc8, 0x74, 0x87, - 0xc8, 0xb9, 0x93, 0x18, 0x50, 0x54, 0x35, 0x4b, 0x69, 0x4e, 0xbc, 0x3b, - 0xd3, 0x49, 0x2e, 0x1f, 0xdc, 0xc1, 0xd2, 0x52, 0xfb, 0x02, 0x03, 0x01, - 0x00, 0x01, 0xa3, 0x82, 0x01, 0x1a, 0x30, 0x82, 0x01, 0x16, 0x30, 0x0f, - 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, - 0x01, 0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, - 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, - 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x40, 0xc2, 0xbd, 0x27, 0x8e, 0xcc, - 0x34, 0x83, 0x30, 0xa2, 0x33, 0xd7, 0xfb, 0x6c, 0xb3, 0xf0, 0xb4, 0x2c, - 0x80, 0xce, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, - 0x16, 0x80, 0x14, 0x3a, 0x9a, 0x85, 0x07, 0x10, 0x67, 0x28, 0xb6, 0xef, - 0xf6, 0xbd, 0x05, 0x41, 0x6e, 0x20, 0xc1, 0x94, 0xda, 0x0f, 0xde, 0x30, - 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, - 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x35, 0x06, 0x03, 0x55, 0x1d, 0x1f, - 0x04, 0x2e, 0x30, 0x2c, 0x30, 0x2a, 0xa0, 0x28, 0xa0, 0x26, 0x86, 0x24, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x67, - 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, - 0x64, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x67, 0x32, 0x2e, 0x63, 0x72, 0x6c, - 0x30, 0x46, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x3f, 0x30, 0x3d, 0x30, - 0x3b, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x33, 0x30, 0x31, 0x06, - 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x25, 0x68, - 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x73, - 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x08, 0x7e, 0x6c, 0x93, - 0x10, 0xc8, 0x38, 0xb8, 0x96, 0xa9, 0x90, 0x4b, 0xff, 0xa1, 0x5f, 0x4f, - 0x04, 0xef, 0x6c, 0x3e, 0x9c, 0x88, 0x06, 0xc9, 0x50, 0x8f, 0xa6, 0x73, - 0xf7, 0x57, 0x31, 0x1b, 0xbe, 0xbc, 0xe4, 0x2f, 0xdb, 0xf8, 0xba, 0xd3, - 0x5b, 0xe0, 0xb4, 0xe7, 0xe6, 0x79, 0x62, 0x0e, 0x0c, 0xa2, 0xd7, 0x6a, - 0x63, 0x73, 0x31, 0xb5, 0xf5, 0xa8, 0x48, 0xa4, 0x3b, 0x08, 0x2d, 0xa2, - 0x5d, 0x90, 0xd7, 0xb4, 0x7c, 0x25, 0x4f, 0x11, 0x56, 0x30, 0xc4, 0xb6, - 0x44, 0x9d, 0x7b, 0x2c, 0x9d, 0xe5, 0x5e, 0xe6, 0xef, 0x0c, 0x61, 0xaa, - 0xbf, 0xe4, 0x2a, 0x1b, 0xee, 0x84, 0x9e, 0xb8, 0x83, 0x7d, 0xc1, 0x43, - 0xce, 0x44, 0xa7, 0x13, 0x70, 0x0d, 0x91, 0x1f, 0xf4, 0xc8, 0x13, 0xad, - 0x83, 0x60, 0xd9, 0xd8, 0x72, 0xa8, 0x73, 0x24, 0x1e, 0xb5, 0xac, 0x22, - 0x0e, 0xca, 0x17, 0x89, 0x62, 0x58, 0x44, 0x1b, 0xab, 0x89, 0x25, 0x01, - 0x00, 0x0f, 0xcd, 0xc4, 0x1b, 0x62, 0xdb, 0x51, 0xb4, 0xd3, 0x0f, 0x51, - 0x2a, 0x9b, 0xf4, 0xbc, 0x73, 0xfc, 0x76, 0xce, 0x36, 0xa4, 0xcd, 0xd9, - 0xd8, 0x2c, 0xea, 0xae, 0x9b, 0xf5, 0x2a, 0xb2, 0x90, 0xd1, 0x4d, 0x75, - 0x18, 0x8a, 0x3f, 0x8a, 0x41, 0x90, 0x23, 0x7d, 0x5b, 0x4b, 0xfe, 0xa4, - 0x03, 0x58, 0x9b, 0x46, 0xb2, 0xc3, 0x60, 0x60, 0x83, 0xf8, 0x7d, 0x50, - 0x41, 0xce, 0xc2, 0xa1, 0x90, 0xc3, 0xbb, 0xef, 0x02, 0x2f, 0xd2, 0x15, - 0x54, 0xee, 0x44, 0x15, 0xd9, 0x0a, 0xae, 0xa7, 0x8a, 0x33, 0xed, 0xb1, - 0x2d, 0x76, 0x36, 0x26, 0xdc, 0x04, 0xeb, 0x9f, 0xf7, 0x61, 0x1f, 0x15, - 0xdc, 0x87, 0x6f, 0xee, 0x46, 0x96, 0x28, 0xad, 0xa1, 0x26, 0x7d, 0x0a, - 0x09, 0xa7, 0x2e, 0x04, 0xa3, 0x8d, 0xbc, 0xf8, 0xbc, 0x04, 0x30, 0x01, -} - -var certSet3Cert37 = []byte{ - 0x30, 0x82, 0x04, 0xd0, 0x30, 0x82, 0x04, 0x39, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x25, 0x0c, 0xe8, 0xe0, 0x30, 0x61, 0x2e, 0x9f, 0x2b, - 0x89, 0xf7, 0x05, 0x4d, 0x7c, 0xf8, 0xfd, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5f, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, - 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, - 0x63, 0x2e, 0x31, 0x37, 0x30, 0x35, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, - 0x2e, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, - 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, - 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, - 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x30, 0x38, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x31, 0x30, 0x37, - 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xca, 0x31, 0x0b, - 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, - 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65, - 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56, - 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, - 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3a, 0x30, - 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28, 0x63, 0x29, 0x20, - 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, - 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, - 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, - 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x45, 0x30, - 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56, 0x65, 0x72, 0x69, - 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, - 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, - 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30, 0x82, 0x01, 0x22, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, - 0x02, 0x82, 0x01, 0x01, 0x00, 0xaf, 0x24, 0x08, 0x08, 0x29, 0x7a, 0x35, - 0x9e, 0x60, 0x0c, 0xaa, 0xe7, 0x4b, 0x3b, 0x4e, 0xdc, 0x7c, 0xbc, 0x3c, - 0x45, 0x1c, 0xbb, 0x2b, 0xe0, 0xfe, 0x29, 0x02, 0xf9, 0x57, 0x08, 0xa3, - 0x64, 0x85, 0x15, 0x27, 0xf5, 0xf1, 0xad, 0xc8, 0x31, 0x89, 0x5d, 0x22, - 0xe8, 0x2a, 0xaa, 0xa6, 0x42, 0xb3, 0x8f, 0xf8, 0xb9, 0x55, 0xb7, 0xb1, - 0xb7, 0x4b, 0xb3, 0xfe, 0x8f, 0x7e, 0x07, 0x57, 0xec, 0xef, 0x43, 0xdb, - 0x66, 0x62, 0x15, 0x61, 0xcf, 0x60, 0x0d, 0xa4, 0xd8, 0xde, 0xf8, 0xe0, - 0xc3, 0x62, 0x08, 0x3d, 0x54, 0x13, 0xeb, 0x49, 0xca, 0x59, 0x54, 0x85, - 0x26, 0xe5, 0x2b, 0x8f, 0x1b, 0x9f, 0xeb, 0xf5, 0xa1, 0x91, 0xc2, 0x33, - 0x49, 0xd8, 0x43, 0x63, 0x6a, 0x52, 0x4b, 0xd2, 0x8f, 0xe8, 0x70, 0x51, - 0x4d, 0xd1, 0x89, 0x69, 0x7b, 0xc7, 0x70, 0xf6, 0xb3, 0xdc, 0x12, 0x74, - 0xdb, 0x7b, 0x5d, 0x4b, 0x56, 0xd3, 0x96, 0xbf, 0x15, 0x77, 0xa1, 0xb0, - 0xf4, 0xa2, 0x25, 0xf2, 0xaf, 0x1c, 0x92, 0x67, 0x18, 0xe5, 0xf4, 0x06, - 0x04, 0xef, 0x90, 0xb9, 0xe4, 0x00, 0xe4, 0xdd, 0x3a, 0xb5, 0x19, 0xff, - 0x02, 0xba, 0xf4, 0x3c, 0xee, 0xe0, 0x8b, 0xeb, 0x37, 0x8b, 0xec, 0xf4, - 0xd7, 0xac, 0xf2, 0xf6, 0xf0, 0x3d, 0xaf, 0xdd, 0x75, 0x91, 0x33, 0x19, - 0x1d, 0x1c, 0x40, 0xcb, 0x74, 0x24, 0x19, 0x21, 0x93, 0xd9, 0x14, 0xfe, - 0xac, 0x2a, 0x52, 0xc7, 0x8f, 0xd5, 0x04, 0x49, 0xe4, 0x8d, 0x63, 0x47, - 0x88, 0x3c, 0x69, 0x83, 0xcb, 0xfe, 0x47, 0xbd, 0x2b, 0x7e, 0x4f, 0xc5, - 0x95, 0xae, 0x0e, 0x9d, 0xd4, 0xd1, 0x43, 0xc0, 0x67, 0x73, 0xe3, 0x14, - 0x08, 0x7e, 0xe5, 0x3f, 0x9f, 0x73, 0xb8, 0x33, 0x0a, 0xcf, 0x5d, 0x3f, - 0x34, 0x87, 0x96, 0x8a, 0xee, 0x53, 0xe8, 0x25, 0x15, 0x02, 0x03, 0x01, - 0x00, 0x01, 0xa3, 0x82, 0x01, 0x9b, 0x30, 0x82, 0x01, 0x97, 0x30, 0x0f, - 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, - 0x01, 0x01, 0xff, 0x30, 0x31, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2a, - 0x30, 0x28, 0x30, 0x26, 0xa0, 0x24, 0xa0, 0x22, 0x86, 0x20, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, - 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, - 0x61, 0x33, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, - 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x3d, - 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, 0x30, 0x34, 0x30, 0x32, 0x06, - 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, 0x30, 0x28, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, 0x74, - 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72, - 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, - 0x73, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, - 0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0, 0x30, 0x09, 0xf3, - 0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30, 0x6d, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x0c, 0x04, 0x61, 0x30, 0x5f, - 0xa1, 0x5d, 0xa0, 0x5b, 0x30, 0x59, 0x30, 0x57, 0x30, 0x55, 0x16, 0x09, - 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, 0x69, 0x66, 0x30, 0x21, 0x30, - 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14, - 0x8f, 0xe5, 0xd3, 0x1a, 0x86, 0xac, 0x8d, 0x8e, 0x6b, 0xc3, 0xcf, 0x80, - 0x6a, 0xd4, 0x48, 0x18, 0x2c, 0x7b, 0x19, 0x2e, 0x30, 0x25, 0x16, 0x23, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, - 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x76, 0x73, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x67, 0x69, 0x66, 0x30, - 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, - 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, - 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x3e, 0x06, 0x03, 0x55, 0x1d, 0x25, - 0x04, 0x37, 0x30, 0x35, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x03, 0x06, 0x09, - 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01, 0x06, 0x0a, 0x60, - 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x08, 0x01, 0x30, 0x0d, 0x06, - 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, - 0x03, 0x81, 0x81, 0x00, 0x13, 0x02, 0xdd, 0xf8, 0xe8, 0x86, 0x00, 0xf2, - 0x5a, 0xf8, 0xf8, 0x20, 0x0c, 0x59, 0x88, 0x62, 0x07, 0xce, 0xce, 0xf7, - 0x4e, 0xf9, 0xbb, 0x59, 0xa1, 0x98, 0xe5, 0xe1, 0x38, 0xdd, 0x4e, 0xbc, - 0x66, 0x18, 0xd3, 0xad, 0xeb, 0x18, 0xf2, 0x0d, 0xc9, 0x6d, 0x3e, 0x4a, - 0x94, 0x20, 0xc3, 0x3c, 0xba, 0xbd, 0x65, 0x54, 0xc6, 0xaf, 0x44, 0xb3, - 0x10, 0xad, 0x2c, 0x6b, 0x3e, 0xab, 0xd7, 0x07, 0xb6, 0xb8, 0x81, 0x63, - 0xc5, 0xf9, 0x5e, 0x2e, 0xe5, 0x2a, 0x67, 0xce, 0xcd, 0x33, 0x0c, 0x2a, - 0xd7, 0x89, 0x56, 0x03, 0x23, 0x1f, 0xb3, 0xbe, 0xe8, 0x3a, 0x08, 0x59, - 0xb4, 0xec, 0x45, 0x35, 0xf7, 0x8a, 0x5b, 0xff, 0x66, 0xcf, 0x50, 0xaf, - 0xc6, 0x6d, 0x57, 0x8d, 0x19, 0x78, 0xb7, 0xb9, 0xa2, 0xd1, 0x57, 0xea, - 0x1f, 0x9a, 0x4b, 0xaf, 0xba, 0xc9, 0x8e, 0x12, 0x7e, 0xc6, 0xbd, 0xff, -} - -var certSet3Cert38 = []byte{ - 0x30, 0x82, 0x04, 0xd2, 0x30, 0x82, 0x03, 0xba, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x2c, 0x69, 0xe1, 0x2f, 0x6a, 0x67, 0x0b, 0xd9, 0x9d, - 0xd2, 0x0f, 0x91, 0x9e, 0xf0, 0x9e, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, - 0xa9, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, - 0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1f, - 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x44, - 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x38, 0x30, 0x36, 0x06, - 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, - 0x30, 0x36, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, - 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x16, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50, - 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, - 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x34, 0x30, 0x36, 0x31, 0x30, - 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x34, 0x30, - 0x36, 0x30, 0x39, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x63, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, - 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, - 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x31, 0x1d, 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x14, 0x44, - 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, - 0x74, 0x65, 0x64, 0x20, 0x53, 0x53, 0x4c, 0x31, 0x1e, 0x30, 0x1c, 0x06, - 0x03, 0x55, 0x04, 0x03, 0x13, 0x15, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, - 0x20, 0x44, 0x56, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x20, 0x2d, - 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, - 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, - 0xea, 0x94, 0x07, 0x85, 0xc8, 0x41, 0x2c, 0xf6, 0x83, 0x12, 0x6c, 0x92, - 0x5f, 0xab, 0x1f, 0x00, 0xd4, 0x96, 0x6f, 0x74, 0xcd, 0x2e, 0x11, 0xe9, - 0x6c, 0x0f, 0x39, 0x01, 0xb9, 0x48, 0x90, 0x40, 0x39, 0x4d, 0xc4, 0xa2, - 0xc8, 0x79, 0x6a, 0xa5, 0x9a, 0xbd, 0x91, 0x44, 0x65, 0x77, 0x54, 0xad, - 0xff, 0x25, 0x5f, 0xee, 0x42, 0xfb, 0xb3, 0x02, 0x0f, 0xea, 0x5d, 0x7a, - 0xdd, 0x1a, 0x54, 0x9e, 0xd7, 0x73, 0x42, 0x9b, 0xcc, 0x79, 0x5f, 0xc5, - 0x4d, 0xf4, 0xb7, 0x0b, 0x18, 0x39, 0x20, 0x7a, 0xdd, 0x50, 0x01, 0x5d, - 0x34, 0x45, 0x5f, 0x4c, 0x11, 0x0e, 0xf5, 0x87, 0x26, 0x26, 0xb4, 0xb0, - 0xf3, 0x7e, 0x71, 0xa0, 0x31, 0x71, 0x50, 0x89, 0x68, 0x5a, 0x63, 0x8a, - 0x14, 0x62, 0xe5, 0x8c, 0x3a, 0x16, 0x55, 0x0d, 0x3e, 0xeb, 0xaa, 0x80, - 0x1d, 0x71, 0x7a, 0xe3, 0x87, 0x07, 0xab, 0xbd, 0xa2, 0x74, 0xcd, 0xda, - 0x08, 0x01, 0x9d, 0x1b, 0xcc, 0x27, 0x88, 0x8c, 0x47, 0xd4, 0x69, 0x25, - 0x42, 0xd6, 0xbb, 0x50, 0x6d, 0x85, 0x50, 0xd0, 0x48, 0x82, 0x0d, 0x08, - 0x9f, 0xe9, 0x23, 0xe3, 0x42, 0xc6, 0x3c, 0x98, 0xb8, 0xbb, 0x6e, 0xc5, - 0x70, 0x13, 0xdf, 0x19, 0x1d, 0x01, 0xfd, 0xd2, 0xb5, 0x4e, 0xe6, 0x62, - 0xf4, 0x07, 0xfa, 0x6b, 0x7d, 0x11, 0x77, 0xc4, 0x62, 0x4f, 0x40, 0x4e, - 0xa5, 0x78, 0x97, 0xab, 0x2c, 0x4d, 0x0c, 0xa7, 0x7c, 0xc3, 0xc4, 0x50, - 0x32, 0x9f, 0xd0, 0x70, 0x9b, 0x0f, 0xff, 0xff, 0x75, 0x59, 0x34, 0x85, - 0xad, 0x49, 0xd5, 0x35, 0xee, 0x4f, 0x5b, 0xd4, 0xd4, 0x36, 0x95, 0xa0, - 0x7e, 0xe8, 0xc5, 0xa1, 0x1c, 0xbd, 0x13, 0x4e, 0x7d, 0xee, 0x63, 0x6a, - 0x96, 0x19, 0x99, 0xc8, 0xa7, 0x2a, 0x00, 0xe6, 0x51, 0x8d, 0x46, 0xeb, - 0x30, 0x58, 0xe8, 0x2d, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, - 0x39, 0x30, 0x82, 0x01, 0x35, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, - 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, - 0x00, 0x30, 0x41, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x3a, 0x30, 0x38, - 0x30, 0x36, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, - 0x07, 0x36, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, - 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x0e, 0x06, 0x03, - 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, - 0x30, 0x2e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, - 0x04, 0x22, 0x30, 0x20, 0x30, 0x1e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x30, 0x01, 0x86, 0x12, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, - 0x2f, 0x74, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x64, 0x2e, 0x63, 0x6f, 0x6d, - 0x30, 0x31, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2a, 0x30, 0x28, 0x30, - 0x26, 0xa0, 0x24, 0xa0, 0x22, 0x86, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x74, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x50, 0x43, 0x41, 0x2e, - 0x63, 0x72, 0x6c, 0x30, 0x29, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x22, - 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, - 0x55, 0x04, 0x03, 0x13, 0x11, 0x53, 0x79, 0x6d, 0x61, 0x6e, 0x74, 0x65, - 0x63, 0x50, 0x4b, 0x49, 0x2d, 0x31, 0x2d, 0x36, 0x39, 0x38, 0x30, 0x1d, - 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x9f, 0xb8, 0xc1, - 0xa9, 0x6c, 0xf2, 0xf5, 0xc0, 0x22, 0x2a, 0x94, 0xed, 0x5c, 0x99, 0xac, - 0xd4, 0xec, 0xd7, 0xc6, 0x07, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, - 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x7b, 0x5b, 0x45, 0xcf, 0xaf, 0xce, - 0xcb, 0x7a, 0xfd, 0x31, 0x92, 0x1a, 0x6a, 0xb6, 0xf3, 0x46, 0xeb, 0x57, - 0x48, 0x50, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, - 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x53, 0x54, - 0xf2, 0x47, 0xa8, 0x02, 0xd7, 0xef, 0xaa, 0x35, 0x78, 0xbe, 0x4a, 0x08, - 0x0d, 0x90, 0x18, 0x4b, 0x6d, 0x9e, 0x2a, 0x53, 0x2b, 0xe9, 0x54, 0x17, - 0x77, 0x74, 0x29, 0x7e, 0xd0, 0x37, 0x07, 0x05, 0xb8, 0xe4, 0xfa, 0xb8, - 0xb4, 0x63, 0x98, 0x44, 0xdc, 0xc6, 0x4f, 0x81, 0x06, 0x8c, 0x3a, 0xbe, - 0xc7, 0x30, 0x57, 0xc6, 0x70, 0xfc, 0xd6, 0x93, 0x19, 0x9f, 0xc3, 0x55, - 0xd7, 0x3e, 0x1f, 0x72, 0x8a, 0x9d, 0x30, 0x5a, 0x35, 0x97, 0x32, 0xcb, - 0x63, 0xe4, 0xc6, 0x72, 0xdf, 0xfb, 0x68, 0xca, 0x69, 0x2f, 0xdb, 0xcd, - 0x50, 0x38, 0x3e, 0x2b, 0xbb, 0xab, 0x3b, 0x82, 0xc7, 0xfd, 0x4b, 0x9b, - 0xbd, 0x7c, 0x41, 0x98, 0xef, 0x01, 0x53, 0xd8, 0x35, 0x8f, 0x25, 0xc9, - 0x03, 0x06, 0xe6, 0x9c, 0x57, 0xc1, 0x51, 0x0f, 0x9e, 0xf6, 0x7d, 0x93, - 0x4d, 0xf8, 0x76, 0xc8, 0x3a, 0x6b, 0xf4, 0xc4, 0x8f, 0x33, 0x32, 0x7f, - 0x9d, 0x21, 0x84, 0x34, 0xd9, 0xa7, 0xf9, 0x92, 0xfa, 0x41, 0x91, 0x61, - 0x84, 0x05, 0x9d, 0xa3, 0x79, 0x46, 0xce, 0x67, 0xe7, 0x81, 0xf2, 0x5e, - 0xac, 0x4c, 0xbc, 0xa8, 0xab, 0x6a, 0x6d, 0x15, 0xe2, 0x9c, 0x4e, 0x5a, - 0xd9, 0x63, 0x80, 0xbc, 0xf7, 0x42, 0xeb, 0x9a, 0x44, 0xc6, 0x8c, 0x6b, - 0x06, 0x36, 0xb4, 0x8b, 0x32, 0x89, 0xde, 0xc2, 0xf1, 0xa8, 0x26, 0xaa, - 0xa9, 0xac, 0xff, 0xea, 0x71, 0xa6, 0xe7, 0x8c, 0x41, 0xfa, 0x17, 0x35, - 0xbb, 0xb3, 0x87, 0x31, 0xa9, 0x93, 0xc2, 0xc8, 0x58, 0xe1, 0x0a, 0x4e, - 0x95, 0x83, 0x9c, 0xb9, 0xed, 0x3b, 0xa5, 0xef, 0x08, 0xe0, 0x74, 0xf9, - 0xc3, 0x1b, 0xe6, 0x07, 0xa3, 0xee, 0x07, 0xd7, 0x42, 0x22, 0x79, 0x21, - 0xa0, 0xa1, 0xd4, 0x1d, 0x26, 0xd3, 0xd0, 0xd6, 0xa6, 0x5d, 0x2b, 0x41, - 0xc0, 0x79, -} - -var certSet3Cert39 = []byte{ - 0x30, 0x82, 0x04, 0xff, 0x30, 0x82, 0x03, 0xe7, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x04, 0x51, 0xd3, 0x40, 0x44, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, - 0xb0, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0d, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, - 0x63, 0x2e, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, - 0x30, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, - 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x20, 0x69, 0x73, 0x20, - 0x69, 0x6e, 0x63, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x65, 0x64, - 0x20, 0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, - 0x65, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, - 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x45, 0x6e, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x2d, - 0x30, 0x2b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x24, 0x45, 0x6e, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, - 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, - 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, - 0x0d, 0x31, 0x34, 0x30, 0x39, 0x32, 0x32, 0x31, 0x37, 0x31, 0x34, 0x35, - 0x37, 0x5a, 0x17, 0x0d, 0x32, 0x34, 0x30, 0x39, 0x32, 0x33, 0x30, 0x31, - 0x33, 0x31, 0x35, 0x33, 0x5a, 0x30, 0x81, 0xbe, 0x31, 0x0b, 0x30, 0x09, - 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, - 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x45, 0x6e, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x28, 0x30, - 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1f, 0x53, 0x65, 0x65, 0x20, - 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, - 0x6e, 0x65, 0x74, 0x2f, 0x6c, 0x65, 0x67, 0x61, 0x6c, 0x2d, 0x74, 0x65, - 0x72, 0x6d, 0x73, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, - 0x13, 0x30, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x39, 0x20, 0x45, - 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x20, 0x2d, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, - 0x6c, 0x79, 0x31, 0x32, 0x30, 0x30, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, - 0x29, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x20, 0x52, 0x6f, 0x6f, - 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, - 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, - 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, - 0x01, 0x01, 0x00, 0xba, 0x84, 0xb6, 0x72, 0xdb, 0x9e, 0x0c, 0x6b, 0xe2, - 0x99, 0xe9, 0x30, 0x01, 0xa7, 0x76, 0xea, 0x32, 0xb8, 0x95, 0x41, 0x1a, - 0xc9, 0xda, 0x61, 0x4e, 0x58, 0x72, 0xcf, 0xfe, 0xf6, 0x82, 0x79, 0xbf, - 0x73, 0x61, 0x06, 0x0a, 0xa5, 0x27, 0xd8, 0xb3, 0x5f, 0xd3, 0x45, 0x4e, - 0x1c, 0x72, 0xd6, 0x4e, 0x32, 0xf2, 0x72, 0x8a, 0x0f, 0xf7, 0x83, 0x19, - 0xd0, 0x6a, 0x80, 0x80, 0x00, 0x45, 0x1e, 0xb0, 0xc7, 0xe7, 0x9a, 0xbf, - 0x12, 0x57, 0x27, 0x1c, 0xa3, 0x68, 0x2f, 0x0a, 0x87, 0xbd, 0x6a, 0x6b, - 0x0e, 0x5e, 0x65, 0xf3, 0x1c, 0x77, 0xd5, 0xd4, 0x85, 0x8d, 0x70, 0x21, - 0xb4, 0xb3, 0x32, 0xe7, 0x8b, 0xa2, 0xd5, 0x86, 0x39, 0x02, 0xb1, 0xb8, - 0xd2, 0x47, 0xce, 0xe4, 0xc9, 0x49, 0xc4, 0x3b, 0xa7, 0xde, 0xfb, 0x54, - 0x7d, 0x57, 0xbe, 0xf0, 0xe8, 0x6e, 0xc2, 0x79, 0xb2, 0x3a, 0x0b, 0x55, - 0xe2, 0x50, 0x98, 0x16, 0x32, 0x13, 0x5c, 0x2f, 0x78, 0x56, 0xc1, 0xc2, - 0x94, 0xb3, 0xf2, 0x5a, 0xe4, 0x27, 0x9a, 0x9f, 0x24, 0xd7, 0xc6, 0xec, - 0xd0, 0x9b, 0x25, 0x82, 0xe3, 0xcc, 0xc2, 0xc4, 0x45, 0xc5, 0x8c, 0x97, - 0x7a, 0x06, 0x6b, 0x2a, 0x11, 0x9f, 0xa9, 0x0a, 0x6e, 0x48, 0x3b, 0x6f, - 0xdb, 0xd4, 0x11, 0x19, 0x42, 0xf7, 0x8f, 0x07, 0xbf, 0xf5, 0x53, 0x5f, - 0x9c, 0x3e, 0xf4, 0x17, 0x2c, 0xe6, 0x69, 0xac, 0x4e, 0x32, 0x4c, 0x62, - 0x77, 0xea, 0xb7, 0xe8, 0xe5, 0xbb, 0x34, 0xbc, 0x19, 0x8b, 0xae, 0x9c, - 0x51, 0xe7, 0xb7, 0x7e, 0xb5, 0x53, 0xb1, 0x33, 0x22, 0xe5, 0x6d, 0xcf, - 0x70, 0x3c, 0x1a, 0xfa, 0xe2, 0x9b, 0x67, 0xb6, 0x83, 0xf4, 0x8d, 0xa5, - 0xaf, 0x62, 0x4c, 0x4d, 0xe0, 0x58, 0xac, 0x64, 0x34, 0x12, 0x03, 0xf8, - 0xb6, 0x8d, 0x94, 0x63, 0x24, 0xa4, 0x71, 0x02, 0x03, 0x01, 0x00, 0x01, - 0xa3, 0x82, 0x01, 0x0f, 0x30, 0x82, 0x01, 0x0b, 0x30, 0x0e, 0x06, 0x03, - 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, - 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, - 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x01, 0x30, 0x33, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x27, 0x30, 0x25, - 0x30, 0x23, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, - 0x86, 0x17, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, - 0x70, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, - 0x74, 0x30, 0x33, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2c, 0x30, 0x2a, - 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86, 0x22, 0x68, 0x74, 0x74, 0x70, - 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, - 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x63, - 0x61, 0x31, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3b, 0x06, 0x03, 0x55, 0x1d, - 0x20, 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0x06, 0x04, 0x55, 0x1d, 0x20, - 0x00, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, - 0x6e, 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x30, 0x1d, 0x06, 0x03, 0x55, - 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x6a, 0x72, 0x26, 0x7a, 0xd0, 0x1e, - 0xef, 0x7d, 0xe7, 0x3b, 0x69, 0x51, 0xd4, 0x6c, 0x8d, 0x9f, 0x90, 0x12, - 0x66, 0xab, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, - 0x16, 0x80, 0x14, 0x68, 0x90, 0xe4, 0x67, 0xa4, 0xa6, 0x53, 0x80, 0xc7, - 0x86, 0x66, 0xa4, 0xf1, 0xf7, 0x4b, 0x43, 0xfb, 0x84, 0xbd, 0x6d, 0x30, - 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, - 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x69, 0x33, 0x83, 0xfc, 0x28, - 0x7a, 0x6f, 0x7d, 0xef, 0x9d, 0x55, 0xeb, 0xc5, 0x3e, 0x7a, 0x9d, 0x75, - 0xb3, 0xcc, 0xc3, 0x38, 0x36, 0xd9, 0x34, 0xa2, 0x28, 0x68, 0x18, 0xea, - 0x1e, 0x69, 0xd3, 0xbd, 0xe7, 0xd0, 0x77, 0xda, 0xb8, 0x00, 0x83, 0x4e, - 0x4a, 0xcf, 0x6f, 0xd1, 0xf1, 0xc1, 0x22, 0x3f, 0x74, 0xe4, 0xf7, 0x98, - 0x49, 0x9e, 0x9b, 0xb6, 0x9e, 0xe1, 0xdb, 0x98, 0x77, 0x2d, 0x56, 0x34, - 0xb1, 0xa8, 0x3c, 0xd9, 0xfd, 0xc0, 0xcd, 0xc7, 0xbf, 0x05, 0x03, 0xd4, - 0x02, 0xc5, 0xf1, 0xe5, 0xc6, 0xda, 0x08, 0xa5, 0x13, 0xc7, 0x62, 0x23, - 0x11, 0xd1, 0x61, 0x30, 0x1d, 0x60, 0x84, 0x45, 0xef, 0x79, 0xa8, 0xc6, - 0x26, 0x93, 0xa4, 0xb7, 0xcd, 0x34, 0xb8, 0x69, 0xc5, 0x13, 0xf6, 0x91, - 0xb3, 0xc9, 0x45, 0x73, 0x76, 0xb6, 0x92, 0xf6, 0x76, 0x0a, 0x5b, 0xe1, - 0x03, 0x47, 0xb7, 0xe9, 0x29, 0x4c, 0x91, 0x32, 0x23, 0x37, 0x4a, 0x9c, - 0x35, 0xd8, 0x78, 0xfd, 0x1d, 0x1f, 0xe4, 0x83, 0x89, 0x24, 0x80, 0xad, - 0xb7, 0xf9, 0xcf, 0xe4, 0x5d, 0xa5, 0xd4, 0x71, 0xc4, 0x85, 0x5b, 0x70, - 0x1f, 0xdb, 0x3f, 0x1c, 0x01, 0xeb, 0x1a, 0x45, 0x26, 0x31, 0x14, 0xcc, - 0x65, 0xbf, 0x67, 0xde, 0xca, 0xcc, 0x33, 0x65, 0xe5, 0x41, 0x91, 0xd7, - 0x37, 0xbe, 0x41, 0x1a, 0x96, 0x9d, 0xe6, 0x8a, 0x97, 0x9d, 0xa7, 0xce, - 0xac, 0x4e, 0x9a, 0x3d, 0xbd, 0x01, 0xa0, 0x6a, 0xd9, 0x4f, 0x22, 0x00, - 0x8b, 0x44, 0xd5, 0x69, 0x62, 0x7b, 0x2e, 0xeb, 0xcc, 0xba, 0xe7, 0x92, - 0x7d, 0x69, 0x67, 0x3d, 0xfc, 0xb8, 0x7c, 0xde, 0x41, 0x87, 0xd0, 0x69, - 0xea, 0xba, 0x0a, 0x18, 0x7a, 0x1a, 0x95, 0x43, 0xb3, 0x79, 0x71, 0x28, - 0x76, 0x6d, 0xa1, 0xfb, 0x57, 0x4a, 0xec, 0x4d, 0xc8, 0x0e, 0x10, -} - -var certSet3Cert40 = []byte{ - 0x30, 0x82, 0x05, 0x00, 0x30, 0x82, 0x03, 0xe8, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x01, 0x07, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, 0x8f, 0x31, 0x0b, - 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, - 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x07, 0x41, 0x72, - 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, - 0x04, 0x07, 0x13, 0x0a, 0x53, 0x63, 0x6f, 0x74, 0x74, 0x73, 0x64, 0x61, - 0x6c, 0x65, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x1c, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x54, - 0x65, 0x63, 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x65, 0x73, 0x2c, - 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x32, 0x30, 0x30, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x29, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, - 0x64, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x1e, 0x17, - 0x0d, 0x31, 0x31, 0x30, 0x35, 0x30, 0x33, 0x30, 0x37, 0x30, 0x30, 0x30, - 0x30, 0x5a, 0x17, 0x0d, 0x33, 0x31, 0x30, 0x35, 0x30, 0x33, 0x30, 0x37, - 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x81, 0xc6, 0x31, 0x0b, 0x30, 0x09, - 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, - 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x07, 0x41, 0x72, 0x69, 0x7a, - 0x6f, 0x6e, 0x61, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x07, - 0x13, 0x0a, 0x53, 0x63, 0x6f, 0x74, 0x74, 0x73, 0x64, 0x61, 0x6c, 0x65, - 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c, 0x53, - 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x54, 0x65, 0x63, - 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x65, 0x73, 0x2c, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x31, 0x33, 0x30, 0x31, 0x06, 0x03, 0x55, 0x04, 0x0b, - 0x13, 0x2a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, - 0x74, 0x73, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, - 0x74, 0x65, 0x63, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, - 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x31, 0x34, 0x30, 0x32, - 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2b, 0x53, 0x74, 0x61, 0x72, 0x66, - 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, - 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, - 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, - 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, - 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xe5, - 0x90, 0x66, 0x4b, 0xec, 0xf9, 0x46, 0x71, 0xa9, 0x20, 0x83, 0xbe, 0xe9, - 0x6c, 0xbf, 0x4a, 0xc9, 0x48, 0x69, 0x81, 0x75, 0x4e, 0x6d, 0x24, 0xf6, - 0xcb, 0x17, 0x13, 0xf8, 0xb0, 0x71, 0x59, 0x84, 0x7a, 0x6b, 0x2b, 0x85, - 0xa4, 0x34, 0xb5, 0x16, 0xe5, 0xcb, 0xcc, 0xe9, 0x41, 0x70, 0x2c, 0xa4, - 0x2e, 0xd6, 0xfa, 0x32, 0x7d, 0xe1, 0xa8, 0xde, 0x94, 0x10, 0xac, 0x31, - 0xc1, 0xc0, 0xd8, 0x6a, 0xff, 0x59, 0x27, 0xab, 0x76, 0xd6, 0xfc, 0x0b, - 0x74, 0x6b, 0xb8, 0xa7, 0xae, 0x3f, 0xc4, 0x54, 0xf4, 0xb4, 0x31, 0x44, - 0xdd, 0x93, 0x56, 0x8c, 0xa4, 0x4c, 0x5e, 0x9b, 0x89, 0xcb, 0x24, 0x83, - 0x9b, 0xe2, 0x57, 0x7d, 0xb7, 0xd8, 0x12, 0x1f, 0xc9, 0x85, 0x6d, 0xf4, - 0xd1, 0x80, 0xf1, 0x50, 0x9b, 0x87, 0xae, 0xd4, 0x0b, 0x10, 0x05, 0xfb, - 0x27, 0xba, 0x28, 0x6d, 0x17, 0xe9, 0x0e, 0xd6, 0x4d, 0xb9, 0x39, 0x55, - 0x06, 0xff, 0x0a, 0x24, 0x05, 0x7e, 0x2f, 0xc6, 0x1d, 0x72, 0x6c, 0xd4, - 0x8b, 0x29, 0x8c, 0x57, 0x7d, 0xda, 0xd9, 0xeb, 0x66, 0x1a, 0xd3, 0x4f, - 0xa7, 0xdf, 0x7f, 0x52, 0xc4, 0x30, 0xc5, 0xa5, 0xc9, 0x0e, 0x02, 0xc5, - 0x53, 0xbf, 0x77, 0x38, 0x68, 0x06, 0x24, 0xc3, 0x66, 0xc8, 0x37, 0x7e, - 0x30, 0x1e, 0x45, 0x71, 0x23, 0x35, 0xff, 0x90, 0xd8, 0x2a, 0x9d, 0x8d, - 0xe7, 0xb0, 0x92, 0x4d, 0x3c, 0x7f, 0x2a, 0x0a, 0x93, 0xdc, 0xcd, 0x16, - 0x46, 0x65, 0xf7, 0x60, 0x84, 0x8b, 0x76, 0x4b, 0x91, 0x27, 0x73, 0x14, - 0x92, 0xe0, 0xea, 0xee, 0x8f, 0x16, 0xea, 0x8d, 0x0e, 0x3e, 0x76, 0x17, - 0xbf, 0x7d, 0x89, 0x80, 0x80, 0x44, 0x43, 0xe7, 0x2d, 0xe0, 0x43, 0x09, - 0x75, 0xda, 0x36, 0xe8, 0xad, 0xdb, 0x89, 0x3a, 0xf5, 0x5d, 0x12, 0x8e, - 0x23, 0x04, 0x83, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x2c, - 0x30, 0x82, 0x01, 0x28, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, - 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0e, 0x06, - 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, - 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, - 0x25, 0x45, 0x81, 0x68, 0x50, 0x26, 0x38, 0x3d, 0x3b, 0x2d, 0x2c, 0xbe, - 0xcd, 0x6a, 0xd9, 0xb6, 0x3d, 0xb3, 0x66, 0x63, 0x30, 0x1f, 0x06, 0x03, - 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x7c, 0x0c, 0x32, - 0x1f, 0xa7, 0xd9, 0x30, 0x7f, 0xc4, 0x7d, 0x68, 0xa3, 0x62, 0xa8, 0xa1, - 0xce, 0xab, 0x07, 0x5b, 0x27, 0x30, 0x3a, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x2e, 0x30, 0x2c, 0x30, 0x2a, 0x06, - 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x1e, 0x68, - 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x73, - 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x74, 0x65, 0x63, 0x68, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x3b, 0x06, 0x03, 0x55, 0x1d, 0x1f, - 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0xa0, 0x2e, 0xa0, 0x2c, 0x86, 0x2a, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x73, - 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x74, 0x65, 0x63, 0x68, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x66, 0x72, 0x6f, 0x6f, 0x74, 0x2d, - 0x67, 0x32, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x4c, 0x06, 0x03, 0x55, 0x1d, - 0x20, 0x04, 0x45, 0x30, 0x43, 0x30, 0x41, 0x06, 0x04, 0x55, 0x1d, 0x20, - 0x00, 0x30, 0x39, 0x30, 0x37, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x02, 0x01, 0x16, 0x2b, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, - 0x2f, 0x63, 0x65, 0x72, 0x74, 0x73, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x66, - 0x69, 0x65, 0x6c, 0x64, 0x74, 0x65, 0x63, 0x68, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x56, 0x65, 0xca, 0xfe, - 0xf3, 0x3f, 0x0a, 0xa8, 0x93, 0x8b, 0x18, 0xc7, 0xde, 0x43, 0x69, 0x13, - 0x34, 0x20, 0xbe, 0x4e, 0x5f, 0x78, 0xa8, 0x6b, 0x9c, 0xdb, 0x6a, 0x4d, - 0x41, 0xdb, 0xc1, 0x13, 0xec, 0xdc, 0x31, 0x00, 0x22, 0x5e, 0xf7, 0x00, - 0x9e, 0x0c, 0xe0, 0x34, 0x65, 0x34, 0xf9, 0xb1, 0x3a, 0x4e, 0x48, 0xc8, - 0x12, 0x81, 0x88, 0x5c, 0x5b, 0x3e, 0x08, 0x53, 0x7a, 0xf7, 0x1a, 0x64, - 0xdf, 0xb8, 0x50, 0x61, 0xcc, 0x53, 0x51, 0x40, 0x29, 0x4b, 0xc2, 0xf4, - 0xae, 0x3a, 0x5f, 0xe4, 0xca, 0xad, 0x26, 0xcc, 0x4e, 0x61, 0x43, 0xe5, - 0xfd, 0x57, 0xa6, 0x37, 0x70, 0xce, 0x43, 0x2b, 0xb0, 0x94, 0xc3, 0x92, - 0xe9, 0xe1, 0x5f, 0xaa, 0x10, 0x49, 0xb7, 0x69, 0xe4, 0xe0, 0xd0, 0x1f, - 0x64, 0xa4, 0x2b, 0xcd, 0x1f, 0x6f, 0xa0, 0xf8, 0x84, 0x24, 0x18, 0xce, - 0x79, 0x3d, 0xa9, 0x91, 0xbf, 0x54, 0x18, 0x13, 0x89, 0x99, 0x54, 0x11, - 0x0d, 0x55, 0xc5, 0x26, 0x0b, 0x79, 0x4f, 0x5a, 0x1c, 0x6e, 0xf9, 0x63, - 0xdb, 0x14, 0x80, 0xa4, 0x07, 0xab, 0xfa, 0xb2, 0xa5, 0xb9, 0x88, 0xdd, - 0x91, 0xfe, 0x65, 0x3b, 0xa4, 0xa3, 0x79, 0xbe, 0x89, 0x4d, 0xe1, 0xd0, - 0xb0, 0xf4, 0xc8, 0x17, 0x0c, 0x0a, 0x96, 0x14, 0x7c, 0x09, 0xb7, 0x6c, - 0xe1, 0xc2, 0xd8, 0x55, 0xd4, 0x18, 0xa0, 0xaa, 0x41, 0x69, 0x70, 0x24, - 0xa3, 0xb9, 0xef, 0xe9, 0x5a, 0xdc, 0x3e, 0xeb, 0x94, 0x4a, 0xf0, 0xb7, - 0xde, 0x5f, 0x0e, 0x76, 0xfa, 0xfb, 0xfb, 0x69, 0x03, 0x45, 0x40, 0x50, - 0xee, 0x72, 0x0c, 0xa4, 0x12, 0x86, 0x81, 0xcd, 0x13, 0xd1, 0x4e, 0xc4, - 0x3c, 0xca, 0x4e, 0x0d, 0xd2, 0x26, 0xf1, 0x00, 0xb7, 0xb4, 0xa6, 0xa2, - 0xe1, 0x6e, 0x7a, 0x81, 0xfd, 0x30, 0xac, 0x7a, 0x1f, 0xc7, 0x59, 0x7b, -} - -var certSet3Cert41 = []byte{ - 0x30, 0x82, 0x05, 0x03, 0x30, 0x82, 0x03, 0xeb, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x04, 0x51, 0xd3, 0x60, 0xee, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, - 0xbe, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0d, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, - 0x63, 0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, - 0x1f, 0x53, 0x65, 0x65, 0x20, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x6c, 0x65, 0x67, - 0x61, 0x6c, 0x2d, 0x74, 0x65, 0x72, 0x6d, 0x73, 0x31, 0x39, 0x30, 0x37, - 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x30, 0x28, 0x63, 0x29, 0x20, 0x32, - 0x30, 0x30, 0x39, 0x20, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, - 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x66, 0x6f, 0x72, 0x20, - 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, - 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x32, 0x30, 0x30, 0x06, - 0x03, 0x55, 0x04, 0x03, 0x13, 0x29, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, - 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, - 0x1e, 0x17, 0x0d, 0x31, 0x34, 0x31, 0x30, 0x32, 0x32, 0x31, 0x37, 0x30, - 0x35, 0x31, 0x34, 0x5a, 0x17, 0x0d, 0x32, 0x34, 0x31, 0x30, 0x32, 0x33, - 0x30, 0x37, 0x33, 0x33, 0x32, 0x32, 0x5a, 0x30, 0x81, 0xba, 0x31, 0x0b, - 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, - 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x45, 0x6e, - 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, - 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1f, 0x53, 0x65, - 0x65, 0x20, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, - 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x6c, 0x65, 0x67, 0x61, 0x6c, 0x2d, - 0x74, 0x65, 0x72, 0x6d, 0x73, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, - 0x04, 0x0b, 0x13, 0x30, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x31, 0x32, - 0x20, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, - 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, - 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x2e, 0x30, 0x2c, 0x06, 0x03, 0x55, 0x04, - 0x03, 0x13, 0x25, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x20, 0x43, - 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, - 0x20, 0x4c, 0x31, 0x4b, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, - 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, - 0x00, 0xda, 0x3f, 0x96, 0xd0, 0x4d, 0xb9, 0x2f, 0x44, 0xe7, 0xdb, 0x39, - 0x5e, 0x9b, 0x50, 0xee, 0x5c, 0xa5, 0x61, 0xda, 0x41, 0x67, 0x53, 0x09, - 0xaa, 0x00, 0x9a, 0x8e, 0x57, 0x7f, 0x29, 0x6b, 0xdb, 0xc7, 0xe1, 0x21, - 0x24, 0xaa, 0x3a, 0xd0, 0x8d, 0x47, 0x23, 0xd2, 0xed, 0x72, 0x16, 0xf0, - 0x91, 0x21, 0xd2, 0x5d, 0xb7, 0xb8, 0x4b, 0xa8, 0x83, 0x8f, 0xb7, 0x91, - 0x32, 0x68, 0xcf, 0xce, 0x25, 0x93, 0x2c, 0xb2, 0x7d, 0x97, 0xc8, 0xfe, - 0xc1, 0xb4, 0x17, 0xba, 0x09, 0x9e, 0x03, 0x90, 0x93, 0x7b, 0x7c, 0x49, - 0x83, 0x22, 0x68, 0x8a, 0x9b, 0xde, 0x47, 0xc3, 0x31, 0x98, 0x7a, 0x2e, - 0x7d, 0x40, 0x0b, 0xd2, 0xef, 0x3e, 0xd3, 0xb2, 0x8c, 0xaa, 0x8f, 0x48, - 0xa9, 0xff, 0x00, 0xe8, 0x29, 0x58, 0x06, 0xf7, 0xb6, 0x93, 0x5a, 0x94, - 0x73, 0x26, 0x26, 0xad, 0x58, 0x0e, 0xe5, 0x42, 0xb8, 0xd5, 0xea, 0x73, - 0x79, 0x64, 0x68, 0x53, 0x25, 0xb8, 0x84, 0xcf, 0x94, 0x7a, 0xae, 0x06, - 0x45, 0x0c, 0xa3, 0x6b, 0x4d, 0xd0, 0xc6, 0xbe, 0xea, 0x18, 0xa4, 0x36, - 0xf0, 0x92, 0xb2, 0xba, 0x1c, 0x88, 0x8f, 0x3a, 0x52, 0x7f, 0xf7, 0x5e, - 0x6d, 0x83, 0x1c, 0x9d, 0xf0, 0x1f, 0xe5, 0xc3, 0xd6, 0xdd, 0xa5, 0x78, - 0x92, 0x3d, 0xb0, 0x6d, 0x2c, 0xea, 0xc9, 0xcf, 0x94, 0x41, 0x19, 0x71, - 0x44, 0x68, 0xba, 0x47, 0x3c, 0x04, 0xe9, 0x5d, 0xba, 0x3e, 0xf0, 0x35, - 0xf7, 0x15, 0xb6, 0x9e, 0xf2, 0x2e, 0x15, 0x1e, 0x3f, 0x47, 0xc8, 0xc8, - 0x38, 0xa7, 0x73, 0x45, 0x5d, 0x4d, 0xb0, 0x3b, 0xb1, 0x8e, 0x17, 0x29, - 0x37, 0xea, 0xdd, 0x05, 0x01, 0x22, 0xbb, 0x94, 0x36, 0x2a, 0x8d, 0x5b, - 0x35, 0xfe, 0x53, 0x19, 0x2f, 0x08, 0x46, 0xc1, 0x2a, 0xb3, 0x1a, 0x62, - 0x1d, 0x4e, 0x2b, 0xd9, 0x1b, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, - 0x01, 0x09, 0x30, 0x82, 0x01, 0x05, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, - 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x0f, - 0x06, 0x03, 0x55, 0x1d, 0x13, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, - 0x02, 0x01, 0x00, 0x30, 0x33, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x01, 0x01, 0x04, 0x27, 0x30, 0x25, 0x30, 0x23, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x17, 0x68, 0x74, 0x74, - 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x65, 0x6e, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x30, 0x30, 0x06, 0x03, - 0x55, 0x1d, 0x1f, 0x04, 0x29, 0x30, 0x27, 0x30, 0x25, 0xa0, 0x23, 0xa0, - 0x21, 0x86, 0x1f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, - 0x6c, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, - 0x74, 0x2f, 0x67, 0x32, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3b, - 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0x06, - 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, - 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x70, 0x61, 0x30, - 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x82, 0xa2, - 0x70, 0x74, 0xdd, 0xbc, 0x53, 0x3f, 0xcf, 0x7b, 0xd4, 0xf7, 0xcd, 0x7f, - 0xa7, 0x60, 0xc6, 0x0a, 0x4c, 0xbf, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, - 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x6a, 0x72, 0x26, 0x7a, 0xd0, - 0x1e, 0xef, 0x7d, 0xe7, 0x3b, 0x69, 0x51, 0xd4, 0x6c, 0x8d, 0x9f, 0x90, - 0x12, 0x66, 0xab, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, - 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x3f, - 0x1c, 0x1a, 0x5b, 0xff, 0x40, 0x22, 0x1d, 0x8f, 0x35, 0x0c, 0x2d, 0xaa, - 0x99, 0x27, 0xab, 0xc0, 0x11, 0x32, 0x70, 0xd7, 0x36, 0x28, 0x69, 0xa5, - 0x8d, 0xb1, 0x27, 0x99, 0x42, 0xbe, 0xc4, 0x93, 0xeb, 0x48, 0x57, 0x43, - 0x71, 0x23, 0xc4, 0xe5, 0x4e, 0xad, 0xae, 0x43, 0x6f, 0x92, 0x76, 0xc5, - 0x19, 0xef, 0xca, 0xbc, 0x6f, 0x42, 0x4c, 0x16, 0x9a, 0x86, 0xa9, 0x04, - 0x38, 0xc7, 0x65, 0xf0, 0xf5, 0x0c, 0xe0, 0x4a, 0xdf, 0xa2, 0xfa, 0xce, - 0x1a, 0x11, 0xa8, 0x9c, 0x69, 0x2f, 0x1b, 0xdf, 0xea, 0xe2, 0x32, 0xf3, - 0xce, 0x4c, 0xbc, 0x46, 0x0c, 0xc0, 0x89, 0x80, 0xd1, 0x87, 0x6b, 0xa2, - 0xcf, 0x6b, 0xd4, 0x7f, 0xfd, 0xf5, 0x60, 0x52, 0x67, 0x57, 0xa0, 0x6d, - 0xd1, 0x64, 0x41, 0x14, 0x6d, 0x34, 0x62, 0xed, 0x06, 0x6c, 0x24, 0xf2, - 0x06, 0xbc, 0x28, 0x02, 0xaf, 0x03, 0x2d, 0xc2, 0x33, 0x05, 0xfb, 0xcb, - 0xaa, 0x16, 0xe8, 0x65, 0x10, 0x43, 0xf5, 0x69, 0x5c, 0xe3, 0x81, 0x58, - 0x99, 0xcd, 0x6b, 0xd3, 0xb8, 0xc7, 0x7b, 0x19, 0x55, 0xc9, 0x40, 0xce, - 0x79, 0x55, 0xb8, 0x73, 0x89, 0xe9, 0x5c, 0x40, 0x66, 0x43, 0x12, 0x7f, - 0x07, 0xb8, 0x65, 0x56, 0xd5, 0x8d, 0xc3, 0xa7, 0xf5, 0xb1, 0xb6, 0x65, - 0x9e, 0xc0, 0x83, 0x36, 0x7f, 0x16, 0x45, 0x3c, 0x74, 0x4b, 0x93, 0x8a, - 0x3c, 0xf1, 0x2b, 0xf5, 0x35, 0x70, 0x73, 0x7b, 0xe7, 0x82, 0x04, 0xb1, - 0x18, 0x98, 0x0e, 0xd4, 0x9c, 0x6f, 0x1a, 0xfc, 0xfc, 0xa7, 0x33, 0xa5, - 0xbb, 0xbb, 0x18, 0xf3, 0x6b, 0x7a, 0x5d, 0x32, 0x87, 0xf7, 0x6d, 0x25, - 0xe4, 0xe2, 0x76, 0x86, 0x21, 0x1e, 0x11, 0x46, 0xcd, 0x76, 0x0e, 0x6f, - 0x4f, 0xa4, 0x21, 0x71, 0x0a, 0x84, 0xa7, 0x2d, 0x36, 0xa9, 0x48, 0x22, - 0x51, 0x7e, 0x82, -} - -var certSet3Cert42 = []byte{ - 0x30, 0x82, 0x05, 0x0e, 0x30, 0x82, 0x03, 0xf6, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x0c, 0x0e, 0xe9, 0x4c, 0xc3, 0x00, 0x00, 0x00, 0x00, 0x51, - 0xd3, 0x77, 0x85, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, - 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, 0xbe, 0x31, 0x0b, 0x30, - 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, - 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x45, 0x6e, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x28, - 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1f, 0x53, 0x65, 0x65, - 0x20, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, - 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x6c, 0x65, 0x67, 0x61, 0x6c, 0x2d, 0x74, - 0x65, 0x72, 0x6d, 0x73, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04, - 0x0b, 0x13, 0x30, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x39, 0x20, - 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, - 0x2e, 0x20, 0x2d, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, - 0x6e, 0x6c, 0x79, 0x31, 0x32, 0x30, 0x30, 0x06, 0x03, 0x55, 0x04, 0x03, - 0x13, 0x29, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x20, 0x52, 0x6f, - 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x1e, 0x17, 0x0d, 0x31, - 0x35, 0x31, 0x30, 0x30, 0x35, 0x31, 0x39, 0x31, 0x33, 0x35, 0x36, 0x5a, - 0x17, 0x0d, 0x33, 0x30, 0x31, 0x32, 0x30, 0x35, 0x31, 0x39, 0x34, 0x33, - 0x35, 0x36, 0x5a, 0x30, 0x81, 0xba, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, - 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, - 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, - 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, - 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1f, 0x53, 0x65, 0x65, 0x20, 0x77, 0x77, - 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, - 0x74, 0x2f, 0x6c, 0x65, 0x67, 0x61, 0x6c, 0x2d, 0x74, 0x65, 0x72, 0x6d, - 0x73, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x30, - 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x31, 0x32, 0x20, 0x45, 0x6e, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, - 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, - 0x31, 0x2e, 0x30, 0x2c, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x25, 0x45, - 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x4c, 0x31, 0x4b, - 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, - 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xda, 0x3f, 0x96, - 0xd0, 0x4d, 0xb9, 0x2f, 0x44, 0xe7, 0xdb, 0x39, 0x5e, 0x9b, 0x50, 0xee, - 0x5c, 0xa5, 0x61, 0xda, 0x41, 0x67, 0x53, 0x09, 0xaa, 0x00, 0x9a, 0x8e, - 0x57, 0x7f, 0x29, 0x6b, 0xdb, 0xc7, 0xe1, 0x21, 0x24, 0xaa, 0x3a, 0xd0, - 0x8d, 0x47, 0x23, 0xd2, 0xed, 0x72, 0x16, 0xf0, 0x91, 0x21, 0xd2, 0x5d, - 0xb7, 0xb8, 0x4b, 0xa8, 0x83, 0x8f, 0xb7, 0x91, 0x32, 0x68, 0xcf, 0xce, - 0x25, 0x93, 0x2c, 0xb2, 0x7d, 0x97, 0xc8, 0xfe, 0xc1, 0xb4, 0x17, 0xba, - 0x09, 0x9e, 0x03, 0x90, 0x93, 0x7b, 0x7c, 0x49, 0x83, 0x22, 0x68, 0x8a, - 0x9b, 0xde, 0x47, 0xc3, 0x31, 0x98, 0x7a, 0x2e, 0x7d, 0x40, 0x0b, 0xd2, - 0xef, 0x3e, 0xd3, 0xb2, 0x8c, 0xaa, 0x8f, 0x48, 0xa9, 0xff, 0x00, 0xe8, - 0x29, 0x58, 0x06, 0xf7, 0xb6, 0x93, 0x5a, 0x94, 0x73, 0x26, 0x26, 0xad, - 0x58, 0x0e, 0xe5, 0x42, 0xb8, 0xd5, 0xea, 0x73, 0x79, 0x64, 0x68, 0x53, - 0x25, 0xb8, 0x84, 0xcf, 0x94, 0x7a, 0xae, 0x06, 0x45, 0x0c, 0xa3, 0x6b, - 0x4d, 0xd0, 0xc6, 0xbe, 0xea, 0x18, 0xa4, 0x36, 0xf0, 0x92, 0xb2, 0xba, - 0x1c, 0x88, 0x8f, 0x3a, 0x52, 0x7f, 0xf7, 0x5e, 0x6d, 0x83, 0x1c, 0x9d, - 0xf0, 0x1f, 0xe5, 0xc3, 0xd6, 0xdd, 0xa5, 0x78, 0x92, 0x3d, 0xb0, 0x6d, - 0x2c, 0xea, 0xc9, 0xcf, 0x94, 0x41, 0x19, 0x71, 0x44, 0x68, 0xba, 0x47, - 0x3c, 0x04, 0xe9, 0x5d, 0xba, 0x3e, 0xf0, 0x35, 0xf7, 0x15, 0xb6, 0x9e, - 0xf2, 0x2e, 0x15, 0x1e, 0x3f, 0x47, 0xc8, 0xc8, 0x38, 0xa7, 0x73, 0x45, - 0x5d, 0x4d, 0xb0, 0x3b, 0xb1, 0x8e, 0x17, 0x29, 0x37, 0xea, 0xdd, 0x05, - 0x01, 0x22, 0xbb, 0x94, 0x36, 0x2a, 0x8d, 0x5b, 0x35, 0xfe, 0x53, 0x19, - 0x2f, 0x08, 0x46, 0xc1, 0x2a, 0xb3, 0x1a, 0x62, 0x1d, 0x4e, 0x2b, 0xd9, - 0x1b, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x0c, 0x30, 0x82, - 0x01, 0x08, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, - 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, - 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, - 0x01, 0x00, 0x30, 0x33, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x01, 0x01, 0x04, 0x27, 0x30, 0x25, 0x30, 0x23, 0x06, 0x08, 0x2b, 0x06, - 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x17, 0x68, 0x74, 0x74, 0x70, - 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x65, 0x6e, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x30, 0x30, 0x06, 0x03, 0x55, - 0x1d, 0x1f, 0x04, 0x29, 0x30, 0x27, 0x30, 0x25, 0xa0, 0x23, 0xa0, 0x21, - 0x86, 0x1f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, - 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, - 0x2f, 0x67, 0x32, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3b, 0x06, - 0x03, 0x55, 0x1d, 0x20, 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0x06, 0x04, - 0x55, 0x1d, 0x20, 0x00, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, - 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, - 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, - 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x70, 0x61, 0x30, 0x1d, - 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x82, 0xa2, 0x70, - 0x74, 0xdd, 0xbc, 0x53, 0x3f, 0xcf, 0x7b, 0xd4, 0xf7, 0xcd, 0x7f, 0xa7, - 0x60, 0xc6, 0x0a, 0x4c, 0xbf, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, - 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x6a, 0x72, 0x26, 0x7a, 0xd0, 0x1e, - 0xef, 0x7d, 0xe7, 0x3b, 0x69, 0x51, 0xd4, 0x6c, 0x8d, 0x9f, 0x90, 0x12, - 0x66, 0xab, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, - 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x39, 0xd5, - 0x8e, 0x98, 0x83, 0x61, 0xc8, 0x2c, 0x63, 0xd3, 0x70, 0x1d, 0x19, 0x30, - 0xcb, 0xf6, 0x09, 0xac, 0xcc, 0x69, 0xd5, 0xc9, 0xdc, 0x37, 0x41, 0xf2, - 0x32, 0x0f, 0xef, 0x74, 0xc3, 0x58, 0xf6, 0x78, 0x27, 0x09, 0x34, 0x08, - 0x95, 0x92, 0x2f, 0xd7, 0xdf, 0xb8, 0xa3, 0xfd, 0x0e, 0x81, 0xe9, 0xa4, - 0x9c, 0xd3, 0x3f, 0x4d, 0x68, 0x2b, 0x15, 0x31, 0x0a, 0x15, 0xcc, 0x52, - 0x04, 0x93, 0xe8, 0x93, 0x50, 0xc3, 0xd9, 0xb1, 0xe2, 0xe1, 0x68, 0xb7, - 0x3a, 0x09, 0x74, 0xf1, 0x34, 0x58, 0x0a, 0x3f, 0x77, 0x98, 0x40, 0xb8, - 0xe6, 0x68, 0xff, 0x5d, 0xe4, 0xc8, 0x46, 0xc5, 0xec, 0x81, 0xd7, 0xc9, - 0x82, 0x18, 0x5c, 0x83, 0xce, 0x71, 0xd8, 0xbc, 0xbf, 0xac, 0x99, 0x02, - 0x93, 0xdb, 0x94, 0x98, 0x84, 0xd2, 0x9c, 0xa6, 0xb5, 0xfe, 0x5c, 0xbb, - 0xf0, 0x4a, 0xaf, 0x21, 0xac, 0xc2, 0x3f, 0x49, 0x24, 0x67, 0xd6, 0x2e, - 0x8e, 0xcf, 0xac, 0xcc, 0x64, 0x15, 0x18, 0x72, 0xe5, 0x6c, 0x77, 0xd3, - 0x52, 0xa8, 0xb9, 0xdd, 0x8d, 0xac, 0x00, 0x4a, 0x35, 0x19, 0xd4, 0x6f, - 0x73, 0xa3, 0x75, 0xef, 0x6b, 0x64, 0xc3, 0xe0, 0x8d, 0x83, 0x12, 0xa1, - 0x8a, 0xe7, 0x0e, 0x86, 0x4d, 0xd8, 0xb4, 0x20, 0x1b, 0xbe, 0x6a, 0xa5, - 0x8c, 0x4b, 0x68, 0x66, 0xe3, 0x2b, 0xc7, 0x58, 0x0b, 0xfb, 0x56, 0x10, - 0xd4, 0x91, 0xfb, 0x1d, 0xd3, 0x31, 0x58, 0x10, 0x8c, 0x44, 0xe3, 0x75, - 0x7b, 0x10, 0x9d, 0xb5, 0x38, 0xb1, 0xf6, 0xaa, 0xca, 0x81, 0x64, 0x6c, - 0xe8, 0xf2, 0xe2, 0x81, 0x55, 0x97, 0x51, 0x7f, 0xe1, 0xc2, 0x27, 0x50, - 0xa2, 0xc9, 0x3c, 0x5b, 0x00, 0x43, 0xf6, 0x5b, 0xb9, 0xd5, 0xa5, 0xfc, - 0xff, 0x07, 0x50, 0x40, 0x67, 0x07, 0xb0, 0x55, 0xf0, 0xb7, 0x7e, 0x6e, - 0x2d, 0xcc, -} - -var certSet3Cert43 = []byte{ - 0x30, 0x82, 0x05, 0x1f, 0x30, 0x82, 0x04, 0x07, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x04, 0x07, 0x27, 0xa4, 0x6b, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x5a, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, - 0x45, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09, - 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30, - 0x11, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65, - 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, - 0x55, 0x04, 0x03, 0x13, 0x19, 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, - 0x72, 0x65, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, - 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x34, - 0x30, 0x34, 0x30, 0x32, 0x31, 0x34, 0x33, 0x36, 0x31, 0x30, 0x5a, 0x17, - 0x0d, 0x32, 0x31, 0x30, 0x34, 0x30, 0x32, 0x31, 0x34, 0x33, 0x35, 0x35, - 0x32, 0x5a, 0x30, 0x81, 0x8d, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, - 0x04, 0x06, 0x13, 0x02, 0x4e, 0x4c, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, - 0x55, 0x04, 0x07, 0x13, 0x09, 0x41, 0x6d, 0x73, 0x74, 0x65, 0x72, 0x64, - 0x61, 0x6d, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x1c, 0x56, 0x65, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x20, 0x45, 0x6e, 0x74, - 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x20, 0x53, 0x6f, 0x6c, 0x75, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, - 0x04, 0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, 0x75, - 0x73, 0x74, 0x31, 0x2e, 0x30, 0x2c, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, - 0x25, 0x56, 0x65, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x20, 0x41, 0x6b, 0x61, - 0x6d, 0x61, 0x69, 0x20, 0x53, 0x75, 0x72, 0x65, 0x53, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x20, 0x43, 0x41, 0x20, 0x47, 0x31, 0x34, 0x2d, 0x53, 0x48, - 0x41, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, - 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xdd, - 0x6e, 0x9e, 0x02, 0x69, 0x02, 0xb5, 0xa3, 0x99, 0x2e, 0x08, 0x64, 0x32, - 0x6a, 0x59, 0xf3, 0xc6, 0x9e, 0xa6, 0x20, 0x07, 0xd2, 0x48, 0xd1, 0xa8, - 0x93, 0xc7, 0xea, 0x47, 0x8f, 0x83, 0x39, 0x40, 0xd7, 0x20, 0x5d, 0x8d, - 0x9a, 0xba, 0xab, 0xd8, 0x70, 0xec, 0x9d, 0x88, 0xd1, 0xbd, 0x62, 0xf6, - 0xdb, 0xec, 0x9d, 0x5e, 0x35, 0x01, 0x76, 0x03, 0x23, 0xe5, 0x6f, 0xd2, - 0xaf, 0x46, 0x35, 0x59, 0x5a, 0x5c, 0xd1, 0xa8, 0x23, 0xc1, 0xeb, 0xe9, - 0x20, 0xd4, 0x49, 0xd6, 0x3f, 0x00, 0xd8, 0xa8, 0x22, 0xde, 0x43, 0x79, - 0x81, 0xac, 0xe9, 0xa4, 0x92, 0xf5, 0x77, 0x70, 0x05, 0x1e, 0x5c, 0xb6, - 0xa0, 0xf7, 0x90, 0xa4, 0xcd, 0xab, 0x28, 0x2c, 0x90, 0xc2, 0xe7, 0x0f, - 0xc3, 0xaf, 0x1c, 0x47, 0x59, 0xd5, 0x84, 0x2e, 0xdf, 0x26, 0x07, 0x45, - 0x23, 0x5a, 0xc6, 0xe8, 0x90, 0xc8, 0x85, 0x4b, 0x8c, 0x16, 0x1e, 0x60, - 0xf9, 0x01, 0x13, 0xf1, 0x14, 0x1f, 0xe6, 0xe8, 0x14, 0xed, 0xc5, 0xd2, - 0x6f, 0x63, 0x28, 0x6e, 0x72, 0x8c, 0x49, 0xae, 0x08, 0x72, 0xc7, 0x93, - 0x95, 0xb4, 0x0b, 0x0c, 0xae, 0x8f, 0x9a, 0x67, 0x84, 0xf5, 0x57, 0x1b, - 0xdb, 0x81, 0xd7, 0x17, 0x9d, 0x41, 0x11, 0x43, 0x19, 0xbd, 0x6d, 0x4a, - 0x85, 0xed, 0x8f, 0x70, 0x25, 0xab, 0x66, 0xab, 0xf6, 0xfa, 0x6d, 0x1c, - 0x3c, 0xab, 0xed, 0x17, 0xbd, 0x56, 0x84, 0xe1, 0xdb, 0x75, 0x33, 0xb2, - 0x28, 0x4b, 0x99, 0x8e, 0xf9, 0x4b, 0x82, 0x33, 0x50, 0x9f, 0x92, 0x53, - 0xed, 0xfa, 0xad, 0x0f, 0x95, 0x9c, 0xa3, 0xf2, 0xcb, 0x60, 0xf0, 0x77, - 0x1d, 0xc9, 0x01, 0x8b, 0x5f, 0x2d, 0x86, 0xbe, 0xbf, 0x36, 0xb8, 0x24, - 0x96, 0x13, 0x7c, 0xc1, 0x86, 0x5a, 0x6c, 0xc1, 0x48, 0x2a, 0x7f, 0x3e, - 0x93, 0x60, 0xc5, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0xb7, - 0x30, 0x82, 0x01, 0xb3, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, - 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x02, - 0x30, 0x4c, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x45, 0x30, 0x43, 0x30, - 0x41, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb1, 0x3e, 0x01, 0x32, - 0x30, 0x34, 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x02, 0x01, 0x16, 0x26, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, - 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2e, 0x6f, 0x6d, 0x6e, 0x69, 0x72, - 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, - 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x30, 0x81, 0xba, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x81, 0xad, 0x30, 0x81, - 0xaa, 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, - 0x01, 0x86, 0x26, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, - 0x73, 0x70, 0x2e, 0x6f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, - 0x65, 0x72, 0x6f, 0x6f, 0x74, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x73, - 0x3a, 0x2f, 0x2f, 0x63, 0x61, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x6f, 0x6d, - 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, - 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x72, 0x6f, 0x6f, 0x74, - 0x2e, 0x63, 0x72, 0x74, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, - 0x2f, 0x2f, 0x63, 0x61, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x6f, 0x6d, 0x6e, - 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x61, - 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x72, 0x6f, 0x6f, 0x74, 0x2e, - 0x64, 0x65, 0x72, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, - 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0xc6, 0x30, 0x1f, 0x06, 0x03, 0x55, - 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xe5, 0x9d, 0x59, 0x30, - 0x82, 0x47, 0x58, 0xcc, 0xac, 0xfa, 0x08, 0x54, 0x36, 0x86, 0x7b, 0x3a, - 0xb5, 0x04, 0x4d, 0xf0, 0x30, 0x42, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, - 0x3b, 0x30, 0x39, 0x30, 0x37, 0xa0, 0x35, 0xa0, 0x33, 0x86, 0x31, 0x68, - 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x64, 0x70, 0x31, 0x2e, 0x70, - 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x4f, 0x6d, 0x6e, 0x69, - 0x72, 0x6f, 0x6f, 0x74, 0x32, 0x30, 0x32, 0x35, 0x2e, 0x63, 0x72, 0x6c, - 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xf8, - 0xbd, 0xfa, 0xaf, 0x73, 0x77, 0xc6, 0xc7, 0x1b, 0xf9, 0x4b, 0x4d, 0x11, - 0xa7, 0xd1, 0x33, 0xaf, 0xaf, 0x72, 0x11, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, - 0x01, 0x01, 0x00, 0x80, 0xd9, 0x7a, 0xed, 0x72, 0x05, 0x37, 0x8f, 0x61, - 0xaa, 0x73, 0x7c, 0x9a, 0x6a, 0xfc, 0xfe, 0x01, 0xe2, 0x19, 0x81, 0x70, - 0x07, 0x25, 0x32, 0xb0, 0xf0, 0x6f, 0x3b, 0xc7, 0x6a, 0x28, 0x3d, 0xe4, - 0x51, 0x87, 0xe6, 0x7e, 0x82, 0xec, 0xae, 0x48, 0xa7, 0xb1, 0x77, 0x38, - 0xc2, 0xd6, 0x56, 0xaf, 0x8f, 0xf2, 0x01, 0xfc, 0x65, 0x65, 0x10, 0x09, - 0xf7, 0x74, 0x29, 0xb5, 0x0e, 0x92, 0xee, 0x90, 0x98, 0xd1, 0x88, 0xa2, - 0x65, 0xb7, 0xcd, 0x9c, 0x0e, 0xa7, 0x86, 0x98, 0x28, 0xbc, 0xae, 0x15, - 0x83, 0xb6, 0x1a, 0xd7, 0x1d, 0xec, 0x19, 0xda, 0x7a, 0x8e, 0x40, 0xf9, - 0x99, 0x15, 0xd5, 0x7d, 0xa5, 0xba, 0xab, 0xfd, 0x26, 0x98, 0x6e, 0x9c, - 0x41, 0x3b, 0xb6, 0x81, 0x18, 0xec, 0x70, 0x48, 0xd7, 0x6e, 0x7f, 0xa6, - 0xe1, 0x77, 0x25, 0xd6, 0xdd, 0x62, 0xe8, 0x52, 0xf3, 0x8c, 0x16, 0x39, - 0x67, 0xe2, 0x22, 0x0d, 0x77, 0x2e, 0xfb, 0x11, 0x6c, 0xe4, 0xdd, 0x38, - 0xb4, 0x27, 0x5f, 0x03, 0xa8, 0x3d, 0x44, 0xe2, 0xf2, 0x84, 0x4b, 0x84, - 0xfd, 0x56, 0xa6, 0x9e, 0x4d, 0x7b, 0xa2, 0x16, 0x4f, 0x07, 0xf5, 0x34, - 0x24, 0x72, 0xa5, 0xa2, 0xfa, 0x16, 0x66, 0x2a, 0xa4, 0x4a, 0x0e, 0xc8, - 0x0d, 0x27, 0x44, 0x9c, 0x77, 0xd4, 0x12, 0x10, 0x87, 0xd2, 0x00, 0x2c, - 0x7a, 0xbb, 0x8e, 0x88, 0x22, 0x91, 0x15, 0xbe, 0xa2, 0x59, 0xca, 0x34, - 0xe0, 0x1c, 0x61, 0x94, 0x86, 0x20, 0x33, 0xcd, 0xe7, 0x4c, 0x5d, 0x3b, - 0x92, 0x3e, 0xcb, 0xd6, 0x2d, 0xea, 0x54, 0xfa, 0xfb, 0xaf, 0x54, 0xf5, - 0xa8, 0xc5, 0x0b, 0xca, 0x8b, 0x87, 0x00, 0xe6, 0x9f, 0xe6, 0x95, 0xbf, - 0xb7, 0xc4, 0xa3, 0x59, 0xf5, 0x16, 0x6c, 0x5f, 0x3e, 0x69, 0x55, 0x80, - 0x39, 0xf6, 0x75, 0x50, 0x14, 0x3e, 0x32, -} - -var certSet3Cert44 = []byte{ - 0x30, 0x82, 0x05, 0x2b, 0x30, 0x82, 0x04, 0x13, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x7e, 0xe1, 0x4a, 0x6f, 0x6f, 0xef, 0xf2, 0xd3, 0x7f, - 0x3f, 0xad, 0x65, 0x4d, 0x3a, 0xda, 0xb4, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, - 0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, - 0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, - 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, - 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28, - 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69, - 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, - 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, - 0x31, 0x45, 0x30, 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56, - 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, - 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, - 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30, - 0x1e, 0x17, 0x0d, 0x31, 0x33, 0x31, 0x30, 0x33, 0x31, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x33, 0x31, 0x30, 0x33, 0x30, - 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x77, 0x31, 0x0b, 0x30, - 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x1d, - 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x14, 0x53, 0x79, 0x6d, - 0x61, 0x6e, 0x74, 0x65, 0x63, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, - 0x04, 0x0b, 0x13, 0x16, 0x53, 0x79, 0x6d, 0x61, 0x6e, 0x74, 0x65, 0x63, - 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, - 0x72, 0x6b, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, - 0x1f, 0x53, 0x79, 0x6d, 0x61, 0x6e, 0x74, 0x65, 0x63, 0x20, 0x43, 0x6c, - 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x45, 0x56, 0x20, 0x53, 0x53, 0x4c, - 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x30, 0x82, 0x01, 0x22, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, - 0x02, 0x82, 0x01, 0x01, 0x00, 0xd8, 0xa1, 0x65, 0x74, 0x23, 0xe8, 0x2b, - 0x64, 0xe2, 0x32, 0xd7, 0x33, 0x37, 0x3d, 0x8e, 0xf5, 0x34, 0x16, 0x48, - 0xdd, 0x4f, 0x7f, 0x87, 0x1c, 0xf8, 0x44, 0x23, 0x13, 0x8e, 0xfb, 0x11, - 0xd8, 0x44, 0x5a, 0x18, 0x71, 0x8e, 0x60, 0x16, 0x26, 0x92, 0x9b, 0xfd, - 0x17, 0x0b, 0xe1, 0x71, 0x70, 0x42, 0xfe, 0xbf, 0xfa, 0x1c, 0xc0, 0xaa, - 0xa3, 0xa7, 0xb5, 0x71, 0xe8, 0xff, 0x18, 0x83, 0xf6, 0xdf, 0x10, 0x0a, - 0x13, 0x62, 0xc8, 0x3d, 0x9c, 0xa7, 0xde, 0x2e, 0x3f, 0x0c, 0xd9, 0x1d, - 0xe7, 0x2e, 0xfb, 0x2a, 0xce, 0xc8, 0x9a, 0x7f, 0x87, 0xbf, 0xd8, 0x4c, - 0x04, 0x15, 0x32, 0xc9, 0xd1, 0xcc, 0x95, 0x71, 0xa0, 0x4e, 0x28, 0x4f, - 0x84, 0xd9, 0x35, 0xfb, 0xe3, 0x86, 0x6f, 0x94, 0x53, 0xe6, 0x72, 0x8a, - 0x63, 0x67, 0x2e, 0xbe, 0x69, 0xf6, 0xf7, 0x6e, 0x8e, 0x9c, 0x60, 0x04, - 0xeb, 0x29, 0xfa, 0xc4, 0x47, 0x42, 0xd2, 0x78, 0x98, 0xe3, 0xec, 0x0b, - 0xa5, 0x92, 0xdc, 0xb7, 0x9a, 0xbd, 0x80, 0x64, 0x2b, 0x38, 0x7c, 0x38, - 0x09, 0x5b, 0x66, 0xf6, 0x2d, 0x95, 0x7a, 0x86, 0xb2, 0x34, 0x2e, 0x85, - 0x9e, 0x90, 0x0e, 0x5f, 0xb7, 0x5d, 0xa4, 0x51, 0x72, 0x46, 0x70, 0x13, - 0xbf, 0x67, 0xf2, 0xb6, 0xa7, 0x4d, 0x14, 0x1e, 0x6c, 0xb9, 0x53, 0xee, - 0x23, 0x1a, 0x4e, 0x8d, 0x48, 0x55, 0x43, 0x41, 0xb1, 0x89, 0x75, 0x6a, - 0x40, 0x28, 0xc5, 0x7d, 0xdd, 0xd2, 0x6e, 0xd2, 0x02, 0x19, 0x2f, 0x7b, - 0x24, 0x94, 0x4b, 0xeb, 0xf1, 0x1a, 0xa9, 0x9b, 0xe3, 0x23, 0x9a, 0xea, - 0xfa, 0x33, 0xab, 0x0a, 0x2c, 0xb7, 0xf4, 0x60, 0x08, 0xdd, 0x9f, 0x1c, - 0xcd, 0xdd, 0x2d, 0x01, 0x66, 0x80, 0xaf, 0xb3, 0x2f, 0x29, 0x1d, 0x23, - 0xb8, 0x8a, 0xe1, 0xa1, 0x70, 0x07, 0x0c, 0x34, 0x0f, 0x02, 0x03, 0x01, - 0x00, 0x01, 0xa3, 0x82, 0x01, 0x5d, 0x30, 0x82, 0x01, 0x59, 0x30, 0x2f, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x23, - 0x30, 0x21, 0x30, 0x1f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x30, 0x01, 0x86, 0x13, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x73, - 0x32, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x30, - 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, - 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x65, 0x06, 0x03, 0x55, - 0x1d, 0x20, 0x04, 0x5e, 0x30, 0x5c, 0x30, 0x5a, 0x06, 0x04, 0x55, 0x1d, - 0x20, 0x00, 0x30, 0x52, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, - 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73, 0x79, 0x6d, 0x61, 0x75, 0x74, 0x68, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x28, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x02, 0x30, 0x1c, 0x1a, 0x1a, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73, - 0x79, 0x6d, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, - 0x70, 0x61, 0x30, 0x30, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x29, 0x30, - 0x27, 0x30, 0x25, 0xa0, 0x23, 0xa0, 0x21, 0x86, 0x1f, 0x68, 0x74, 0x74, - 0x70, 0x3a, 0x2f, 0x2f, 0x73, 0x31, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, 0x61, 0x33, 0x2d, 0x67, 0x35, - 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, - 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x29, 0x06, 0x03, - 0x55, 0x1d, 0x11, 0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, 0x31, - 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x53, 0x79, - 0x6d, 0x61, 0x6e, 0x74, 0x65, 0x63, 0x50, 0x4b, 0x49, 0x2d, 0x31, 0x2d, - 0x35, 0x33, 0x33, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, - 0x04, 0x14, 0x01, 0x59, 0xab, 0xe7, 0xdd, 0x3a, 0x0b, 0x59, 0xa6, 0x64, - 0x63, 0xd6, 0xcf, 0x20, 0x07, 0x57, 0xd5, 0x91, 0xe7, 0x6a, 0x30, 0x1f, - 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x7f, - 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0, 0x30, 0x09, 0xf3, 0x43, - 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, - 0x01, 0x01, 0x00, 0x42, 0x01, 0x55, 0x7b, 0xd0, 0x16, 0x1a, 0x5d, 0x58, - 0xe8, 0xbb, 0x9b, 0xa8, 0x4d, 0xd7, 0xf3, 0xd7, 0xeb, 0x13, 0x94, 0x86, - 0xd6, 0x7f, 0x21, 0x0b, 0x47, 0xbc, 0x57, 0x9b, 0x92, 0x5d, 0x4f, 0x05, - 0x9f, 0x38, 0xa4, 0x10, 0x7c, 0xcf, 0x83, 0xbe, 0x06, 0x43, 0x46, 0x8d, - 0x08, 0xbc, 0x6a, 0xd7, 0x10, 0xa6, 0xfa, 0xab, 0xaf, 0x2f, 0x61, 0xa8, - 0x63, 0xf2, 0x65, 0xdf, 0x7f, 0x4c, 0x88, 0x12, 0x88, 0x4f, 0xb3, 0x69, - 0xd9, 0xff, 0x27, 0xc0, 0x0a, 0x97, 0x91, 0x8f, 0x56, 0xfb, 0x89, 0xc4, - 0xa8, 0xbb, 0x92, 0x2d, 0x1b, 0x73, 0xb0, 0xc6, 0xab, 0x36, 0xf4, 0x96, - 0x6c, 0x20, 0x08, 0xef, 0x0a, 0x1e, 0x66, 0x24, 0x45, 0x4f, 0x67, 0x00, - 0x40, 0xc8, 0x07, 0x54, 0x74, 0x33, 0x3b, 0xa6, 0xad, 0xbb, 0x23, 0x9f, - 0x66, 0xed, 0xa2, 0x44, 0x70, 0x34, 0xfb, 0x0e, 0xea, 0x01, 0xfd, 0xcf, - 0x78, 0x74, 0xdf, 0xa7, 0xad, 0x55, 0xb7, 0x5f, 0x4d, 0xf6, 0xd6, 0x3f, - 0xe0, 0x86, 0xce, 0x24, 0xc7, 0x42, 0xa9, 0x13, 0x14, 0x44, 0x35, 0x4b, - 0xb6, 0xdf, 0xc9, 0x60, 0xac, 0x0c, 0x7f, 0xd9, 0x93, 0x21, 0x4b, 0xee, - 0x9c, 0xe4, 0x49, 0x02, 0x98, 0xd3, 0x60, 0x7b, 0x5c, 0xbc, 0xd5, 0x30, - 0x2f, 0x07, 0xce, 0x44, 0x42, 0xc4, 0x0b, 0x99, 0xfe, 0xe6, 0x9f, 0xfc, - 0xb0, 0x78, 0x86, 0x51, 0x6d, 0xd1, 0x2c, 0x9d, 0xc6, 0x96, 0xfb, 0x85, - 0x82, 0xbb, 0x04, 0x2f, 0xf7, 0x62, 0x80, 0xef, 0x62, 0xda, 0x7f, 0xf6, - 0x0e, 0xac, 0x90, 0xb8, 0x56, 0xbd, 0x79, 0x3f, 0xf2, 0x80, 0x6e, 0xa3, - 0xd9, 0xb9, 0x0f, 0x5d, 0x3a, 0x07, 0x1d, 0x91, 0x93, 0x86, 0x4b, 0x29, - 0x4c, 0xe1, 0xdc, 0xb5, 0xe1, 0xe0, 0x33, 0x9d, 0xb3, 0xcb, 0x36, 0x91, - 0x4b, 0xfe, 0xa1, 0xb4, 0xee, 0xf0, 0xf9, -} - -var certSet3Cert45 = []byte{ - 0x30, 0x82, 0x05, 0x38, 0x30, 0x82, 0x04, 0x20, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x51, 0x3f, 0xb9, 0x74, 0x38, 0x70, 0xb7, 0x34, 0x40, - 0x41, 0x8d, 0x30, 0x93, 0x06, 0x99, 0xff, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, - 0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, - 0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, - 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, - 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28, - 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69, - 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, - 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, - 0x31, 0x45, 0x30, 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56, - 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, - 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, - 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30, - 0x1e, 0x17, 0x0d, 0x31, 0x33, 0x31, 0x30, 0x33, 0x31, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x33, 0x31, 0x30, 0x33, 0x30, - 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x7e, 0x31, 0x0b, 0x30, - 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x1d, - 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x14, 0x53, 0x79, 0x6d, - 0x61, 0x6e, 0x74, 0x65, 0x63, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, - 0x04, 0x0b, 0x13, 0x16, 0x53, 0x79, 0x6d, 0x61, 0x6e, 0x74, 0x65, 0x63, - 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, - 0x72, 0x6b, 0x31, 0x2f, 0x30, 0x2d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, - 0x26, 0x53, 0x79, 0x6d, 0x61, 0x6e, 0x74, 0x65, 0x63, 0x20, 0x43, 0x6c, - 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, - 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x20, 0x2d, - 0x20, 0x47, 0x34, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, - 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, - 0xb2, 0xd8, 0x05, 0xca, 0x1c, 0x74, 0x2d, 0xb5, 0x17, 0x56, 0x39, 0xc5, - 0x4a, 0x52, 0x09, 0x96, 0xe8, 0x4b, 0xd8, 0x0c, 0xf1, 0x68, 0x9f, 0x9a, - 0x42, 0x28, 0x62, 0xc3, 0xa5, 0x30, 0x53, 0x7e, 0x55, 0x11, 0x82, 0x5b, - 0x03, 0x7a, 0x0d, 0x2f, 0xe1, 0x79, 0x04, 0xc9, 0xb4, 0x96, 0x77, 0x19, - 0x81, 0x01, 0x94, 0x59, 0xf9, 0xbc, 0xf7, 0x7a, 0x99, 0x27, 0x82, 0x2d, - 0xb7, 0x83, 0xdd, 0x5a, 0x27, 0x7f, 0xb2, 0x03, 0x7a, 0x9c, 0x53, 0x25, - 0xe9, 0x48, 0x1f, 0x46, 0x4f, 0xc8, 0x9d, 0x29, 0xf8, 0xbe, 0x79, 0x56, - 0xf6, 0xf7, 0xfd, 0xd9, 0x3a, 0x68, 0xda, 0x8b, 0x4b, 0x82, 0x33, 0x41, - 0x12, 0xc3, 0xc8, 0x3c, 0xcc, 0xd6, 0x96, 0x7a, 0x84, 0x21, 0x1a, 0x22, - 0x04, 0x03, 0x27, 0x17, 0x8b, 0x1c, 0x68, 0x61, 0x93, 0x0f, 0x0e, 0x51, - 0x80, 0x33, 0x1d, 0xb4, 0xb5, 0xce, 0xeb, 0x7e, 0xd0, 0x62, 0xac, 0xee, - 0xb3, 0x7b, 0x01, 0x74, 0xef, 0x69, 0x35, 0xeb, 0xca, 0xd5, 0x3d, 0xa9, - 0xee, 0x97, 0x98, 0xca, 0x8d, 0xaa, 0x44, 0x0e, 0x25, 0x99, 0x4a, 0x15, - 0x96, 0xa4, 0xce, 0x6d, 0x02, 0x54, 0x1f, 0x2a, 0x6a, 0x26, 0xe2, 0x06, - 0x3a, 0x63, 0x48, 0xac, 0xb4, 0x4c, 0xd1, 0x75, 0x93, 0x50, 0xff, 0x13, - 0x2f, 0xd6, 0xda, 0xe1, 0xc6, 0x18, 0xf5, 0x9f, 0xc9, 0x25, 0x5d, 0xf3, - 0x00, 0x3a, 0xde, 0x26, 0x4d, 0xb4, 0x29, 0x09, 0xcd, 0x0f, 0x3d, 0x23, - 0x6f, 0x16, 0x4a, 0x81, 0x16, 0xfb, 0xf2, 0x83, 0x10, 0xc3, 0xb8, 0xd6, - 0xd8, 0x55, 0x32, 0x3d, 0xf1, 0xbd, 0x0f, 0xbd, 0x8c, 0x52, 0x95, 0x4a, - 0x16, 0x97, 0x7a, 0x52, 0x21, 0x63, 0x75, 0x2f, 0x16, 0xf9, 0xc4, 0x66, - 0xbe, 0xf5, 0xb5, 0x09, 0xd8, 0xff, 0x27, 0x00, 0xcd, 0x44, 0x7c, 0x6f, - 0x4b, 0x3f, 0xb0, 0xf7, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, - 0x63, 0x30, 0x82, 0x01, 0x5f, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, - 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, - 0x00, 0x30, 0x30, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x29, 0x30, 0x27, - 0x30, 0x25, 0xa0, 0x23, 0xa0, 0x21, 0x86, 0x1f, 0x68, 0x74, 0x74, 0x70, - 0x3a, 0x2f, 0x2f, 0x73, 0x31, 0x2e, 0x73, 0x79, 0x6d, 0x63, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, 0x61, 0x33, 0x2d, 0x67, 0x35, 0x2e, - 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, - 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x2f, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x23, 0x30, 0x21, 0x30, - 0x1f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, - 0x13, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x73, 0x32, 0x2e, 0x73, - 0x79, 0x6d, 0x63, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x6b, 0x06, 0x03, - 0x55, 0x1d, 0x20, 0x04, 0x64, 0x30, 0x62, 0x30, 0x60, 0x06, 0x0a, 0x60, - 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x07, 0x36, 0x30, 0x52, 0x30, - 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, - 0x1a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, - 0x73, 0x79, 0x6d, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x63, 0x70, 0x73, 0x30, 0x28, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x02, 0x02, 0x30, 0x1c, 0x1a, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73, 0x79, 0x6d, 0x61, 0x75, 0x74, - 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x70, 0x61, 0x30, 0x29, 0x06, - 0x03, 0x55, 0x1d, 0x11, 0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, - 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x53, - 0x79, 0x6d, 0x61, 0x6e, 0x74, 0x65, 0x63, 0x50, 0x4b, 0x49, 0x2d, 0x31, - 0x2d, 0x35, 0x33, 0x34, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, - 0x16, 0x04, 0x14, 0x5f, 0x60, 0xcf, 0x61, 0x90, 0x55, 0xdf, 0x84, 0x43, - 0x14, 0x8a, 0x60, 0x2a, 0xb2, 0xf5, 0x7a, 0xf4, 0x43, 0x18, 0xef, 0x30, - 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, - 0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0, 0x30, 0x09, 0xf3, - 0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, - 0x82, 0x01, 0x01, 0x00, 0x5e, 0x94, 0x56, 0x49, 0xdd, 0x8e, 0x2d, 0x65, - 0xf5, 0xc1, 0x36, 0x51, 0xb6, 0x03, 0xe3, 0xda, 0x9e, 0x73, 0x19, 0xf2, - 0x1f, 0x59, 0xab, 0x58, 0x7e, 0x6c, 0x26, 0x05, 0x2c, 0xfa, 0x81, 0xd7, - 0x5c, 0x23, 0x17, 0x22, 0x2c, 0x37, 0x93, 0xf7, 0x86, 0xec, 0x85, 0xe6, - 0xb0, 0xa3, 0xfd, 0x1f, 0xe2, 0x32, 0xa8, 0x45, 0x6f, 0xe1, 0xd9, 0xfb, - 0xb9, 0xaf, 0xd2, 0x70, 0xa0, 0x32, 0x42, 0x65, 0xbf, 0x84, 0xfe, 0x16, - 0x2a, 0x8f, 0x3f, 0xc5, 0xa6, 0xd6, 0xa3, 0x93, 0x7d, 0x43, 0xe9, 0x74, - 0x21, 0x91, 0x35, 0x28, 0xf4, 0x63, 0xe9, 0x2e, 0xed, 0xf7, 0xf5, 0x5c, - 0x7f, 0x4b, 0x9a, 0xb5, 0x20, 0xe9, 0x0a, 0xbd, 0xe0, 0x45, 0x10, 0x0c, - 0x14, 0x94, 0x9a, 0x5d, 0xa5, 0xe3, 0x4b, 0x91, 0xe8, 0x24, 0x9b, 0x46, - 0x40, 0x65, 0xf4, 0x22, 0x72, 0xcd, 0x99, 0xf8, 0x88, 0x11, 0xf5, 0xf3, - 0x7f, 0xe6, 0x33, 0x82, 0xe6, 0xa8, 0xc5, 0x7e, 0xfe, 0xd0, 0x08, 0xe2, - 0x25, 0x58, 0x08, 0x71, 0x68, 0xe6, 0xcd, 0xa2, 0xe6, 0x14, 0xde, 0x4e, - 0x52, 0x24, 0x2d, 0xfd, 0xe5, 0x79, 0x13, 0x53, 0xe7, 0x5e, 0x2f, 0x2d, - 0x4d, 0x1b, 0x6d, 0x40, 0x15, 0x52, 0x2b, 0xf7, 0x87, 0x89, 0x78, 0x12, - 0x81, 0x6e, 0xd9, 0x4d, 0xaa, 0x2d, 0x78, 0xd4, 0xc2, 0x2c, 0x3d, 0x08, - 0x5f, 0x87, 0x91, 0x9e, 0x1f, 0x0e, 0xb0, 0xde, 0x30, 0x52, 0x64, 0x86, - 0x89, 0xaa, 0x9d, 0x66, 0x9c, 0x0e, 0x76, 0x0c, 0x80, 0xf2, 0x74, 0xd8, - 0x2a, 0xf8, 0xb8, 0x3a, 0xce, 0xd7, 0xd6, 0x0f, 0x11, 0xbe, 0x6b, 0xab, - 0x14, 0xf5, 0xbd, 0x41, 0xa0, 0x22, 0x63, 0x89, 0xf1, 0xba, 0x0f, 0x6f, - 0x29, 0x63, 0x66, 0x2d, 0x3f, 0xac, 0x8c, 0x72, 0xc5, 0xfb, 0xc7, 0xe4, - 0xd4, 0x0f, 0xf2, 0x3b, 0x4f, 0x8c, 0x29, 0xc7, -} - -var certSet3Cert46 = []byte{ - 0x30, 0x82, 0x05, 0x49, 0x30, 0x82, 0x04, 0x31, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x69, 0x87, 0x94, 0x19, 0xd9, 0xe3, 0x62, 0x70, 0x74, - 0x9d, 0xbb, 0xe5, 0x9d, 0xc6, 0x68, 0x5e, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, - 0xbd, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, - 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, - 0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, - 0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, - 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, - 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28, - 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x38, 0x20, 0x56, 0x65, 0x72, 0x69, - 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, - 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, - 0x31, 0x38, 0x30, 0x36, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2f, 0x56, - 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x55, 0x6e, 0x69, 0x76, - 0x65, 0x72, 0x73, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, - 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, - 0x17, 0x0d, 0x31, 0x33, 0x30, 0x34, 0x30, 0x39, 0x30, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x33, 0x30, 0x34, 0x30, 0x38, 0x32, - 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0x84, 0x31, 0x0b, 0x30, - 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x1d, - 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x14, 0x53, 0x79, 0x6d, - 0x61, 0x6e, 0x74, 0x65, 0x63, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, - 0x04, 0x0b, 0x13, 0x16, 0x53, 0x79, 0x6d, 0x61, 0x6e, 0x74, 0x65, 0x63, - 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, - 0x72, 0x6b, 0x31, 0x35, 0x30, 0x33, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, - 0x2c, 0x53, 0x79, 0x6d, 0x61, 0x6e, 0x74, 0x65, 0x63, 0x20, 0x43, 0x6c, - 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, - 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x53, 0x48, 0x41, 0x32, - 0x35, 0x36, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, - 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, - 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xbe, 0x38, 0x16, 0x51, 0x8b, 0x80, - 0xdb, 0xea, 0x0e, 0x4d, 0xec, 0xe8, 0x3f, 0x5c, 0xc4, 0x7c, 0xa2, 0x5d, - 0xed, 0x3b, 0xaf, 0xa5, 0xd6, 0x9e, 0x10, 0x35, 0x2c, 0xe3, 0xc5, 0xe5, - 0xa8, 0xde, 0x8c, 0x86, 0x17, 0x26, 0xe6, 0xde, 0x0b, 0x51, 0x4a, 0x2c, - 0xd0, 0xfb, 0xd1, 0x14, 0x5a, 0x72, 0xf7, 0xc9, 0xdd, 0xb8, 0x83, 0x1c, - 0xc6, 0x46, 0x8c, 0x31, 0x25, 0x91, 0x0e, 0x59, 0x17, 0xa3, 0xd0, 0x13, - 0x8c, 0x92, 0xc1, 0xaf, 0x81, 0x54, 0x4e, 0xbc, 0x62, 0x02, 0x9e, 0xaa, - 0xa7, 0x1a, 0x57, 0xd8, 0xca, 0xa6, 0x99, 0x7a, 0x70, 0x56, 0x4f, 0x98, - 0x07, 0x2e, 0x4b, 0x96, 0xd0, 0x4c, 0x39, 0x53, 0xb9, 0x61, 0x2f, 0x3b, - 0x76, 0x7c, 0x8e, 0x05, 0x9e, 0x99, 0x44, 0xd1, 0x03, 0x54, 0x77, 0x29, - 0x2b, 0x56, 0x2a, 0xaa, 0x61, 0xe4, 0x84, 0x2f, 0x12, 0x15, 0x3c, 0xbd, - 0xd7, 0x8a, 0xe8, 0x09, 0x1e, 0x56, 0xf1, 0xb5, 0x14, 0xac, 0x8a, 0x84, - 0xce, 0xae, 0x78, 0xa2, 0x60, 0x0a, 0x53, 0x7e, 0x13, 0x4c, 0x1a, 0x40, - 0x70, 0x0e, 0x52, 0x59, 0xff, 0x5a, 0x68, 0x2e, 0x4c, 0x46, 0x13, 0x3b, - 0x39, 0x09, 0x82, 0x78, 0x02, 0x35, 0x49, 0x20, 0x08, 0x82, 0xb3, 0xb1, - 0x6c, 0x89, 0x0f, 0x6e, 0x1e, 0x35, 0x25, 0xb0, 0x2c, 0x24, 0x83, 0xe3, - 0xc5, 0x50, 0x2c, 0xba, 0x46, 0x90, 0x45, 0x87, 0x0d, 0x72, 0xff, 0x5d, - 0x11, 0x38, 0xc5, 0x91, 0x76, 0xc5, 0x2c, 0xfb, 0x05, 0x2a, 0x82, 0x95, - 0xa1, 0x59, 0x63, 0xe3, 0xd0, 0x26, 0x58, 0xcd, 0x67, 0x56, 0x3a, 0xba, - 0xdf, 0x7c, 0xd2, 0xd2, 0x3b, 0xd8, 0xde, 0x1a, 0x7a, 0x77, 0xe4, 0x0c, - 0x8c, 0x0b, 0xeb, 0x2b, 0xc2, 0x22, 0xb0, 0xbd, 0x55, 0xba, 0xd9, 0xb9, - 0x55, 0xd1, 0x22, 0x7a, 0xc6, 0x02, 0x4e, 0x3f, 0xc3, 0x35, 0x02, 0x03, - 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x7a, 0x30, 0x82, 0x01, 0x76, 0x30, - 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, - 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x3e, 0x06, 0x03, 0x55, - 0x1d, 0x1f, 0x04, 0x37, 0x30, 0x35, 0x30, 0x33, 0xa0, 0x31, 0xa0, 0x2f, - 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, - 0x2e, 0x77, 0x73, 0x2e, 0x73, 0x79, 0x6d, 0x61, 0x6e, 0x74, 0x65, 0x63, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, - 0x61, 0x6c, 0x2d, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, - 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, - 0x02, 0x01, 0x06, 0x30, 0x37, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x01, 0x01, 0x04, 0x2b, 0x30, 0x29, 0x30, 0x27, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x1b, 0x68, 0x74, 0x74, - 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x77, 0x73, 0x2e, - 0x73, 0x79, 0x6d, 0x61, 0x6e, 0x74, 0x65, 0x63, 0x2e, 0x63, 0x6f, 0x6d, - 0x30, 0x6b, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x64, 0x30, 0x62, 0x30, - 0x60, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x07, - 0x36, 0x30, 0x52, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x77, 0x77, 0x77, 0x2e, 0x73, 0x79, 0x6d, 0x61, 0x75, 0x74, 0x68, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x28, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x02, 0x30, 0x1c, 0x1a, 0x1a, 0x68, - 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73, 0x79, - 0x6d, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x70, - 0x61, 0x30, 0x2a, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x23, 0x30, 0x21, - 0xa4, 0x1f, 0x30, 0x1d, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, - 0x03, 0x13, 0x12, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x4d, - 0x50, 0x4b, 0x49, 0x2d, 0x32, 0x2d, 0x33, 0x37, 0x33, 0x30, 0x1d, 0x06, - 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xdb, 0x62, 0x20, 0xfb, - 0x7d, 0x02, 0x89, 0x7c, 0xd2, 0x3b, 0x6f, 0xc7, 0xe4, 0x32, 0x6c, 0x05, - 0x52, 0x1d, 0xad, 0xb1, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, - 0x18, 0x30, 0x16, 0x80, 0x14, 0xb6, 0x77, 0xfa, 0x69, 0x48, 0x47, 0x9f, - 0x53, 0x12, 0xd5, 0xc2, 0xea, 0x07, 0x32, 0x76, 0x07, 0xd1, 0x97, 0x07, - 0x19, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x19, 0xcc, 0x95, - 0xe2, 0x2f, 0x7b, 0x49, 0xd0, 0x48, 0x90, 0x53, 0xf4, 0x07, 0xb1, 0x20, - 0x44, 0x35, 0x70, 0x14, 0xd5, 0x44, 0x37, 0x31, 0xef, 0xef, 0x70, 0xd1, - 0x2d, 0x4c, 0xe9, 0x2d, 0xb0, 0x53, 0x91, 0x01, 0x4c, 0x54, 0xe7, 0x7d, - 0x9b, 0xda, 0x3a, 0xff, 0xb7, 0xcb, 0x14, 0xad, 0x30, 0x0f, 0x69, 0x1a, - 0x2a, 0xf0, 0xbc, 0xcd, 0x35, 0xeb, 0x48, 0xdc, 0xb9, 0x87, 0xfd, 0xcf, - 0xb1, 0x5a, 0xf6, 0x05, 0xda, 0x3c, 0x64, 0xe6, 0x2b, 0xe6, 0xdc, 0x73, - 0x5e, 0x9a, 0xd8, 0x0c, 0x9b, 0xd2, 0x97, 0xb3, 0xe8, 0xfa, 0x87, 0x95, - 0x53, 0xe1, 0x99, 0xad, 0x88, 0xe8, 0xfa, 0xbc, 0x09, 0x4d, 0xa2, 0xc4, - 0x6a, 0x1b, 0x28, 0x3b, 0x2d, 0xc3, 0x21, 0x15, 0xee, 0x14, 0xfa, 0x9d, - 0x98, 0x10, 0xeb, 0x9f, 0x3e, 0xe6, 0x24, 0x24, 0x5f, 0x7a, 0x1c, 0x05, - 0xbb, 0x9a, 0x31, 0x23, 0x58, 0x79, 0x4c, 0xec, 0x6d, 0x18, 0x19, 0x4d, - 0x51, 0x1f, 0x08, 0x61, 0xbd, 0x91, 0x05, 0x0c, 0x5a, 0x9c, 0x26, 0xfc, - 0x0b, 0xa5, 0x20, 0x25, 0xbf, 0x6a, 0x1b, 0x2b, 0xf7, 0x02, 0x09, 0x72, - 0x69, 0x83, 0x32, 0x14, 0xc3, 0x60, 0x5b, 0x7e, 0xfd, 0x9a, 0x32, 0xfa, - 0xb4, 0x95, 0x0e, 0x1a, 0xf9, 0x3b, 0x09, 0xa4, 0x54, 0x47, 0x9a, 0x0c, - 0xce, 0x32, 0xaf, 0xd1, 0x21, 0xcc, 0x7f, 0xd2, 0x06, 0xef, 0x60, 0x0e, - 0x62, 0x6f, 0x6f, 0x81, 0x1a, 0x17, 0x9d, 0xc8, 0xcb, 0x28, 0xcc, 0xe2, - 0x5f, 0x6e, 0x2c, 0x7a, 0xb4, 0xcb, 0x47, 0x7c, 0x74, 0x68, 0x7b, 0x48, - 0x71, 0x02, 0x9c, 0x23, 0x09, 0xf3, 0x5a, 0xae, 0x5f, 0x42, 0x2e, 0x5f, - 0x2b, 0x59, 0x2d, 0x52, 0x88, 0xe5, 0x8d, 0x0b, 0xb3, 0xa8, 0x61, 0xf9, - 0x4b, 0x9b, 0x55, 0xd6, 0xda, 0xb1, 0x92, 0x3b, 0xbf, 0xc3, 0x9b, 0xf9, - 0x2c, -} - -var certSet3Cert47 = []byte{ - 0x30, 0x82, 0x05, 0x86, 0x30, 0x82, 0x04, 0x6e, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x04, 0x07, 0x27, 0x9a, 0xa9, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x5a, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, - 0x45, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09, - 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30, - 0x11, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65, - 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, - 0x55, 0x04, 0x03, 0x13, 0x19, 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, - 0x72, 0x65, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, - 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x33, - 0x31, 0x32, 0x31, 0x39, 0x32, 0x30, 0x30, 0x37, 0x33, 0x32, 0x5a, 0x17, - 0x0d, 0x31, 0x37, 0x31, 0x32, 0x31, 0x39, 0x32, 0x30, 0x30, 0x36, 0x35, - 0x35, 0x5a, 0x30, 0x81, 0x8b, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, - 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, - 0x55, 0x04, 0x08, 0x13, 0x0a, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, - 0x74, 0x6f, 0x6e, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, - 0x13, 0x07, 0x52, 0x65, 0x64, 0x6d, 0x6f, 0x6e, 0x64, 0x31, 0x1e, 0x30, - 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x15, 0x4d, 0x69, 0x63, 0x72, - 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, - 0x04, 0x0b, 0x13, 0x0c, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, - 0x74, 0x20, 0x49, 0x54, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, - 0x03, 0x13, 0x15, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, - 0x20, 0x49, 0x54, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x53, 0x48, 0x41, 0x32, - 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, - 0x30, 0x82, 0x02, 0x0a, 0x02, 0x82, 0x02, 0x01, 0x00, 0xd1, 0xe8, 0x37, - 0xa7, 0x76, 0x8a, 0x70, 0x4b, 0x19, 0xf0, 0x20, 0x37, 0x09, 0x24, 0x37, - 0x7f, 0xea, 0xfb, 0x78, 0xe6, 0x05, 0xba, 0x6a, 0xad, 0x4e, 0x27, 0x0d, - 0xfc, 0x72, 0x6a, 0xd9, 0x6c, 0x21, 0xc4, 0x64, 0x11, 0x95, 0x73, 0x10, - 0x0a, 0x5c, 0x25, 0x7b, 0x88, 0x6c, 0x94, 0x04, 0xfd, 0xc7, 0xdb, 0xae, - 0x7b, 0xdc, 0x4a, 0x08, 0xb3, 0x3e, 0x16, 0xf1, 0xd0, 0xad, 0xdb, 0x30, - 0x6d, 0xd7, 0x1a, 0x1e, 0x52, 0xb5, 0x3d, 0xf0, 0x47, 0x19, 0x03, 0xe2, - 0x7d, 0xa6, 0xbd, 0x57, 0x13, 0x3f, 0x54, 0xea, 0x3a, 0xa3, 0xb1, 0x77, - 0xfc, 0x42, 0xf0, 0x63, 0x49, 0x6a, 0x91, 0x80, 0x2e, 0x30, 0x49, 0xc0, - 0x8a, 0xeb, 0x2b, 0xaf, 0xfe, 0x3a, 0xeb, 0x07, 0x5d, 0x06, 0xf7, 0xe9, - 0xfd, 0x84, 0x0e, 0x91, 0xbd, 0x09, 0x20, 0x29, 0xe8, 0x6e, 0x5d, 0x09, - 0xce, 0x15, 0xd3, 0xe7, 0xef, 0xdb, 0x50, 0xeb, 0x44, 0xef, 0x18, 0x57, - 0xab, 0x04, 0x1d, 0xbc, 0x31, 0xf9, 0xf7, 0x7b, 0x2a, 0x13, 0xcf, 0xd1, - 0x3d, 0x51, 0xaf, 0x1b, 0xc5, 0xb5, 0x7b, 0xe7, 0xb0, 0xfc, 0x53, 0xbb, - 0x9a, 0xe7, 0x63, 0xde, 0x41, 0x33, 0xb6, 0x47, 0x24, 0x69, 0x5d, 0xb8, - 0x46, 0xa7, 0xff, 0xad, 0xab, 0xdf, 0x4f, 0x7a, 0x78, 0x25, 0x27, 0x21, - 0x26, 0x34, 0xca, 0x02, 0x6e, 0x37, 0x51, 0xf0, 0xed, 0x58, 0x1a, 0x60, - 0x94, 0xf6, 0xc4, 0x93, 0xd8, 0xdd, 0x30, 0x24, 0x25, 0xd7, 0x1c, 0xeb, - 0x19, 0x94, 0x35, 0x5d, 0x93, 0xb2, 0xae, 0xaa, 0x29, 0x83, 0x73, 0xc4, - 0x74, 0x59, 0x05, 0x52, 0x67, 0x9d, 0xda, 0x67, 0x51, 0x39, 0x05, 0x3a, - 0x36, 0xea, 0xf2, 0x1e, 0x76, 0x2b, 0x14, 0xae, 0xec, 0x3d, 0xf9, 0x14, - 0x99, 0x8b, 0x07, 0x6e, 0xbc, 0xe7, 0x0c, 0x56, 0xde, 0xac, 0xbe, 0xae, - 0xdb, 0x75, 0x32, 0x90, 0x9e, 0x63, 0xbd, 0x74, 0xbf, 0xe0, 0x0a, 0xca, - 0xf8, 0x34, 0x96, 0x67, 0x84, 0xcd, 0xd1, 0x42, 0x38, 0x78, 0xc7, 0x99, - 0xb6, 0x0c, 0xce, 0xb6, 0x0f, 0xe9, 0x1b, 0xcb, 0xf4, 0x59, 0xbe, 0x11, - 0x0e, 0xcb, 0x2c, 0x32, 0xc8, 0xfa, 0x83, 0x29, 0x64, 0x79, 0x3c, 0x8b, - 0x4b, 0xf0, 0x32, 0x74, 0x6c, 0xf3, 0x93, 0xb8, 0x96, 0x6b, 0x5d, 0x57, - 0x5a, 0x68, 0xc1, 0xcc, 0x0c, 0x79, 0x8a, 0x19, 0xde, 0xf5, 0x49, 0x02, - 0x5e, 0x08, 0x80, 0x01, 0x89, 0x0c, 0x32, 0xcd, 0xd2, 0xd6, 0x96, 0xd5, - 0x4b, 0xa0, 0xf3, 0xec, 0xbf, 0xab, 0xf4, 0x7d, 0xb3, 0xa1, 0xb9, 0x7c, - 0xda, 0x4e, 0xd7, 0xe5, 0xb7, 0xac, 0xb9, 0xf2, 0x25, 0x5f, 0x01, 0xcb, - 0x8c, 0x96, 0xa8, 0x28, 0xae, 0xc1, 0x33, 0x5a, 0xf6, 0x3f, 0x08, 0x90, - 0xdc, 0xeb, 0xff, 0x39, 0xd8, 0x26, 0xc8, 0x12, 0x9d, 0x1c, 0x9a, 0xaa, - 0xa9, 0xc0, 0x16, 0x8e, 0x86, 0xed, 0x67, 0x52, 0x96, 0x00, 0x7f, 0x0d, - 0x92, 0x3d, 0x3d, 0xd9, 0x70, 0x36, 0xe5, 0xea, 0x42, 0x6f, 0x1f, 0xae, - 0x95, 0xe5, 0x5b, 0x5d, 0xf8, 0xd0, 0x3a, 0xc7, 0xd4, 0xde, 0x77, 0x86, - 0xd0, 0xfc, 0x9e, 0x4e, 0xe2, 0xe2, 0xb8, 0xa9, 0x68, 0x37, 0x09, 0xc4, - 0x39, 0xe3, 0x85, 0xb8, 0x89, 0xf3, 0x1f, 0x6e, 0xb7, 0x6d, 0x1f, 0x4a, - 0x2f, 0x18, 0x09, 0x6f, 0xde, 0x4a, 0x01, 0x8f, 0x14, 0xc9, 0xb7, 0xa6, - 0xee, 0xa7, 0x63, 0x9f, 0x33, 0xa4, 0x54, 0x7c, 0x42, 0x83, 0x68, 0xb8, - 0xa5, 0xdf, 0xbf, 0xec, 0xb9, 0x1a, 0x5d, 0x13, 0x3b, 0xd9, 0xad, 0x68, - 0xfd, 0x20, 0x0a, 0x55, 0x91, 0x21, 0x64, 0xf9, 0xd7, 0x13, 0x01, 0xa0, - 0x08, 0x5d, 0x59, 0x89, 0x1b, 0x44, 0xaf, 0xa4, 0xac, 0xc7, 0x05, 0x10, - 0xfa, 0x41, 0x4a, 0xa8, 0xfb, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, - 0x01, 0x20, 0x30, 0x82, 0x01, 0x1c, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, - 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, - 0x01, 0x00, 0x30, 0x53, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x4c, 0x30, - 0x4a, 0x30, 0x48, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb1, 0x3e, - 0x01, 0x00, 0x30, 0x3b, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x02, 0x01, 0x16, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, - 0x2f, 0x63, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, - 0x6f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, - 0x63, 0x66, 0x6d, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, - 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x1d, 0x06, 0x03, 0x55, - 0x1d, 0x25, 0x04, 0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x03, 0x02, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, - 0x16, 0x80, 0x14, 0xe5, 0x9d, 0x59, 0x30, 0x82, 0x47, 0x58, 0xcc, 0xac, - 0xfa, 0x08, 0x54, 0x36, 0x86, 0x7b, 0x3a, 0xb5, 0x04, 0x4d, 0xf0, 0x30, - 0x42, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3b, 0x30, 0x39, 0x30, 0x37, - 0xa0, 0x35, 0xa0, 0x33, 0x86, 0x31, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, - 0x2f, 0x63, 0x64, 0x70, 0x31, 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, - 0x2d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, - 0x52, 0x4c, 0x2f, 0x4f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x32, - 0x30, 0x32, 0x35, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, - 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x51, 0xaf, 0x24, 0x26, 0x9c, 0xf4, - 0x68, 0x22, 0x57, 0x80, 0x26, 0x2b, 0x3b, 0x46, 0x62, 0x15, 0x7b, 0x1e, - 0xcc, 0xa5, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, - 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x76, 0x85, - 0xc5, 0x23, 0x31, 0x1f, 0xb4, 0x73, 0xea, 0xa0, 0xbc, 0xa5, 0xed, 0xdf, - 0x45, 0x43, 0x6a, 0x7f, 0x69, 0x20, 0x1b, 0x80, 0xb2, 0xfb, 0x1c, 0xdd, - 0xaa, 0x7f, 0x88, 0xd3, 0x31, 0x41, 0x36, 0xf7, 0xfb, 0xfb, 0x6b, 0xad, - 0x98, 0x8c, 0x78, 0x1f, 0x9d, 0x11, 0x67, 0x3a, 0xcd, 0x4b, 0xec, 0xa8, - 0xbc, 0x9d, 0x15, 0x19, 0xc4, 0x3b, 0x0b, 0xa7, 0x93, 0xce, 0xe8, 0xfc, - 0x9d, 0x5b, 0xe8, 0x1f, 0xcb, 0x56, 0xae, 0x76, 0x43, 0x2b, 0xc7, 0x13, - 0x51, 0x77, 0x41, 0xa8, 0x66, 0x4c, 0x5f, 0xa7, 0xd1, 0xd7, 0xaa, 0x75, - 0xc5, 0x1b, 0x29, 0x4c, 0xc9, 0xf4, 0x6d, 0xa1, 0x5e, 0xa1, 0x85, 0x93, - 0x16, 0xc2, 0xcb, 0x3b, 0xab, 0x14, 0x7d, 0x44, 0xfd, 0xda, 0x25, 0x29, - 0x86, 0x2a, 0xfe, 0x63, 0x20, 0xca, 0xd2, 0x0b, 0xc2, 0x34, 0x15, 0xbb, - 0xaf, 0x5b, 0x7f, 0x8a, 0xe0, 0xaa, 0xed, 0x45, 0xa6, 0xea, 0x79, 0xdb, - 0xd8, 0x35, 0x66, 0x54, 0x43, 0xde, 0x37, 0x33, 0xd1, 0xe4, 0xe0, 0xcd, - 0x57, 0xca, 0x71, 0xb0, 0x7d, 0xe9, 0x16, 0x77, 0x64, 0xe8, 0x59, 0x97, - 0xb9, 0xd5, 0x2e, 0xd1, 0xb4, 0x91, 0xda, 0x77, 0x71, 0xf3, 0x4a, 0x0f, - 0x48, 0xd2, 0x34, 0x99, 0x60, 0x95, 0x37, 0xac, 0x1f, 0x01, 0xcd, 0x10, - 0x9d, 0xe8, 0x2a, 0xa5, 0x20, 0xc7, 0x50, 0x9b, 0xb3, 0x6c, 0x49, 0x78, - 0x2b, 0x58, 0x92, 0x64, 0x89, 0xb8, 0x95, 0x36, 0xa8, 0x34, 0xaa, 0xf0, - 0x41, 0xd2, 0x95, 0x5a, 0x24, 0x54, 0x97, 0x4d, 0x6e, 0x05, 0xc4, 0x95, - 0xad, 0xc4, 0x7a, 0xa3, 0x39, 0xfb, 0x79, 0x06, 0x8a, 0x9b, 0xa6, 0x4f, - 0xd9, 0x22, 0xfa, 0x44, 0x4e, 0x36, 0xf3, 0xc9, 0x0f, 0xa6, 0x39, 0xe7, - 0x80, 0xb2, 0x5e, 0xbf, 0xbd, 0x39, 0xd1, 0x46, 0xe5, 0x55, 0x47, 0xdb, - 0xbc, 0x6e, -} - -var certSet3Cert48 = []byte{ - 0x30, 0x82, 0x05, 0xa3, 0x30, 0x82, 0x03, 0x8b, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x75, 0x96, 0xc2, 0x3e, 0xfa, 0x89, 0x59, 0x45, 0x6e, - 0x79, 0xf7, 0x17, 0xba, 0xcf, 0x64, 0xf3, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x55, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x43, - 0x4e, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, - 0x57, 0x6f, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, - 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x2a, 0x30, 0x28, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x13, 0x21, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x74, 0x79, 0x20, 0x6f, 0x66, 0x20, 0x57, 0x6f, 0x53, 0x69, 0x67, - 0x6e, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x34, 0x31, 0x31, 0x30, 0x38, 0x30, - 0x30, 0x35, 0x38, 0x35, 0x38, 0x5a, 0x17, 0x0d, 0x32, 0x39, 0x31, 0x31, - 0x30, 0x38, 0x30, 0x30, 0x35, 0x38, 0x35, 0x38, 0x5a, 0x30, 0x52, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x43, 0x4e, - 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x57, - 0x6f, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, - 0x69, 0x74, 0x65, 0x64, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, - 0x03, 0x13, 0x1e, 0x57, 0x6f, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, - 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x4f, 0x56, 0x20, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, - 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, - 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd6, 0x74, 0x87, 0xaf, 0x99, 0xc0, - 0x57, 0x96, 0x99, 0xc2, 0x89, 0x74, 0x3c, 0x92, 0x55, 0x99, 0xbf, 0x1f, - 0x07, 0x00, 0x35, 0x05, 0x26, 0x96, 0x16, 0x5b, 0x03, 0xc1, 0x42, 0x37, - 0x33, 0xbe, 0x3f, 0x0d, 0x4f, 0xff, 0xbb, 0x94, 0x26, 0x91, 0xd7, 0x14, - 0x16, 0x78, 0x1b, 0xf7, 0x13, 0xa2, 0x4b, 0x4c, 0xe5, 0x5c, 0xa7, 0x10, - 0x40, 0x35, 0x59, 0x30, 0xd1, 0x77, 0x99, 0xe3, 0x9d, 0x29, 0xc2, 0xbe, - 0x31, 0x95, 0xbd, 0x92, 0x61, 0x5b, 0xb0, 0x23, 0xfb, 0x67, 0x58, 0xd5, - 0x52, 0xe4, 0x7b, 0x2f, 0xf0, 0x73, 0x1c, 0x73, 0x94, 0x55, 0xba, 0xc8, - 0x68, 0x59, 0x02, 0x10, 0x10, 0xe4, 0xf7, 0x11, 0xf0, 0xc3, 0xb6, 0xd7, - 0xae, 0x56, 0x80, 0x00, 0x9e, 0x65, 0x64, 0xa6, 0x83, 0x91, 0x41, 0xe6, - 0xed, 0xa7, 0x7a, 0x65, 0xa5, 0x1f, 0x30, 0x2e, 0x13, 0x3c, 0xbf, 0xdf, - 0x63, 0x97, 0xf3, 0x96, 0xf0, 0x52, 0x32, 0xb4, 0xf4, 0x7b, 0x98, 0x57, - 0xed, 0x36, 0x4f, 0xf7, 0x21, 0x4a, 0x28, 0x9d, 0xdd, 0x1c, 0x92, 0xb3, - 0x4d, 0x8d, 0x9c, 0x58, 0x8b, 0x17, 0x21, 0xd8, 0xdc, 0xa1, 0xb7, 0xae, - 0x73, 0x78, 0x8a, 0xc4, 0xb6, 0xe9, 0x7f, 0x28, 0x8e, 0x9a, 0xd5, 0x2e, - 0x9e, 0x39, 0xe9, 0xda, 0x59, 0x74, 0xe3, 0xc8, 0x97, 0x10, 0x32, 0x94, - 0x19, 0x59, 0xd4, 0x0f, 0x89, 0x57, 0x44, 0xe6, 0xe5, 0x2b, 0x17, 0x30, - 0x62, 0x52, 0x98, 0x7f, 0xab, 0x0d, 0xa5, 0x01, 0xea, 0x04, 0x41, 0xca, - 0xfa, 0x13, 0x0e, 0x3b, 0x87, 0x06, 0xba, 0xbd, 0x47, 0x31, 0xd7, 0x63, - 0x03, 0x01, 0xf4, 0xbe, 0xa1, 0x37, 0x11, 0x9f, 0x1e, 0x01, 0x95, 0x4e, - 0x0f, 0x3f, 0x54, 0x1e, 0x92, 0xa6, 0x9f, 0x30, 0x8c, 0xfe, 0x98, 0xe8, - 0x56, 0x96, 0x66, 0x04, 0xe1, 0x35, 0xfe, 0x59, 0xac, 0x57, 0x02, 0x03, - 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x70, 0x30, 0x82, 0x01, 0x6c, 0x30, - 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, - 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x16, - 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x30, 0x12, - 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, - 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x30, 0x06, 0x03, 0x55, 0x1d, - 0x1f, 0x04, 0x29, 0x30, 0x27, 0x30, 0x25, 0xa0, 0x23, 0xa0, 0x21, 0x86, - 0x1f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x73, - 0x31, 0x2e, 0x77, 0x6f, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x63, 0x61, 0x31, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x6d, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x61, 0x30, 0x5f, - 0x30, 0x27, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, - 0x86, 0x1b, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, - 0x70, 0x31, 0x2e, 0x77, 0x6f, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x63, 0x61, 0x31, 0x30, 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x28, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x61, 0x69, 0x61, 0x31, 0x2e, 0x77, 0x6f, 0x73, 0x69, 0x67, - 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x61, 0x31, 0x67, 0x32, 0x2d, - 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x33, 0x2e, 0x63, 0x65, 0x72, 0x30, - 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xf9, 0x8b, - 0xec, 0x04, 0x38, 0x6a, 0x3f, 0xaa, 0x06, 0xc6, 0x94, 0xad, 0x73, 0x95, - 0x2a, 0xb0, 0xc8, 0xe6, 0xb8, 0xfb, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, - 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xe1, 0x66, 0xcf, 0x0e, 0xd1, - 0xf1, 0xb3, 0x4b, 0xb7, 0x06, 0x20, 0x14, 0xfe, 0x87, 0x12, 0xd5, 0xf6, - 0xfe, 0xfb, 0x3e, 0x30, 0x46, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x3f, - 0x30, 0x3d, 0x30, 0x3b, 0x06, 0x0c, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, - 0x9b, 0x51, 0x06, 0x03, 0x02, 0x01, 0x30, 0x2b, 0x30, 0x29, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1d, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x6f, 0x73, - 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x2f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, - 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x02, 0x01, 0x00, 0x5e, - 0x67, 0xba, 0x78, 0x32, 0x05, 0xb6, 0xb7, 0xaf, 0xe7, 0xde, 0x6a, 0x7a, - 0x82, 0x64, 0x0e, 0xa0, 0x0b, 0xf2, 0x9e, 0x9a, 0xba, 0xc6, 0x2b, 0x6f, - 0x56, 0x3a, 0xb4, 0x62, 0x57, 0xab, 0x7c, 0xad, 0x60, 0x50, 0x96, 0x34, - 0x9c, 0xa3, 0x88, 0xcf, 0xd9, 0x8f, 0x50, 0xaf, 0xf6, 0xf0, 0x00, 0x36, - 0x1b, 0x1f, 0x1f, 0x87, 0x55, 0x3c, 0x60, 0x9a, 0xf0, 0xb0, 0x0d, 0x9a, - 0x80, 0x2d, 0x8a, 0x3b, 0xbe, 0x05, 0xb3, 0xd7, 0xa0, 0x80, 0xb6, 0xb8, - 0x19, 0xeb, 0x51, 0xdb, 0xec, 0x64, 0x54, 0xf1, 0x1a, 0x89, 0x4a, 0x48, - 0xa1, 0x4d, 0x3f, 0x31, 0x7d, 0xc4, 0x79, 0x94, 0x4b, 0xf1, 0xde, 0xab, - 0x83, 0xaf, 0x5f, 0x86, 0xbe, 0x96, 0x1c, 0xb3, 0x3e, 0x1c, 0xe7, 0xbc, - 0x96, 0xb2, 0xe8, 0x5a, 0xac, 0xb5, 0x58, 0xcb, 0x3c, 0x56, 0x6f, 0x0a, - 0xa7, 0xa5, 0xd0, 0x36, 0x89, 0x82, 0x26, 0x8c, 0xb9, 0x1f, 0xb6, 0xeb, - 0x8f, 0x7e, 0x78, 0xfc, 0x5b, 0x8b, 0x79, 0x1c, 0xd6, 0xdf, 0x47, 0xa7, - 0x56, 0xf4, 0x98, 0x4e, 0xc7, 0xa9, 0xd5, 0x0e, 0x75, 0x56, 0x06, 0x7f, - 0xb4, 0x37, 0x46, 0x08, 0xc6, 0xe9, 0x4f, 0x8b, 0x5b, 0x43, 0x1c, 0xe0, - 0x45, 0x3e, 0x95, 0x20, 0x71, 0xc0, 0x1c, 0x98, 0x16, 0xef, 0xf2, 0x78, - 0xdf, 0xac, 0x4d, 0xbb, 0xbf, 0x56, 0x0e, 0xcf, 0x85, 0xaf, 0xcf, 0xbf, - 0x04, 0xed, 0x72, 0x6b, 0xfd, 0x1f, 0x57, 0x0e, 0x58, 0x91, 0x44, 0x11, - 0x58, 0x3b, 0x62, 0x3b, 0x09, 0x78, 0xb3, 0xa4, 0x75, 0x6a, 0xec, 0xb3, - 0xc2, 0x2b, 0x32, 0xcc, 0xb3, 0x8d, 0xc3, 0xa3, 0x6e, 0xdc, 0x8a, 0xd5, - 0xe8, 0x4a, 0xc4, 0x0b, 0x7b, 0xdb, 0x30, 0x5d, 0x95, 0x33, 0xc3, 0xd1, - 0xa3, 0x69, 0x64, 0x5b, 0xa8, 0xaa, 0x96, 0x48, 0x73, 0x73, 0xe3, 0xc9, - 0xb9, 0x24, 0xdf, 0x17, 0x75, 0xaa, 0xaf, 0x07, 0x3a, 0xcf, 0xbe, 0x9b, - 0x8a, 0x80, 0xa7, 0xbf, 0x7c, 0xe2, 0xe9, 0x2a, 0xe6, 0xfd, 0xb0, 0x2c, - 0xe7, 0xe6, 0xe6, 0x7e, 0xb3, 0x35, 0x15, 0x65, 0x00, 0xf4, 0xe1, 0x39, - 0x73, 0x0e, 0x28, 0x4b, 0xf0, 0x0c, 0x98, 0x9e, 0x3a, 0xeb, 0xce, 0x7b, - 0x7a, 0x9e, 0x40, 0xc1, 0x50, 0x65, 0x96, 0x9a, 0xe7, 0x4b, 0x77, 0xcd, - 0xdd, 0xcb, 0x7d, 0x97, 0xb4, 0xea, 0x09, 0xb2, 0xe9, 0x49, 0x28, 0xc3, - 0x30, 0xe0, 0x87, 0x15, 0xf0, 0x26, 0xea, 0xd8, 0x03, 0xfd, 0xec, 0xda, - 0x08, 0x83, 0x65, 0xdc, 0x77, 0xc5, 0x6e, 0x3d, 0x34, 0xf7, 0x87, 0xc3, - 0x1c, 0x1d, 0x26, 0x33, 0xec, 0x33, 0xac, 0xc6, 0x99, 0x53, 0xab, 0x60, - 0xf4, 0xb0, 0xd9, 0xee, 0x64, 0x5a, 0x33, 0x07, 0x70, 0x13, 0x74, 0x88, - 0x07, 0xf5, 0x86, 0xf9, 0x18, 0xd3, 0xb2, 0x47, 0xc8, 0xae, 0x03, 0x4a, - 0x53, 0xde, 0x1c, 0x65, 0xd6, 0x0a, 0x2e, 0x3a, 0x51, 0x93, 0xee, 0xb7, - 0xe3, 0x6f, 0x0a, 0xfb, 0xe9, 0xfe, 0x4e, 0xe8, 0xbb, 0x1d, 0xc2, 0x97, - 0xab, 0x0a, 0xb9, 0xed, 0x36, 0x32, 0x1b, 0x4d, 0xa1, 0xcc, 0x03, 0xa6, - 0x9d, 0xb3, 0xd9, 0x1c, 0xd5, 0x67, 0xe2, 0x8f, 0x74, 0x3c, 0x92, 0x2a, - 0x74, 0xb1, 0x56, 0x50, 0xdf, 0x53, 0x15, 0xd7, 0x21, 0xd6, 0xeb, 0xf3, - 0xfb, 0x63, 0xe3, 0x20, 0x2c, 0x0a, 0x74, 0x37, 0x0b, 0xc1, 0xa1, 0x35, - 0x6a, 0x84, 0x70, 0xf4, 0x45, 0xf8, 0xb2, 0xb6, 0x81, 0x49, 0xaa, 0xfd, - 0x54, 0x45, 0x90, 0x4d, 0xe7, 0x04, 0x07, 0x5f, 0x78, 0x14, 0xdd, 0x3a, - 0xbb, 0x2b, 0xf9, 0x72, 0x50, 0xec, 0x68, 0xea, 0x3c, 0xa8, 0xd1, 0x80, - 0xbb, 0xbe, 0x35, 0x43, 0x97, 0xc3, 0x32, 0xb2, 0xf5, 0xaa, 0xad, 0xc9, - 0x7f, 0x83, 0x9f, 0x7d, 0x69, 0x1e, 0x15, -} - -var certSet3Cert49 = []byte{ - 0x30, 0x82, 0x05, 0xe1, 0x30, 0x82, 0x04, 0xc9, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x04, 0x07, 0x27, 0xaa, 0x47, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x5a, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, - 0x45, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09, - 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30, - 0x11, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65, - 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, - 0x55, 0x04, 0x03, 0x13, 0x19, 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, - 0x72, 0x65, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, - 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x34, - 0x30, 0x35, 0x30, 0x37, 0x31, 0x37, 0x30, 0x34, 0x30, 0x39, 0x5a, 0x17, - 0x0d, 0x31, 0x38, 0x30, 0x35, 0x30, 0x37, 0x31, 0x37, 0x30, 0x33, 0x33, - 0x30, 0x5a, 0x30, 0x81, 0x8b, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, - 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, - 0x55, 0x04, 0x08, 0x13, 0x0a, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, - 0x74, 0x6f, 0x6e, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, - 0x13, 0x07, 0x52, 0x65, 0x64, 0x6d, 0x6f, 0x6e, 0x64, 0x31, 0x1e, 0x30, - 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x15, 0x4d, 0x69, 0x63, 0x72, - 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, - 0x04, 0x0b, 0x13, 0x0c, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, - 0x74, 0x20, 0x49, 0x54, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, - 0x03, 0x13, 0x15, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, - 0x20, 0x49, 0x54, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x53, 0x48, 0x41, 0x32, - 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, - 0x30, 0x82, 0x02, 0x0a, 0x02, 0x82, 0x02, 0x01, 0x00, 0xd1, 0xe8, 0x37, - 0xa7, 0x76, 0x8a, 0x70, 0x4b, 0x19, 0xf0, 0x20, 0x37, 0x09, 0x24, 0x37, - 0x7f, 0xea, 0xfb, 0x78, 0xe6, 0x05, 0xba, 0x6a, 0xad, 0x4e, 0x27, 0x0d, - 0xfc, 0x72, 0x6a, 0xd9, 0x6c, 0x21, 0xc4, 0x64, 0x11, 0x95, 0x73, 0x10, - 0x0a, 0x5c, 0x25, 0x7b, 0x88, 0x6c, 0x94, 0x04, 0xfd, 0xc7, 0xdb, 0xae, - 0x7b, 0xdc, 0x4a, 0x08, 0xb3, 0x3e, 0x16, 0xf1, 0xd0, 0xad, 0xdb, 0x30, - 0x6d, 0xd7, 0x1a, 0x1e, 0x52, 0xb5, 0x3d, 0xf0, 0x47, 0x19, 0x03, 0xe2, - 0x7d, 0xa6, 0xbd, 0x57, 0x13, 0x3f, 0x54, 0xea, 0x3a, 0xa3, 0xb1, 0x77, - 0xfc, 0x42, 0xf0, 0x63, 0x49, 0x6a, 0x91, 0x80, 0x2e, 0x30, 0x49, 0xc0, - 0x8a, 0xeb, 0x2b, 0xaf, 0xfe, 0x3a, 0xeb, 0x07, 0x5d, 0x06, 0xf7, 0xe9, - 0xfd, 0x84, 0x0e, 0x91, 0xbd, 0x09, 0x20, 0x29, 0xe8, 0x6e, 0x5d, 0x09, - 0xce, 0x15, 0xd3, 0xe7, 0xef, 0xdb, 0x50, 0xeb, 0x44, 0xef, 0x18, 0x57, - 0xab, 0x04, 0x1d, 0xbc, 0x31, 0xf9, 0xf7, 0x7b, 0x2a, 0x13, 0xcf, 0xd1, - 0x3d, 0x51, 0xaf, 0x1b, 0xc5, 0xb5, 0x7b, 0xe7, 0xb0, 0xfc, 0x53, 0xbb, - 0x9a, 0xe7, 0x63, 0xde, 0x41, 0x33, 0xb6, 0x47, 0x24, 0x69, 0x5d, 0xb8, - 0x46, 0xa7, 0xff, 0xad, 0xab, 0xdf, 0x4f, 0x7a, 0x78, 0x25, 0x27, 0x21, - 0x26, 0x34, 0xca, 0x02, 0x6e, 0x37, 0x51, 0xf0, 0xed, 0x58, 0x1a, 0x60, - 0x94, 0xf6, 0xc4, 0x93, 0xd8, 0xdd, 0x30, 0x24, 0x25, 0xd7, 0x1c, 0xeb, - 0x19, 0x94, 0x35, 0x5d, 0x93, 0xb2, 0xae, 0xaa, 0x29, 0x83, 0x73, 0xc4, - 0x74, 0x59, 0x05, 0x52, 0x67, 0x9d, 0xda, 0x67, 0x51, 0x39, 0x05, 0x3a, - 0x36, 0xea, 0xf2, 0x1e, 0x76, 0x2b, 0x14, 0xae, 0xec, 0x3d, 0xf9, 0x14, - 0x99, 0x8b, 0x07, 0x6e, 0xbc, 0xe7, 0x0c, 0x56, 0xde, 0xac, 0xbe, 0xae, - 0xdb, 0x75, 0x32, 0x90, 0x9e, 0x63, 0xbd, 0x74, 0xbf, 0xe0, 0x0a, 0xca, - 0xf8, 0x34, 0x96, 0x67, 0x84, 0xcd, 0xd1, 0x42, 0x38, 0x78, 0xc7, 0x99, - 0xb6, 0x0c, 0xce, 0xb6, 0x0f, 0xe9, 0x1b, 0xcb, 0xf4, 0x59, 0xbe, 0x11, - 0x0e, 0xcb, 0x2c, 0x32, 0xc8, 0xfa, 0x83, 0x29, 0x64, 0x79, 0x3c, 0x8b, - 0x4b, 0xf0, 0x32, 0x74, 0x6c, 0xf3, 0x93, 0xb8, 0x96, 0x6b, 0x5d, 0x57, - 0x5a, 0x68, 0xc1, 0xcc, 0x0c, 0x79, 0x8a, 0x19, 0xde, 0xf5, 0x49, 0x02, - 0x5e, 0x08, 0x80, 0x01, 0x89, 0x0c, 0x32, 0xcd, 0xd2, 0xd6, 0x96, 0xd5, - 0x4b, 0xa0, 0xf3, 0xec, 0xbf, 0xab, 0xf4, 0x7d, 0xb3, 0xa1, 0xb9, 0x7c, - 0xda, 0x4e, 0xd7, 0xe5, 0xb7, 0xac, 0xb9, 0xf2, 0x25, 0x5f, 0x01, 0xcb, - 0x8c, 0x96, 0xa8, 0x28, 0xae, 0xc1, 0x33, 0x5a, 0xf6, 0x3f, 0x08, 0x90, - 0xdc, 0xeb, 0xff, 0x39, 0xd8, 0x26, 0xc8, 0x12, 0x9d, 0x1c, 0x9a, 0xaa, - 0xa9, 0xc0, 0x16, 0x8e, 0x86, 0xed, 0x67, 0x52, 0x96, 0x00, 0x7f, 0x0d, - 0x92, 0x3d, 0x3d, 0xd9, 0x70, 0x36, 0xe5, 0xea, 0x42, 0x6f, 0x1f, 0xae, - 0x95, 0xe5, 0x5b, 0x5d, 0xf8, 0xd0, 0x3a, 0xc7, 0xd4, 0xde, 0x77, 0x86, - 0xd0, 0xfc, 0x9e, 0x4e, 0xe2, 0xe2, 0xb8, 0xa9, 0x68, 0x37, 0x09, 0xc4, - 0x39, 0xe3, 0x85, 0xb8, 0x89, 0xf3, 0x1f, 0x6e, 0xb7, 0x6d, 0x1f, 0x4a, - 0x2f, 0x18, 0x09, 0x6f, 0xde, 0x4a, 0x01, 0x8f, 0x14, 0xc9, 0xb7, 0xa6, - 0xee, 0xa7, 0x63, 0x9f, 0x33, 0xa4, 0x54, 0x7c, 0x42, 0x83, 0x68, 0xb8, - 0xa5, 0xdf, 0xbf, 0xec, 0xb9, 0x1a, 0x5d, 0x13, 0x3b, 0xd9, 0xad, 0x68, - 0xfd, 0x20, 0x0a, 0x55, 0x91, 0x21, 0x64, 0xf9, 0xd7, 0x13, 0x01, 0xa0, - 0x08, 0x5d, 0x59, 0x89, 0x1b, 0x44, 0xaf, 0xa4, 0xac, 0xc7, 0x05, 0x10, - 0xfa, 0x41, 0x4a, 0xa8, 0xfb, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, - 0x01, 0x7b, 0x30, 0x82, 0x01, 0x77, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, - 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, - 0x01, 0x00, 0x30, 0x60, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x59, 0x30, - 0x57, 0x30, 0x48, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb1, 0x3e, - 0x01, 0x00, 0x30, 0x3b, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x02, 0x01, 0x16, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, - 0x2f, 0x63, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, - 0x6f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, - 0x63, 0x66, 0x6d, 0x30, 0x0b, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, - 0x82, 0x37, 0x2a, 0x01, 0x30, 0x42, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x01, 0x01, 0x04, 0x36, 0x30, 0x34, 0x30, 0x32, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x26, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x6f, 0x6d, - 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, - 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x72, 0x6f, 0x6f, 0x74, - 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, - 0x03, 0x02, 0x01, 0x86, 0x30, 0x27, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, - 0x20, 0x30, 0x1e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, - 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, - 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x09, 0x30, 0x1f, 0x06, - 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xe5, 0x9d, - 0x59, 0x30, 0x82, 0x47, 0x58, 0xcc, 0xac, 0xfa, 0x08, 0x54, 0x36, 0x86, - 0x7b, 0x3a, 0xb5, 0x04, 0x4d, 0xf0, 0x30, 0x42, 0x06, 0x03, 0x55, 0x1d, - 0x1f, 0x04, 0x3b, 0x30, 0x39, 0x30, 0x37, 0xa0, 0x35, 0xa0, 0x33, 0x86, - 0x31, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x64, 0x70, 0x31, - 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72, 0x75, 0x73, - 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x4f, 0x6d, - 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x32, 0x30, 0x32, 0x35, 0x2e, 0x63, - 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, - 0x14, 0x51, 0xaf, 0x24, 0x26, 0x9c, 0xf4, 0x68, 0x22, 0x57, 0x80, 0x26, - 0x2b, 0x3b, 0x46, 0x62, 0x15, 0x7b, 0x1e, 0xcc, 0xa5, 0x30, 0x0d, 0x06, - 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, - 0x03, 0x82, 0x01, 0x01, 0x00, 0x69, 0x62, 0xf6, 0x84, 0x91, 0x00, 0xc4, - 0x6f, 0x82, 0x7b, 0x24, 0xe1, 0x42, 0xa2, 0xa5, 0x8b, 0x82, 0x5c, 0xa7, - 0xc5, 0x44, 0xcb, 0xe7, 0x52, 0x76, 0x63, 0xd3, 0x76, 0x9e, 0x78, 0xe2, - 0x69, 0x35, 0xb1, 0x38, 0xba, 0xb0, 0x96, 0xc6, 0x1f, 0xac, 0x7b, 0xc6, - 0xb2, 0x65, 0x77, 0x8b, 0x7d, 0x8d, 0xae, 0x64, 0xb9, 0xa5, 0x8c, 0x17, - 0xca, 0x58, 0x65, 0xc3, 0xad, 0x82, 0xf5, 0xc5, 0xa2, 0xf5, 0x01, 0x13, - 0x93, 0xc6, 0x7e, 0x44, 0xe5, 0xc4, 0x61, 0xfa, 0x03, 0xb6, 0x56, 0xc1, - 0x72, 0xe1, 0xc8, 0x28, 0xc5, 0x69, 0x21, 0x8f, 0xac, 0x6e, 0xfd, 0x7f, - 0x43, 0x83, 0x36, 0xb8, 0xc0, 0xd6, 0xa0, 0x28, 0xfe, 0x1a, 0x45, 0xbe, - 0xfd, 0x93, 0x8c, 0x8d, 0xa4, 0x64, 0x79, 0x1f, 0x14, 0xdb, 0xa1, 0x9f, - 0x21, 0xdc, 0xc0, 0x4e, 0x7b, 0x17, 0x22, 0x17, 0xb1, 0xb6, 0x3c, 0xd3, - 0x9b, 0xe2, 0x0a, 0xa3, 0x7e, 0x99, 0xb0, 0xc1, 0xac, 0xd8, 0xf4, 0x86, - 0xdf, 0x3c, 0xda, 0x7d, 0x14, 0x9c, 0x40, 0xc1, 0x7c, 0xd2, 0x18, 0x6f, - 0xf1, 0x4f, 0x26, 0x45, 0x09, 0x95, 0x94, 0x5c, 0xda, 0xd0, 0x98, 0xf8, - 0xf4, 0x4c, 0x82, 0x96, 0x10, 0xde, 0xac, 0x30, 0xcb, 0x2b, 0xae, 0xf9, - 0x92, 0xea, 0xbf, 0x79, 0x03, 0xfc, 0x1e, 0x3f, 0xac, 0x09, 0xa4, 0x3f, - 0x65, 0xfd, 0x91, 0x4f, 0x96, 0x24, 0xa7, 0xce, 0xb4, 0x4e, 0x6a, 0x96, - 0x29, 0x17, 0xae, 0xc0, 0xa8, 0xdf, 0x17, 0x22, 0xf4, 0x17, 0xe3, 0xdc, - 0x1c, 0x39, 0x06, 0x56, 0x10, 0xea, 0xea, 0xb5, 0x74, 0x17, 0x3c, 0x4e, - 0xdd, 0x7e, 0x91, 0x0a, 0xa8, 0x0b, 0x78, 0x07, 0xa7, 0x31, 0x44, 0x08, - 0x31, 0xab, 0x18, 0x84, 0x0f, 0x12, 0x9c, 0xe7, 0xde, 0x84, 0x2c, 0xe9, - 0x6d, 0x93, 0x45, 0xbf, 0xa8, 0xc1, 0x3f, 0x34, 0xdc, -} - -var certSet3Cert50 = []byte{ - 0x30, 0x82, 0x05, 0xe5, 0x30, 0x82, 0x03, 0xcd, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x10, 0x13, 0x8b, 0xfe, 0xf3, 0x32, 0x94, 0xf9, 0xd8, 0x16, - 0xf9, 0x45, 0xc2, 0x71, 0x95, 0x29, 0x98, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x7d, - 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, - 0x4c, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, - 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x4c, 0x74, 0x64, - 0x2e, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x22, - 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x44, 0x69, 0x67, 0x69, 0x74, - 0x61, 0x6c, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x65, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x31, 0x29, - 0x30, 0x27, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x20, 0x53, 0x74, 0x61, - 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x35, 0x31, - 0x32, 0x31, 0x36, 0x30, 0x31, 0x30, 0x30, 0x30, 0x35, 0x5a, 0x17, 0x0d, - 0x33, 0x30, 0x31, 0x32, 0x31, 0x36, 0x30, 0x31, 0x30, 0x30, 0x30, 0x35, - 0x5a, 0x30, 0x78, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, - 0x13, 0x02, 0x49, 0x4c, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x13, 0x0d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, - 0x4c, 0x74, 0x64, 0x2e, 0x31, 0x29, 0x30, 0x27, 0x06, 0x03, 0x55, 0x04, - 0x0b, 0x13, 0x20, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, - 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31, - 0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1d, 0x53, 0x74, - 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, - 0x20, 0x33, 0x20, 0x4f, 0x56, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, - 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, - 0xaf, 0x67, 0x1c, 0x6f, 0xe5, 0x45, 0xe0, 0xd7, 0x46, 0x4b, 0x75, 0x2c, - 0xb6, 0x80, 0xf2, 0x9a, 0x17, 0x4d, 0x2d, 0xff, 0xde, 0xae, 0xd2, 0xd4, - 0x00, 0x8a, 0x3a, 0xb8, 0x31, 0xfe, 0x8e, 0x37, 0x9e, 0xfa, 0xaa, 0xd5, - 0xa3, 0x5b, 0x16, 0x12, 0xc1, 0x19, 0x3e, 0x34, 0x85, 0x96, 0xc3, 0xbe, - 0xd3, 0xb3, 0x43, 0xf4, 0x8d, 0x6f, 0x16, 0xbd, 0x30, 0xba, 0x07, 0xfc, - 0xd8, 0x9a, 0xc1, 0x79, 0x89, 0x80, 0x6d, 0xa0, 0x8c, 0xbe, 0xdd, 0x37, - 0xf7, 0xeb, 0x05, 0xd3, 0x53, 0x7f, 0x57, 0x58, 0x76, 0x55, 0xb6, 0xa8, - 0xa8, 0x86, 0x44, 0xb8, 0xbb, 0xd0, 0x13, 0xda, 0xfd, 0x8f, 0xe1, 0xf2, - 0xcd, 0xa0, 0x15, 0x38, 0x55, 0x56, 0xce, 0x26, 0xcf, 0x7c, 0x93, 0x75, - 0x29, 0x7a, 0x0a, 0xab, 0xfb, 0xba, 0x09, 0x38, 0x20, 0x11, 0x57, 0x07, - 0x5d, 0x7f, 0x49, 0x9f, 0x2a, 0x4a, 0x67, 0x1e, 0x9e, 0x58, 0xe9, 0xc7, - 0x7f, 0xf9, 0xc3, 0xed, 0xfe, 0x5f, 0x4d, 0xaf, 0xb8, 0x4f, 0x9d, 0xdf, - 0x69, 0x2d, 0x69, 0x1b, 0x3a, 0x58, 0x81, 0x69, 0x63, 0x30, 0xea, 0x87, - 0x8d, 0x0f, 0x52, 0x9d, 0x5a, 0xda, 0x39, 0x44, 0xba, 0x9f, 0x89, 0x9f, - 0x36, 0xb6, 0xc2, 0x19, 0x5c, 0xd9, 0x26, 0x78, 0xd9, 0xae, 0x5e, 0xfc, - 0x95, 0x90, 0xbf, 0xe8, 0x11, 0xc0, 0x47, 0x0f, 0x77, 0x89, 0xdd, 0x6a, - 0x28, 0x4f, 0x0a, 0xbc, 0x32, 0x64, 0x57, 0x43, 0x3d, 0x08, 0x65, 0x93, - 0xe5, 0x45, 0xae, 0xdd, 0x28, 0x0c, 0x27, 0x2c, 0x8e, 0xa6, 0x2b, 0x09, - 0x03, 0x5d, 0xa1, 0x78, 0xd2, 0x8c, 0xab, 0xb6, 0x6b, 0xb9, 0x46, 0xc9, - 0x19, 0x00, 0x39, 0xb9, 0xbf, 0xc6, 0x13, 0x2b, 0x73, 0x72, 0x1f, 0xf2, - 0x3e, 0x37, 0xb8, 0xe8, 0xb9, 0x14, 0x65, 0x88, 0x4d, 0xe2, 0xf1, 0x1b, - 0xd8, 0xa5, 0x1d, 0x3b, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, - 0x64, 0x30, 0x82, 0x01, 0x60, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, - 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, - 0x03, 0x55, 0x1d, 0x25, 0x04, 0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, - 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x03, 0x01, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, - 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, - 0x30, 0x32, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2b, 0x30, 0x29, 0x30, - 0x27, 0xa0, 0x25, 0xa0, 0x23, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x73, - 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x66, 0x73, 0x63, 0x61, - 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x66, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x01, 0x01, 0x04, 0x5a, 0x30, 0x58, 0x30, 0x24, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, - 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x73, 0x74, - 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x30, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x24, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x61, 0x69, 0x61, 0x2e, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x63, 0x65, 0x72, 0x74, 0x73, 0x2f, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x74, - 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xb1, - 0x3f, 0x1c, 0x92, 0x7b, 0x92, 0xb0, 0x5a, 0x25, 0xb3, 0x38, 0xfb, 0x9c, - 0x07, 0xa4, 0x26, 0x50, 0x32, 0xe3, 0x51, 0x30, 0x1f, 0x06, 0x03, 0x55, - 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x4e, 0x0b, 0xef, 0x1a, - 0xa4, 0x40, 0x5b, 0xa5, 0x17, 0x69, 0x87, 0x30, 0xca, 0x34, 0x68, 0x43, - 0xd0, 0x41, 0xae, 0xf2, 0x30, 0x3f, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, - 0x38, 0x30, 0x36, 0x30, 0x34, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, - 0x2c, 0x30, 0x2a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, - 0x01, 0x16, 0x1e, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, - 0x77, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x30, 0x0d, 0x06, - 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, - 0x03, 0x82, 0x02, 0x01, 0x00, 0x85, 0xf2, 0xe8, 0x14, 0xd3, 0x1b, 0xc1, - 0xa1, 0x16, 0x1d, 0xa4, 0xf4, 0x4d, 0xba, 0x51, 0x8b, 0x5c, 0x52, 0xb1, - 0x54, 0x54, 0x12, 0x16, 0x17, 0x9c, 0x96, 0x78, 0x6f, 0xd3, 0xbf, 0xdf, - 0x43, 0x36, 0xf5, 0x12, 0x89, 0x61, 0x72, 0x44, 0xdf, 0x1c, 0x9b, 0x09, - 0x4f, 0x60, 0x26, 0x68, 0xc1, 0xe6, 0x66, 0x50, 0x70, 0xb3, 0x6a, 0xf1, - 0xa8, 0x6a, 0x0c, 0x1e, 0x2e, 0x93, 0xf1, 0xee, 0x07, 0x3e, 0x09, 0xdd, - 0x30, 0x45, 0xb2, 0x56, 0x8e, 0xdc, 0x2c, 0x5c, 0xab, 0x49, 0xfa, 0xb9, - 0x04, 0x03, 0x40, 0x15, 0x7a, 0xb5, 0x30, 0xe0, 0x1d, 0x91, 0x8f, 0xa6, - 0xd6, 0x6f, 0x1f, 0x99, 0xa0, 0x84, 0x95, 0x39, 0xbd, 0xac, 0x77, 0x7f, - 0x72, 0x4b, 0xdd, 0x2d, 0xae, 0xff, 0xa8, 0x58, 0x1d, 0x46, 0x27, 0xd4, - 0x83, 0xc7, 0x69, 0x64, 0x9f, 0x19, 0xbb, 0x10, 0xf8, 0x04, 0x42, 0x87, - 0x59, 0x5d, 0x02, 0xb1, 0xd6, 0xe5, 0xc8, 0xda, 0x43, 0x30, 0xa3, 0xe8, - 0x37, 0xa5, 0xd2, 0x48, 0x0b, 0xa2, 0x83, 0x4e, 0x9d, 0x4f, 0x83, 0x58, - 0x9d, 0xd7, 0x47, 0x22, 0xb1, 0x89, 0xf0, 0x89, 0x3b, 0x3d, 0x28, 0x43, - 0x2c, 0x9b, 0x17, 0x7c, 0x03, 0xee, 0x9d, 0x26, 0x25, 0xe0, 0x04, 0xb8, - 0x1d, 0x04, 0x57, 0x42, 0x47, 0xda, 0x58, 0x69, 0xf0, 0xd3, 0x29, 0xab, - 0x12, 0x02, 0x99, 0x2b, 0x2a, 0xd8, 0x9d, 0xa0, 0x1f, 0x54, 0x5e, 0x23, - 0x9a, 0x0c, 0xd2, 0x99, 0x58, 0xc4, 0xa1, 0xe5, 0x49, 0xc2, 0x25, 0xa7, - 0x64, 0x20, 0x52, 0x2e, 0xe7, 0x89, 0xf5, 0x19, 0xc0, 0x8b, 0xd0, 0x63, - 0xb1, 0x78, 0x1e, 0xbe, 0x01, 0x47, 0xbe, 0x76, 0x81, 0x46, 0xf1, 0x99, - 0x1f, 0x94, 0x9a, 0xbe, 0xfa, 0x82, 0x15, 0xb5, 0x84, 0x84, 0x79, 0x75, - 0x93, 0xba, 0x9f, 0xb5, 0xe4, 0x9b, 0xc2, 0xcb, 0x69, 0x5c, 0xbd, 0x1f, - 0x55, 0x0a, 0xa7, 0x26, 0x30, 0x05, 0x51, 0xbe, 0x65, 0xee, 0x57, 0xa9, - 0x6a, 0xdf, 0xbd, 0xf9, 0x36, 0x2f, 0xad, 0x1e, 0x46, 0x41, 0x2b, 0xb1, - 0x88, 0xd0, 0x88, 0x25, 0x85, 0x40, 0x17, 0x79, 0xbf, 0x3d, 0x8d, 0xe2, - 0xf4, 0x2d, 0xea, 0x30, 0x31, 0xdf, 0xa1, 0x40, 0xcb, 0x35, 0xff, 0x82, - 0x9f, 0xf5, 0x99, 0x3c, 0x4a, 0xfd, 0x9d, 0xa1, 0xd1, 0x55, 0xcc, 0x20, - 0xa8, 0x1c, 0xd8, 0x20, 0x05, 0xab, 0xb3, 0x14, 0x65, 0x95, 0x53, 0xd8, - 0xe8, 0x8e, 0x57, 0xc5, 0x77, 0x6b, 0x2d, 0x4d, 0x88, 0xe9, 0x5d, 0x62, - 0xd5, 0xa2, 0xf8, 0x70, 0xe1, 0x70, 0xeb, 0x45, 0x23, 0x0e, 0xf0, 0x00, - 0x46, 0xc2, 0x48, 0x31, 0xe8, 0xe7, 0x36, 0x80, 0x36, 0x2d, 0x22, 0xf2, - 0x01, 0x27, 0x53, 0xeb, 0xce, 0xa7, 0x69, 0x49, 0x82, 0xbf, 0xe7, 0x0f, - 0x9c, 0xf3, 0x20, 0x2e, 0xf5, 0xfa, 0x5d, 0xce, 0xea, 0x58, 0x3a, 0x8f, - 0xd8, 0xaa, 0x7d, 0x30, 0xb7, 0x74, 0x96, 0x7c, 0x3d, 0x6e, 0xb4, 0xec, - 0x4a, 0x3b, 0x59, 0xb6, 0xa9, 0x50, 0x0d, 0x0f, 0x05, 0x06, 0x70, 0x26, - 0xb9, 0x95, 0x91, 0xd1, 0x5e, 0x24, 0x8c, 0x8f, 0xca, 0x74, 0x57, 0x97, - 0x90, 0x8b, 0x5a, 0xb7, 0xfe, 0x8d, 0xad, 0xd8, 0xe8, 0xc2, 0x06, 0xbc, - 0x08, 0x56, 0x21, 0x02, 0x12, 0x53, 0xc6, 0x9f, 0x86, 0x04, 0x58, 0xca, - 0x2d, 0xf8, 0x03, 0x0d, 0x57, 0x0b, 0x1c, 0x37, 0xbd, 0xf0, 0x5a, 0x35, - 0xf2, 0xfe, 0x3b, 0xd6, 0xa4, 0x37, 0x15, 0xe9, 0xf8, 0x08, 0x92, 0x96, - 0x3d, 0x74, 0xc8, 0xb5, 0x5c, 0x6e, 0x65, 0x08, 0xe7, 0xdf, 0x69, 0x73, - 0x9c, 0xec, 0xe3, 0x30, 0x5a, 0xa6, 0xdf, 0x5c, 0xbe, 0xda, 0x7f, 0x00, - 0xee, 0xa5, 0xda, 0x2b, 0x5c, 0x1e, 0x2a, 0x6a, 0xc0, 0xa3, 0xae, 0x1e, - 0xf1, -} - -var certSet3Cert51 = []byte{ - 0x30, 0x82, 0x06, 0x5c, 0x30, 0x82, 0x04, 0x44, 0xa0, 0x03, 0x02, 0x01, - 0x02, 0x02, 0x07, 0x19, 0xc2, 0x85, 0x30, 0xe9, 0x3b, 0x36, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, - 0x00, 0x30, 0x7d, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, - 0x13, 0x02, 0x49, 0x4c, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x13, 0x0d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, - 0x4c, 0x74, 0x64, 0x2e, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, - 0x0b, 0x13, 0x22, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x44, 0x69, - 0x67, 0x69, 0x74, 0x61, 0x6c, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, - 0x67, 0x31, 0x29, 0x30, 0x27, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x20, - 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x43, 0x65, 0x72, - 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, - 0x30, 0x36, 0x30, 0x39, 0x31, 0x37, 0x32, 0x32, 0x34, 0x36, 0x33, 0x36, - 0x5a, 0x17, 0x0d, 0x31, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, - 0x39, 0x35, 0x39, 0x5a, 0x30, 0x55, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, - 0x55, 0x04, 0x06, 0x13, 0x02, 0x43, 0x4e, 0x31, 0x1a, 0x30, 0x18, 0x06, - 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x57, 0x6f, 0x53, 0x69, 0x67, 0x6e, - 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, - 0x2a, 0x30, 0x28, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x21, 0x43, 0x65, - 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, - 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x6f, 0x66, - 0x20, 0x57, 0x6f, 0x53, 0x69, 0x67, 0x6e, 0x30, 0x82, 0x02, 0x22, 0x30, - 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, - 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, 0x30, 0x82, 0x02, 0x0a, 0x02, - 0x82, 0x02, 0x01, 0x00, 0xbd, 0xca, 0x8d, 0xac, 0xb8, 0x91, 0x15, 0x56, - 0x97, 0x7b, 0x6b, 0x5c, 0x7a, 0xc2, 0xde, 0x6b, 0xd9, 0xa1, 0xb0, 0xc3, - 0x10, 0x23, 0xfa, 0xa7, 0xa1, 0xb2, 0xcc, 0x31, 0xfa, 0x3e, 0xd9, 0xa6, - 0x29, 0x6f, 0x16, 0x3d, 0xe0, 0x6b, 0xf8, 0xb8, 0x40, 0x5f, 0xdb, 0x39, - 0xa8, 0x00, 0x7a, 0x8b, 0xa0, 0x4d, 0x54, 0x7d, 0xc2, 0x22, 0x78, 0xfc, - 0x8e, 0x09, 0xb8, 0xa8, 0x85, 0xd7, 0xcc, 0x95, 0x97, 0x4b, 0x74, 0xd8, - 0x9e, 0x7e, 0xf0, 0x00, 0xe4, 0x0e, 0x89, 0xae, 0x49, 0x28, 0x44, 0x1a, - 0x10, 0x99, 0x32, 0x0f, 0x25, 0x88, 0x53, 0xa4, 0x0d, 0xb3, 0x0f, 0x12, - 0x08, 0x16, 0x0b, 0x03, 0x71, 0x27, 0x1c, 0x7f, 0xe1, 0xdb, 0xd2, 0xfd, - 0x67, 0x68, 0xc4, 0x05, 0x5d, 0x0a, 0x0e, 0x5d, 0x70, 0xd7, 0xd8, 0x97, - 0xa0, 0xbc, 0x53, 0x41, 0x9a, 0x91, 0x8d, 0xf4, 0x9e, 0x36, 0x66, 0x7a, - 0x7e, 0x56, 0xc1, 0x90, 0x5f, 0xe6, 0xb1, 0x68, 0x20, 0x36, 0xa4, 0x8c, - 0x24, 0x2c, 0x2c, 0x47, 0x0b, 0x59, 0x76, 0x66, 0x30, 0xb5, 0xbe, 0xde, - 0xed, 0x8f, 0xf8, 0x9d, 0xd3, 0xbb, 0x01, 0x30, 0xe6, 0xf2, 0xf3, 0x0e, - 0xe0, 0x2c, 0x92, 0x80, 0xf3, 0x85, 0xf9, 0x28, 0x8a, 0xb4, 0x54, 0x2e, - 0x9a, 0xed, 0xf7, 0x76, 0xfc, 0x15, 0x68, 0x16, 0xeb, 0x4a, 0x6c, 0xeb, - 0x2e, 0x12, 0x8f, 0xd4, 0xcf, 0xfe, 0x0c, 0xc7, 0x5c, 0x1d, 0x0b, 0x7e, - 0x05, 0x32, 0xbe, 0x5e, 0xb0, 0x09, 0x2a, 0x42, 0xd5, 0xc9, 0x4e, 0x90, - 0xb3, 0x59, 0x0d, 0xbb, 0x7a, 0x7e, 0xcd, 0xd5, 0x08, 0x5a, 0xb4, 0x7f, - 0xd8, 0x1c, 0x69, 0x11, 0xf9, 0x27, 0x0f, 0x7b, 0x06, 0xaf, 0x54, 0x83, - 0x18, 0x7b, 0xe1, 0xdd, 0x54, 0x7a, 0x51, 0x68, 0x6e, 0x77, 0xfc, 0xc6, - 0xbf, 0x52, 0x4a, 0x66, 0x46, 0xa1, 0xb2, 0x67, 0x1a, 0xbb, 0xa3, 0x4f, - 0x77, 0xa0, 0xbe, 0x5d, 0xff, 0xfc, 0x56, 0x0b, 0x43, 0x72, 0x77, 0x90, - 0xca, 0x9e, 0xf9, 0xf2, 0x39, 0xf5, 0x0d, 0xa9, 0xf4, 0xea, 0xd7, 0xe7, - 0xb3, 0x10, 0x2f, 0x30, 0x42, 0x37, 0x21, 0xcc, 0x30, 0x70, 0xc9, 0x86, - 0x98, 0x0f, 0xcc, 0x58, 0x4d, 0x83, 0xbb, 0x7d, 0xe5, 0x1a, 0xa5, 0x37, - 0x8d, 0xb6, 0xac, 0x32, 0x97, 0x00, 0x3a, 0x63, 0x71, 0x24, 0x1e, 0x9e, - 0x37, 0xc4, 0xff, 0x74, 0xd4, 0x37, 0xc0, 0xe2, 0xfe, 0x88, 0x46, 0x60, - 0x11, 0xdd, 0x08, 0x3f, 0x50, 0x36, 0xab, 0xb8, 0x7a, 0xa4, 0x95, 0x62, - 0x6a, 0x6e, 0xb0, 0xca, 0x6a, 0x21, 0x5a, 0x69, 0xf3, 0xf3, 0xfb, 0x1d, - 0x70, 0x39, 0x95, 0xf3, 0xa7, 0x6e, 0xa6, 0x81, 0x89, 0xa1, 0x88, 0xc5, - 0x3b, 0x71, 0xca, 0xa3, 0x52, 0xee, 0x83, 0xbb, 0xfd, 0xa0, 0x77, 0xf4, - 0xe4, 0x6f, 0xe7, 0x42, 0xdb, 0x6d, 0x4a, 0x99, 0x8a, 0x34, 0x48, 0xbc, - 0x17, 0xdc, 0xe4, 0x80, 0x08, 0x22, 0xb6, 0xf2, 0x31, 0xc0, 0x3f, 0x04, - 0x3e, 0xeb, 0x9f, 0x20, 0x79, 0xd6, 0xb8, 0x06, 0x64, 0x64, 0x02, 0x31, - 0xd7, 0xa9, 0xcd, 0x52, 0xfb, 0x84, 0x45, 0x69, 0x09, 0x00, 0x2a, 0xdc, - 0x55, 0x8b, 0xc4, 0x06, 0x46, 0x4b, 0xc0, 0x4a, 0x1d, 0x09, 0x5b, 0x39, - 0x28, 0xfd, 0xa9, 0xab, 0xce, 0x00, 0xf9, 0x2e, 0x48, 0x4b, 0x26, 0xe6, - 0x30, 0x4c, 0xa5, 0x58, 0xca, 0xb4, 0x44, 0x82, 0x4f, 0xe7, 0x91, 0x1e, - 0x33, 0xc3, 0xb0, 0x93, 0xff, 0x11, 0xfc, 0x81, 0xd2, 0xca, 0x1f, 0x71, - 0x29, 0xdd, 0x76, 0x4f, 0x92, 0x25, 0xaf, 0x1d, 0x81, 0xb7, 0x0f, 0x2f, - 0x8c, 0xc3, 0x06, 0xcc, 0x2f, 0x27, 0xa3, 0x4a, 0xe4, 0x0e, 0x99, 0xba, - 0x7c, 0x1e, 0x45, 0x1f, 0x7f, 0xaa, 0x19, 0x45, 0x96, 0xfd, 0xfc, 0x3d, - 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x07, 0x30, 0x82, 0x01, - 0x03, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, - 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x02, 0x30, 0x0e, 0x06, - 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, - 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, - 0xe1, 0x66, 0xcf, 0x0e, 0xd1, 0xf1, 0xb3, 0x4b, 0xb7, 0x06, 0x20, 0x14, - 0xfe, 0x87, 0x12, 0xd5, 0xf6, 0xfe, 0xfb, 0x3e, 0x30, 0x1f, 0x06, 0x03, - 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x4e, 0x0b, 0xef, - 0x1a, 0xa4, 0x40, 0x5b, 0xa5, 0x17, 0x69, 0x87, 0x30, 0xca, 0x34, 0x68, - 0x43, 0xd0, 0x41, 0xae, 0xf2, 0x30, 0x69, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x5d, 0x30, 0x5b, 0x30, 0x27, 0x06, - 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x1b, 0x68, - 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x63, 0x61, 0x30, 0x30, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x30, 0x02, 0x86, 0x24, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x61, - 0x69, 0x61, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x73, 0x2f, 0x63, 0x61, - 0x2e, 0x63, 0x72, 0x74, 0x30, 0x32, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, - 0x2b, 0x30, 0x29, 0x30, 0x27, 0xa0, 0x25, 0xa0, 0x23, 0x86, 0x21, 0x68, - 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x73, 0x74, - 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, - 0x66, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, - 0x82, 0x02, 0x01, 0x00, 0xb6, 0x6d, 0xf8, 0x70, 0xfb, 0xe2, 0x0d, 0x4c, - 0x98, 0xb3, 0x07, 0x49, 0x15, 0xf5, 0x04, 0xc4, 0x6c, 0xca, 0xca, 0xf5, - 0x68, 0xa0, 0x08, 0xfe, 0x12, 0x6d, 0x9c, 0x04, 0x06, 0xc9, 0xad, 0x9a, - 0x91, 0x52, 0x3e, 0x78, 0xc4, 0x5c, 0xee, 0x9f, 0x54, 0x1d, 0xee, 0xe3, - 0xf1, 0x5e, 0x30, 0xc9, 0x49, 0xe1, 0x39, 0xe0, 0xa6, 0x9d, 0x36, 0x6c, - 0x57, 0xfa, 0xe6, 0x34, 0x4f, 0x55, 0xe8, 0x87, 0xa8, 0x2c, 0xdd, 0x05, - 0xf1, 0x58, 0x12, 0x91, 0xe8, 0xca, 0xce, 0x28, 0x78, 0x8f, 0xdf, 0x07, - 0x85, 0x01, 0xa5, 0xdc, 0x45, 0x96, 0x05, 0xd4, 0x80, 0xb2, 0x2b, 0x05, - 0x9a, 0xcb, 0x9a, 0xa5, 0x8b, 0xe0, 0x3a, 0x67, 0xe6, 0x73, 0x47, 0xbe, - 0x4a, 0xfd, 0x27, 0xb1, 0x88, 0xef, 0xe6, 0xca, 0xcf, 0x8d, 0x0e, 0x26, - 0x9f, 0xfa, 0x5f, 0x57, 0x78, 0xad, 0x6d, 0xfe, 0xae, 0x9b, 0x35, 0x08, - 0xb1, 0xc3, 0xba, 0xc1, 0x00, 0x4a, 0x4b, 0x7d, 0x14, 0xbd, 0xf7, 0xf1, - 0xd3, 0x55, 0x18, 0xac, 0xd0, 0x33, 0x70, 0x88, 0x6d, 0xc4, 0x09, 0x71, - 0x14, 0xa6, 0x2b, 0x4f, 0x88, 0x81, 0xe7, 0x0b, 0x00, 0x37, 0xa9, 0x15, - 0x7d, 0x7e, 0xd7, 0x01, 0x96, 0x3f, 0x2f, 0xaf, 0x7b, 0x62, 0xae, 0x0a, - 0x4a, 0xbf, 0x4b, 0x39, 0x2e, 0x35, 0x10, 0x8b, 0xfe, 0x04, 0x39, 0xe4, - 0x3c, 0x3a, 0x0c, 0x09, 0x56, 0x40, 0x3a, 0xb5, 0xf4, 0xc2, 0x68, 0x0c, - 0xb5, 0xf9, 0x52, 0xcd, 0xee, 0x9d, 0xf8, 0x98, 0xfc, 0x78, 0xe7, 0x58, - 0x47, 0x8f, 0x1c, 0x73, 0x58, 0x69, 0x33, 0xab, 0xff, 0xdd, 0xdf, 0x8e, - 0x24, 0x01, 0x77, 0x98, 0x19, 0x3a, 0xb0, 0x66, 0x79, 0xbc, 0xe1, 0x08, - 0xa3, 0x0e, 0x4f, 0xc1, 0x04, 0xb3, 0xf3, 0x01, 0xc8, 0xeb, 0xd3, 0x59, - 0x1c, 0x35, 0xd2, 0x93, 0x1e, 0x70, 0x65, 0x82, 0x7f, 0xdb, 0xcf, 0xfb, - 0xc8, 0x99, 0x12, 0x60, 0xc3, 0x44, 0x6f, 0x3a, 0x80, 0x4b, 0xd7, 0xbe, - 0x21, 0xaa, 0x14, 0x7a, 0x64, 0xcb, 0xdd, 0x37, 0x43, 0x45, 0x5b, 0x32, - 0x2e, 0x45, 0xf0, 0xd9, 0x59, 0x1f, 0x6b, 0x18, 0xf0, 0x7c, 0xe9, 0x55, - 0x36, 0x19, 0x61, 0x5f, 0xb5, 0x7d, 0xf1, 0x8d, 0xbd, 0x88, 0xe4, 0x75, - 0x4b, 0x98, 0xdd, 0x27, 0xb0, 0xe4, 0x84, 0x44, 0x2a, 0x61, 0x84, 0x57, - 0x05, 0x82, 0x11, 0x1f, 0xaa, 0x35, 0x58, 0xf3, 0x20, 0x0e, 0xaf, 0x59, - 0xef, 0xfa, 0x55, 0x72, 0x72, 0x0d, 0x26, 0xd0, 0x9b, 0x53, 0x49, 0xac, - 0xce, 0x37, 0x2e, 0x65, 0x61, 0xff, 0xf6, 0xec, 0x1b, 0xea, 0xf6, 0xf1, - 0xa6, 0xd3, 0xd1, 0xb5, 0x7b, 0xbe, 0x35, 0xf4, 0x22, 0xc1, 0xbc, 0x8d, - 0x01, 0xbd, 0x68, 0x5e, 0x83, 0x0d, 0x2f, 0xec, 0xd6, 0xda, 0x63, 0x0c, - 0x27, 0xd1, 0x54, 0x3e, 0xe4, 0xa8, 0xd3, 0xce, 0x4b, 0x32, 0xb8, 0x91, - 0x94, 0xff, 0xfb, 0x5b, 0x49, 0x2d, 0x75, 0x18, 0xa8, 0xba, 0x71, 0x9a, - 0x3b, 0xae, 0xd9, 0xc0, 0xa9, 0x4f, 0x87, 0x91, 0xed, 0x8b, 0x7b, 0x6b, - 0x20, 0x98, 0x89, 0x39, 0x83, 0x4f, 0x80, 0xc4, 0x69, 0xcc, 0x17, 0xc9, - 0xc8, 0x4e, 0xbe, 0xe4, 0xa9, 0xa5, 0x81, 0x76, 0x70, 0x06, 0x04, 0x32, - 0xcd, 0x83, 0x65, 0xf4, 0xbc, 0x7d, 0x3e, 0x13, 0xbc, 0xd2, 0xe8, 0x6f, - 0x63, 0xaa, 0xb5, 0x3b, 0xda, 0x8d, 0x86, 0x32, 0x82, 0x78, 0x9d, 0xd9, - 0xcc, 0xff, 0xbf, 0x57, 0x64, 0x74, 0xed, 0x28, 0x3d, 0x44, 0x62, 0x15, - 0x61, 0x4b, 0xf7, 0x94, 0xb0, 0x0d, 0x2a, 0x67, 0x1c, 0xf0, 0xcb, 0x9b, - 0xa5, 0x92, 0xbf, 0xf8, 0x41, 0x5a, 0xc1, 0x3d, 0x60, 0xed, 0x9f, 0xbb, - 0xb8, 0x6d, 0x9b, 0xce, 0xa9, 0x6a, 0x16, 0x3f, 0x7e, 0xea, 0x06, 0xf1, -} diff --git a/vendor/github.com/lucas-clemente/quic-go/LICENSE b/vendor/github.com/lucas-clemente/quic-go/LICENSE deleted file mode 100644 index 51378befb8e..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2016 the quic-go authors & Google, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/lucas-clemente/quic-go/ackhandler/interfaces.go b/vendor/github.com/lucas-clemente/quic-go/ackhandler/interfaces.go deleted file mode 100644 index e100264a18f..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/ackhandler/interfaces.go +++ /dev/null @@ -1,32 +0,0 @@ -package ackhandler - -import ( - "time" - - "github.com/lucas-clemente/quic-go/frames" - "github.com/lucas-clemente/quic-go/protocol" -) - -// SentPacketHandler handles ACKs received for outgoing packets -type SentPacketHandler interface { - // SentPacket may modify the packet - SentPacket(packet *Packet) error - ReceivedAck(ackFrame *frames.AckFrame, withPacketNumber protocol.PacketNumber, recvTime time.Time) error - - SendingAllowed() bool - GetStopWaitingFrame(force bool) *frames.StopWaitingFrame - DequeuePacketForRetransmission() (packet *Packet) - GetLeastUnacked() protocol.PacketNumber - - GetAlarmTimeout() time.Time - OnAlarm() -} - -// ReceivedPacketHandler handles ACKs needed to send for incoming packets -type ReceivedPacketHandler interface { - ReceivedPacket(packetNumber protocol.PacketNumber, shouldInstigateAck bool) error - ReceivedStopWaiting(*frames.StopWaitingFrame) error - - GetAlarmTimeout() time.Time - GetAckFrame() *frames.AckFrame -} diff --git a/vendor/github.com/lucas-clemente/quic-go/ackhandler/packet.go b/vendor/github.com/lucas-clemente/quic-go/ackhandler/packet.go deleted file mode 100644 index e9dbf6ab49d..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/ackhandler/packet.go +++ /dev/null @@ -1,34 +0,0 @@ -package ackhandler - -import ( - "time" - - "github.com/lucas-clemente/quic-go/frames" - "github.com/lucas-clemente/quic-go/protocol" -) - -// A Packet is a packet -// +gen linkedlist -type Packet struct { - PacketNumber protocol.PacketNumber - Frames []frames.Frame - Length protocol.ByteCount - EncryptionLevel protocol.EncryptionLevel - - SendTime time.Time -} - -// GetFramesForRetransmission gets all the frames for retransmission -func (p *Packet) GetFramesForRetransmission() []frames.Frame { - var fs []frames.Frame - for _, frame := range p.Frames { - switch frame.(type) { - case *frames.AckFrame: - continue - case *frames.StopWaitingFrame: - continue - } - fs = append(fs, frame) - } - return fs -} diff --git a/vendor/github.com/lucas-clemente/quic-go/ackhandler/packet_linkedlist.go b/vendor/github.com/lucas-clemente/quic-go/ackhandler/packet_linkedlist.go deleted file mode 100644 index a827b214eb5..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/ackhandler/packet_linkedlist.go +++ /dev/null @@ -1,214 +0,0 @@ -// Generated by: main -// TypeWriter: linkedlist -// Directive: +gen on Packet - -package ackhandler - -// List is a modification of http://golang.org/pkg/container/list/ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// PacketElement is an element of a linked list. -type PacketElement struct { - // Next and previous pointers in the doubly-linked list of elements. - // To simplify the implementation, internally a list l is implemented - // as a ring, such that &l.root is both the next element of the last - // list element (l.Back()) and the previous element of the first list - // element (l.Front()). - next, prev *PacketElement - - // The list to which this element belongs. - list *PacketList - - // The value stored with this element. - Value Packet -} - -// Next returns the next list element or nil. -func (e *PacketElement) Next() *PacketElement { - if p := e.next; e.list != nil && p != &e.list.root { - return p - } - return nil -} - -// Prev returns the previous list element or nil. -func (e *PacketElement) Prev() *PacketElement { - if p := e.prev; e.list != nil && p != &e.list.root { - return p - } - return nil -} - -// PacketList represents a doubly linked list. -// The zero value for PacketList is an empty list ready to use. -type PacketList struct { - root PacketElement // sentinel list element, only &root, root.prev, and root.next are used - len int // current list length excluding (this) sentinel element -} - -// Init initializes or clears list l. -func (l *PacketList) Init() *PacketList { - l.root.next = &l.root - l.root.prev = &l.root - l.len = 0 - return l -} - -// NewPacketList returns an initialized list. -func NewPacketList() *PacketList { return new(PacketList).Init() } - -// Len returns the number of elements of list l. -// The complexity is O(1). -func (l *PacketList) Len() int { return l.len } - -// Front returns the first element of list l or nil. -func (l *PacketList) Front() *PacketElement { - if l.len == 0 { - return nil - } - return l.root.next -} - -// Back returns the last element of list l or nil. -func (l *PacketList) Back() *PacketElement { - if l.len == 0 { - return nil - } - return l.root.prev -} - -// lazyInit lazily initializes a zero PacketList value. -func (l *PacketList) lazyInit() { - if l.root.next == nil { - l.Init() - } -} - -// insert inserts e after at, increments l.len, and returns e. -func (l *PacketList) insert(e, at *PacketElement) *PacketElement { - n := at.next - at.next = e - e.prev = at - e.next = n - n.prev = e - e.list = l - l.len++ - return e -} - -// insertValue is a convenience wrapper for insert(&PacketElement{Value: v}, at). -func (l *PacketList) insertValue(v Packet, at *PacketElement) *PacketElement { - return l.insert(&PacketElement{Value: v}, at) -} - -// remove removes e from its list, decrements l.len, and returns e. -func (l *PacketList) remove(e *PacketElement) *PacketElement { - e.prev.next = e.next - e.next.prev = e.prev - e.next = nil // avoid memory leaks - e.prev = nil // avoid memory leaks - e.list = nil - l.len-- - return e -} - -// Remove removes e from l if e is an element of list l. -// It returns the element value e.Value. -func (l *PacketList) Remove(e *PacketElement) Packet { - if e.list == l { - // if e.list == l, l must have been initialized when e was inserted - // in l or l == nil (e is a zero PacketElement) and l.remove will crash - l.remove(e) - } - return e.Value -} - -// PushFront inserts a new element e with value v at the front of list l and returns e. -func (l *PacketList) PushFront(v Packet) *PacketElement { - l.lazyInit() - return l.insertValue(v, &l.root) -} - -// PushBack inserts a new element e with value v at the back of list l and returns e. -func (l *PacketList) PushBack(v Packet) *PacketElement { - l.lazyInit() - return l.insertValue(v, l.root.prev) -} - -// InsertBefore inserts a new element e with value v immediately before mark and returns e. -// If mark is not an element of l, the list is not modified. -func (l *PacketList) InsertBefore(v Packet, mark *PacketElement) *PacketElement { - if mark.list != l { - return nil - } - // see comment in PacketList.Remove about initialization of l - return l.insertValue(v, mark.prev) -} - -// InsertAfter inserts a new element e with value v immediately after mark and returns e. -// If mark is not an element of l, the list is not modified. -func (l *PacketList) InsertAfter(v Packet, mark *PacketElement) *PacketElement { - if mark.list != l { - return nil - } - // see comment in PacketList.Remove about initialization of l - return l.insertValue(v, mark) -} - -// MoveToFront moves element e to the front of list l. -// If e is not an element of l, the list is not modified. -func (l *PacketList) MoveToFront(e *PacketElement) { - if e.list != l || l.root.next == e { - return - } - // see comment in PacketList.Remove about initialization of l - l.insert(l.remove(e), &l.root) -} - -// MoveToBack moves element e to the back of list l. -// If e is not an element of l, the list is not modified. -func (l *PacketList) MoveToBack(e *PacketElement) { - if e.list != l || l.root.prev == e { - return - } - // see comment in PacketList.Remove about initialization of l - l.insert(l.remove(e), l.root.prev) -} - -// MoveBefore moves element e to its new position before mark. -// If e or mark is not an element of l, or e == mark, the list is not modified. -func (l *PacketList) MoveBefore(e, mark *PacketElement) { - if e.list != l || e == mark || mark.list != l { - return - } - l.insert(l.remove(e), mark.prev) -} - -// MoveAfter moves element e to its new position after mark. -// If e is not an element of l, or e == mark, the list is not modified. -func (l *PacketList) MoveAfter(e, mark *PacketElement) { - if e.list != l || e == mark || mark.list != l { - return - } - l.insert(l.remove(e), mark) -} - -// PushBackList inserts a copy of an other list at the back of list l. -// The lists l and other may be the same. -func (l *PacketList) PushBackList(other *PacketList) { - l.lazyInit() - for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() { - l.insertValue(e.Value, l.root.prev) - } -} - -// PushFrontList inserts a copy of an other list at the front of list l. -// The lists l and other may be the same. -func (l *PacketList) PushFrontList(other *PacketList) { - l.lazyInit() - for i, e := other.Len(), other.Back(); i > 0; i, e = i-1, e.Prev() { - l.insertValue(e.Value, &l.root) - } -} diff --git a/vendor/github.com/lucas-clemente/quic-go/ackhandler/received_packet_handler.go b/vendor/github.com/lucas-clemente/quic-go/ackhandler/received_packet_handler.go deleted file mode 100644 index c5e9dc29998..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/ackhandler/received_packet_handler.go +++ /dev/null @@ -1,139 +0,0 @@ -package ackhandler - -import ( - "errors" - "time" - - "github.com/lucas-clemente/quic-go/frames" - "github.com/lucas-clemente/quic-go/protocol" -) - -var errInvalidPacketNumber = errors.New("ReceivedPacketHandler: Invalid packet number") - -type receivedPacketHandler struct { - largestObserved protocol.PacketNumber - ignorePacketsBelow protocol.PacketNumber - largestObservedReceivedTime time.Time - - packetHistory *receivedPacketHistory - - ackSendDelay time.Duration - - packetsReceivedSinceLastAck int - retransmittablePacketsReceivedSinceLastAck int - ackQueued bool - ackAlarm time.Time - lastAck *frames.AckFrame -} - -// NewReceivedPacketHandler creates a new receivedPacketHandler -func NewReceivedPacketHandler() ReceivedPacketHandler { - return &receivedPacketHandler{ - packetHistory: newReceivedPacketHistory(), - ackSendDelay: protocol.AckSendDelay, - } -} - -func (h *receivedPacketHandler) ReceivedPacket(packetNumber protocol.PacketNumber, shouldInstigateAck bool) error { - if packetNumber == 0 { - return errInvalidPacketNumber - } - - if packetNumber > h.ignorePacketsBelow { - if err := h.packetHistory.ReceivedPacket(packetNumber); err != nil { - return err - } - } - - if packetNumber > h.largestObserved { - h.largestObserved = packetNumber - h.largestObservedReceivedTime = time.Now() - } - - h.maybeQueueAck(packetNumber, shouldInstigateAck) - return nil -} - -func (h *receivedPacketHandler) ReceivedStopWaiting(f *frames.StopWaitingFrame) error { - // ignore if StopWaiting is unneeded, because we already received a StopWaiting with a higher LeastUnacked - if h.ignorePacketsBelow >= f.LeastUnacked { - return nil - } - - h.ignorePacketsBelow = f.LeastUnacked - 1 - - h.packetHistory.DeleteBelow(f.LeastUnacked) - return nil -} - -func (h *receivedPacketHandler) maybeQueueAck(packetNumber protocol.PacketNumber, shouldInstigateAck bool) { - h.packetsReceivedSinceLastAck++ - - if shouldInstigateAck { - h.retransmittablePacketsReceivedSinceLastAck++ - } - - // always ack the first packet - if h.lastAck == nil { - h.ackQueued = true - } - - // Always send an ack every 20 packets in order to allow the peer to discard - // information from the SentPacketManager and provide an RTT measurement. - if h.packetsReceivedSinceLastAck >= protocol.MaxPacketsReceivedBeforeAckSend { - h.ackQueued = true - } - - // if the packet number is smaller than the largest acked packet, it must have been reported missing with the last ACK - // note that it cannot be a duplicate because they're already filtered out by ReceivedPacket() - if h.lastAck != nil && packetNumber < h.lastAck.LargestAcked { - h.ackQueued = true - } - - // check if a new missing range above the previously was created - if h.lastAck != nil && h.packetHistory.GetHighestAckRange().FirstPacketNumber > h.lastAck.LargestAcked { - h.ackQueued = true - } - - if !h.ackQueued && shouldInstigateAck { - if h.retransmittablePacketsReceivedSinceLastAck >= protocol.RetransmittablePacketsBeforeAck { - h.ackQueued = true - } else { - if h.ackAlarm.IsZero() { - h.ackAlarm = time.Now().Add(h.ackSendDelay) - } - } - } - - if h.ackQueued { - // cancel the ack alarm - h.ackAlarm = time.Time{} - } -} - -func (h *receivedPacketHandler) GetAckFrame() *frames.AckFrame { - if !h.ackQueued && (h.ackAlarm.IsZero() || h.ackAlarm.After(time.Now())) { - return nil - } - - ackRanges := h.packetHistory.GetAckRanges() - ack := &frames.AckFrame{ - LargestAcked: h.largestObserved, - LowestAcked: ackRanges[len(ackRanges)-1].FirstPacketNumber, - PacketReceivedTime: h.largestObservedReceivedTime, - } - - if len(ackRanges) > 1 { - ack.AckRanges = ackRanges - } - - h.lastAck = ack - h.ackAlarm = time.Time{} - h.ackQueued = false - h.packetsReceivedSinceLastAck = 0 - h.retransmittablePacketsReceivedSinceLastAck = 0 - - return ack -} - -func (h *receivedPacketHandler) GetAlarmTimeout() time.Time { return h.ackAlarm } diff --git a/vendor/github.com/lucas-clemente/quic-go/ackhandler/received_packet_history.go b/vendor/github.com/lucas-clemente/quic-go/ackhandler/received_packet_history.go deleted file mode 100644 index 791dec17fef..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/ackhandler/received_packet_history.go +++ /dev/null @@ -1,145 +0,0 @@ -package ackhandler - -import ( - "github.com/lucas-clemente/quic-go/frames" - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" - "github.com/lucas-clemente/quic-go/qerr" -) - -type receivedPacketHistory struct { - ranges *utils.PacketIntervalList - - // the map is used as a replacement for a set here. The bool is always supposed to be set to true - receivedPacketNumbers map[protocol.PacketNumber]bool - lowestInReceivedPacketNumbers protocol.PacketNumber -} - -var ( - errTooManyOutstandingReceivedAckRanges = qerr.Error(qerr.TooManyOutstandingReceivedPackets, "Too many outstanding received ACK ranges") - errTooManyOutstandingReceivedPackets = qerr.Error(qerr.TooManyOutstandingReceivedPackets, "Too many outstanding received packets") -) - -// newReceivedPacketHistory creates a new received packet history -func newReceivedPacketHistory() *receivedPacketHistory { - return &receivedPacketHistory{ - ranges: utils.NewPacketIntervalList(), - receivedPacketNumbers: make(map[protocol.PacketNumber]bool), - } -} - -// ReceivedPacket registers a packet with PacketNumber p and updates the ranges -func (h *receivedPacketHistory) ReceivedPacket(p protocol.PacketNumber) error { - if h.ranges.Len() >= protocol.MaxTrackedReceivedAckRanges { - return errTooManyOutstandingReceivedAckRanges - } - - if len(h.receivedPacketNumbers) >= protocol.MaxTrackedReceivedPackets { - return errTooManyOutstandingReceivedPackets - } - - h.receivedPacketNumbers[p] = true - - if h.ranges.Len() == 0 { - h.ranges.PushBack(utils.PacketInterval{Start: p, End: p}) - return nil - } - - for el := h.ranges.Back(); el != nil; el = el.Prev() { - // p already included in an existing range. Nothing to do here - if p >= el.Value.Start && p <= el.Value.End { - return nil - } - - var rangeExtended bool - if el.Value.End == p-1 { // extend a range at the end - rangeExtended = true - el.Value.End = p - } else if el.Value.Start == p+1 { // extend a range at the beginning - rangeExtended = true - el.Value.Start = p - } - - // if a range was extended (either at the beginning or at the end, maybe it is possible to merge two ranges into one) - if rangeExtended { - prev := el.Prev() - if prev != nil && prev.Value.End+1 == el.Value.Start { // merge two ranges - prev.Value.End = el.Value.End - h.ranges.Remove(el) - return nil - } - return nil // if the two ranges were not merge, we're done here - } - - // create a new range at the end - if p > el.Value.End { - h.ranges.InsertAfter(utils.PacketInterval{Start: p, End: p}, el) - return nil - } - } - - // create a new range at the beginning - h.ranges.InsertBefore(utils.PacketInterval{Start: p, End: p}, h.ranges.Front()) - - return nil -} - -// DeleteBelow deletes all entries below the leastUnacked packet number -func (h *receivedPacketHistory) DeleteBelow(leastUnacked protocol.PacketNumber) { - h.lowestInReceivedPacketNumbers = utils.MaxPacketNumber(h.lowestInReceivedPacketNumbers, leastUnacked) - - nextEl := h.ranges.Front() - for el := h.ranges.Front(); nextEl != nil; el = nextEl { - nextEl = el.Next() - - if leastUnacked > el.Value.Start && leastUnacked <= el.Value.End { - for i := el.Value.Start; i < leastUnacked; i++ { // adjust start value of a range - delete(h.receivedPacketNumbers, i) - } - el.Value.Start = leastUnacked - } else if el.Value.End < leastUnacked { // delete a whole range - for i := el.Value.Start; i <= el.Value.End; i++ { - delete(h.receivedPacketNumbers, i) - } - h.ranges.Remove(el) - } else { // no ranges affected. Nothing to do - return - } - } -} - -// IsDuplicate determines if a packet should be regarded as a duplicate packet -// note that after receiving a StopWaitingFrame, all packets below the LeastUnacked should be regarded as duplicates, even if the packet was just delayed -func (h *receivedPacketHistory) IsDuplicate(p protocol.PacketNumber) bool { - if p < h.lowestInReceivedPacketNumbers { - return true - } - - _, ok := h.receivedPacketNumbers[p] - return ok -} - -// GetAckRanges gets a slice of all AckRanges that can be used in an AckFrame -func (h *receivedPacketHistory) GetAckRanges() []frames.AckRange { - if h.ranges.Len() == 0 { - return nil - } - - var ackRanges []frames.AckRange - - for el := h.ranges.Back(); el != nil; el = el.Prev() { - ackRanges = append(ackRanges, frames.AckRange{FirstPacketNumber: el.Value.Start, LastPacketNumber: el.Value.End}) - } - - return ackRanges -} - -func (h *receivedPacketHistory) GetHighestAckRange() frames.AckRange { - ackRange := frames.AckRange{} - if h.ranges.Len() > 0 { - r := h.ranges.Back().Value - ackRange.FirstPacketNumber = r.Start - ackRange.LastPacketNumber = r.End - } - return ackRange -} diff --git a/vendor/github.com/lucas-clemente/quic-go/ackhandler/retransmittable.go b/vendor/github.com/lucas-clemente/quic-go/ackhandler/retransmittable.go deleted file mode 100644 index 17437b8c916..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/ackhandler/retransmittable.go +++ /dev/null @@ -1,38 +0,0 @@ -package ackhandler - -import ( - "github.com/lucas-clemente/quic-go/frames" -) - -// Returns a new slice with all non-retransmittable frames deleted. -func stripNonRetransmittableFrames(fs []frames.Frame) []frames.Frame { - res := make([]frames.Frame, 0, len(fs)) - for _, f := range fs { - if IsFrameRetransmittable(f) { - res = append(res, f) - } - } - return res -} - -// IsFrameRetransmittable returns true if the frame should be retransmitted. -func IsFrameRetransmittable(f frames.Frame) bool { - switch f.(type) { - case *frames.StopWaitingFrame: - return false - case *frames.AckFrame: - return false - default: - return true - } -} - -// HasRetransmittableFrames returns true if at least one frame is retransmittable. -func HasRetransmittableFrames(fs []frames.Frame) bool { - for _, f := range fs { - if IsFrameRetransmittable(f) { - return true - } - } - return false -} diff --git a/vendor/github.com/lucas-clemente/quic-go/ackhandler/sent_packet_handler.go b/vendor/github.com/lucas-clemente/quic-go/ackhandler/sent_packet_handler.go deleted file mode 100644 index 300b665ed91..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/ackhandler/sent_packet_handler.go +++ /dev/null @@ -1,403 +0,0 @@ -package ackhandler - -import ( - "errors" - "fmt" - "time" - - "github.com/lucas-clemente/quic-go/congestion" - "github.com/lucas-clemente/quic-go/frames" - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" - "github.com/lucas-clemente/quic-go/qerr" -) - -const ( - // Maximum reordering in time space before time based loss detection considers a packet lost. - // In fraction of an RTT. - timeReorderingFraction = 1.0 / 8 - // defaultRTOTimeout is the RTO time on new connections - defaultRTOTimeout = 500 * time.Millisecond - // Minimum time in the future an RTO alarm may be set for. - minRTOTimeout = 200 * time.Millisecond - // maxRTOTimeout is the maximum RTO time - maxRTOTimeout = 60 * time.Second -) - -var ( - // ErrDuplicateOrOutOfOrderAck occurs when a duplicate or an out-of-order ACK is received - ErrDuplicateOrOutOfOrderAck = errors.New("SentPacketHandler: Duplicate or out-of-order ACK") - // ErrTooManyTrackedSentPackets occurs when the sentPacketHandler has to keep track of too many packets - ErrTooManyTrackedSentPackets = errors.New("Too many outstanding non-acked and non-retransmitted packets") - // ErrAckForSkippedPacket occurs when the client sent an ACK for a packet number that we intentionally skipped - ErrAckForSkippedPacket = qerr.Error(qerr.InvalidAckData, "Received an ACK for a skipped packet number") - errAckForUnsentPacket = qerr.Error(qerr.InvalidAckData, "Received ACK for an unsent package") -) - -var errPacketNumberNotIncreasing = errors.New("Already sent a packet with a higher packet number") - -type sentPacketHandler struct { - lastSentPacketNumber protocol.PacketNumber - skippedPackets []protocol.PacketNumber - - LargestAcked protocol.PacketNumber - - largestReceivedPacketWithAck protocol.PacketNumber - - packetHistory *PacketList - stopWaitingManager stopWaitingManager - - retransmissionQueue []*Packet - - bytesInFlight protocol.ByteCount - - congestion congestion.SendAlgorithm - rttStats *congestion.RTTStats - - // The number of times an RTO has been sent without receiving an ack. - rtoCount uint32 - - // The time at which the next packet will be considered lost based on early transmit or exceeding the reordering window in time. - lossTime time.Time - - // The alarm timeout - alarm time.Time -} - -// NewSentPacketHandler creates a new sentPacketHandler -func NewSentPacketHandler(rttStats *congestion.RTTStats) SentPacketHandler { - congestion := congestion.NewCubicSender( - congestion.DefaultClock{}, - rttStats, - false, /* don't use reno since chromium doesn't (why?) */ - protocol.InitialCongestionWindow, - protocol.DefaultMaxCongestionWindow, - ) - - return &sentPacketHandler{ - packetHistory: NewPacketList(), - stopWaitingManager: stopWaitingManager{}, - rttStats: rttStats, - congestion: congestion, - } -} - -func (h *sentPacketHandler) largestInOrderAcked() protocol.PacketNumber { - if f := h.packetHistory.Front(); f != nil { - return f.Value.PacketNumber - 1 - } - return h.LargestAcked -} - -func (h *sentPacketHandler) SentPacket(packet *Packet) error { - if packet.PacketNumber <= h.lastSentPacketNumber { - return errPacketNumberNotIncreasing - } - - if protocol.PacketNumber(len(h.retransmissionQueue)+h.packetHistory.Len()+1) > protocol.MaxTrackedSentPackets { - return ErrTooManyTrackedSentPackets - } - - for p := h.lastSentPacketNumber + 1; p < packet.PacketNumber; p++ { - h.skippedPackets = append(h.skippedPackets, p) - - if len(h.skippedPackets) > protocol.MaxTrackedSkippedPackets { - h.skippedPackets = h.skippedPackets[1:] - } - } - - h.lastSentPacketNumber = packet.PacketNumber - now := time.Now() - - packet.Frames = stripNonRetransmittableFrames(packet.Frames) - isRetransmittable := len(packet.Frames) != 0 - - if isRetransmittable { - packet.SendTime = now - h.bytesInFlight += packet.Length - h.packetHistory.PushBack(*packet) - } - - h.congestion.OnPacketSent( - now, - h.bytesInFlight, - packet.PacketNumber, - packet.Length, - isRetransmittable, - ) - - h.updateLossDetectionAlarm() - return nil -} - -func (h *sentPacketHandler) ReceivedAck(ackFrame *frames.AckFrame, withPacketNumber protocol.PacketNumber, rcvTime time.Time) error { - if ackFrame.LargestAcked > h.lastSentPacketNumber { - return errAckForUnsentPacket - } - - // duplicate or out-of-order ACK - if withPacketNumber <= h.largestReceivedPacketWithAck { - return ErrDuplicateOrOutOfOrderAck - } - h.largestReceivedPacketWithAck = withPacketNumber - - // ignore repeated ACK (ACKs that don't have a higher LargestAcked than the last ACK) - if ackFrame.LargestAcked <= h.largestInOrderAcked() { - return nil - } - h.LargestAcked = ackFrame.LargestAcked - - if h.skippedPacketsAcked(ackFrame) { - return ErrAckForSkippedPacket - } - - rttUpdated := h.maybeUpdateRTT(ackFrame.LargestAcked, ackFrame.DelayTime, rcvTime) - - if rttUpdated { - h.congestion.MaybeExitSlowStart() - } - - ackedPackets, err := h.determineNewlyAckedPackets(ackFrame) - if err != nil { - return err - } - - if len(ackedPackets) > 0 { - for _, p := range ackedPackets { - h.onPacketAcked(p) - h.congestion.OnPacketAcked(p.Value.PacketNumber, p.Value.Length, h.bytesInFlight) - } - } - - h.detectLostPackets() - h.updateLossDetectionAlarm() - - h.garbageCollectSkippedPackets() - h.stopWaitingManager.ReceivedAck(ackFrame) - - return nil -} - -func (h *sentPacketHandler) determineNewlyAckedPackets(ackFrame *frames.AckFrame) ([]*PacketElement, error) { - var ackedPackets []*PacketElement - ackRangeIndex := 0 - for el := h.packetHistory.Front(); el != nil; el = el.Next() { - packet := el.Value - packetNumber := packet.PacketNumber - - // Ignore packets below the LowestAcked - if packetNumber < ackFrame.LowestAcked { - continue - } - // Break after LargestAcked is reached - if packetNumber > ackFrame.LargestAcked { - break - } - - if ackFrame.HasMissingRanges() { - ackRange := ackFrame.AckRanges[len(ackFrame.AckRanges)-1-ackRangeIndex] - - for packetNumber > ackRange.LastPacketNumber && ackRangeIndex < len(ackFrame.AckRanges)-1 { - ackRangeIndex++ - ackRange = ackFrame.AckRanges[len(ackFrame.AckRanges)-1-ackRangeIndex] - } - - if packetNumber >= ackRange.FirstPacketNumber { // packet i contained in ACK range - if packetNumber > ackRange.LastPacketNumber { - return nil, fmt.Errorf("BUG: ackhandler would have acked wrong packet 0x%x, while evaluating range 0x%x -> 0x%x", packetNumber, ackRange.FirstPacketNumber, ackRange.LastPacketNumber) - } - ackedPackets = append(ackedPackets, el) - } - } else { - ackedPackets = append(ackedPackets, el) - } - } - - return ackedPackets, nil -} - -func (h *sentPacketHandler) maybeUpdateRTT(largestAcked protocol.PacketNumber, ackDelay time.Duration, rcvTime time.Time) bool { - for el := h.packetHistory.Front(); el != nil; el = el.Next() { - packet := el.Value - if packet.PacketNumber == largestAcked { - h.rttStats.UpdateRTT(rcvTime.Sub(packet.SendTime), ackDelay, time.Now()) - return true - } - // Packets are sorted by number, so we can stop searching - if packet.PacketNumber > largestAcked { - break - } - } - return false -} - -func (h *sentPacketHandler) updateLossDetectionAlarm() { - // Cancel the alarm if no packets are outstanding - if h.packetHistory.Len() == 0 { - h.alarm = time.Time{} - return - } - - // TODO(#496): Handle handshake packets separately - // TODO(#497): TLP - if !h.lossTime.IsZero() { - // Early retransmit timer or time loss detection. - h.alarm = h.lossTime - } else { - // RTO - h.alarm = time.Now().Add(h.computeRTOTimeout()) - } -} - -func (h *sentPacketHandler) detectLostPackets() { - h.lossTime = time.Time{} - now := time.Now() - - maxRTT := float64(utils.MaxDuration(h.rttStats.LatestRTT(), h.rttStats.SmoothedRTT())) - delayUntilLost := time.Duration((1.0 + timeReorderingFraction) * maxRTT) - - var lostPackets []*PacketElement - for el := h.packetHistory.Front(); el != nil; el = el.Next() { - packet := el.Value - - if packet.PacketNumber > h.LargestAcked { - break - } - - timeSinceSent := now.Sub(packet.SendTime) - if timeSinceSent > delayUntilLost { - lostPackets = append(lostPackets, el) - } else if h.lossTime.IsZero() { - // Note: This conditional is only entered once per call - h.lossTime = now.Add(delayUntilLost - timeSinceSent) - } - } - - if len(lostPackets) > 0 { - for _, p := range lostPackets { - h.queuePacketForRetransmission(p) - h.congestion.OnPacketLost(p.Value.PacketNumber, p.Value.Length, h.bytesInFlight) - } - } -} - -func (h *sentPacketHandler) OnAlarm() { - // TODO(#496): Handle handshake packets separately - // TODO(#497): TLP - if !h.lossTime.IsZero() { - // Early retransmit or time loss detection - h.detectLostPackets() - } else { - // RTO - h.retransmitOldestTwoPackets() - h.rtoCount++ - } - - h.updateLossDetectionAlarm() -} - -func (h *sentPacketHandler) GetAlarmTimeout() time.Time { - return h.alarm -} - -func (h *sentPacketHandler) onPacketAcked(packetElement *PacketElement) { - h.bytesInFlight -= packetElement.Value.Length - h.rtoCount = 0 - // TODO(#497): h.tlpCount = 0 - h.packetHistory.Remove(packetElement) -} - -func (h *sentPacketHandler) DequeuePacketForRetransmission() *Packet { - if len(h.retransmissionQueue) == 0 { - return nil - } - packet := h.retransmissionQueue[0] - // Shift the slice and don't retain anything that isn't needed. - copy(h.retransmissionQueue, h.retransmissionQueue[1:]) - h.retransmissionQueue[len(h.retransmissionQueue)-1] = nil - h.retransmissionQueue = h.retransmissionQueue[:len(h.retransmissionQueue)-1] - return packet -} - -func (h *sentPacketHandler) GetLeastUnacked() protocol.PacketNumber { - return h.largestInOrderAcked() + 1 -} - -func (h *sentPacketHandler) GetStopWaitingFrame(force bool) *frames.StopWaitingFrame { - return h.stopWaitingManager.GetStopWaitingFrame(force) -} - -func (h *sentPacketHandler) SendingAllowed() bool { - congestionLimited := h.bytesInFlight > h.congestion.GetCongestionWindow() - maxTrackedLimited := protocol.PacketNumber(len(h.retransmissionQueue)+h.packetHistory.Len()) >= protocol.MaxTrackedSentPackets - if congestionLimited { - utils.Debugf("Congestion limited: bytes in flight %d, window %d", - h.bytesInFlight, - h.congestion.GetCongestionWindow()) - } - // Workaround for #555: - // Always allow sending of retransmissions. This should probably be limited - // to RTOs, but we currently don't have a nice way of distinguishing them. - haveRetransmissions := len(h.retransmissionQueue) > 0 - return !maxTrackedLimited && (!congestionLimited || haveRetransmissions) -} - -func (h *sentPacketHandler) retransmitOldestTwoPackets() { - if p := h.packetHistory.Front(); p != nil { - h.queueRTO(p) - } - if p := h.packetHistory.Front(); p != nil { - h.queueRTO(p) - } -} - -func (h *sentPacketHandler) queueRTO(el *PacketElement) { - packet := &el.Value - utils.Debugf( - "\tQueueing packet 0x%x for retransmission (RTO), %d outstanding", - packet.PacketNumber, - h.packetHistory.Len(), - ) - h.queuePacketForRetransmission(el) - h.congestion.OnPacketLost(packet.PacketNumber, packet.Length, h.bytesInFlight) - h.congestion.OnRetransmissionTimeout(true) -} - -func (h *sentPacketHandler) queuePacketForRetransmission(packetElement *PacketElement) { - packet := &packetElement.Value - h.bytesInFlight -= packet.Length - h.retransmissionQueue = append(h.retransmissionQueue, packet) - h.packetHistory.Remove(packetElement) - h.stopWaitingManager.QueuedRetransmissionForPacketNumber(packet.PacketNumber) -} - -func (h *sentPacketHandler) computeRTOTimeout() time.Duration { - rto := h.congestion.RetransmissionDelay() - if rto == 0 { - rto = defaultRTOTimeout - } - rto = utils.MaxDuration(rto, minRTOTimeout) - // Exponential backoff - rto = rto << h.rtoCount - return utils.MinDuration(rto, maxRTOTimeout) -} - -func (h *sentPacketHandler) skippedPacketsAcked(ackFrame *frames.AckFrame) bool { - for _, p := range h.skippedPackets { - if ackFrame.AcksPacket(p) { - return true - } - } - return false -} - -func (h *sentPacketHandler) garbageCollectSkippedPackets() { - lioa := h.largestInOrderAcked() - deleteIndex := 0 - for i, p := range h.skippedPackets { - if p <= lioa { - deleteIndex = i + 1 - } - } - h.skippedPackets = h.skippedPackets[deleteIndex:] -} diff --git a/vendor/github.com/lucas-clemente/quic-go/ackhandler/stop_waiting_manager.go b/vendor/github.com/lucas-clemente/quic-go/ackhandler/stop_waiting_manager.go deleted file mode 100644 index dfd79ae0feb..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/ackhandler/stop_waiting_manager.go +++ /dev/null @@ -1,42 +0,0 @@ -package ackhandler - -import ( - "github.com/lucas-clemente/quic-go/frames" - "github.com/lucas-clemente/quic-go/protocol" -) - -// This stopWaitingManager is not supposed to satisfy the StopWaitingManager interface, which is a remnant of the legacy AckHandler, and should be remove once we drop support for QUIC 33 -type stopWaitingManager struct { - largestLeastUnackedSent protocol.PacketNumber - nextLeastUnacked protocol.PacketNumber - - lastStopWaitingFrame *frames.StopWaitingFrame -} - -func (s *stopWaitingManager) GetStopWaitingFrame(force bool) *frames.StopWaitingFrame { - if s.nextLeastUnacked <= s.largestLeastUnackedSent { - if force { - return s.lastStopWaitingFrame - } - return nil - } - - s.largestLeastUnackedSent = s.nextLeastUnacked - swf := &frames.StopWaitingFrame{ - LeastUnacked: s.nextLeastUnacked, - } - s.lastStopWaitingFrame = swf - return swf -} - -func (s *stopWaitingManager) ReceivedAck(ack *frames.AckFrame) { - if ack.LargestAcked >= s.nextLeastUnacked { - s.nextLeastUnacked = ack.LargestAcked + 1 - } -} - -func (s *stopWaitingManager) QueuedRetransmissionForPacketNumber(p protocol.PacketNumber) { - if p >= s.nextLeastUnacked { - s.nextLeastUnacked = p + 1 - } -} diff --git a/vendor/github.com/lucas-clemente/quic-go/buffer_pool.go b/vendor/github.com/lucas-clemente/quic-go/buffer_pool.go deleted file mode 100644 index f592d475a32..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/buffer_pool.go +++ /dev/null @@ -1,26 +0,0 @@ -package quic - -import ( - "sync" - - "github.com/lucas-clemente/quic-go/protocol" -) - -var bufferPool sync.Pool - -func getPacketBuffer() []byte { - return bufferPool.Get().([]byte) -} - -func putPacketBuffer(buf []byte) { - if cap(buf) != int(protocol.MaxReceivePacketSize) { - panic("putPacketBuffer called with packet of wrong size!") - } - bufferPool.Put(buf[:0]) -} - -func init() { - bufferPool.New = func() interface{} { - return make([]byte, 0, protocol.MaxReceivePacketSize) - } -} diff --git a/vendor/github.com/lucas-clemente/quic-go/client.go b/vendor/github.com/lucas-clemente/quic-go/client.go deleted file mode 100644 index 2e18de801cb..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/client.go +++ /dev/null @@ -1,335 +0,0 @@ -package quic - -import ( - "bytes" - "crypto/tls" - "errors" - "fmt" - "net" - "strings" - "sync" - "time" - - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" - "github.com/lucas-clemente/quic-go/qerr" -) - -type client struct { - mutex sync.Mutex - listenErr error - - conn connection - hostname string - - errorChan chan struct{} - handshakeChan <-chan handshakeEvent - - tlsConf *tls.Config - config *Config - versionNegotiated bool // has version negotiation completed yet - - connectionID protocol.ConnectionID - version protocol.VersionNumber - - session packetHandler -} - -var ( - errCloseSessionForNewVersion = errors.New("closing session in order to recreate it with a new version") -) - -// DialAddr establishes a new QUIC connection to a server. -// The hostname for SNI is taken from the given address. -func DialAddr(addr string, tlsConf *tls.Config, config *Config) (Session, error) { - udpAddr, err := net.ResolveUDPAddr("udp", addr) - if err != nil { - return nil, err - } - udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0}) - if err != nil { - return nil, err - } - return Dial(udpConn, udpAddr, addr, tlsConf, config) -} - -// DialAddrNonFWSecure establishes a new QUIC connection to a server. -// The hostname for SNI is taken from the given address. -func DialAddrNonFWSecure( - addr string, - tlsConf *tls.Config, - config *Config, -) (NonFWSession, error) { - udpAddr, err := net.ResolveUDPAddr("udp", addr) - if err != nil { - return nil, err - } - udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0}) - if err != nil { - return nil, err - } - return DialNonFWSecure(udpConn, udpAddr, addr, tlsConf, config) -} - -// DialNonFWSecure establishes a new non-forward-secure QUIC connection to a server using a net.PacketConn. -// The host parameter is used for SNI. -func DialNonFWSecure( - pconn net.PacketConn, - remoteAddr net.Addr, - host string, - tlsConf *tls.Config, - config *Config, -) (NonFWSession, error) { - connID, err := utils.GenerateConnectionID() - if err != nil { - return nil, err - } - - var hostname string - if tlsConf != nil { - hostname = tlsConf.ServerName - } - - if hostname == "" { - hostname, _, err = net.SplitHostPort(host) - if err != nil { - return nil, err - } - } - - clientConfig := populateClientConfig(config) - c := &client{ - conn: &conn{pconn: pconn, currentAddr: remoteAddr}, - connectionID: connID, - hostname: hostname, - tlsConf: tlsConf, - config: clientConfig, - version: clientConfig.Versions[0], - errorChan: make(chan struct{}), - } - - err = c.createNewSession(nil) - if err != nil { - return nil, err - } - - utils.Infof("Starting new connection to %s (%s -> %s), connectionID %x, version %d", hostname, c.conn.LocalAddr().String(), c.conn.RemoteAddr().String(), c.connectionID, c.version) - - return c.session.(NonFWSession), c.establishSecureConnection() -} - -// Dial establishes a new QUIC connection to a server using a net.PacketConn. -// The host parameter is used for SNI. -func Dial( - pconn net.PacketConn, - remoteAddr net.Addr, - host string, - tlsConf *tls.Config, - config *Config, -) (Session, error) { - sess, err := DialNonFWSecure(pconn, remoteAddr, host, tlsConf, config) - if err != nil { - return nil, err - } - err = sess.WaitUntilHandshakeComplete() - if err != nil { - return nil, err - } - return sess, nil -} - -// populateClientConfig populates fields in the quic.Config with their default values, if none are set -// it may be called with nil -func populateClientConfig(config *Config) *Config { - if config == nil { - config = &Config{} - } - versions := config.Versions - if len(versions) == 0 { - versions = protocol.SupportedVersions - } - - handshakeTimeout := protocol.DefaultHandshakeTimeout - if config.HandshakeTimeout != 0 { - handshakeTimeout = config.HandshakeTimeout - } - - maxReceiveStreamFlowControlWindow := config.MaxReceiveStreamFlowControlWindow - if maxReceiveStreamFlowControlWindow == 0 { - maxReceiveStreamFlowControlWindow = protocol.DefaultMaxReceiveStreamFlowControlWindowClient - } - maxReceiveConnectionFlowControlWindow := config.MaxReceiveConnectionFlowControlWindow - if maxReceiveConnectionFlowControlWindow == 0 { - maxReceiveConnectionFlowControlWindow = protocol.DefaultMaxReceiveConnectionFlowControlWindowClient - } - - return &Config{ - Versions: versions, - HandshakeTimeout: handshakeTimeout, - RequestConnectionIDTruncation: config.RequestConnectionIDTruncation, - MaxReceiveStreamFlowControlWindow: maxReceiveStreamFlowControlWindow, - MaxReceiveConnectionFlowControlWindow: maxReceiveConnectionFlowControlWindow, - KeepAlive: config.KeepAlive, - } -} - -// establishSecureConnection returns as soon as the connection is secure (as opposed to forward-secure) -func (c *client) establishSecureConnection() error { - go c.listen() - - select { - case <-c.errorChan: - return c.listenErr - case ev := <-c.handshakeChan: - if ev.err != nil { - return ev.err - } - if ev.encLevel != protocol.EncryptionSecure { - return fmt.Errorf("Client BUG: Expected encryption level to be secure, was %s", ev.encLevel) - } - return nil - } -} - -// Listen listens -func (c *client) listen() { - var err error - - for { - var n int - var addr net.Addr - data := getPacketBuffer() - data = data[:protocol.MaxReceivePacketSize] - // The packet size should not exceed protocol.MaxReceivePacketSize bytes - // If it does, we only read a truncated packet, which will then end up undecryptable - n, addr, err = c.conn.Read(data) - if err != nil { - if !strings.HasSuffix(err.Error(), "use of closed network connection") { - c.session.Close(err) - } - break - } - data = data[:n] - - c.handlePacket(addr, data) - } -} - -func (c *client) handlePacket(remoteAddr net.Addr, packet []byte) { - rcvTime := time.Now() - - r := bytes.NewReader(packet) - hdr, err := ParsePublicHeader(r, protocol.PerspectiveServer) - if err != nil { - utils.Errorf("error parsing packet from %s: %s", remoteAddr.String(), err.Error()) - // drop this packet if we can't parse the Public Header - return - } - hdr.Raw = packet[:len(packet)-r.Len()] - - c.mutex.Lock() - defer c.mutex.Unlock() - - if hdr.ResetFlag { - cr := c.conn.RemoteAddr() - // check if the remote address and the connection ID match - // otherwise this might be an attacker trying to inject a PUBLIC_RESET to kill the connection - if cr.Network() != remoteAddr.Network() || cr.String() != remoteAddr.String() || hdr.ConnectionID != c.connectionID { - utils.Infof("Received a spoofed Public Reset. Ignoring.") - return - } - pr, err := parsePublicReset(r) - if err != nil { - utils.Infof("Received a Public Reset for connection %x. An error occurred parsing the packet.") - return - } - utils.Infof("Received Public Reset, rejected packet number: %#x.", pr.rejectedPacketNumber) - c.session.closeRemote(qerr.Error(qerr.PublicReset, fmt.Sprintf("Received a Public Reset for packet number %#x", pr.rejectedPacketNumber))) - return - } - - // ignore delayed / duplicated version negotiation packets - if c.versionNegotiated && hdr.VersionFlag { - return - } - - // this is the first packet after the client sent a packet with the VersionFlag set - // if the server doesn't send a version negotiation packet, it supports the suggested version - if !hdr.VersionFlag && !c.versionNegotiated { - c.versionNegotiated = true - } - - if hdr.VersionFlag { - // version negotiation packets have no payload - if err := c.handlePacketWithVersionFlag(hdr); err != nil { - c.session.Close(err) - } - return - } - - c.session.handlePacket(&receivedPacket{ - remoteAddr: remoteAddr, - publicHeader: hdr, - data: packet[len(packet)-r.Len():], - rcvTime: rcvTime, - }) -} - -func (c *client) handlePacketWithVersionFlag(hdr *PublicHeader) error { - for _, v := range hdr.SupportedVersions { - if v == c.version { - // the version negotiation packet contains the version that we offered - // this might be a packet sent by an attacker (or by a terribly broken server implementation) - // ignore it - return nil - } - } - - newVersion := protocol.ChooseSupportedVersion(c.config.Versions, hdr.SupportedVersions) - if newVersion == protocol.VersionUnsupported { - return qerr.InvalidVersion - } - - // switch to negotiated version - c.version = newVersion - c.versionNegotiated = true - var err error - c.connectionID, err = utils.GenerateConnectionID() - if err != nil { - return err - } - utils.Infof("Switching to QUIC version %d. New connection ID: %x", newVersion, c.connectionID) - - c.session.Close(errCloseSessionForNewVersion) - return c.createNewSession(hdr.SupportedVersions) -} - -func (c *client) createNewSession(negotiatedVersions []protocol.VersionNumber) error { - var err error - c.session, c.handshakeChan, err = newClientSession( - c.conn, - c.hostname, - c.version, - c.connectionID, - c.tlsConf, - c.config, - negotiatedVersions, - ) - if err != nil { - return err - } - - go func() { - // session.run() returns as soon as the session is closed - err := c.session.run() - if err == errCloseSessionForNewVersion { - return - } - c.listenErr = err - close(c.errorChan) - - utils.Infof("Connection %x closed.", c.connectionID) - c.conn.Close() - }() - return nil -} diff --git a/vendor/github.com/lucas-clemente/quic-go/congestion/bandwidth.go b/vendor/github.com/lucas-clemente/quic-go/congestion/bandwidth.go deleted file mode 100644 index e76ea161c35..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/congestion/bandwidth.go +++ /dev/null @@ -1,22 +0,0 @@ -package congestion - -import ( - "time" - - "github.com/lucas-clemente/quic-go/protocol" -) - -// Bandwidth of a connection -type Bandwidth uint64 - -const ( - // BitsPerSecond is 1 bit per second - BitsPerSecond Bandwidth = 1 - // BytesPerSecond is 1 byte per second - BytesPerSecond = 8 * BitsPerSecond -) - -// BandwidthFromDelta calculates the bandwidth from a number of bytes and a time delta -func BandwidthFromDelta(bytes protocol.ByteCount, delta time.Duration) Bandwidth { - return Bandwidth(bytes) * Bandwidth(time.Second) / Bandwidth(delta) * BytesPerSecond -} diff --git a/vendor/github.com/lucas-clemente/quic-go/congestion/clock.go b/vendor/github.com/lucas-clemente/quic-go/congestion/clock.go deleted file mode 100644 index 405fae70f95..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/congestion/clock.go +++ /dev/null @@ -1,18 +0,0 @@ -package congestion - -import "time" - -// A Clock returns the current time -type Clock interface { - Now() time.Time -} - -// DefaultClock implements the Clock interface using the Go stdlib clock. -type DefaultClock struct{} - -var _ Clock = DefaultClock{} - -// Now gets the current time -func (DefaultClock) Now() time.Time { - return time.Now() -} diff --git a/vendor/github.com/lucas-clemente/quic-go/congestion/cubic.go b/vendor/github.com/lucas-clemente/quic-go/congestion/cubic.go deleted file mode 100644 index 62e7355639e..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/congestion/cubic.go +++ /dev/null @@ -1,228 +0,0 @@ -package congestion - -import ( - "math" - "time" - - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" -) - -// This cubic implementation is based on the one found in Chromiums's QUIC -// implementation, in the files net/quic/congestion_control/cubic.{hh,cc}. - -// Constants based on TCP defaults. -// The following constants are in 2^10 fractions of a second instead of ms to -// allow a 10 shift right to divide. - -// 1024*1024^3 (first 1024 is from 0.100^3) -// where 0.100 is 100 ms which is the scaling -// round trip time. -const cubeScale = 40 -const cubeCongestionWindowScale = 410 -const cubeFactor protocol.PacketNumber = 1 << cubeScale / cubeCongestionWindowScale - -const defaultNumConnections = 2 - -// Default Cubic backoff factor -const beta float32 = 0.7 - -// Additional backoff factor when loss occurs in the concave part of the Cubic -// curve. This additional backoff factor is expected to give up bandwidth to -// new concurrent flows and speed up convergence. -const betaLastMax float32 = 0.85 - -// If true, Cubic's epoch is shifted when the sender is application-limited. -const shiftQuicCubicEpochWhenAppLimited = true - -const maxCubicTimeInterval = 30 * time.Millisecond - -// Cubic implements the cubic algorithm from TCP -type Cubic struct { - clock Clock - // Number of connections to simulate. - numConnections int - // Time when this cycle started, after last loss event. - epoch time.Time - // Time when sender went into application-limited period. Zero if not in - // application-limited period. - appLimitedStartTime time.Time - // Time when we updated last_congestion_window. - lastUpdateTime time.Time - // Last congestion window (in packets) used. - lastCongestionWindow protocol.PacketNumber - // Max congestion window (in packets) used just before last loss event. - // Note: to improve fairness to other streams an additional back off is - // applied to this value if the new value is below our latest value. - lastMaxCongestionWindow protocol.PacketNumber - // Number of acked packets since the cycle started (epoch). - ackedPacketsCount protocol.PacketNumber - // TCP Reno equivalent congestion window in packets. - estimatedTCPcongestionWindow protocol.PacketNumber - // Origin point of cubic function. - originPointCongestionWindow protocol.PacketNumber - // Time to origin point of cubic function in 2^10 fractions of a second. - timeToOriginPoint uint32 - // Last congestion window in packets computed by cubic function. - lastTargetCongestionWindow protocol.PacketNumber -} - -// NewCubic returns a new Cubic instance -func NewCubic(clock Clock) *Cubic { - c := &Cubic{ - clock: clock, - numConnections: defaultNumConnections, - } - c.Reset() - return c -} - -// Reset is called after a timeout to reset the cubic state -func (c *Cubic) Reset() { - c.epoch = time.Time{} - c.appLimitedStartTime = time.Time{} - c.lastUpdateTime = time.Time{} - c.lastCongestionWindow = 0 - c.lastMaxCongestionWindow = 0 - c.ackedPacketsCount = 0 - c.estimatedTCPcongestionWindow = 0 - c.originPointCongestionWindow = 0 - c.timeToOriginPoint = 0 - c.lastTargetCongestionWindow = 0 -} - -func (c *Cubic) alpha() float32 { - // TCPFriendly alpha is described in Section 3.3 of the CUBIC paper. Note that - // beta here is a cwnd multiplier, and is equal to 1-beta from the paper. - // We derive the equivalent alpha for an N-connection emulation as: - b := c.beta() - return 3 * float32(c.numConnections) * float32(c.numConnections) * (1 - b) / (1 + b) -} - -func (c *Cubic) beta() float32 { - // kNConnectionBeta is the backoff factor after loss for our N-connection - // emulation, which emulates the effective backoff of an ensemble of N - // TCP-Reno connections on a single loss event. The effective multiplier is - // computed as: - return (float32(c.numConnections) - 1 + beta) / float32(c.numConnections) -} - -// OnApplicationLimited is called on ack arrival when sender is unable to use -// the available congestion window. Resets Cubic state during quiescence. -func (c *Cubic) OnApplicationLimited() { - if shiftQuicCubicEpochWhenAppLimited { - // When sender is not using the available congestion window, Cubic's epoch - // should not continue growing. Record the time when sender goes into an - // app-limited period here, to compensate later when cwnd growth happens. - if c.appLimitedStartTime.IsZero() { - c.appLimitedStartTime = c.clock.Now() - } - } else { - // When sender is not using the available congestion window, Cubic's epoch - // should not continue growing. Reset the epoch when in such a period. - c.epoch = time.Time{} - } -} - -// CongestionWindowAfterPacketLoss computes a new congestion window to use after -// a loss event. Returns the new congestion window in packets. The new -// congestion window is a multiplicative decrease of our current window. -func (c *Cubic) CongestionWindowAfterPacketLoss(currentCongestionWindow protocol.PacketNumber) protocol.PacketNumber { - if currentCongestionWindow < c.lastMaxCongestionWindow { - // We never reached the old max, so assume we are competing with another - // flow. Use our extra back off factor to allow the other flow to go up. - c.lastMaxCongestionWindow = protocol.PacketNumber(betaLastMax * float32(currentCongestionWindow)) - } else { - c.lastMaxCongestionWindow = currentCongestionWindow - } - c.epoch = time.Time{} // Reset time. - return protocol.PacketNumber(float32(currentCongestionWindow) * c.beta()) -} - -// CongestionWindowAfterAck computes a new congestion window to use after a received ACK. -// Returns the new congestion window in packets. The new congestion window -// follows a cubic function that depends on the time passed since last -// packet loss. -func (c *Cubic) CongestionWindowAfterAck(currentCongestionWindow protocol.PacketNumber, delayMin time.Duration) protocol.PacketNumber { - c.ackedPacketsCount++ // Packets acked. - currentTime := c.clock.Now() - - // Cubic is "independent" of RTT, the update is limited by the time elapsed. - if c.lastCongestionWindow == currentCongestionWindow && (currentTime.Sub(c.lastUpdateTime) <= maxCubicTimeInterval) { - return utils.MaxPacketNumber(c.lastTargetCongestionWindow, c.estimatedTCPcongestionWindow) - } - c.lastCongestionWindow = currentCongestionWindow - c.lastUpdateTime = currentTime - - if c.epoch.IsZero() { - // First ACK after a loss event. - c.epoch = currentTime // Start of epoch. - c.ackedPacketsCount = 1 // Reset count. - // Reset estimated_tcp_congestion_window_ to be in sync with cubic. - c.estimatedTCPcongestionWindow = currentCongestionWindow - if c.lastMaxCongestionWindow <= currentCongestionWindow { - c.timeToOriginPoint = 0 - c.originPointCongestionWindow = currentCongestionWindow - } else { - c.timeToOriginPoint = uint32(math.Cbrt(float64(cubeFactor * (c.lastMaxCongestionWindow - currentCongestionWindow)))) - c.originPointCongestionWindow = c.lastMaxCongestionWindow - } - } else { - // If sender was app-limited, then freeze congestion window growth during - // app-limited period. Continue growth now by shifting the epoch-start - // through the app-limited period. - if shiftQuicCubicEpochWhenAppLimited && !c.appLimitedStartTime.IsZero() { - shift := currentTime.Sub(c.appLimitedStartTime) - c.epoch = c.epoch.Add(shift) - c.appLimitedStartTime = time.Time{} - } - } - - // Change the time unit from microseconds to 2^10 fractions per second. Take - // the round trip time in account. This is done to allow us to use shift as a - // divide operator. - elapsedTime := int64((currentTime.Add(delayMin).Sub(c.epoch)/time.Microsecond)<<10) / 1000000 - - offset := int64(c.timeToOriginPoint) - elapsedTime - // Right-shifts of negative, signed numbers have - // implementation-dependent behavior. Force the offset to be - // positive, similar to the kernel implementation. - if offset < 0 { - offset = -offset - } - deltaCongestionWindow := protocol.PacketNumber((cubeCongestionWindowScale * offset * offset * offset) >> cubeScale) - var targetCongestionWindow protocol.PacketNumber - if elapsedTime > int64(c.timeToOriginPoint) { - targetCongestionWindow = c.originPointCongestionWindow + deltaCongestionWindow - } else { - targetCongestionWindow = c.originPointCongestionWindow - deltaCongestionWindow - } - // With dynamic beta/alpha based on number of active streams, it is possible - // for the required_ack_count to become much lower than acked_packets_count_ - // suddenly, leading to more than one iteration through the following loop. - for { - // Update estimated TCP congestion_window. - requiredAckCount := protocol.PacketNumber(float32(c.estimatedTCPcongestionWindow) / c.alpha()) - if c.ackedPacketsCount < requiredAckCount { - break - } - c.ackedPacketsCount -= requiredAckCount - c.estimatedTCPcongestionWindow++ - } - - // We have a new cubic congestion window. - c.lastTargetCongestionWindow = targetCongestionWindow - - // Compute target congestion_window based on cubic target and estimated TCP - // congestion_window, use highest (fastest). - if targetCongestionWindow < c.estimatedTCPcongestionWindow { - targetCongestionWindow = c.estimatedTCPcongestionWindow - } - - return targetCongestionWindow -} - -// SetNumConnections sets the number of emulated connections -func (c *Cubic) SetNumConnections(n int) { - c.numConnections = n -} diff --git a/vendor/github.com/lucas-clemente/quic-go/congestion/cubic_sender.go b/vendor/github.com/lucas-clemente/quic-go/congestion/cubic_sender.go deleted file mode 100644 index 02e4206b6d7..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/congestion/cubic_sender.go +++ /dev/null @@ -1,298 +0,0 @@ -package congestion - -import ( - "time" - - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" -) - -const ( - maxBurstBytes = 3 * protocol.DefaultTCPMSS - defaultMinimumCongestionWindow protocol.PacketNumber = 2 - renoBeta float32 = 0.7 // Reno backoff factor. -) - -type cubicSender struct { - hybridSlowStart HybridSlowStart - prr PrrSender - rttStats *RTTStats - stats connectionStats - cubic *Cubic - - reno bool - - // Track the largest packet that has been sent. - largestSentPacketNumber protocol.PacketNumber - - // Track the largest packet that has been acked. - largestAckedPacketNumber protocol.PacketNumber - - // Track the largest packet number outstanding when a CWND cutback occurs. - largestSentAtLastCutback protocol.PacketNumber - - // Congestion window in packets. - congestionWindow protocol.PacketNumber - - // Slow start congestion window in packets, aka ssthresh. - slowstartThreshold protocol.PacketNumber - - // Whether the last loss event caused us to exit slowstart. - // Used for stats collection of slowstartPacketsLost - lastCutbackExitedSlowstart bool - - // When true, exit slow start with large cutback of congestion window. - slowStartLargeReduction bool - - // Minimum congestion window in packets. - minCongestionWindow protocol.PacketNumber - - // Maximum number of outstanding packets for tcp. - maxTCPCongestionWindow protocol.PacketNumber - - // Number of connections to simulate. - numConnections int - - // ACK counter for the Reno implementation. - congestionWindowCount protocol.ByteCount - - initialCongestionWindow protocol.PacketNumber - initialMaxCongestionWindow protocol.PacketNumber -} - -// NewCubicSender makes a new cubic sender -func NewCubicSender(clock Clock, rttStats *RTTStats, reno bool, initialCongestionWindow, initialMaxCongestionWindow protocol.PacketNumber) SendAlgorithmWithDebugInfo { - return &cubicSender{ - rttStats: rttStats, - initialCongestionWindow: initialCongestionWindow, - initialMaxCongestionWindow: initialMaxCongestionWindow, - congestionWindow: initialCongestionWindow, - minCongestionWindow: defaultMinimumCongestionWindow, - slowstartThreshold: initialMaxCongestionWindow, - maxTCPCongestionWindow: initialMaxCongestionWindow, - numConnections: defaultNumConnections, - cubic: NewCubic(clock), - reno: reno, - } -} - -func (c *cubicSender) TimeUntilSend(now time.Time, bytesInFlight protocol.ByteCount) time.Duration { - if c.InRecovery() { - // PRR is used when in recovery. - return c.prr.TimeUntilSend(c.GetCongestionWindow(), bytesInFlight, c.GetSlowStartThreshold()) - } - if c.GetCongestionWindow() > bytesInFlight { - return 0 - } - return utils.InfDuration -} - -func (c *cubicSender) OnPacketSent(sentTime time.Time, bytesInFlight protocol.ByteCount, packetNumber protocol.PacketNumber, bytes protocol.ByteCount, isRetransmittable bool) bool { - // Only update bytesInFlight for data packets. - if !isRetransmittable { - return false - } - if c.InRecovery() { - // PRR is used when in recovery. - c.prr.OnPacketSent(bytes) - } - c.largestSentPacketNumber = packetNumber - c.hybridSlowStart.OnPacketSent(packetNumber) - return true -} - -func (c *cubicSender) InRecovery() bool { - return c.largestAckedPacketNumber <= c.largestSentAtLastCutback && c.largestAckedPacketNumber != 0 -} - -func (c *cubicSender) InSlowStart() bool { - return c.GetCongestionWindow() < c.GetSlowStartThreshold() -} - -func (c *cubicSender) GetCongestionWindow() protocol.ByteCount { - return protocol.ByteCount(c.congestionWindow) * protocol.DefaultTCPMSS -} - -func (c *cubicSender) GetSlowStartThreshold() protocol.ByteCount { - return protocol.ByteCount(c.slowstartThreshold) * protocol.DefaultTCPMSS -} - -func (c *cubicSender) ExitSlowstart() { - c.slowstartThreshold = c.congestionWindow -} - -func (c *cubicSender) SlowstartThreshold() protocol.PacketNumber { - return c.slowstartThreshold -} - -func (c *cubicSender) MaybeExitSlowStart() { - if c.InSlowStart() && c.hybridSlowStart.ShouldExitSlowStart(c.rttStats.LatestRTT(), c.rttStats.MinRTT(), c.GetCongestionWindow()/protocol.DefaultTCPMSS) { - c.ExitSlowstart() - } -} - -func (c *cubicSender) OnPacketAcked(ackedPacketNumber protocol.PacketNumber, ackedBytes protocol.ByteCount, bytesInFlight protocol.ByteCount) { - c.largestAckedPacketNumber = utils.MaxPacketNumber(ackedPacketNumber, c.largestAckedPacketNumber) - if c.InRecovery() { - // PRR is used when in recovery. - c.prr.OnPacketAcked(ackedBytes) - return - } - c.maybeIncreaseCwnd(ackedPacketNumber, ackedBytes, bytesInFlight) - if c.InSlowStart() { - c.hybridSlowStart.OnPacketAcked(ackedPacketNumber) - } -} - -func (c *cubicSender) OnPacketLost(packetNumber protocol.PacketNumber, lostBytes protocol.ByteCount, bytesInFlight protocol.ByteCount) { - // TCP NewReno (RFC6582) says that once a loss occurs, any losses in packets - // already sent should be treated as a single loss event, since it's expected. - if packetNumber <= c.largestSentAtLastCutback { - if c.lastCutbackExitedSlowstart { - c.stats.slowstartPacketsLost++ - c.stats.slowstartBytesLost += lostBytes - if c.slowStartLargeReduction { - if c.stats.slowstartPacketsLost == 1 || (c.stats.slowstartBytesLost/protocol.DefaultTCPMSS) > (c.stats.slowstartBytesLost-lostBytes)/protocol.DefaultTCPMSS { - // Reduce congestion window by 1 for every mss of bytes lost. - c.congestionWindow = utils.MaxPacketNumber(c.congestionWindow-1, c.minCongestionWindow) - } - c.slowstartThreshold = c.congestionWindow - } - } - return - } - c.lastCutbackExitedSlowstart = c.InSlowStart() - if c.InSlowStart() { - c.stats.slowstartPacketsLost++ - } - - c.prr.OnPacketLost(bytesInFlight) - - // TODO(chromium): Separate out all of slow start into a separate class. - if c.slowStartLargeReduction && c.InSlowStart() { - c.congestionWindow = c.congestionWindow - 1 - } else if c.reno { - c.congestionWindow = protocol.PacketNumber(float32(c.congestionWindow) * c.RenoBeta()) - } else { - c.congestionWindow = c.cubic.CongestionWindowAfterPacketLoss(c.congestionWindow) - } - // Enforce a minimum congestion window. - if c.congestionWindow < c.minCongestionWindow { - c.congestionWindow = c.minCongestionWindow - } - c.slowstartThreshold = c.congestionWindow - c.largestSentAtLastCutback = c.largestSentPacketNumber - // reset packet count from congestion avoidance mode. We start - // counting again when we're out of recovery. - c.congestionWindowCount = 0 -} - -func (c *cubicSender) RenoBeta() float32 { - // kNConnectionBeta is the backoff factor after loss for our N-connection - // emulation, which emulates the effective backoff of an ensemble of N - // TCP-Reno connections on a single loss event. The effective multiplier is - // computed as: - return (float32(c.numConnections) - 1. + renoBeta) / float32(c.numConnections) -} - -// Called when we receive an ack. Normal TCP tracks how many packets one ack -// represents, but quic has a separate ack for each packet. -func (c *cubicSender) maybeIncreaseCwnd(ackedPacketNumber protocol.PacketNumber, ackedBytes protocol.ByteCount, bytesInFlight protocol.ByteCount) { - // Do not increase the congestion window unless the sender is close to using - // the current window. - if !c.isCwndLimited(bytesInFlight) { - c.cubic.OnApplicationLimited() - return - } - if c.congestionWindow >= c.maxTCPCongestionWindow { - return - } - if c.InSlowStart() { - // TCP slow start, exponential growth, increase by one for each ACK. - c.congestionWindow++ - return - } - if c.reno { - // Classic Reno congestion avoidance. - c.congestionWindowCount++ - // Divide by num_connections to smoothly increase the CWND at a faster - // rate than conventional Reno. - if protocol.PacketNumber(c.congestionWindowCount*protocol.ByteCount(c.numConnections)) >= c.congestionWindow { - c.congestionWindow++ - c.congestionWindowCount = 0 - } - } else { - c.congestionWindow = utils.MinPacketNumber(c.maxTCPCongestionWindow, c.cubic.CongestionWindowAfterAck(c.congestionWindow, c.rttStats.MinRTT())) - } -} - -func (c *cubicSender) isCwndLimited(bytesInFlight protocol.ByteCount) bool { - congestionWindow := c.GetCongestionWindow() - if bytesInFlight >= congestionWindow { - return true - } - availableBytes := congestionWindow - bytesInFlight - slowStartLimited := c.InSlowStart() && bytesInFlight > congestionWindow/2 - return slowStartLimited || availableBytes <= maxBurstBytes -} - -// BandwidthEstimate returns the current bandwidth estimate -func (c *cubicSender) BandwidthEstimate() Bandwidth { - srtt := c.rttStats.SmoothedRTT() - if srtt == 0 { - // If we haven't measured an rtt, the bandwidth estimate is unknown. - return 0 - } - return BandwidthFromDelta(c.GetCongestionWindow(), srtt) -} - -// HybridSlowStart returns the hybrid slow start instance for testing -func (c *cubicSender) HybridSlowStart() *HybridSlowStart { - return &c.hybridSlowStart -} - -// SetNumEmulatedConnections sets the number of emulated connections -func (c *cubicSender) SetNumEmulatedConnections(n int) { - c.numConnections = utils.Max(n, 1) - c.cubic.SetNumConnections(c.numConnections) -} - -// OnRetransmissionTimeout is called on an retransmission timeout -func (c *cubicSender) OnRetransmissionTimeout(packetsRetransmitted bool) { - c.largestSentAtLastCutback = 0 - if !packetsRetransmitted { - return - } - c.hybridSlowStart.Restart() - c.cubic.Reset() - c.slowstartThreshold = c.congestionWindow / 2 - c.congestionWindow = c.minCongestionWindow -} - -// OnConnectionMigration is called when the connection is migrated (?) -func (c *cubicSender) OnConnectionMigration() { - c.hybridSlowStart.Restart() - c.prr = PrrSender{} - c.largestSentPacketNumber = 0 - c.largestAckedPacketNumber = 0 - c.largestSentAtLastCutback = 0 - c.lastCutbackExitedSlowstart = false - c.cubic.Reset() - c.congestionWindowCount = 0 - c.congestionWindow = c.initialCongestionWindow - c.slowstartThreshold = c.initialMaxCongestionWindow - c.maxTCPCongestionWindow = c.initialMaxCongestionWindow -} - -// SetSlowStartLargeReduction allows enabling the SSLR experiment -func (c *cubicSender) SetSlowStartLargeReduction(enabled bool) { - c.slowStartLargeReduction = enabled -} - -// RetransmissionDelay gives the time to retransmission -func (c *cubicSender) RetransmissionDelay() time.Duration { - if c.rttStats.SmoothedRTT() == 0 { - return 0 - } - return c.rttStats.SmoothedRTT() + c.rttStats.MeanDeviation()*4 -} diff --git a/vendor/github.com/lucas-clemente/quic-go/congestion/hybrid_slow_start.go b/vendor/github.com/lucas-clemente/quic-go/congestion/hybrid_slow_start.go deleted file mode 100644 index 01a64f8262b..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/congestion/hybrid_slow_start.go +++ /dev/null @@ -1,111 +0,0 @@ -package congestion - -import ( - "time" - - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" -) - -// Note(pwestin): the magic clamping numbers come from the original code in -// tcp_cubic.c. -const hybridStartLowWindow = protocol.ByteCount(16) - -// Number of delay samples for detecting the increase of delay. -const hybridStartMinSamples = uint32(8) - -// Exit slow start if the min rtt has increased by more than 1/8th. -const hybridStartDelayFactorExp = 3 // 2^3 = 8 -// The original paper specifies 2 and 8ms, but those have changed over time. -const hybridStartDelayMinThresholdUs = int64(4000) -const hybridStartDelayMaxThresholdUs = int64(16000) - -// HybridSlowStart implements the TCP hybrid slow start algorithm -type HybridSlowStart struct { - endPacketNumber protocol.PacketNumber - lastSentPacketNumber protocol.PacketNumber - started bool - currentMinRTT time.Duration - rttSampleCount uint32 - hystartFound bool -} - -// StartReceiveRound is called for the start of each receive round (burst) in the slow start phase. -func (s *HybridSlowStart) StartReceiveRound(lastSent protocol.PacketNumber) { - s.endPacketNumber = lastSent - s.currentMinRTT = 0 - s.rttSampleCount = 0 - s.started = true -} - -// IsEndOfRound returns true if this ack is the last packet number of our current slow start round. -func (s *HybridSlowStart) IsEndOfRound(ack protocol.PacketNumber) bool { - return s.endPacketNumber < ack -} - -// ShouldExitSlowStart should be called on every new ack frame, since a new -// RTT measurement can be made then. -// rtt: the RTT for this ack packet. -// minRTT: is the lowest delay (RTT) we have seen during the session. -// congestionWindow: the congestion window in packets. -func (s *HybridSlowStart) ShouldExitSlowStart(latestRTT time.Duration, minRTT time.Duration, congestionWindow protocol.ByteCount) bool { - if !s.started { - // Time to start the hybrid slow start. - s.StartReceiveRound(s.lastSentPacketNumber) - } - if s.hystartFound { - return true - } - // Second detection parameter - delay increase detection. - // Compare the minimum delay (s.currentMinRTT) of the current - // burst of packets relative to the minimum delay during the session. - // Note: we only look at the first few(8) packets in each burst, since we - // only want to compare the lowest RTT of the burst relative to previous - // bursts. - s.rttSampleCount++ - if s.rttSampleCount <= hybridStartMinSamples { - if s.currentMinRTT == 0 || s.currentMinRTT > latestRTT { - s.currentMinRTT = latestRTT - } - } - // We only need to check this once per round. - if s.rttSampleCount == hybridStartMinSamples { - // Divide minRTT by 8 to get a rtt increase threshold for exiting. - minRTTincreaseThresholdUs := int64(minRTT / time.Microsecond >> hybridStartDelayFactorExp) - // Ensure the rtt threshold is never less than 2ms or more than 16ms. - minRTTincreaseThresholdUs = utils.MinInt64(minRTTincreaseThresholdUs, hybridStartDelayMaxThresholdUs) - minRTTincreaseThreshold := time.Duration(utils.MaxInt64(minRTTincreaseThresholdUs, hybridStartDelayMinThresholdUs)) * time.Microsecond - - if s.currentMinRTT > (minRTT + minRTTincreaseThreshold) { - s.hystartFound = true - } - } - // Exit from slow start if the cwnd is greater than 16 and - // increasing delay is found. - return congestionWindow >= hybridStartLowWindow && s.hystartFound -} - -// OnPacketSent is called when a packet was sent -func (s *HybridSlowStart) OnPacketSent(packetNumber protocol.PacketNumber) { - s.lastSentPacketNumber = packetNumber -} - -// OnPacketAcked gets invoked after ShouldExitSlowStart, so it's best to end -// the round when the final packet of the burst is received and start it on -// the next incoming ack. -func (s *HybridSlowStart) OnPacketAcked(ackedPacketNumber protocol.PacketNumber) { - if s.IsEndOfRound(ackedPacketNumber) { - s.started = false - } -} - -// Started returns true if started -func (s *HybridSlowStart) Started() bool { - return s.started -} - -// Restart the slow start phase -func (s *HybridSlowStart) Restart() { - s.started = false - s.hystartFound = false -} diff --git a/vendor/github.com/lucas-clemente/quic-go/congestion/interface.go b/vendor/github.com/lucas-clemente/quic-go/congestion/interface.go deleted file mode 100644 index bbce0a637af..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/congestion/interface.go +++ /dev/null @@ -1,37 +0,0 @@ -package congestion - -import ( - "time" - - "github.com/lucas-clemente/quic-go/protocol" -) - -// A SendAlgorithm performs congestion control and calculates the congestion window -type SendAlgorithm interface { - TimeUntilSend(now time.Time, bytesInFlight protocol.ByteCount) time.Duration - OnPacketSent(sentTime time.Time, bytesInFlight protocol.ByteCount, packetNumber protocol.PacketNumber, bytes protocol.ByteCount, isRetransmittable bool) bool - GetCongestionWindow() protocol.ByteCount - MaybeExitSlowStart() - OnPacketAcked(number protocol.PacketNumber, ackedBytes protocol.ByteCount, bytesInFlight protocol.ByteCount) - OnPacketLost(number protocol.PacketNumber, lostBytes protocol.ByteCount, bytesInFlight protocol.ByteCount) - SetNumEmulatedConnections(n int) - OnRetransmissionTimeout(packetsRetransmitted bool) - OnConnectionMigration() - RetransmissionDelay() time.Duration - - // Experiments - SetSlowStartLargeReduction(enabled bool) -} - -// SendAlgorithmWithDebugInfo adds some debug functions to SendAlgorithm -type SendAlgorithmWithDebugInfo interface { - SendAlgorithm - BandwidthEstimate() Bandwidth - - // Stuff only used in testing - - HybridSlowStart() *HybridSlowStart - SlowstartThreshold() protocol.PacketNumber - RenoBeta() float32 - InRecovery() bool -} diff --git a/vendor/github.com/lucas-clemente/quic-go/congestion/prr_sender.go b/vendor/github.com/lucas-clemente/quic-go/congestion/prr_sender.go deleted file mode 100644 index b8a0a10be67..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/congestion/prr_sender.go +++ /dev/null @@ -1,63 +0,0 @@ -package congestion - -import ( - "time" - - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" -) - -// PrrSender implements the Proportional Rate Reduction (PRR) per RFC 6937 -type PrrSender struct { - bytesSentSinceLoss protocol.ByteCount - bytesDeliveredSinceLoss protocol.ByteCount - ackCountSinceLoss protocol.ByteCount - bytesInFlightBeforeLoss protocol.ByteCount -} - -// OnPacketSent should be called after a packet was sent -func (p *PrrSender) OnPacketSent(sentBytes protocol.ByteCount) { - p.bytesSentSinceLoss += sentBytes -} - -// OnPacketLost should be called on the first loss that triggers a recovery -// period and all other methods in this class should only be called when in -// recovery. -func (p *PrrSender) OnPacketLost(bytesInFlight protocol.ByteCount) { - p.bytesSentSinceLoss = 0 - p.bytesInFlightBeforeLoss = bytesInFlight - p.bytesDeliveredSinceLoss = 0 - p.ackCountSinceLoss = 0 -} - -// OnPacketAcked should be called after a packet was acked -func (p *PrrSender) OnPacketAcked(ackedBytes protocol.ByteCount) { - p.bytesDeliveredSinceLoss += ackedBytes - p.ackCountSinceLoss++ -} - -// TimeUntilSend calculates the time until a packet can be sent -func (p *PrrSender) TimeUntilSend(congestionWindow, bytesInFlight, slowstartThreshold protocol.ByteCount) time.Duration { - // Return QuicTime::Zero In order to ensure limited transmit always works. - if p.bytesSentSinceLoss == 0 || bytesInFlight < protocol.DefaultTCPMSS { - return 0 - } - if congestionWindow > bytesInFlight { - // During PRR-SSRB, limit outgoing packets to 1 extra MSS per ack, instead - // of sending the entire available window. This prevents burst retransmits - // when more packets are lost than the CWND reduction. - // limit = MAX(prr_delivered - prr_out, DeliveredData) + MSS - if p.bytesDeliveredSinceLoss+p.ackCountSinceLoss*protocol.DefaultTCPMSS <= p.bytesSentSinceLoss { - return utils.InfDuration - } - return 0 - } - // Implement Proportional Rate Reduction (RFC6937). - // Checks a simplified version of the PRR formula that doesn't use division: - // AvailableSendWindow = - // CEIL(prr_delivered * ssthresh / BytesInFlightAtLoss) - prr_sent - if p.bytesDeliveredSinceLoss*slowstartThreshold > p.bytesSentSinceLoss*p.bytesInFlightBeforeLoss { - return 0 - } - return utils.InfDuration -} diff --git a/vendor/github.com/lucas-clemente/quic-go/congestion/rtt_stats.go b/vendor/github.com/lucas-clemente/quic-go/congestion/rtt_stats.go deleted file mode 100644 index 546c1cb98a0..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/congestion/rtt_stats.go +++ /dev/null @@ -1,182 +0,0 @@ -package congestion - -import ( - "time" - - "github.com/lucas-clemente/quic-go/internal/utils" -) - -const ( - initialRTTus = 100 * 1000 - rttAlpha float32 = 0.125 - oneMinusAlpha float32 = (1 - rttAlpha) - rttBeta float32 = 0.25 - oneMinusBeta float32 = (1 - rttBeta) - halfWindow float32 = 0.5 - quarterWindow float32 = 0.25 -) - -type rttSample struct { - rtt time.Duration - time time.Time -} - -// RTTStats provides round-trip statistics -type RTTStats struct { - initialRTTus int64 - - recentMinRTTwindow time.Duration - minRTT time.Duration - latestRTT time.Duration - smoothedRTT time.Duration - meanDeviation time.Duration - - numMinRTTsamplesRemaining uint32 - - newMinRTT rttSample - recentMinRTT rttSample - halfWindowRTT rttSample - quarterWindowRTT rttSample -} - -// NewRTTStats makes a properly initialized RTTStats object -func NewRTTStats() *RTTStats { - return &RTTStats{ - initialRTTus: initialRTTus, - recentMinRTTwindow: utils.InfDuration, - } -} - -// InitialRTTus is the initial RTT in us -func (r *RTTStats) InitialRTTus() int64 { return r.initialRTTus } - -// MinRTT Returns the minRTT for the entire connection. -// May return Zero if no valid updates have occurred. -func (r *RTTStats) MinRTT() time.Duration { return r.minRTT } - -// LatestRTT returns the most recent rtt measurement. -// May return Zero if no valid updates have occurred. -func (r *RTTStats) LatestRTT() time.Duration { return r.latestRTT } - -// RecentMinRTT the minRTT since SampleNewRecentMinRtt has been called, or the -// minRTT for the entire connection if SampleNewMinRtt was never called. -func (r *RTTStats) RecentMinRTT() time.Duration { return r.recentMinRTT.rtt } - -// SmoothedRTT returns the EWMA smoothed RTT for the connection. -// May return Zero if no valid updates have occurred. -func (r *RTTStats) SmoothedRTT() time.Duration { return r.smoothedRTT } - -// GetQuarterWindowRTT gets the quarter window RTT -func (r *RTTStats) GetQuarterWindowRTT() time.Duration { return r.quarterWindowRTT.rtt } - -// GetHalfWindowRTT gets the half window RTT -func (r *RTTStats) GetHalfWindowRTT() time.Duration { return r.halfWindowRTT.rtt } - -// MeanDeviation gets the mean deviation -func (r *RTTStats) MeanDeviation() time.Duration { return r.meanDeviation } - -// SetRecentMinRTTwindow sets how old a recent min rtt sample can be. -func (r *RTTStats) SetRecentMinRTTwindow(recentMinRTTwindow time.Duration) { - r.recentMinRTTwindow = recentMinRTTwindow -} - -// UpdateRTT updates the RTT based on a new sample. -func (r *RTTStats) UpdateRTT(sendDelta, ackDelay time.Duration, now time.Time) { - if sendDelta == utils.InfDuration || sendDelta <= 0 { - utils.Debugf("Ignoring measured sendDelta, because it's is either infinite, zero, or negative: %d", sendDelta/time.Microsecond) - return - } - - // Update r.minRTT first. r.minRTT does not use an rttSample corrected for - // ackDelay but the raw observed sendDelta, since poor clock granularity at - // the client may cause a high ackDelay to result in underestimation of the - // r.minRTT. - if r.minRTT == 0 || r.minRTT > sendDelta { - r.minRTT = sendDelta - } - r.updateRecentMinRTT(sendDelta, now) - - // Correct for ackDelay if information received from the peer results in a - // positive RTT sample. Otherwise, we use the sendDelta as a reasonable - // measure for smoothedRTT. - sample := sendDelta - if sample > ackDelay { - sample -= ackDelay - } - r.latestRTT = sample - // First time call. - if r.smoothedRTT == 0 { - r.smoothedRTT = sample - r.meanDeviation = sample / 2 - } else { - r.meanDeviation = time.Duration(oneMinusBeta*float32(r.meanDeviation/time.Microsecond)+rttBeta*float32(utils.AbsDuration(r.smoothedRTT-sample)/time.Microsecond)) * time.Microsecond - r.smoothedRTT = time.Duration((float32(r.smoothedRTT/time.Microsecond)*oneMinusAlpha)+(float32(sample/time.Microsecond)*rttAlpha)) * time.Microsecond - } -} - -func (r *RTTStats) updateRecentMinRTT(sample time.Duration, now time.Time) { // Recent minRTT update. - if r.numMinRTTsamplesRemaining > 0 { - r.numMinRTTsamplesRemaining-- - if r.newMinRTT.rtt == 0 || sample <= r.newMinRTT.rtt { - r.newMinRTT = rttSample{rtt: sample, time: now} - } - if r.numMinRTTsamplesRemaining == 0 { - r.recentMinRTT = r.newMinRTT - r.halfWindowRTT = r.newMinRTT - r.quarterWindowRTT = r.newMinRTT - } - } - - // Update the three recent rtt samples. - if r.recentMinRTT.rtt == 0 || sample <= r.recentMinRTT.rtt { - r.recentMinRTT = rttSample{rtt: sample, time: now} - r.halfWindowRTT = r.recentMinRTT - r.quarterWindowRTT = r.recentMinRTT - } else if sample <= r.halfWindowRTT.rtt { - r.halfWindowRTT = rttSample{rtt: sample, time: now} - r.quarterWindowRTT = r.halfWindowRTT - } else if sample <= r.quarterWindowRTT.rtt { - r.quarterWindowRTT = rttSample{rtt: sample, time: now} - } - - // Expire old min rtt samples. - if r.recentMinRTT.time.Before(now.Add(-r.recentMinRTTwindow)) { - r.recentMinRTT = r.halfWindowRTT - r.halfWindowRTT = r.quarterWindowRTT - r.quarterWindowRTT = rttSample{rtt: sample, time: now} - } else if r.halfWindowRTT.time.Before(now.Add(-time.Duration(float32(r.recentMinRTTwindow/time.Microsecond)*halfWindow) * time.Microsecond)) { - r.halfWindowRTT = r.quarterWindowRTT - r.quarterWindowRTT = rttSample{rtt: sample, time: now} - } else if r.quarterWindowRTT.time.Before(now.Add(-time.Duration(float32(r.recentMinRTTwindow/time.Microsecond)*quarterWindow) * time.Microsecond)) { - r.quarterWindowRTT = rttSample{rtt: sample, time: now} - } -} - -// SampleNewRecentMinRTT forces RttStats to sample a new recent min rtt within the next -// |numSamples| UpdateRTT calls. -func (r *RTTStats) SampleNewRecentMinRTT(numSamples uint32) { - r.numMinRTTsamplesRemaining = numSamples - r.newMinRTT = rttSample{} -} - -// OnConnectionMigration is called when connection migrates and rtt measurement needs to be reset. -func (r *RTTStats) OnConnectionMigration() { - r.latestRTT = 0 - r.minRTT = 0 - r.smoothedRTT = 0 - r.meanDeviation = 0 - r.initialRTTus = initialRTTus - r.numMinRTTsamplesRemaining = 0 - r.recentMinRTTwindow = utils.InfDuration - r.recentMinRTT = rttSample{} - r.halfWindowRTT = rttSample{} - r.quarterWindowRTT = rttSample{} -} - -// ExpireSmoothedMetrics causes the smoothed_rtt to be increased to the latest_rtt if the latest_rtt -// is larger. The mean deviation is increased to the most recent deviation if -// it's larger. -func (r *RTTStats) ExpireSmoothedMetrics() { - r.meanDeviation = utils.MaxDuration(r.meanDeviation, utils.AbsDuration(r.smoothedRTT-r.latestRTT)) - r.smoothedRTT = utils.MaxDuration(r.smoothedRTT, r.latestRTT) -} diff --git a/vendor/github.com/lucas-clemente/quic-go/congestion/stats.go b/vendor/github.com/lucas-clemente/quic-go/congestion/stats.go deleted file mode 100644 index 8f272b26d99..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/congestion/stats.go +++ /dev/null @@ -1,8 +0,0 @@ -package congestion - -import "github.com/lucas-clemente/quic-go/protocol" - -type connectionStats struct { - slowstartPacketsLost protocol.PacketNumber - slowstartBytesLost protocol.ByteCount -} diff --git a/vendor/github.com/lucas-clemente/quic-go/conn.go b/vendor/github.com/lucas-clemente/quic-go/conn.go deleted file mode 100644 index 700c1471c68..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/conn.go +++ /dev/null @@ -1,54 +0,0 @@ -package quic - -import ( - "net" - "sync" -) - -type connection interface { - Write([]byte) error - Read([]byte) (int, net.Addr, error) - Close() error - LocalAddr() net.Addr - RemoteAddr() net.Addr - SetCurrentRemoteAddr(net.Addr) -} - -type conn struct { - mutex sync.RWMutex - - pconn net.PacketConn - currentAddr net.Addr -} - -var _ connection = &conn{} - -func (c *conn) Write(p []byte) error { - _, err := c.pconn.WriteTo(p, c.currentAddr) - return err -} - -func (c *conn) Read(p []byte) (int, net.Addr, error) { - return c.pconn.ReadFrom(p) -} - -func (c *conn) SetCurrentRemoteAddr(addr net.Addr) { - c.mutex.Lock() - c.currentAddr = addr - c.mutex.Unlock() -} - -func (c *conn) LocalAddr() net.Addr { - return c.pconn.LocalAddr() -} - -func (c *conn) RemoteAddr() net.Addr { - c.mutex.RLock() - addr := c.currentAddr - c.mutex.RUnlock() - return addr -} - -func (c *conn) Close() error { - return c.pconn.Close() -} diff --git a/vendor/github.com/lucas-clemente/quic-go/crypto/AEAD.go b/vendor/github.com/lucas-clemente/quic-go/crypto/AEAD.go deleted file mode 100644 index a59ce6e8e6b..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/crypto/AEAD.go +++ /dev/null @@ -1,9 +0,0 @@ -package crypto - -import "github.com/lucas-clemente/quic-go/protocol" - -// An AEAD implements QUIC's authenticated encryption and associated data -type AEAD interface { - Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, error) - Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte -} diff --git a/vendor/github.com/lucas-clemente/quic-go/crypto/aesgcm_aead.go b/vendor/github.com/lucas-clemente/quic-go/crypto/aesgcm_aead.go deleted file mode 100644 index a738cc2b176..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/crypto/aesgcm_aead.go +++ /dev/null @@ -1,58 +0,0 @@ -package crypto - -import ( - "crypto/cipher" - "errors" - - "github.com/lucas-clemente/aes12" - - "github.com/lucas-clemente/quic-go/protocol" -) - -type aeadAESGCM struct { - otherIV []byte - myIV []byte - encrypter cipher.AEAD - decrypter cipher.AEAD -} - -// NewAEADAESGCM creates a AEAD using AES-GCM with 12 bytes tag size -// -// AES-GCM support is a bit hacky, since the go stdlib does not support 12 byte -// tag size, and couples the cipher and aes packages closely. -// See https://github.com/lucas-clemente/aes12. -func NewAEADAESGCM(otherKey []byte, myKey []byte, otherIV []byte, myIV []byte) (AEAD, error) { - if len(myKey) != 16 || len(otherKey) != 16 || len(myIV) != 4 || len(otherIV) != 4 { - return nil, errors.New("AES-GCM: expected 16-byte keys and 4-byte IVs") - } - encrypterCipher, err := aes12.NewCipher(myKey) - if err != nil { - return nil, err - } - encrypter, err := aes12.NewGCM(encrypterCipher) - if err != nil { - return nil, err - } - decrypterCipher, err := aes12.NewCipher(otherKey) - if err != nil { - return nil, err - } - decrypter, err := aes12.NewGCM(decrypterCipher) - if err != nil { - return nil, err - } - return &aeadAESGCM{ - otherIV: otherIV, - myIV: myIV, - encrypter: encrypter, - decrypter: decrypter, - }, nil -} - -func (aead *aeadAESGCM) Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, error) { - return aead.decrypter.Open(dst, makeNonce(aead.otherIV, packetNumber), src, associatedData) -} - -func (aead *aeadAESGCM) Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte { - return aead.encrypter.Seal(dst, makeNonce(aead.myIV, packetNumber), src, associatedData) -} diff --git a/vendor/github.com/lucas-clemente/quic-go/crypto/cert_cache.go b/vendor/github.com/lucas-clemente/quic-go/crypto/cert_cache.go deleted file mode 100644 index 3ebdc1ae5c7..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/crypto/cert_cache.go +++ /dev/null @@ -1,48 +0,0 @@ -package crypto - -import ( - "fmt" - "hash/fnv" - - "github.com/hashicorp/golang-lru" - "github.com/lucas-clemente/quic-go/protocol" -) - -var ( - compressedCertsCache *lru.Cache -) - -func getCompressedCert(chain [][]byte, pCommonSetHashes, pCachedHashes []byte) ([]byte, error) { - // Hash all inputs - hasher := fnv.New64a() - for _, v := range chain { - hasher.Write(v) - } - hasher.Write(pCommonSetHashes) - hasher.Write(pCachedHashes) - hash := hasher.Sum64() - - var result []byte - - resultI, isCached := compressedCertsCache.Get(hash) - if isCached { - result = resultI.([]byte) - } else { - var err error - result, err = compressChain(chain, pCommonSetHashes, pCachedHashes) - if err != nil { - return nil, err - } - compressedCertsCache.Add(hash, result) - } - - return result, nil -} - -func init() { - var err error - compressedCertsCache, err = lru.New(protocol.NumCachedCertificates) - if err != nil { - panic(fmt.Sprintf("fatal error in quic-go: could not create lru cache: %s", err.Error())) - } -} diff --git a/vendor/github.com/lucas-clemente/quic-go/crypto/cert_chain.go b/vendor/github.com/lucas-clemente/quic-go/crypto/cert_chain.go deleted file mode 100644 index f3bc9fbf02e..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/crypto/cert_chain.go +++ /dev/null @@ -1,113 +0,0 @@ -package crypto - -import ( - "crypto/tls" - "errors" - "strings" -) - -// A CertChain holds a certificate and a private key -type CertChain interface { - SignServerProof(sni string, chlo []byte, serverConfigData []byte) ([]byte, error) - GetCertsCompressed(sni string, commonSetHashes, cachedHashes []byte) ([]byte, error) - GetLeafCert(sni string) ([]byte, error) -} - -// proofSource stores a key and a certificate for the server proof -type certChain struct { - config *tls.Config -} - -var _ CertChain = &certChain{} - -var errNoMatchingCertificate = errors.New("no matching certificate found") - -// NewCertChain loads the key and cert from files -func NewCertChain(tlsConfig *tls.Config) CertChain { - return &certChain{config: tlsConfig} -} - -// SignServerProof signs CHLO and server config for use in the server proof -func (c *certChain) SignServerProof(sni string, chlo []byte, serverConfigData []byte) ([]byte, error) { - cert, err := c.getCertForSNI(sni) - if err != nil { - return nil, err - } - - return signServerProof(cert, chlo, serverConfigData) -} - -// GetCertsCompressed gets the certificate in the format described by the QUIC crypto doc -func (c *certChain) GetCertsCompressed(sni string, pCommonSetHashes, pCachedHashes []byte) ([]byte, error) { - cert, err := c.getCertForSNI(sni) - if err != nil { - return nil, err - } - return getCompressedCert(cert.Certificate, pCommonSetHashes, pCachedHashes) -} - -// GetLeafCert gets the leaf certificate -func (c *certChain) GetLeafCert(sni string) ([]byte, error) { - cert, err := c.getCertForSNI(sni) - if err != nil { - return nil, err - } - return cert.Certificate[0], nil -} - -func (cc *certChain) getCertForSNI(sni string) (*tls.Certificate, error) { - c := cc.config - c, err := maybeGetConfigForClient(c, sni) - if err != nil { - return nil, err - } - // The rest of this function is mostly copied from crypto/tls.getCertificate - - if c.GetCertificate != nil { - cert, err := c.GetCertificate(&tls.ClientHelloInfo{ServerName: sni}) - if cert != nil || err != nil { - return cert, err - } - } - - if len(c.Certificates) == 0 { - return nil, errNoMatchingCertificate - } - - if len(c.Certificates) == 1 || c.NameToCertificate == nil { - // There's only one choice, so no point doing any work. - return &c.Certificates[0], nil - } - - name := strings.ToLower(sni) - for len(name) > 0 && name[len(name)-1] == '.' { - name = name[:len(name)-1] - } - - if cert, ok := c.NameToCertificate[name]; ok { - return cert, nil - } - - // try replacing labels in the name with wildcards until we get a - // match. - labels := strings.Split(name, ".") - for i := range labels { - labels[i] = "*" - candidate := strings.Join(labels, ".") - if cert, ok := c.NameToCertificate[candidate]; ok { - return cert, nil - } - } - - // If nothing matches, return the first certificate. - return &c.Certificates[0], nil -} - -func maybeGetConfigForClient(c *tls.Config, sni string) (*tls.Config, error) { - if c.GetConfigForClient == nil { - return c, nil - } - return c.GetConfigForClient(&tls.ClientHelloInfo{ - ServerName: sni, - }) -} diff --git a/vendor/github.com/lucas-clemente/quic-go/crypto/cert_compression.go b/vendor/github.com/lucas-clemente/quic-go/crypto/cert_compression.go deleted file mode 100644 index ea5ecff363a..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/crypto/cert_compression.go +++ /dev/null @@ -1,272 +0,0 @@ -package crypto - -import ( - "bytes" - "compress/flate" - "compress/zlib" - "encoding/binary" - "errors" - "fmt" - "hash/fnv" - - "github.com/lucas-clemente/quic-go/internal/utils" -) - -type entryType uint8 - -const ( - entryCompressed entryType = 1 - entryCached entryType = 2 - entryCommon entryType = 3 -) - -type entry struct { - t entryType - h uint64 // set hash - i uint32 // index -} - -func compressChain(chain [][]byte, pCommonSetHashes, pCachedHashes []byte) ([]byte, error) { - res := &bytes.Buffer{} - - cachedHashes, err := splitHashes(pCachedHashes) - if err != nil { - return nil, err - } - - setHashes, err := splitHashes(pCommonSetHashes) - if err != nil { - return nil, err - } - - chainHashes := make([]uint64, len(chain)) - for i := range chain { - chainHashes[i] = HashCert(chain[i]) - } - - entries := buildEntries(chain, chainHashes, cachedHashes, setHashes) - - totalUncompressedLen := 0 - for i, e := range entries { - res.WriteByte(uint8(e.t)) - switch e.t { - case entryCached: - utils.WriteUint64(res, e.h) - case entryCommon: - utils.WriteUint64(res, e.h) - utils.WriteUint32(res, e.i) - case entryCompressed: - totalUncompressedLen += 4 + len(chain[i]) - } - } - res.WriteByte(0) // end of list - - if totalUncompressedLen > 0 { - gz, err := zlib.NewWriterLevelDict(res, flate.BestCompression, buildZlibDictForEntries(entries, chain)) - if err != nil { - return nil, fmt.Errorf("cert compression failed: %s", err.Error()) - } - - utils.WriteUint32(res, uint32(totalUncompressedLen)) - - for i, e := range entries { - if e.t != entryCompressed { - continue - } - lenCert := len(chain[i]) - gz.Write([]byte{ - byte(lenCert & 0xff), - byte((lenCert >> 8) & 0xff), - byte((lenCert >> 16) & 0xff), - byte((lenCert >> 24) & 0xff), - }) - gz.Write(chain[i]) - } - - gz.Close() - } - - return res.Bytes(), nil -} - -func decompressChain(data []byte) ([][]byte, error) { - var chain [][]byte - var entries []entry - r := bytes.NewReader(data) - - var numCerts int - var hasCompressedCerts bool - for { - entryTypeByte, err := r.ReadByte() - if entryTypeByte == 0 { - break - } - - et := entryType(entryTypeByte) - if err != nil { - return nil, err - } - - numCerts++ - - switch et { - case entryCached: - // we're not sending any certificate hashes in the CHLO, so there shouldn't be any cached certificates in the chain - return nil, errors.New("unexpected cached certificate") - case entryCommon: - e := entry{t: entryCommon} - e.h, err = utils.ReadUint64(r) - if err != nil { - return nil, err - } - e.i, err = utils.ReadUint32(r) - if err != nil { - return nil, err - } - certSet, ok := certSets[e.h] - if !ok { - return nil, errors.New("unknown certSet") - } - if e.i >= uint32(len(certSet)) { - return nil, errors.New("certificate not found in certSet") - } - entries = append(entries, e) - chain = append(chain, certSet[e.i]) - case entryCompressed: - hasCompressedCerts = true - entries = append(entries, entry{t: entryCompressed}) - chain = append(chain, nil) - default: - return nil, errors.New("unknown entryType") - } - } - - if numCerts == 0 { - return make([][]byte, 0), nil - } - - if hasCompressedCerts { - uncompressedLength, err := utils.ReadUint32(r) - if err != nil { - fmt.Println(4) - return nil, err - } - - zlibDict := buildZlibDictForEntries(entries, chain) - gz, err := zlib.NewReaderDict(r, zlibDict) - if err != nil { - return nil, err - } - defer gz.Close() - - var totalLength uint32 - var certIndex int - for totalLength < uncompressedLength { - lenBytes := make([]byte, 4) - _, err := gz.Read(lenBytes) - if err != nil { - return nil, err - } - certLen := binary.LittleEndian.Uint32(lenBytes) - - cert := make([]byte, certLen) - n, err := gz.Read(cert) - if uint32(n) != certLen && err != nil { - return nil, err - } - - for { - if certIndex >= len(entries) { - return nil, errors.New("CertCompression BUG: no element to save uncompressed certificate") - } - if entries[certIndex].t == entryCompressed { - chain[certIndex] = cert - certIndex++ - break - } - certIndex++ - } - - totalLength += 4 + certLen - } - } - - return chain, nil -} - -func buildEntries(chain [][]byte, chainHashes, cachedHashes, setHashes []uint64) []entry { - res := make([]entry, len(chain)) -chainLoop: - for i := range chain { - // Check if hash is in cachedHashes - for j := range cachedHashes { - if chainHashes[i] == cachedHashes[j] { - res[i] = entry{t: entryCached, h: chainHashes[i]} - continue chainLoop - } - } - - // Go through common sets and check if it's in there - for _, setHash := range setHashes { - set, ok := certSets[setHash] - if !ok { - // We don't have this set - continue - } - // We have this set, check if chain[i] is in the set - pos := set.findCertInSet(chain[i]) - if pos >= 0 { - // Found - res[i] = entry{t: entryCommon, h: setHash, i: uint32(pos)} - continue chainLoop - } - } - - res[i] = entry{t: entryCompressed} - } - return res -} - -func buildZlibDictForEntries(entries []entry, chain [][]byte) []byte { - var dict bytes.Buffer - - // First the cached and common in reverse order - for i := len(entries) - 1; i >= 0; i-- { - if entries[i].t == entryCompressed { - continue - } - dict.Write(chain[i]) - } - - dict.Write(certDictZlib) - return dict.Bytes() -} - -func splitHashes(hashes []byte) ([]uint64, error) { - if len(hashes)%8 != 0 { - return nil, errors.New("expected a multiple of 8 bytes for CCS / CCRT hashes") - } - n := len(hashes) / 8 - res := make([]uint64, n) - for i := 0; i < n; i++ { - res[i] = binary.LittleEndian.Uint64(hashes[i*8 : (i+1)*8]) - } - return res, nil -} - -func getCommonCertificateHashes() []byte { - ccs := make([]byte, 8*len(certSets)) - i := 0 - for certSetHash := range certSets { - binary.LittleEndian.PutUint64(ccs[i*8:(i+1)*8], certSetHash) - i++ - } - return ccs -} - -// HashCert calculates the FNV1a hash of a certificate -func HashCert(cert []byte) uint64 { - h := fnv.New64a() - h.Write(cert) - return h.Sum64() -} diff --git a/vendor/github.com/lucas-clemente/quic-go/crypto/cert_dict.go b/vendor/github.com/lucas-clemente/quic-go/crypto/cert_dict.go deleted file mode 100644 index 300ec713130..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/crypto/cert_dict.go +++ /dev/null @@ -1,128 +0,0 @@ -package crypto - -var certDictZlib = []byte{ - 0x04, 0x02, 0x30, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, - 0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, - 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x30, - 0x5f, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01, - 0x06, 0x06, 0x0b, 0x60, 0x86, 0x48, 0x01, 0x86, 0xfd, 0x6d, 0x01, 0x07, - 0x17, 0x01, 0x30, 0x33, 0x20, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, - 0x64, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x20, 0x53, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x34, - 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, - 0x32, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x2d, 0x61, 0x69, 0x61, 0x2e, - 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x45, 0x2d, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, - 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x45, 0x2e, 0x63, 0x65, - 0x72, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x4a, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x2f, 0x63, 0x70, 0x73, 0x20, 0x28, 0x63, 0x29, 0x30, 0x30, 0x09, 0x06, - 0x03, 0x55, 0x1d, 0x13, 0x04, 0x02, 0x30, 0x00, 0x30, 0x1d, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, - 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x7b, 0x30, 0x1d, 0x06, 0x03, 0x55, - 0x1d, 0x0e, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, - 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd2, - 0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x2e, - 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, - 0x04, 0x14, 0xb4, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, - 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x30, 0x0b, 0x06, 0x03, - 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02, 0x01, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, - 0x81, 0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, - 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, - 0x13, 0x07, 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31, 0x13, 0x30, - 0x11, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0a, 0x53, 0x63, 0x6f, 0x74, - 0x74, 0x73, 0x64, 0x61, 0x6c, 0x65, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, - 0x55, 0x04, 0x0a, 0x13, 0x11, 0x47, 0x6f, 0x44, 0x61, 0x64, 0x64, 0x79, - 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x33, - 0x30, 0x31, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2a, 0x68, 0x74, 0x74, - 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x65, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, - 0x6f, 0x72, 0x79, 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, - 0x13, 0x27, 0x47, 0x6f, 0x20, 0x44, 0x61, 0x64, 0x64, 0x79, 0x20, 0x53, - 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, - 0x04, 0x05, 0x13, 0x08, 0x30, 0x37, 0x39, 0x36, 0x39, 0x32, 0x38, 0x37, - 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x31, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, - 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x05, 0xa0, 0x30, 0x0c, - 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, - 0x30, 0x1d, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, - 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, - 0x1d, 0x25, 0x04, 0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, - 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x03, 0x02, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, - 0x04, 0x04, 0x03, 0x02, 0x05, 0xa0, 0x30, 0x33, 0x06, 0x03, 0x55, 0x1d, - 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86, - 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, - 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x67, 0x64, 0x73, 0x31, 0x2d, 0x32, 0x30, 0x2a, 0x30, 0x28, 0x06, 0x08, - 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, - 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, - 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, - 0x70, 0x73, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, - 0x0d, 0x31, 0x33, 0x30, 0x35, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x73, 0x30, 0x39, 0x30, 0x37, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x02, 0x30, 0x44, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, - 0x3d, 0x30, 0x3b, 0x30, 0x39, 0x06, 0x0b, 0x60, 0x86, 0x48, 0x01, 0x86, - 0xf8, 0x45, 0x01, 0x07, 0x17, 0x06, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, - 0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x1b, 0x53, 0x31, 0x17, - 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65, 0x72, - 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, - 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56, 0x65, - 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, - 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3b, 0x30, 0x39, - 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x54, 0x65, 0x72, 0x6d, 0x73, - 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65, 0x20, 0x61, 0x74, 0x20, 0x68, - 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, - 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x72, 0x70, 0x61, 0x20, 0x28, 0x63, 0x29, 0x30, 0x31, 0x10, 0x30, 0x0e, - 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x31, 0x13, 0x30, 0x11, - 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0a, 0x47, 0x31, 0x13, 0x30, 0x11, - 0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x3c, 0x02, 0x01, - 0x03, 0x13, 0x02, 0x55, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, - 0x03, 0x14, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, - 0x31, 0x1d, 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, 0x0f, 0x13, 0x14, 0x50, - 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, 0x4f, 0x72, 0x67, 0x61, 0x6e, - 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x12, 0x31, 0x21, 0x30, - 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x18, 0x44, 0x6f, 0x6d, 0x61, - 0x69, 0x6e, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x20, 0x56, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x64, 0x31, 0x14, 0x31, 0x31, - 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x28, 0x53, 0x65, 0x65, - 0x20, 0x77, 0x77, 0x77, 0x2e, 0x72, 0x3a, 0x2f, 0x2f, 0x73, 0x65, 0x63, - 0x75, 0x72, 0x65, 0x2e, 0x67, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, - 0x69, 0x67, 0x6e, 0x31, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x41, - 0x2e, 0x63, 0x72, 0x6c, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, - 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x45, 0x63, 0x72, - 0x6c, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x73, 0x64, 0x31, 0x1a, - 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x45, 0x56, 0x49, 0x6e, 0x74, 0x6c, 0x2d, 0x63, 0x63, 0x72, - 0x74, 0x2e, 0x67, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x69, 0x63, 0x65, 0x72, - 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x31, 0x6f, 0x63, 0x73, 0x70, 0x2e, - 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, - 0x30, 0x39, 0x72, 0x61, 0x70, 0x69, 0x64, 0x73, 0x73, 0x6c, 0x2e, 0x63, - 0x6f, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, - 0x79, 0x2f, 0x30, 0x81, 0x80, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, - 0x07, 0x01, 0x01, 0x04, 0x74, 0x30, 0x72, 0x30, 0x24, 0x06, 0x08, 0x2b, - 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, - 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6f, 0x64, - 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x4a, 0x06, - 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x3e, 0x68, - 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, - 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, - 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x67, 0x64, 0x5f, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x2e, 0x63, 0x72, - 0x74, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, - 0x80, 0x14, 0xfd, 0xac, 0x61, 0x32, 0x93, 0x6c, 0x45, 0xd6, 0xe2, 0xee, - 0x85, 0x5f, 0x9a, 0xba, 0xe7, 0x76, 0x99, 0x68, 0xcc, 0xe7, 0x30, 0x27, - 0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x86, 0x30, - 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x73, -} diff --git a/vendor/github.com/lucas-clemente/quic-go/crypto/cert_manager.go b/vendor/github.com/lucas-clemente/quic-go/crypto/cert_manager.go deleted file mode 100644 index 5aaa1877c3b..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/crypto/cert_manager.go +++ /dev/null @@ -1,130 +0,0 @@ -package crypto - -import ( - "crypto/tls" - "crypto/x509" - "errors" - "hash/fnv" - "time" - - "github.com/lucas-clemente/quic-go/qerr" -) - -// CertManager manages the certificates sent by the server -type CertManager interface { - SetData([]byte) error - GetCommonCertificateHashes() []byte - GetLeafCert() []byte - GetLeafCertHash() (uint64, error) - VerifyServerProof(proof, chlo, serverConfigData []byte) bool - Verify(hostname string) error -} - -type certManager struct { - chain []*x509.Certificate - config *tls.Config -} - -var _ CertManager = &certManager{} - -var errNoCertificateChain = errors.New("CertManager BUG: No certicifate chain loaded") - -// NewCertManager creates a new CertManager -func NewCertManager(tlsConfig *tls.Config) CertManager { - return &certManager{config: tlsConfig} -} - -// SetData takes the byte-slice sent in the SHLO and decompresses it into the certificate chain -func (c *certManager) SetData(data []byte) error { - byteChain, err := decompressChain(data) - if err != nil { - return qerr.Error(qerr.InvalidCryptoMessageParameter, "Certificate data invalid") - } - - chain := make([]*x509.Certificate, len(byteChain)) - for i, data := range byteChain { - cert, err := x509.ParseCertificate(data) - if err != nil { - return err - } - chain[i] = cert - } - - c.chain = chain - return nil -} - -func (c *certManager) GetCommonCertificateHashes() []byte { - return getCommonCertificateHashes() -} - -// GetLeafCert returns the leaf certificate of the certificate chain -// it returns nil if the certificate chain has not yet been set -func (c *certManager) GetLeafCert() []byte { - if len(c.chain) == 0 { - return nil - } - return c.chain[0].Raw -} - -// GetLeafCertHash calculates the FNV1a_64 hash of the leaf certificate -func (c *certManager) GetLeafCertHash() (uint64, error) { - leafCert := c.GetLeafCert() - if leafCert == nil { - return 0, errNoCertificateChain - } - - h := fnv.New64a() - _, err := h.Write(leafCert) - if err != nil { - return 0, err - } - return h.Sum64(), nil -} - -// VerifyServerProof verifies the signature of the server config -// it should only be called after the certificate chain has been set, otherwise it returns false -func (c *certManager) VerifyServerProof(proof, chlo, serverConfigData []byte) bool { - if len(c.chain) == 0 { - return false - } - - return verifyServerProof(proof, c.chain[0], chlo, serverConfigData) -} - -// Verify verifies the certificate chain -func (c *certManager) Verify(hostname string) error { - if len(c.chain) == 0 { - return errNoCertificateChain - } - - if c.config != nil && c.config.InsecureSkipVerify { - return nil - } - - leafCert := c.chain[0] - - var opts x509.VerifyOptions - if c.config != nil { - opts.Roots = c.config.RootCAs - if c.config.Time == nil { - opts.CurrentTime = time.Now() - } else { - opts.CurrentTime = c.config.Time() - } - } - // we don't need to care about the tls.Config.ServerName here, since hostname has already been set to that value in the session setup - opts.DNSName = hostname - - // the first certificate is the leaf certificate, all others are intermediates - if len(c.chain) > 1 { - intermediates := x509.NewCertPool() - for i := 1; i < len(c.chain); i++ { - intermediates.AddCert(c.chain[i]) - } - opts.Intermediates = intermediates - } - - _, err := leafCert.Verify(opts) - return err -} diff --git a/vendor/github.com/lucas-clemente/quic-go/crypto/cert_sets.go b/vendor/github.com/lucas-clemente/quic-go/crypto/cert_sets.go deleted file mode 100644 index 1552668c801..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/crypto/cert_sets.go +++ /dev/null @@ -1,24 +0,0 @@ -package crypto - -import ( - "bytes" - - "github.com/lucas-clemente/quic-go-certificates" -) - -type certSet [][]byte - -var certSets = map[uint64]certSet{ - certsets.CertSet2Hash: certsets.CertSet2, - certsets.CertSet3Hash: certsets.CertSet3, -} - -// findCertInSet searches for the cert in the set. Negative return value means not found. -func (s *certSet) findCertInSet(cert []byte) int { - for i, c := range *s { - if bytes.Equal(c, cert) { - return i - } - } - return -1 -} diff --git a/vendor/github.com/lucas-clemente/quic-go/crypto/chacha20poly1305_aead.go b/vendor/github.com/lucas-clemente/quic-go/crypto/chacha20poly1305_aead.go deleted file mode 100644 index 5c58c4e3caf..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/crypto/chacha20poly1305_aead.go +++ /dev/null @@ -1,53 +0,0 @@ -// +build ignore - -package crypto - -import ( - "crypto/cipher" - "errors" - - "github.com/aead/chacha20" - - "github.com/lucas-clemente/quic-go/protocol" -) - -type aeadChacha20Poly1305 struct { - otherIV []byte - myIV []byte - encrypter cipher.AEAD - decrypter cipher.AEAD -} - -// NewAEADChacha20Poly1305 creates a AEAD using chacha20poly1305 -func NewAEADChacha20Poly1305(otherKey []byte, myKey []byte, otherIV []byte, myIV []byte) (AEAD, error) { - if len(myKey) != 32 || len(otherKey) != 32 || len(myIV) != 4 || len(otherIV) != 4 { - return nil, errors.New("chacha20poly1305: expected 32-byte keys and 4-byte IVs") - } - // copy because ChaCha20Poly1305 expects array pointers - var MyKey, OtherKey [32]byte - copy(MyKey[:], myKey) - copy(OtherKey[:], otherKey) - - encrypter, err := chacha20.NewChaCha20Poly1305WithTagSize(&MyKey, 12) - if err != nil { - return nil, err - } - decrypter, err := chacha20.NewChaCha20Poly1305WithTagSize(&OtherKey, 12) - if err != nil { - return nil, err - } - return &aeadChacha20Poly1305{ - otherIV: otherIV, - myIV: myIV, - encrypter: encrypter, - decrypter: decrypter, - }, nil -} - -func (aead *aeadChacha20Poly1305) Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, error) { - return aead.decrypter.Open(dst, makeNonce(aead.otherIV, packetNumber), src, associatedData) -} - -func (aead *aeadChacha20Poly1305) Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte { - return aead.encrypter.Seal(dst, makeNonce(aead.myIV, packetNumber), src, associatedData) -} diff --git a/vendor/github.com/lucas-clemente/quic-go/crypto/curve_25519.go b/vendor/github.com/lucas-clemente/quic-go/crypto/curve_25519.go deleted file mode 100644 index a570d6b31a3..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/crypto/curve_25519.go +++ /dev/null @@ -1,45 +0,0 @@ -package crypto - -import ( - "crypto/rand" - "errors" - - "golang.org/x/crypto/curve25519" -) - -// KeyExchange manages the exchange of keys -type curve25519KEX struct { - secret [32]byte - public [32]byte -} - -var _ KeyExchange = &curve25519KEX{} - -// NewCurve25519KEX creates a new KeyExchange using Curve25519, see https://cr.yp.to/ecdh.html -func NewCurve25519KEX() (KeyExchange, error) { - c := &curve25519KEX{} - if _, err := rand.Read(c.secret[:]); err != nil { - return nil, errors.New("Curve25519: could not create private key") - } - // See https://cr.yp.to/ecdh.html - c.secret[0] &= 248 - c.secret[31] &= 127 - c.secret[31] |= 64 - curve25519.ScalarBaseMult(&c.public, &c.secret) - return c, nil -} - -func (c *curve25519KEX) PublicKey() []byte { - return c.public[:] -} - -func (c *curve25519KEX) CalculateSharedKey(otherPublic []byte) ([]byte, error) { - if len(otherPublic) != 32 { - return nil, errors.New("Curve25519: expected public key of 32 byte") - } - var res [32]byte - var otherPublicArray [32]byte - copy(otherPublicArray[:], otherPublic) - curve25519.ScalarMult(&res, &c.secret, &otherPublicArray) - return res[:], nil -} diff --git a/vendor/github.com/lucas-clemente/quic-go/crypto/key_derivation.go b/vendor/github.com/lucas-clemente/quic-go/crypto/key_derivation.go deleted file mode 100644 index accdbeaa2cf..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/crypto/key_derivation.go +++ /dev/null @@ -1,101 +0,0 @@ -package crypto - -import ( - "bytes" - "crypto/sha256" - "io" - - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" - - "golang.org/x/crypto/hkdf" -) - -// DeriveKeysChacha20 derives the client and server keys and creates a matching chacha20poly1305 AEAD instance -// func DeriveKeysChacha20(version protocol.VersionNumber, forwardSecure bool, sharedSecret, nonces []byte, connID protocol.ConnectionID, chlo []byte, scfg []byte, cert []byte, divNonce []byte) (AEAD, error) { -// otherKey, myKey, otherIV, myIV, err := deriveKeys(version, forwardSecure, sharedSecret, nonces, connID, chlo, scfg, cert, divNonce, 32) -// if err != nil { -// return nil, err -// } -// return NewAEADChacha20Poly1305(otherKey, myKey, otherIV, myIV) -// } - -// DeriveKeysAESGCM derives the client and server keys and creates a matching AES-GCM AEAD instance -func DeriveKeysAESGCM(forwardSecure bool, sharedSecret, nonces []byte, connID protocol.ConnectionID, chlo []byte, scfg []byte, cert []byte, divNonce []byte, pers protocol.Perspective) (AEAD, error) { - var swap bool - if pers == protocol.PerspectiveClient { - swap = true - } - otherKey, myKey, otherIV, myIV, err := deriveKeys(forwardSecure, sharedSecret, nonces, connID, chlo, scfg, cert, divNonce, 16, swap) - if err != nil { - return nil, err - } - return NewAEADAESGCM(otherKey, myKey, otherIV, myIV) -} - -// deriveKeys derives the keys and the IVs -// swap should be set true if generating the values for the client, and false for the server -func deriveKeys(forwardSecure bool, sharedSecret, nonces []byte, connID protocol.ConnectionID, chlo, scfg, cert, divNonce []byte, keyLen int, swap bool) ([]byte, []byte, []byte, []byte, error) { - var info bytes.Buffer - if forwardSecure { - info.Write([]byte("QUIC forward secure key expansion\x00")) - } else { - info.Write([]byte("QUIC key expansion\x00")) - } - utils.WriteUint64(&info, uint64(connID)) - info.Write(chlo) - info.Write(scfg) - info.Write(cert) - - r := hkdf.New(sha256.New, sharedSecret, nonces, info.Bytes()) - - s := make([]byte, 2*keyLen+2*4) - if _, err := io.ReadFull(r, s); err != nil { - return nil, nil, nil, nil, err - } - - key1 := s[:keyLen] - key2 := s[keyLen : 2*keyLen] - iv1 := s[2*keyLen : 2*keyLen+4] - iv2 := s[2*keyLen+4:] - - var otherKey, myKey []byte - var otherIV, myIV []byte - - if !forwardSecure { - if err := diversify(key2, iv2, divNonce); err != nil { - return nil, nil, nil, nil, err - } - } - - if swap { - otherKey = key2 - myKey = key1 - otherIV = iv2 - myIV = iv1 - } else { - otherKey = key1 - myKey = key2 - otherIV = iv1 - myIV = iv2 - } - - return otherKey, myKey, otherIV, myIV, nil -} - -func diversify(key, iv, divNonce []byte) error { - secret := make([]byte, len(key)+len(iv)) - copy(secret, key) - copy(secret[len(key):], iv) - - r := hkdf.New(sha256.New, secret, divNonce, []byte("QUIC key diversification")) - - if _, err := io.ReadFull(r, key); err != nil { - return err - } - if _, err := io.ReadFull(r, iv); err != nil { - return err - } - - return nil -} diff --git a/vendor/github.com/lucas-clemente/quic-go/crypto/key_exchange.go b/vendor/github.com/lucas-clemente/quic-go/crypto/key_exchange.go deleted file mode 100644 index d240b9c90f2..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/crypto/key_exchange.go +++ /dev/null @@ -1,7 +0,0 @@ -package crypto - -// KeyExchange manages the exchange of keys -type KeyExchange interface { - PublicKey() []byte - CalculateSharedKey(otherPublic []byte) ([]byte, error) -} diff --git a/vendor/github.com/lucas-clemente/quic-go/crypto/nonce.go b/vendor/github.com/lucas-clemente/quic-go/crypto/nonce.go deleted file mode 100644 index 9b6d4164539..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/crypto/nonce.go +++ /dev/null @@ -1,14 +0,0 @@ -package crypto - -import ( - "encoding/binary" - - "github.com/lucas-clemente/quic-go/protocol" -) - -func makeNonce(iv []byte, packetNumber protocol.PacketNumber) []byte { - res := make([]byte, 12) - copy(res[0:4], iv) - binary.LittleEndian.PutUint64(res[4:12], uint64(packetNumber)) - return res -} diff --git a/vendor/github.com/lucas-clemente/quic-go/crypto/null_aead.go b/vendor/github.com/lucas-clemente/quic-go/crypto/null_aead.go deleted file mode 100644 index ed8566337a8..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/crypto/null_aead.go +++ /dev/null @@ -1,80 +0,0 @@ -package crypto - -import ( - "encoding/binary" - "errors" - - "github.com/lucas-clemente/fnv128a" - "github.com/lucas-clemente/quic-go/protocol" -) - -// nullAEAD handles not-yet encrypted packets -type nullAEAD struct { - perspective protocol.Perspective - version protocol.VersionNumber -} - -var _ AEAD = &nullAEAD{} - -// NewNullAEAD creates a NullAEAD -func NewNullAEAD(p protocol.Perspective, v protocol.VersionNumber) AEAD { - return &nullAEAD{ - perspective: p, - version: v, - } -} - -// Open and verify the ciphertext -func (n *nullAEAD) Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, error) { - if len(src) < 12 { - return nil, errors.New("NullAEAD: ciphertext cannot be less than 12 bytes long") - } - - hash := fnv128a.New() - hash.Write(associatedData) - hash.Write(src[12:]) - if n.version >= protocol.Version37 { - if n.perspective == protocol.PerspectiveServer { - hash.Write([]byte("Client")) - } else { - hash.Write([]byte("Server")) - } - } - testHigh, testLow := hash.Sum128() - - low := binary.LittleEndian.Uint64(src) - high := binary.LittleEndian.Uint32(src[8:]) - - if uint32(testHigh&0xffffffff) != high || testLow != low { - return nil, errors.New("NullAEAD: failed to authenticate received data") - } - return src[12:], nil -} - -// Seal writes hash and ciphertext to the buffer -func (n *nullAEAD) Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte { - if cap(dst) < 12+len(src) { - dst = make([]byte, 12+len(src)) - } else { - dst = dst[:12+len(src)] - } - - hash := fnv128a.New() - hash.Write(associatedData) - hash.Write(src) - - if n.version >= protocol.Version37 { - if n.perspective == protocol.PerspectiveServer { - hash.Write([]byte("Server")) - } else { - hash.Write([]byte("Client")) - } - } - - high, low := hash.Sum128() - - copy(dst[12:], src) - binary.LittleEndian.PutUint64(dst, low) - binary.LittleEndian.PutUint32(dst[8:], uint32(high)) - return dst -} diff --git a/vendor/github.com/lucas-clemente/quic-go/crypto/server_proof.go b/vendor/github.com/lucas-clemente/quic-go/crypto/server_proof.go deleted file mode 100644 index 456ad32d5d4..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/crypto/server_proof.go +++ /dev/null @@ -1,66 +0,0 @@ -package crypto - -import ( - "crypto" - "crypto/ecdsa" - "crypto/rand" - "crypto/rsa" - "crypto/sha256" - "crypto/tls" - "crypto/x509" - "encoding/asn1" - "errors" - "math/big" -) - -type ecdsaSignature struct { - R, S *big.Int -} - -// signServerProof signs CHLO and server config for use in the server proof -func signServerProof(cert *tls.Certificate, chlo []byte, serverConfigData []byte) ([]byte, error) { - hash := sha256.New() - hash.Write([]byte("QUIC CHLO and server config signature\x00")) - chloHash := sha256.Sum256(chlo) - hash.Write([]byte{32, 0, 0, 0}) - hash.Write(chloHash[:]) - hash.Write(serverConfigData) - - key, ok := cert.PrivateKey.(crypto.Signer) - if !ok { - return nil, errors.New("expected PrivateKey to implement crypto.Signer") - } - - opts := crypto.SignerOpts(crypto.SHA256) - - if _, ok = key.(*rsa.PrivateKey); ok { - opts = &rsa.PSSOptions{SaltLength: 32, Hash: crypto.SHA256} - } - - return key.Sign(rand.Reader, hash.Sum(nil), opts) -} - -// verifyServerProof verifies the server proof signature -func verifyServerProof(proof []byte, cert *x509.Certificate, chlo []byte, serverConfigData []byte) bool { - hash := sha256.New() - hash.Write([]byte("QUIC CHLO and server config signature\x00")) - chloHash := sha256.Sum256(chlo) - hash.Write([]byte{32, 0, 0, 0}) - hash.Write(chloHash[:]) - hash.Write(serverConfigData) - - // RSA - if cert.PublicKeyAlgorithm == x509.RSA { - opts := &rsa.PSSOptions{SaltLength: 32, Hash: crypto.SHA256} - err := rsa.VerifyPSS(cert.PublicKey.(*rsa.PublicKey), crypto.SHA256, hash.Sum(nil), proof, opts) - return err == nil - } - - // ECDSA - signature := &ecdsaSignature{} - rest, err := asn1.Unmarshal(proof, signature) - if err != nil || len(rest) != 0 { - return false - } - return ecdsa.Verify(cert.PublicKey.(*ecdsa.PublicKey), hash.Sum(nil), signature.R, signature.S) -} diff --git a/vendor/github.com/lucas-clemente/quic-go/crypto/source_address_token.go b/vendor/github.com/lucas-clemente/quic-go/crypto/source_address_token.go deleted file mode 100644 index 3dcb26a757c..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/crypto/source_address_token.go +++ /dev/null @@ -1,76 +0,0 @@ -package crypto - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "crypto/sha256" - "fmt" - "io" - - "golang.org/x/crypto/hkdf" -) - -// StkSource is used to create and verify source address tokens -type StkSource interface { - // NewToken creates a new token - NewToken([]byte) ([]byte, error) - // DecodeToken decodes a token - DecodeToken([]byte) ([]byte, error) -} - -type stkSource struct { - aead cipher.AEAD -} - -const stkKeySize = 16 - -// Chrome currently sets this to 12, but discusses changing it to 16. We start -// at 16 :) -const stkNonceSize = 16 - -// NewStkSource creates a source for source address tokens -func NewStkSource() (StkSource, error) { - secret := make([]byte, 32) - if _, err := rand.Read(secret); err != nil { - return nil, err - } - key, err := deriveKey(secret) - if err != nil { - return nil, err - } - c, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - aead, err := cipher.NewGCMWithNonceSize(c, stkNonceSize) - if err != nil { - return nil, err - } - return &stkSource{aead: aead}, nil -} - -func (s *stkSource) NewToken(data []byte) ([]byte, error) { - nonce := make([]byte, stkNonceSize) - if _, err := rand.Read(nonce); err != nil { - return nil, err - } - return s.aead.Seal(nonce, nonce, data, nil), nil -} - -func (s *stkSource) DecodeToken(p []byte) ([]byte, error) { - if len(p) < stkNonceSize { - return nil, fmt.Errorf("STK too short: %d", len(p)) - } - nonce := p[:stkNonceSize] - return s.aead.Open(nil, nonce, p[stkNonceSize:], nil) -} - -func deriveKey(secret []byte) ([]byte, error) { - r := hkdf.New(sha256.New, secret, nil, []byte("QUIC source address token key")) - key := make([]byte, stkKeySize) - if _, err := io.ReadFull(r, key); err != nil { - return nil, err - } - return key, nil -} diff --git a/vendor/github.com/lucas-clemente/quic-go/example/client/main.go b/vendor/github.com/lucas-clemente/quic-go/example/client/main.go deleted file mode 100644 index f4e3e57b163..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/example/client/main.go +++ /dev/null @@ -1,52 +0,0 @@ -package main - -import ( - "bytes" - "flag" - "io" - "net/http" - "sync" - - "github.com/lucas-clemente/quic-go/h2quic" - "github.com/lucas-clemente/quic-go/internal/utils" -) - -func main() { - verbose := flag.Bool("v", false, "verbose") - flag.Parse() - urls := flag.Args() - - if *verbose { - utils.SetLogLevel(utils.LogLevelDebug) - } else { - utils.SetLogLevel(utils.LogLevelInfo) - } - utils.SetLogTimeFormat("") - - hclient := &http.Client{ - Transport: &h2quic.RoundTripper{}, - } - - var wg sync.WaitGroup - wg.Add(len(urls)) - for _, addr := range urls { - utils.Infof("GET %s", addr) - go func(addr string) { - rsp, err := hclient.Get(addr) - if err != nil { - panic(err) - } - utils.Infof("Got response for %s: %#v", addr, rsp) - - body := &bytes.Buffer{} - _, err = io.Copy(body, rsp.Body) - if err != nil { - panic(err) - } - utils.Infof("Request Body:") - utils.Infof("%s", body.Bytes()) - wg.Done() - }(addr) - } - wg.Wait() -} diff --git a/vendor/github.com/lucas-clemente/quic-go/example/echo/echo.go b/vendor/github.com/lucas-clemente/quic-go/example/echo/echo.go deleted file mode 100644 index 0f39c12701a..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/example/echo/echo.go +++ /dev/null @@ -1,105 +0,0 @@ -package main - -import ( - "crypto/rand" - "crypto/rsa" - "crypto/tls" - "crypto/x509" - "encoding/pem" - "fmt" - "io" - "log" - "math/big" - - quic "github.com/lucas-clemente/quic-go" -) - -const addr = "localhost:4242" - -const message = "foobar" - -// We start a server echoing data on the first stream the client opens, -// then connect with a client, send the message, and wait for its receipt. -func main() { - go func() { log.Fatal(echoServer()) }() - - err := clientMain() - if err != nil { - panic(err) - } -} - -// Start a server that echos all data on the first stream opened by the client -func echoServer() error { - listener, err := quic.ListenAddr(addr, generateTLSConfig(), nil) - if err != nil { - return err - } - sess, err := listener.Accept() - if err != nil { - return err - } - stream, err := sess.AcceptStream() - if err != nil { - panic(err) - } - // Echo through the loggingWriter - _, err = io.Copy(loggingWriter{stream}, stream) - return err -} - -func clientMain() error { - session, err := quic.DialAddr(addr, &tls.Config{InsecureSkipVerify: true}, nil) - if err != nil { - return err - } - - stream, err := session.OpenStreamSync() - if err != nil { - return err - } - - fmt.Printf("Client: Sending '%s'\n", message) - _, err = stream.Write([]byte(message)) - if err != nil { - return err - } - - buf := make([]byte, len(message)) - _, err = io.ReadFull(stream, buf) - if err != nil { - return err - } - fmt.Printf("Client: Got '%s'\n", buf) - - return nil -} - -// A wrapper for io.Writer that also logs the message. -type loggingWriter struct{ io.Writer } - -func (w loggingWriter) Write(b []byte) (int, error) { - fmt.Printf("Server: Got '%s'\n", string(b)) - return w.Writer.Write(b) -} - -// Setup a bare-bones TLS config for the server -func generateTLSConfig() *tls.Config { - key, err := rsa.GenerateKey(rand.Reader, 1024) - if err != nil { - panic(err) - } - template := x509.Certificate{SerialNumber: big.NewInt(1)} - certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) - if err != nil { - panic(err) - } - keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}) - certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) - - tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) - if err != nil { - panic(err) - } - return &tls.Config{Certificates: []tls.Certificate{tlsCert}} -} diff --git a/vendor/github.com/lucas-clemente/quic-go/example/main.go b/vendor/github.com/lucas-clemente/quic-go/example/main.go deleted file mode 100644 index d6f330fc39f..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/example/main.go +++ /dev/null @@ -1,160 +0,0 @@ -package main - -import ( - "crypto/md5" - "errors" - "flag" - "fmt" - "io" - "io/ioutil" - "log" - "mime/multipart" - "net/http" - "path" - "runtime" - "strings" - "sync" - - _ "net/http/pprof" - - "github.com/lucas-clemente/quic-go/h2quic" - "github.com/lucas-clemente/quic-go/internal/utils" -) - -type binds []string - -func (b binds) String() string { - return strings.Join(b, ",") -} - -func (b *binds) Set(v string) error { - *b = strings.Split(v, ",") - return nil -} - -// Size is needed by the /demo/upload handler to determine the size of the uploaded file -type Size interface { - Size() int64 -} - -func init() { - http.HandleFunc("/demo/tile", func(w http.ResponseWriter, r *http.Request) { - // Small 40x40 png - w.Write([]byte{ - 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, - 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x28, - 0x01, 0x03, 0x00, 0x00, 0x00, 0xb6, 0x30, 0x2a, 0x2e, 0x00, 0x00, 0x00, - 0x03, 0x50, 0x4c, 0x54, 0x45, 0x5a, 0xc3, 0x5a, 0xad, 0x38, 0xaa, 0xdb, - 0x00, 0x00, 0x00, 0x0b, 0x49, 0x44, 0x41, 0x54, 0x78, 0x01, 0x63, 0x18, - 0x61, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x01, 0xe2, 0xb8, 0x75, 0x22, 0x00, - 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, - }) - }) - - http.HandleFunc("/demo/tiles", func(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, "") - for i := 0; i < 200; i++ { - fmt.Fprintf(w, ``, i) - } - io.WriteString(w, "") - }) - - http.HandleFunc("/demo/echo", func(w http.ResponseWriter, r *http.Request) { - body, err := ioutil.ReadAll(r.Body) - if err != nil { - fmt.Printf("error reading body while handling /echo: %s\n", err.Error()) - } - w.Write(body) - }) - - // accept file uploads and return the MD5 of the uploaded file - // maximum accepted file size is 1 GB - http.HandleFunc("/demo/upload", func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodPost { - err := r.ParseMultipartForm(1 << 30) // 1 GB - if err == nil { - var file multipart.File - file, _, err = r.FormFile("uploadfile") - if err == nil { - var size int64 - if sizeInterface, ok := file.(Size); ok { - size = sizeInterface.Size() - b := make([]byte, size) - file.Read(b) - md5 := md5.Sum(b) - fmt.Fprintf(w, "%x", md5) - return - } - err = errors.New("couldn't get uploaded file size") - } - } - if err != nil { - utils.Infof("Error receiving upload: %#v", err) - } - } - io.WriteString(w, `
    -
    - -
    `) - }) -} - -func getBuildDir() string { - _, filename, _, ok := runtime.Caller(0) - if !ok { - panic("Failed to get current frame") - } - - return path.Dir(filename) -} - -func main() { - // defer profile.Start().Stop() - go func() { - log.Println(http.ListenAndServe("localhost:6060", nil)) - }() - // runtime.SetBlockProfileRate(1) - - verbose := flag.Bool("v", false, "verbose") - bs := binds{} - flag.Var(&bs, "bind", "bind to") - certPath := flag.String("certpath", getBuildDir(), "certificate directory") - www := flag.String("www", "/var/www", "www data") - tcp := flag.Bool("tcp", false, "also listen on TCP") - flag.Parse() - - if *verbose { - utils.SetLogLevel(utils.LogLevelDebug) - } else { - utils.SetLogLevel(utils.LogLevelInfo) - } - utils.SetLogTimeFormat("") - - certFile := *certPath + "/fullchain.pem" - keyFile := *certPath + "/privkey.pem" - - http.Handle("/", http.FileServer(http.Dir(*www))) - - if len(bs) == 0 { - bs = binds{"localhost:6121"} - } - - var wg sync.WaitGroup - wg.Add(len(bs)) - for _, b := range bs { - bCap := b - go func() { - var err error - if *tcp { - err = h2quic.ListenAndServe(bCap, certFile, keyFile, nil) - } else { - err = h2quic.ListenAndServeQUIC(bCap, certFile, keyFile, nil) - } - if err != nil { - fmt.Println(err) - } - wg.Done() - }() - } - wg.Wait() -} diff --git a/vendor/github.com/lucas-clemente/quic-go/flowcontrol/flow_control_manager.go b/vendor/github.com/lucas-clemente/quic-go/flowcontrol/flow_control_manager.go deleted file mode 100644 index 9362d60a155..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/flowcontrol/flow_control_manager.go +++ /dev/null @@ -1,240 +0,0 @@ -package flowcontrol - -import ( - "errors" - "fmt" - "sync" - - "github.com/lucas-clemente/quic-go/congestion" - "github.com/lucas-clemente/quic-go/handshake" - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" - "github.com/lucas-clemente/quic-go/qerr" -) - -type flowControlManager struct { - connectionParameters handshake.ConnectionParametersManager - rttStats *congestion.RTTStats - - streamFlowController map[protocol.StreamID]*flowController - connFlowController *flowController - mutex sync.RWMutex -} - -var _ FlowControlManager = &flowControlManager{} - -var errMapAccess = errors.New("Error accessing the flowController map.") - -// NewFlowControlManager creates a new flow control manager -func NewFlowControlManager(connectionParameters handshake.ConnectionParametersManager, rttStats *congestion.RTTStats) FlowControlManager { - return &flowControlManager{ - connectionParameters: connectionParameters, - rttStats: rttStats, - streamFlowController: make(map[protocol.StreamID]*flowController), - connFlowController: newFlowController(0, false, connectionParameters, rttStats), - } -} - -// NewStream creates new flow controllers for a stream -// it does nothing if the stream already exists -func (f *flowControlManager) NewStream(streamID protocol.StreamID, contributesToConnection bool) { - f.mutex.Lock() - defer f.mutex.Unlock() - - if _, ok := f.streamFlowController[streamID]; ok { - return - } - - f.streamFlowController[streamID] = newFlowController(streamID, contributesToConnection, f.connectionParameters, f.rttStats) -} - -// RemoveStream removes a closed stream from flow control -func (f *flowControlManager) RemoveStream(streamID protocol.StreamID) { - f.mutex.Lock() - delete(f.streamFlowController, streamID) - f.mutex.Unlock() -} - -// ResetStream should be called when receiving a RstStreamFrame -// it updates the byte offset to the value in the RstStreamFrame -// streamID must not be 0 here -func (f *flowControlManager) ResetStream(streamID protocol.StreamID, byteOffset protocol.ByteCount) error { - f.mutex.Lock() - defer f.mutex.Unlock() - - streamFlowController, err := f.getFlowController(streamID) - if err != nil { - return err - } - increment, err := streamFlowController.UpdateHighestReceived(byteOffset) - if err != nil { - return qerr.StreamDataAfterTermination - } - - if streamFlowController.CheckFlowControlViolation() { - return qerr.Error(qerr.FlowControlReceivedTooMuchData, fmt.Sprintf("Received %d bytes on stream %d, allowed %d bytes", byteOffset, streamID, streamFlowController.receiveWindow)) - } - - if streamFlowController.ContributesToConnection() { - f.connFlowController.IncrementHighestReceived(increment) - if f.connFlowController.CheckFlowControlViolation() { - return qerr.Error(qerr.FlowControlReceivedTooMuchData, fmt.Sprintf("Received %d bytes for the connection, allowed %d bytes", f.connFlowController.highestReceived, f.connFlowController.receiveWindow)) - } - } - - return nil -} - -// UpdateHighestReceived updates the highest received byte offset for a stream -// it adds the number of additional bytes to connection level flow control -// streamID must not be 0 here -func (f *flowControlManager) UpdateHighestReceived(streamID protocol.StreamID, byteOffset protocol.ByteCount) error { - f.mutex.Lock() - defer f.mutex.Unlock() - - streamFlowController, err := f.getFlowController(streamID) - if err != nil { - return err - } - // UpdateHighestReceived returns an ErrReceivedSmallerByteOffset when StreamFrames got reordered - // this error can be ignored here - increment, _ := streamFlowController.UpdateHighestReceived(byteOffset) - - if streamFlowController.CheckFlowControlViolation() { - return qerr.Error(qerr.FlowControlReceivedTooMuchData, fmt.Sprintf("Received %d bytes on stream %d, allowed %d bytes", byteOffset, streamID, streamFlowController.receiveWindow)) - } - - if streamFlowController.ContributesToConnection() { - f.connFlowController.IncrementHighestReceived(increment) - if f.connFlowController.CheckFlowControlViolation() { - return qerr.Error(qerr.FlowControlReceivedTooMuchData, fmt.Sprintf("Received %d bytes for the connection, allowed %d bytes", f.connFlowController.highestReceived, f.connFlowController.receiveWindow)) - } - } - - return nil -} - -// streamID must not be 0 here -func (f *flowControlManager) AddBytesRead(streamID protocol.StreamID, n protocol.ByteCount) error { - f.mutex.Lock() - defer f.mutex.Unlock() - - fc, err := f.getFlowController(streamID) - if err != nil { - return err - } - - fc.AddBytesRead(n) - if fc.ContributesToConnection() { - f.connFlowController.AddBytesRead(n) - } - - return nil -} - -func (f *flowControlManager) GetWindowUpdates() (res []WindowUpdate) { - f.mutex.Lock() - defer f.mutex.Unlock() - - // get WindowUpdates for streams - for id, fc := range f.streamFlowController { - if necessary, newIncrement, offset := fc.MaybeUpdateWindow(); necessary { - res = append(res, WindowUpdate{StreamID: id, Offset: offset}) - if fc.ContributesToConnection() && newIncrement != 0 { - f.connFlowController.EnsureMinimumWindowIncrement(protocol.ByteCount(float64(newIncrement) * protocol.ConnectionFlowControlMultiplier)) - } - } - } - // get a WindowUpdate for the connection - if necessary, _, offset := f.connFlowController.MaybeUpdateWindow(); necessary { - res = append(res, WindowUpdate{StreamID: 0, Offset: offset}) - } - - return -} - -func (f *flowControlManager) GetReceiveWindow(streamID protocol.StreamID) (protocol.ByteCount, error) { - f.mutex.RLock() - defer f.mutex.RUnlock() - - // StreamID can be 0 when retransmitting - if streamID == 0 { - return f.connFlowController.receiveWindow, nil - } - - flowController, err := f.getFlowController(streamID) - if err != nil { - return 0, err - } - return flowController.receiveWindow, nil -} - -// streamID must not be 0 here -func (f *flowControlManager) AddBytesSent(streamID protocol.StreamID, n protocol.ByteCount) error { - f.mutex.Lock() - defer f.mutex.Unlock() - - fc, err := f.getFlowController(streamID) - if err != nil { - return err - } - - fc.AddBytesSent(n) - if fc.ContributesToConnection() { - f.connFlowController.AddBytesSent(n) - } - - return nil -} - -// must not be called with StreamID 0 -func (f *flowControlManager) SendWindowSize(streamID protocol.StreamID) (protocol.ByteCount, error) { - f.mutex.RLock() - defer f.mutex.RUnlock() - - fc, err := f.getFlowController(streamID) - if err != nil { - return 0, err - } - res := fc.SendWindowSize() - - if fc.ContributesToConnection() { - res = utils.MinByteCount(res, f.connFlowController.SendWindowSize()) - } - - return res, nil -} - -func (f *flowControlManager) RemainingConnectionWindowSize() protocol.ByteCount { - f.mutex.RLock() - defer f.mutex.RUnlock() - - return f.connFlowController.SendWindowSize() -} - -// streamID may be 0 here -func (f *flowControlManager) UpdateWindow(streamID protocol.StreamID, offset protocol.ByteCount) (bool, error) { - f.mutex.Lock() - defer f.mutex.Unlock() - - var fc *flowController - if streamID == 0 { - fc = f.connFlowController - } else { - var err error - fc, err = f.getFlowController(streamID) - if err != nil { - return false, err - } - } - - return fc.UpdateSendWindow(offset), nil -} - -func (f *flowControlManager) getFlowController(streamID protocol.StreamID) (*flowController, error) { - streamFlowController, ok := f.streamFlowController[streamID] - if !ok { - return nil, errMapAccess - } - return streamFlowController, nil -} diff --git a/vendor/github.com/lucas-clemente/quic-go/flowcontrol/flow_controller.go b/vendor/github.com/lucas-clemente/quic-go/flowcontrol/flow_controller.go deleted file mode 100644 index 387ee05b952..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/flowcontrol/flow_controller.go +++ /dev/null @@ -1,198 +0,0 @@ -package flowcontrol - -import ( - "errors" - "time" - - "github.com/lucas-clemente/quic-go/congestion" - "github.com/lucas-clemente/quic-go/handshake" - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" -) - -type flowController struct { - streamID protocol.StreamID - contributesToConnection bool // does the stream contribute to connection level flow control - - connectionParameters handshake.ConnectionParametersManager - rttStats *congestion.RTTStats - - bytesSent protocol.ByteCount - sendWindow protocol.ByteCount - - lastWindowUpdateTime time.Time - - bytesRead protocol.ByteCount - highestReceived protocol.ByteCount - receiveWindow protocol.ByteCount - receiveWindowIncrement protocol.ByteCount - maxReceiveWindowIncrement protocol.ByteCount -} - -// ErrReceivedSmallerByteOffset occurs if the ByteOffset received is smaller than a ByteOffset that was set previously -var ErrReceivedSmallerByteOffset = errors.New("Received a smaller byte offset") - -// newFlowController gets a new flow controller -func newFlowController(streamID protocol.StreamID, contributesToConnection bool, connectionParameters handshake.ConnectionParametersManager, rttStats *congestion.RTTStats) *flowController { - fc := flowController{ - streamID: streamID, - contributesToConnection: contributesToConnection, - connectionParameters: connectionParameters, - rttStats: rttStats, - } - - if streamID == 0 { - fc.receiveWindow = connectionParameters.GetReceiveConnectionFlowControlWindow() - fc.receiveWindowIncrement = fc.receiveWindow - fc.maxReceiveWindowIncrement = connectionParameters.GetMaxReceiveConnectionFlowControlWindow() - } else { - fc.receiveWindow = connectionParameters.GetReceiveStreamFlowControlWindow() - fc.receiveWindowIncrement = fc.receiveWindow - fc.maxReceiveWindowIncrement = connectionParameters.GetMaxReceiveStreamFlowControlWindow() - } - - return &fc -} - -func (c *flowController) ContributesToConnection() bool { - return c.contributesToConnection -} - -func (c *flowController) getSendWindow() protocol.ByteCount { - if c.sendWindow == 0 { - if c.streamID == 0 { - return c.connectionParameters.GetSendConnectionFlowControlWindow() - } - return c.connectionParameters.GetSendStreamFlowControlWindow() - } - return c.sendWindow -} - -func (c *flowController) AddBytesSent(n protocol.ByteCount) { - c.bytesSent += n -} - -// UpdateSendWindow should be called after receiving a WindowUpdateFrame -// it returns true if the window was actually updated -func (c *flowController) UpdateSendWindow(newOffset protocol.ByteCount) bool { - if newOffset > c.sendWindow { - c.sendWindow = newOffset - return true - } - return false -} - -func (c *flowController) SendWindowSize() protocol.ByteCount { - sendWindow := c.getSendWindow() - - if c.bytesSent > sendWindow { // should never happen, but make sure we don't do an underflow here - return 0 - } - return sendWindow - c.bytesSent -} - -func (c *flowController) SendWindowOffset() protocol.ByteCount { - return c.getSendWindow() -} - -// UpdateHighestReceived updates the highestReceived value, if the byteOffset is higher -// Should **only** be used for the stream-level FlowController -// it returns an ErrReceivedSmallerByteOffset if the received byteOffset is smaller than any byteOffset received before -// This error occurs every time StreamFrames get reordered and has to be ignored in that case -// It should only be treated as an error when resetting a stream -func (c *flowController) UpdateHighestReceived(byteOffset protocol.ByteCount) (protocol.ByteCount, error) { - if byteOffset == c.highestReceived { - return 0, nil - } - if byteOffset > c.highestReceived { - increment := byteOffset - c.highestReceived - c.highestReceived = byteOffset - return increment, nil - } - return 0, ErrReceivedSmallerByteOffset -} - -// IncrementHighestReceived adds an increment to the highestReceived value -// Should **only** be used for the connection-level FlowController -func (c *flowController) IncrementHighestReceived(increment protocol.ByteCount) { - c.highestReceived += increment -} - -func (c *flowController) AddBytesRead(n protocol.ByteCount) { - // pretend we sent a WindowUpdate when reading the first byte - // this way auto-tuning of the window increment already works for the first WindowUpdate - if c.bytesRead == 0 { - c.lastWindowUpdateTime = time.Now() - } - c.bytesRead += n -} - -// MaybeUpdateWindow updates the receive window, if necessary -// if the receive window increment is changed, the new value is returned, otherwise a 0 -// the last return value is the new offset of the receive window -func (c *flowController) MaybeUpdateWindow() (bool, protocol.ByteCount /* new increment */, protocol.ByteCount /* new offset */) { - diff := c.receiveWindow - c.bytesRead - - // Chromium implements the same threshold - if diff < (c.receiveWindowIncrement / 2) { - var newWindowIncrement protocol.ByteCount - oldWindowIncrement := c.receiveWindowIncrement - - c.maybeAdjustWindowIncrement() - if c.receiveWindowIncrement != oldWindowIncrement { - newWindowIncrement = c.receiveWindowIncrement - } - - c.lastWindowUpdateTime = time.Now() - c.receiveWindow = c.bytesRead + c.receiveWindowIncrement - return true, newWindowIncrement, c.receiveWindow - } - - return false, 0, 0 -} - -// maybeAdjustWindowIncrement increases the receiveWindowIncrement if we're sending WindowUpdates too often -func (c *flowController) maybeAdjustWindowIncrement() { - if c.lastWindowUpdateTime.IsZero() { - return - } - - rtt := c.rttStats.SmoothedRTT() - if rtt == 0 { - return - } - - timeSinceLastWindowUpdate := time.Since(c.lastWindowUpdateTime) - - // interval between the window updates is sufficiently large, no need to increase the increment - if timeSinceLastWindowUpdate >= 2*rtt { - return - } - - oldWindowSize := c.receiveWindowIncrement - c.receiveWindowIncrement = utils.MinByteCount(2*c.receiveWindowIncrement, c.maxReceiveWindowIncrement) - - // debug log, if the window size was actually increased - if oldWindowSize < c.receiveWindowIncrement { - newWindowSize := c.receiveWindowIncrement / (1 << 10) - if c.streamID == 0 { - utils.Debugf("Increasing receive flow control window for the connection to %d kB", newWindowSize) - } else { - utils.Debugf("Increasing receive flow control window increment for stream %d to %d kB", c.streamID, newWindowSize) - } - } -} - -// EnsureMinimumWindowIncrement sets a minimum window increment -// it is intended be used for the connection-level flow controller -// it should make sure that the connection-level window is increased when a stream-level window grows -func (c *flowController) EnsureMinimumWindowIncrement(inc protocol.ByteCount) { - if inc > c.receiveWindowIncrement { - c.receiveWindowIncrement = utils.MinByteCount(inc, c.maxReceiveWindowIncrement) - c.lastWindowUpdateTime = time.Time{} // disables autotuning for the next window update - } -} - -func (c *flowController) CheckFlowControlViolation() bool { - return c.highestReceived > c.receiveWindow -} diff --git a/vendor/github.com/lucas-clemente/quic-go/flowcontrol/interface.go b/vendor/github.com/lucas-clemente/quic-go/flowcontrol/interface.go deleted file mode 100644 index e1ea3fac686..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/flowcontrol/interface.go +++ /dev/null @@ -1,26 +0,0 @@ -package flowcontrol - -import "github.com/lucas-clemente/quic-go/protocol" - -// WindowUpdate provides the data for WindowUpdateFrames. -type WindowUpdate struct { - StreamID protocol.StreamID - Offset protocol.ByteCount -} - -// A FlowControlManager manages the flow control -type FlowControlManager interface { - NewStream(streamID protocol.StreamID, contributesToConnectionFlow bool) - RemoveStream(streamID protocol.StreamID) - // methods needed for receiving data - ResetStream(streamID protocol.StreamID, byteOffset protocol.ByteCount) error - UpdateHighestReceived(streamID protocol.StreamID, byteOffset protocol.ByteCount) error - AddBytesRead(streamID protocol.StreamID, n protocol.ByteCount) error - GetWindowUpdates() []WindowUpdate - GetReceiveWindow(streamID protocol.StreamID) (protocol.ByteCount, error) - // methods needed for sending data - AddBytesSent(streamID protocol.StreamID, n protocol.ByteCount) error - SendWindowSize(streamID protocol.StreamID) (protocol.ByteCount, error) - RemainingConnectionWindowSize() protocol.ByteCount - UpdateWindow(streamID protocol.StreamID, offset protocol.ByteCount) (bool, error) -} diff --git a/vendor/github.com/lucas-clemente/quic-go/frames/ack_frame.go b/vendor/github.com/lucas-clemente/quic-go/frames/ack_frame.go deleted file mode 100644 index ceeba48c77c..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/frames/ack_frame.go +++ /dev/null @@ -1,468 +0,0 @@ -package frames - -import ( - "bytes" - "errors" - "time" - - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" -) - -var ( - // ErrInvalidAckRanges occurs when a client sends inconsistent ACK ranges - ErrInvalidAckRanges = errors.New("AckFrame: ACK frame contains invalid ACK ranges") - // ErrInvalidFirstAckRange occurs when the first ACK range contains no packets - ErrInvalidFirstAckRange = errors.New("AckFrame: ACK frame has invalid first ACK range") -) - -var ( - errInconsistentAckLargestAcked = errors.New("internal inconsistency: LargestAcked does not match ACK ranges") - errInconsistentAckLowestAcked = errors.New("internal inconsistency: LowestAcked does not match ACK ranges") -) - -// An AckFrame is an ACK frame in QUIC -type AckFrame struct { - LargestAcked protocol.PacketNumber - LowestAcked protocol.PacketNumber - AckRanges []AckRange // has to be ordered. The ACK range with the highest FirstPacketNumber goes first, the ACK range with the lowest FirstPacketNumber goes last - - // time when the LargestAcked was receiveid - // this field Will not be set for received ACKs frames - PacketReceivedTime time.Time - DelayTime time.Duration -} - -// ParseAckFrame reads an ACK frame -func ParseAckFrame(r *bytes.Reader, version protocol.VersionNumber) (*AckFrame, error) { - frame := &AckFrame{} - - typeByte, err := r.ReadByte() - if err != nil { - return nil, err - } - - hasMissingRanges := false - if typeByte&0x20 == 0x20 { - hasMissingRanges = true - } - - largestAckedLen := 2 * ((typeByte & 0x0C) >> 2) - if largestAckedLen == 0 { - largestAckedLen = 1 - } - - missingSequenceNumberDeltaLen := 2 * (typeByte & 0x03) - if missingSequenceNumberDeltaLen == 0 { - missingSequenceNumberDeltaLen = 1 - } - - largestAcked, err := utils.ReadUintN(r, largestAckedLen) - if err != nil { - return nil, err - } - frame.LargestAcked = protocol.PacketNumber(largestAcked) - - delay, err := utils.ReadUfloat16(r) - if err != nil { - return nil, err - } - frame.DelayTime = time.Duration(delay) * time.Microsecond - - var numAckBlocks uint8 - if hasMissingRanges { - numAckBlocks, err = r.ReadByte() - if err != nil { - return nil, err - } - } - - if hasMissingRanges && numAckBlocks == 0 { - return nil, ErrInvalidAckRanges - } - - ackBlockLength, err := utils.ReadUintN(r, missingSequenceNumberDeltaLen) - if err != nil { - return nil, err - } - if frame.LargestAcked > 0 && ackBlockLength < 1 { - return nil, ErrInvalidFirstAckRange - } - - if ackBlockLength > largestAcked { - return nil, ErrInvalidAckRanges - } - - if hasMissingRanges { - ackRange := AckRange{ - FirstPacketNumber: protocol.PacketNumber(largestAcked-ackBlockLength) + 1, - LastPacketNumber: frame.LargestAcked, - } - frame.AckRanges = append(frame.AckRanges, ackRange) - - var inLongBlock bool - var lastRangeComplete bool - for i := uint8(0); i < numAckBlocks; i++ { - var gap uint8 - gap, err = r.ReadByte() - if err != nil { - return nil, err - } - - ackBlockLength, err = utils.ReadUintN(r, missingSequenceNumberDeltaLen) - if err != nil { - return nil, err - } - - length := protocol.PacketNumber(ackBlockLength) - - if inLongBlock { - frame.AckRanges[len(frame.AckRanges)-1].FirstPacketNumber -= protocol.PacketNumber(gap) + length - frame.AckRanges[len(frame.AckRanges)-1].LastPacketNumber -= protocol.PacketNumber(gap) - } else { - lastRangeComplete = false - ackRange := AckRange{ - LastPacketNumber: frame.AckRanges[len(frame.AckRanges)-1].FirstPacketNumber - protocol.PacketNumber(gap) - 1, - } - ackRange.FirstPacketNumber = ackRange.LastPacketNumber - length + 1 - frame.AckRanges = append(frame.AckRanges, ackRange) - } - - if length > 0 { - lastRangeComplete = true - } - - inLongBlock = (ackBlockLength == 0) - } - - // if the last range was not complete, FirstPacketNumber and LastPacketNumber make no sense - // remove the range from frame.AckRanges - if !lastRangeComplete { - frame.AckRanges = frame.AckRanges[:len(frame.AckRanges)-1] - } - - frame.LowestAcked = frame.AckRanges[len(frame.AckRanges)-1].FirstPacketNumber - } else { - if frame.LargestAcked == 0 { - frame.LowestAcked = 0 - } else { - frame.LowestAcked = protocol.PacketNumber(largestAcked + 1 - ackBlockLength) - } - } - - if !frame.validateAckRanges() { - return nil, ErrInvalidAckRanges - } - - var numTimestamp byte - numTimestamp, err = r.ReadByte() - if err != nil { - return nil, err - } - - if numTimestamp > 0 { - // Delta Largest acked - _, err = r.ReadByte() - if err != nil { - return nil, err - } - // First Timestamp - _, err = utils.ReadUint32(r) - if err != nil { - return nil, err - } - - for i := 0; i < int(numTimestamp)-1; i++ { - // Delta Largest acked - _, err = r.ReadByte() - if err != nil { - return nil, err - } - - // Time Since Previous Timestamp - _, err = utils.ReadUint16(r) - if err != nil { - return nil, err - } - } - } - - return frame, nil -} - -// Write writes an ACK frame. -func (f *AckFrame) Write(b *bytes.Buffer, version protocol.VersionNumber) error { - largestAckedLen := protocol.GetPacketNumberLength(f.LargestAcked) - - typeByte := uint8(0x40) - - if largestAckedLen != protocol.PacketNumberLen1 { - typeByte ^= (uint8(largestAckedLen / 2)) << 2 - } - - missingSequenceNumberDeltaLen := f.getMissingSequenceNumberDeltaLen() - if missingSequenceNumberDeltaLen != protocol.PacketNumberLen1 { - typeByte ^= (uint8(missingSequenceNumberDeltaLen / 2)) - } - - if f.HasMissingRanges() { - typeByte |= 0x20 - } - - b.WriteByte(typeByte) - - switch largestAckedLen { - case protocol.PacketNumberLen1: - b.WriteByte(uint8(f.LargestAcked)) - case protocol.PacketNumberLen2: - utils.WriteUint16(b, uint16(f.LargestAcked)) - case protocol.PacketNumberLen4: - utils.WriteUint32(b, uint32(f.LargestAcked)) - case protocol.PacketNumberLen6: - utils.WriteUint48(b, uint64(f.LargestAcked)) - } - - f.DelayTime = time.Since(f.PacketReceivedTime) - utils.WriteUfloat16(b, uint64(f.DelayTime/time.Microsecond)) - - var numRanges uint64 - var numRangesWritten uint64 - if f.HasMissingRanges() { - numRanges = f.numWritableNackRanges() - if numRanges > 0xFF { - panic("AckFrame: Too many ACK ranges") - } - b.WriteByte(uint8(numRanges - 1)) - } - - var firstAckBlockLength protocol.PacketNumber - if !f.HasMissingRanges() { - firstAckBlockLength = f.LargestAcked - f.LowestAcked + 1 - } else { - if f.LargestAcked != f.AckRanges[0].LastPacketNumber { - return errInconsistentAckLargestAcked - } - if f.LowestAcked != f.AckRanges[len(f.AckRanges)-1].FirstPacketNumber { - return errInconsistentAckLowestAcked - } - firstAckBlockLength = f.LargestAcked - f.AckRanges[0].FirstPacketNumber + 1 - numRangesWritten++ - } - - switch missingSequenceNumberDeltaLen { - case protocol.PacketNumberLen1: - b.WriteByte(uint8(firstAckBlockLength)) - case protocol.PacketNumberLen2: - utils.WriteUint16(b, uint16(firstAckBlockLength)) - case protocol.PacketNumberLen4: - utils.WriteUint32(b, uint32(firstAckBlockLength)) - case protocol.PacketNumberLen6: - utils.WriteUint48(b, uint64(firstAckBlockLength)) - } - - for i, ackRange := range f.AckRanges { - if i == 0 { - continue - } - - length := ackRange.LastPacketNumber - ackRange.FirstPacketNumber + 1 - gap := f.AckRanges[i-1].FirstPacketNumber - ackRange.LastPacketNumber - 1 - - num := gap/0xFF + 1 - if gap%0xFF == 0 { - num-- - } - - if num == 1 { - b.WriteByte(uint8(gap)) - switch missingSequenceNumberDeltaLen { - case protocol.PacketNumberLen1: - b.WriteByte(uint8(length)) - case protocol.PacketNumberLen2: - utils.WriteUint16(b, uint16(length)) - case protocol.PacketNumberLen4: - utils.WriteUint32(b, uint32(length)) - case protocol.PacketNumberLen6: - utils.WriteUint48(b, uint64(length)) - } - numRangesWritten++ - } else { - for i := 0; i < int(num); i++ { - var lengthWritten uint64 - var gapWritten uint8 - - if i == int(num)-1 { // last block - lengthWritten = uint64(length) - gapWritten = uint8(1 + ((gap - 1) % 255)) - } else { - lengthWritten = 0 - gapWritten = 0xFF - } - - b.WriteByte(gapWritten) - switch missingSequenceNumberDeltaLen { - case protocol.PacketNumberLen1: - b.WriteByte(uint8(lengthWritten)) - case protocol.PacketNumberLen2: - utils.WriteUint16(b, uint16(lengthWritten)) - case protocol.PacketNumberLen4: - utils.WriteUint32(b, uint32(lengthWritten)) - case protocol.PacketNumberLen6: - utils.WriteUint48(b, lengthWritten) - } - - numRangesWritten++ - } - } - - // this is needed if not all AckRanges can be written to the ACK frame (if there are more than 0xFF) - if numRangesWritten >= numRanges { - break - } - } - - if numRanges != numRangesWritten { - return errors.New("BUG: Inconsistent number of ACK ranges written") - } - - b.WriteByte(0) // no timestamps - - return nil -} - -// MinLength of a written frame -func (f *AckFrame) MinLength(version protocol.VersionNumber) (protocol.ByteCount, error) { - length := protocol.ByteCount(1 + 2 + 1) // 1 TypeByte, 2 ACK delay time, 1 Num Timestamp - length += protocol.ByteCount(protocol.GetPacketNumberLength(f.LargestAcked)) - - missingSequenceNumberDeltaLen := protocol.ByteCount(f.getMissingSequenceNumberDeltaLen()) - - if f.HasMissingRanges() { - length += (1 + missingSequenceNumberDeltaLen) * protocol.ByteCount(f.numWritableNackRanges()) - } else { - length += missingSequenceNumberDeltaLen - } - - length += (1 + 2) * 0 /* TODO: num_timestamps */ - - return length, nil -} - -// HasMissingRanges returns if this frame reports any missing packets -func (f *AckFrame) HasMissingRanges() bool { - return len(f.AckRanges) > 0 -} - -func (f *AckFrame) validateAckRanges() bool { - if len(f.AckRanges) == 0 { - return true - } - - // if there are missing packets, there will always be at least 2 ACK ranges - if len(f.AckRanges) == 1 { - return false - } - - if f.AckRanges[0].LastPacketNumber != f.LargestAcked { - return false - } - - // check the validity of every single ACK range - for _, ackRange := range f.AckRanges { - if ackRange.FirstPacketNumber > ackRange.LastPacketNumber { - return false - } - } - - // check the consistency for ACK with multiple NACK ranges - for i, ackRange := range f.AckRanges { - if i == 0 { - continue - } - lastAckRange := f.AckRanges[i-1] - if lastAckRange.FirstPacketNumber <= ackRange.FirstPacketNumber { - return false - } - if lastAckRange.FirstPacketNumber <= ackRange.LastPacketNumber+1 { - return false - } - } - - return true -} - -// numWritableNackRanges calculates the number of ACK blocks that are about to be written -// this number is different from len(f.AckRanges) for the case of long gaps (> 255 packets) -func (f *AckFrame) numWritableNackRanges() uint64 { - if len(f.AckRanges) == 0 { - return 0 - } - - var numRanges uint64 - for i, ackRange := range f.AckRanges { - if i == 0 { - continue - } - - lastAckRange := f.AckRanges[i-1] - gap := lastAckRange.FirstPacketNumber - ackRange.LastPacketNumber - 1 - rangeLength := 1 + uint64(gap)/0xFF - if uint64(gap)%0xFF == 0 { - rangeLength-- - } - - if numRanges+rangeLength < 0xFF { - numRanges += rangeLength - } else { - break - } - } - - return numRanges + 1 -} - -func (f *AckFrame) getMissingSequenceNumberDeltaLen() protocol.PacketNumberLen { - var maxRangeLength protocol.PacketNumber - - if f.HasMissingRanges() { - for _, ackRange := range f.AckRanges { - rangeLength := ackRange.LastPacketNumber - ackRange.FirstPacketNumber + 1 - if rangeLength > maxRangeLength { - maxRangeLength = rangeLength - } - } - } else { - maxRangeLength = f.LargestAcked - f.LowestAcked + 1 - } - - if maxRangeLength <= 0xFF { - return protocol.PacketNumberLen1 - } - if maxRangeLength <= 0xFFFF { - return protocol.PacketNumberLen2 - } - if maxRangeLength <= 0xFFFFFFFF { - return protocol.PacketNumberLen4 - } - - return protocol.PacketNumberLen6 -} - -// AcksPacket determines if this ACK frame acks a certain packet number -func (f *AckFrame) AcksPacket(p protocol.PacketNumber) bool { - if p < f.LowestAcked || p > f.LargestAcked { // this is just a performance optimization - return false - } - - if f.HasMissingRanges() { - // TODO: this could be implemented as a binary search - for _, ackRange := range f.AckRanges { - if p >= ackRange.FirstPacketNumber && p <= ackRange.LastPacketNumber { - return true - } - } - return false - } - // if packet doesn't have missing ranges - return (p >= f.LowestAcked && p <= f.LargestAcked) -} diff --git a/vendor/github.com/lucas-clemente/quic-go/frames/ack_range.go b/vendor/github.com/lucas-clemente/quic-go/frames/ack_range.go deleted file mode 100644 index ac65d33ea04..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/frames/ack_range.go +++ /dev/null @@ -1,9 +0,0 @@ -package frames - -import "github.com/lucas-clemente/quic-go/protocol" - -// AckRange is an ACK range -type AckRange struct { - FirstPacketNumber protocol.PacketNumber - LastPacketNumber protocol.PacketNumber -} diff --git a/vendor/github.com/lucas-clemente/quic-go/frames/blocked_frame.go b/vendor/github.com/lucas-clemente/quic-go/frames/blocked_frame.go deleted file mode 100644 index 44645780d7f..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/frames/blocked_frame.go +++ /dev/null @@ -1,44 +0,0 @@ -package frames - -import ( - "bytes" - - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" -) - -// A BlockedFrame in QUIC -type BlockedFrame struct { - StreamID protocol.StreamID -} - -//Write writes a BlockedFrame frame -func (f *BlockedFrame) Write(b *bytes.Buffer, version protocol.VersionNumber) error { - b.WriteByte(0x05) - utils.WriteUint32(b, uint32(f.StreamID)) - return nil -} - -// MinLength of a written frame -func (f *BlockedFrame) MinLength(version protocol.VersionNumber) (protocol.ByteCount, error) { - return 1 + 4, nil -} - -// ParseBlockedFrame parses a BLOCKED frame -func ParseBlockedFrame(r *bytes.Reader) (*BlockedFrame, error) { - frame := &BlockedFrame{} - - // read the TypeByte - _, err := r.ReadByte() - if err != nil { - return nil, err - } - - sid, err := utils.ReadUint32(r) - if err != nil { - return nil, err - } - frame.StreamID = protocol.StreamID(sid) - - return frame, nil -} diff --git a/vendor/github.com/lucas-clemente/quic-go/frames/connection_close_frame.go b/vendor/github.com/lucas-clemente/quic-go/frames/connection_close_frame.go deleted file mode 100644 index 5a7ed04cfe3..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/frames/connection_close_frame.go +++ /dev/null @@ -1,73 +0,0 @@ -package frames - -import ( - "bytes" - "errors" - "io" - "math" - - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" - "github.com/lucas-clemente/quic-go/qerr" -) - -// A ConnectionCloseFrame in QUIC -type ConnectionCloseFrame struct { - ErrorCode qerr.ErrorCode - ReasonPhrase string -} - -// ParseConnectionCloseFrame reads a CONNECTION_CLOSE frame -func ParseConnectionCloseFrame(r *bytes.Reader) (*ConnectionCloseFrame, error) { - frame := &ConnectionCloseFrame{} - - // read the TypeByte - _, err := r.ReadByte() - if err != nil { - return nil, err - } - - errorCode, err := utils.ReadUint32(r) - if err != nil { - return nil, err - } - frame.ErrorCode = qerr.ErrorCode(errorCode) - - reasonPhraseLen, err := utils.ReadUint16(r) - if err != nil { - return nil, err - } - - if reasonPhraseLen > uint16(protocol.MaxPacketSize) { - return nil, qerr.Error(qerr.InvalidConnectionCloseData, "reason phrase too long") - } - - reasonPhrase := make([]byte, reasonPhraseLen) - if _, err := io.ReadFull(r, reasonPhrase); err != nil { - return nil, err - } - frame.ReasonPhrase = string(reasonPhrase) - - return frame, nil -} - -// MinLength of a written frame -func (f *ConnectionCloseFrame) MinLength(version protocol.VersionNumber) (protocol.ByteCount, error) { - return 1 + 4 + 2 + protocol.ByteCount(len(f.ReasonPhrase)), nil -} - -// Write writes an CONNECTION_CLOSE frame. -func (f *ConnectionCloseFrame) Write(b *bytes.Buffer, version protocol.VersionNumber) error { - b.WriteByte(0x02) - utils.WriteUint32(b, uint32(f.ErrorCode)) - - if len(f.ReasonPhrase) > math.MaxUint16 { - return errors.New("ConnectionFrame: ReasonPhrase too long") - } - - reasonPhraseLen := uint16(len(f.ReasonPhrase)) - utils.WriteUint16(b, reasonPhraseLen) - b.WriteString(f.ReasonPhrase) - - return nil -} diff --git a/vendor/github.com/lucas-clemente/quic-go/frames/frame.go b/vendor/github.com/lucas-clemente/quic-go/frames/frame.go deleted file mode 100644 index 464e6693a93..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/frames/frame.go +++ /dev/null @@ -1,13 +0,0 @@ -package frames - -import ( - "bytes" - - "github.com/lucas-clemente/quic-go/protocol" -) - -// A Frame in QUIC -type Frame interface { - Write(b *bytes.Buffer, version protocol.VersionNumber) error - MinLength(version protocol.VersionNumber) (protocol.ByteCount, error) -} diff --git a/vendor/github.com/lucas-clemente/quic-go/frames/goaway_frame.go b/vendor/github.com/lucas-clemente/quic-go/frames/goaway_frame.go deleted file mode 100644 index e00a6cf5ade..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/frames/goaway_frame.go +++ /dev/null @@ -1,73 +0,0 @@ -package frames - -import ( - "bytes" - "io" - - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" - "github.com/lucas-clemente/quic-go/qerr" -) - -// A GoawayFrame is a GOAWAY frame -type GoawayFrame struct { - ErrorCode qerr.ErrorCode - LastGoodStream protocol.StreamID - ReasonPhrase string -} - -// ParseGoawayFrame parses a GOAWAY frame -func ParseGoawayFrame(r *bytes.Reader) (*GoawayFrame, error) { - frame := &GoawayFrame{} - - _, err := r.ReadByte() - if err != nil { - return nil, err - } - - errorCode, err := utils.ReadUint32(r) - if err != nil { - return nil, err - } - frame.ErrorCode = qerr.ErrorCode(errorCode) - - lastGoodStream, err := utils.ReadUint32(r) - if err != nil { - return nil, err - } - frame.LastGoodStream = protocol.StreamID(lastGoodStream) - - reasonPhraseLen, err := utils.ReadUint16(r) - if err != nil { - return nil, err - } - - if reasonPhraseLen > uint16(protocol.MaxPacketSize) { - return nil, qerr.Error(qerr.InvalidGoawayData, "reason phrase too long") - } - - reasonPhrase := make([]byte, reasonPhraseLen) - if _, err := io.ReadFull(r, reasonPhrase); err != nil { - return nil, err - } - frame.ReasonPhrase = string(reasonPhrase) - - return frame, nil -} - -func (f *GoawayFrame) Write(b *bytes.Buffer, version protocol.VersionNumber) error { - typeByte := uint8(0x03) - b.WriteByte(typeByte) - - utils.WriteUint32(b, uint32(f.ErrorCode)) - utils.WriteUint32(b, uint32(f.LastGoodStream)) - utils.WriteUint16(b, uint16(len(f.ReasonPhrase))) - b.WriteString(f.ReasonPhrase) - - return nil -} - -// MinLength of a written frame -func (f *GoawayFrame) MinLength(version protocol.VersionNumber) (protocol.ByteCount, error) { - return protocol.ByteCount(1 + 4 + 4 + 2 + len(f.ReasonPhrase)), nil -} diff --git a/vendor/github.com/lucas-clemente/quic-go/frames/log.go b/vendor/github.com/lucas-clemente/quic-go/frames/log.go deleted file mode 100644 index 6b7fdcec455..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/frames/log.go +++ /dev/null @@ -1,28 +0,0 @@ -package frames - -import "github.com/lucas-clemente/quic-go/internal/utils" - -// LogFrame logs a frame, either sent or received -func LogFrame(frame Frame, sent bool) { - if !utils.Debug() { - return - } - dir := "<-" - if sent { - dir = "->" - } - switch f := frame.(type) { - case *StreamFrame: - utils.Debugf("\t%s &frames.StreamFrame{StreamID: %d, FinBit: %t, Offset: 0x%x, Data length: 0x%x, Offset + Data length: 0x%x}", dir, f.StreamID, f.FinBit, f.Offset, f.DataLen(), f.Offset+f.DataLen()) - case *StopWaitingFrame: - if sent { - utils.Debugf("\t%s &frames.StopWaitingFrame{LeastUnacked: 0x%x, PacketNumberLen: 0x%x}", dir, f.LeastUnacked, f.PacketNumberLen) - } else { - utils.Debugf("\t%s &frames.StopWaitingFrame{LeastUnacked: 0x%x}", dir, f.LeastUnacked) - } - case *AckFrame: - utils.Debugf("\t%s &frames.AckFrame{LargestAcked: 0x%x, LowestAcked: 0x%x, AckRanges: %#v, DelayTime: %s}", dir, f.LargestAcked, f.LowestAcked, f.AckRanges, f.DelayTime.String()) - default: - utils.Debugf("\t%s %#v", dir, frame) - } -} diff --git a/vendor/github.com/lucas-clemente/quic-go/frames/ping_frame.go b/vendor/github.com/lucas-clemente/quic-go/frames/ping_frame.go deleted file mode 100644 index 8486af57b14..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/frames/ping_frame.go +++ /dev/null @@ -1,33 +0,0 @@ -package frames - -import ( - "bytes" - - "github.com/lucas-clemente/quic-go/protocol" -) - -// A PingFrame is a ping frame -type PingFrame struct{} - -// ParsePingFrame parses a Ping frame -func ParsePingFrame(r *bytes.Reader) (*PingFrame, error) { - frame := &PingFrame{} - - _, err := r.ReadByte() - if err != nil { - return nil, err - } - - return frame, nil -} - -func (f *PingFrame) Write(b *bytes.Buffer, version protocol.VersionNumber) error { - typeByte := uint8(0x07) - b.WriteByte(typeByte) - return nil -} - -// MinLength of a written frame -func (f *PingFrame) MinLength(version protocol.VersionNumber) (protocol.ByteCount, error) { - return 1, nil -} diff --git a/vendor/github.com/lucas-clemente/quic-go/frames/rst_stream_frame.go b/vendor/github.com/lucas-clemente/quic-go/frames/rst_stream_frame.go deleted file mode 100644 index ea2531c684c..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/frames/rst_stream_frame.go +++ /dev/null @@ -1,59 +0,0 @@ -package frames - -import ( - "bytes" - - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" -) - -// A RstStreamFrame in QUIC -type RstStreamFrame struct { - StreamID protocol.StreamID - ErrorCode uint32 - ByteOffset protocol.ByteCount -} - -//Write writes a RST_STREAM frame -func (f *RstStreamFrame) Write(b *bytes.Buffer, version protocol.VersionNumber) error { - b.WriteByte(0x01) - utils.WriteUint32(b, uint32(f.StreamID)) - utils.WriteUint64(b, uint64(f.ByteOffset)) - utils.WriteUint32(b, f.ErrorCode) - return nil -} - -// MinLength of a written frame -func (f *RstStreamFrame) MinLength(version protocol.VersionNumber) (protocol.ByteCount, error) { - return 1 + 4 + 8 + 4, nil -} - -// ParseRstStreamFrame parses a RST_STREAM frame -func ParseRstStreamFrame(r *bytes.Reader) (*RstStreamFrame, error) { - frame := &RstStreamFrame{} - - // read the TypeByte - _, err := r.ReadByte() - if err != nil { - return nil, err - } - - sid, err := utils.ReadUint32(r) - if err != nil { - return nil, err - } - frame.StreamID = protocol.StreamID(sid) - - byteOffset, err := utils.ReadUint64(r) - if err != nil { - return nil, err - } - frame.ByteOffset = protocol.ByteCount(byteOffset) - - frame.ErrorCode, err = utils.ReadUint32(r) - if err != nil { - return nil, err - } - - return frame, nil -} diff --git a/vendor/github.com/lucas-clemente/quic-go/frames/stop_waiting_frame.go b/vendor/github.com/lucas-clemente/quic-go/frames/stop_waiting_frame.go deleted file mode 100644 index 91f937a8601..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/frames/stop_waiting_frame.go +++ /dev/null @@ -1,91 +0,0 @@ -package frames - -import ( - "bytes" - "errors" - - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" - "github.com/lucas-clemente/quic-go/qerr" -) - -// A StopWaitingFrame in QUIC -type StopWaitingFrame struct { - LeastUnacked protocol.PacketNumber - PacketNumberLen protocol.PacketNumberLen - PacketNumber protocol.PacketNumber -} - -var ( - errLeastUnackedHigherThanPacketNumber = errors.New("StopWaitingFrame: LeastUnacked can't be greater than the packet number") - errPacketNumberNotSet = errors.New("StopWaitingFrame: PacketNumber not set") - errPacketNumberLenNotSet = errors.New("StopWaitingFrame: PacketNumberLen not set") -) - -func (f *StopWaitingFrame) Write(b *bytes.Buffer, version protocol.VersionNumber) error { - // packetNumber is the packet number of the packet that this StopWaitingFrame will be sent with - typeByte := uint8(0x06) - b.WriteByte(typeByte) - - // make sure the PacketNumber was set - if f.PacketNumber == protocol.PacketNumber(0) { - return errPacketNumberNotSet - } - - if f.LeastUnacked > f.PacketNumber { - return errLeastUnackedHigherThanPacketNumber - } - - leastUnackedDelta := uint64(f.PacketNumber - f.LeastUnacked) - - switch f.PacketNumberLen { - case protocol.PacketNumberLen1: - b.WriteByte(uint8(leastUnackedDelta)) - case protocol.PacketNumberLen2: - utils.WriteUint16(b, uint16(leastUnackedDelta)) - case protocol.PacketNumberLen4: - utils.WriteUint32(b, uint32(leastUnackedDelta)) - case protocol.PacketNumberLen6: - utils.WriteUint48(b, leastUnackedDelta) - default: - return errPacketNumberLenNotSet - } - - return nil -} - -// MinLength of a written frame -func (f *StopWaitingFrame) MinLength(version protocol.VersionNumber) (protocol.ByteCount, error) { - minLength := protocol.ByteCount(1) // typeByte - - if f.PacketNumberLen == protocol.PacketNumberLenInvalid { - return 0, errPacketNumberLenNotSet - } - minLength += protocol.ByteCount(f.PacketNumberLen) - - return minLength, nil -} - -// ParseStopWaitingFrame parses a StopWaiting frame -func ParseStopWaitingFrame(r *bytes.Reader, packetNumber protocol.PacketNumber, packetNumberLen protocol.PacketNumberLen, version protocol.VersionNumber) (*StopWaitingFrame, error) { - frame := &StopWaitingFrame{} - - // read the TypeByte - _, err := r.ReadByte() - if err != nil { - return nil, err - } - - leastUnackedDelta, err := utils.ReadUintN(r, uint8(packetNumberLen)) - if err != nil { - return nil, err - } - - if leastUnackedDelta > uint64(packetNumber) { - return nil, qerr.Error(qerr.InvalidStopWaitingData, "invalid LeastUnackedDelta") - } - - frame.LeastUnacked = protocol.PacketNumber(uint64(packetNumber) - leastUnackedDelta) - - return frame, nil -} diff --git a/vendor/github.com/lucas-clemente/quic-go/frames/stream_frame.go b/vendor/github.com/lucas-clemente/quic-go/frames/stream_frame.go deleted file mode 100644 index 7dd6223564e..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/frames/stream_frame.go +++ /dev/null @@ -1,212 +0,0 @@ -package frames - -import ( - "bytes" - "errors" - - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" - "github.com/lucas-clemente/quic-go/qerr" -) - -// A StreamFrame of QUIC -type StreamFrame struct { - StreamID protocol.StreamID - FinBit bool - DataLenPresent bool - Offset protocol.ByteCount - Data []byte -} - -var ( - errInvalidStreamIDLen = errors.New("StreamFrame: Invalid StreamID length") - errInvalidOffsetLen = errors.New("StreamFrame: Invalid offset length") -) - -// ParseStreamFrame reads a stream frame. The type byte must not have been read yet. -func ParseStreamFrame(r *bytes.Reader) (*StreamFrame, error) { - frame := &StreamFrame{} - - typeByte, err := r.ReadByte() - if err != nil { - return nil, err - } - - frame.FinBit = typeByte&0x40 > 0 - frame.DataLenPresent = typeByte&0x20 > 0 - offsetLen := typeByte & 0x1C >> 2 - if offsetLen != 0 { - offsetLen++ - } - streamIDLen := typeByte&0x03 + 1 - - sid, err := utils.ReadUintN(r, streamIDLen) - if err != nil { - return nil, err - } - frame.StreamID = protocol.StreamID(sid) - - offset, err := utils.ReadUintN(r, offsetLen) - if err != nil { - return nil, err - } - frame.Offset = protocol.ByteCount(offset) - - var dataLen uint16 - if frame.DataLenPresent { - dataLen, err = utils.ReadUint16(r) - if err != nil { - return nil, err - } - } - - if dataLen > uint16(protocol.MaxPacketSize) { - return nil, qerr.Error(qerr.InvalidStreamData, "data len too large") - } - - if !frame.DataLenPresent { - // The rest of the packet is data - dataLen = uint16(r.Len()) - } - if dataLen != 0 { - frame.Data = make([]byte, dataLen) - n, err := r.Read(frame.Data) - if n != int(dataLen) { - return nil, errors.New("BUG: StreamFrame could not read dataLen bytes") - } - if err != nil { - return nil, err - } - } - - if frame.Offset+frame.DataLen() < frame.Offset { - return nil, qerr.Error(qerr.InvalidStreamData, "data overflows maximum offset") - } - - if !frame.FinBit && frame.DataLen() == 0 { - return nil, qerr.EmptyStreamFrameNoFin - } - - return frame, nil -} - -// WriteStreamFrame writes a stream frame. -func (f *StreamFrame) Write(b *bytes.Buffer, version protocol.VersionNumber) error { - if len(f.Data) == 0 && !f.FinBit { - return errors.New("StreamFrame: attempting to write empty frame without FIN") - } - - typeByte := uint8(0x80) // sets the leftmost bit to 1 - - if f.FinBit { - typeByte ^= 0x40 - } - - if f.DataLenPresent { - typeByte ^= 0x20 - } - - offsetLength := f.getOffsetLength() - - if offsetLength > 0 { - typeByte ^= (uint8(offsetLength) - 1) << 2 - } - - streamIDLen := f.calculateStreamIDLength() - typeByte ^= streamIDLen - 1 - - b.WriteByte(typeByte) - - switch streamIDLen { - case 1: - b.WriteByte(uint8(f.StreamID)) - case 2: - utils.WriteUint16(b, uint16(f.StreamID)) - case 3: - utils.WriteUint24(b, uint32(f.StreamID)) - case 4: - utils.WriteUint32(b, uint32(f.StreamID)) - default: - return errInvalidStreamIDLen - } - - switch offsetLength { - case 0: - case 2: - utils.WriteUint16(b, uint16(f.Offset)) - case 3: - utils.WriteUint24(b, uint32(f.Offset)) - case 4: - utils.WriteUint32(b, uint32(f.Offset)) - case 5: - utils.WriteUint40(b, uint64(f.Offset)) - case 6: - utils.WriteUint48(b, uint64(f.Offset)) - case 7: - utils.WriteUint56(b, uint64(f.Offset)) - case 8: - utils.WriteUint64(b, uint64(f.Offset)) - default: - return errInvalidOffsetLen - } - - if f.DataLenPresent { - utils.WriteUint16(b, uint16(len(f.Data))) - } - - b.Write(f.Data) - - return nil -} - -func (f *StreamFrame) calculateStreamIDLength() uint8 { - if f.StreamID < (1 << 8) { - return 1 - } else if f.StreamID < (1 << 16) { - return 2 - } else if f.StreamID < (1 << 24) { - return 3 - } - return 4 -} - -func (f *StreamFrame) getOffsetLength() protocol.ByteCount { - if f.Offset == 0 { - return 0 - } - if f.Offset < (1 << 16) { - return 2 - } - if f.Offset < (1 << 24) { - return 3 - } - if f.Offset < (1 << 32) { - return 4 - } - if f.Offset < (1 << 40) { - return 5 - } - if f.Offset < (1 << 48) { - return 6 - } - if f.Offset < (1 << 56) { - return 7 - } - return 8 -} - -// MinLength returns the length of the header of a StreamFrame -// the total length of the StreamFrame is frame.MinLength() + frame.DataLen() -func (f *StreamFrame) MinLength(protocol.VersionNumber) (protocol.ByteCount, error) { - length := protocol.ByteCount(1) + protocol.ByteCount(f.calculateStreamIDLength()) + f.getOffsetLength() - if f.DataLenPresent { - length += 2 - } - - return length, nil -} - -// DataLen gives the length of data in bytes -func (f *StreamFrame) DataLen() protocol.ByteCount { - return protocol.ByteCount(len(f.Data)) -} diff --git a/vendor/github.com/lucas-clemente/quic-go/frames/window_update_frame.go b/vendor/github.com/lucas-clemente/quic-go/frames/window_update_frame.go deleted file mode 100644 index 9b8b4598698..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/frames/window_update_frame.go +++ /dev/null @@ -1,54 +0,0 @@ -package frames - -import ( - "bytes" - - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" -) - -// A WindowUpdateFrame in QUIC -type WindowUpdateFrame struct { - StreamID protocol.StreamID - ByteOffset protocol.ByteCount -} - -//Write writes a RST_STREAM frame -func (f *WindowUpdateFrame) Write(b *bytes.Buffer, version protocol.VersionNumber) error { - typeByte := uint8(0x04) - b.WriteByte(typeByte) - - utils.WriteUint32(b, uint32(f.StreamID)) - utils.WriteUint64(b, uint64(f.ByteOffset)) - return nil -} - -// MinLength of a written frame -func (f *WindowUpdateFrame) MinLength(version protocol.VersionNumber) (protocol.ByteCount, error) { - return 1 + 4 + 8, nil -} - -// ParseWindowUpdateFrame parses a RST_STREAM frame -func ParseWindowUpdateFrame(r *bytes.Reader) (*WindowUpdateFrame, error) { - frame := &WindowUpdateFrame{} - - // read the TypeByte - _, err := r.ReadByte() - if err != nil { - return nil, err - } - - sid, err := utils.ReadUint32(r) - if err != nil { - return nil, err - } - frame.StreamID = protocol.StreamID(sid) - - byteOffset, err := utils.ReadUint64(r) - if err != nil { - return nil, err - } - frame.ByteOffset = protocol.ByteCount(byteOffset) - - return frame, nil -} diff --git a/vendor/github.com/lucas-clemente/quic-go/h2quic/client.go b/vendor/github.com/lucas-clemente/quic-go/h2quic/client.go deleted file mode 100644 index 866b11abc7c..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/h2quic/client.go +++ /dev/null @@ -1,296 +0,0 @@ -package h2quic - -import ( - "crypto/tls" - "errors" - "fmt" - "io" - "net" - "net/http" - "strings" - "sync" - - "golang.org/x/net/http2" - "golang.org/x/net/http2/hpack" - "golang.org/x/net/idna" - - quic "github.com/lucas-clemente/quic-go" - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" - "github.com/lucas-clemente/quic-go/qerr" -) - -type roundTripperOpts struct { - DisableCompression bool -} - -var dialAddr = quic.DialAddr - -// client is a HTTP2 client doing QUIC requests -type client struct { - mutex sync.RWMutex - - tlsConf *tls.Config - config *quic.Config - opts *roundTripperOpts - - hostname string - encryptionLevel protocol.EncryptionLevel - handshakeErr error - dialOnce sync.Once - - session quic.Session - headerStream quic.Stream - headerErr *qerr.QuicError - headerErrored chan struct{} // this channel is closed if an error occurs on the header stream - requestWriter *requestWriter - - responses map[protocol.StreamID]chan *http.Response -} - -var _ http.RoundTripper = &client{} - -var defaultQuicConfig = &quic.Config{ - RequestConnectionIDTruncation: true, - KeepAlive: true, -} - -// newClient creates a new client -func newClient( - hostname string, - tlsConfig *tls.Config, - opts *roundTripperOpts, - quicConfig *quic.Config, -) *client { - config := defaultQuicConfig - if quicConfig != nil { - config = quicConfig - } - return &client{ - hostname: authorityAddr("https", hostname), - responses: make(map[protocol.StreamID]chan *http.Response), - encryptionLevel: protocol.EncryptionUnencrypted, - tlsConf: tlsConfig, - config: config, - opts: opts, - headerErrored: make(chan struct{}), - } -} - -// dial dials the connection -func (c *client) dial() error { - var err error - c.session, err = dialAddr(c.hostname, c.tlsConf, c.config) - if err != nil { - return err - } - - // once the version has been negotiated, open the header stream - c.headerStream, err = c.session.OpenStream() - if err != nil { - return err - } - if c.headerStream.StreamID() != 3 { - return errors.New("h2quic Client BUG: StreamID of Header Stream is not 3") - } - c.requestWriter = newRequestWriter(c.headerStream) - go c.handleHeaderStream() - return nil -} - -func (c *client) handleHeaderStream() { - decoder := hpack.NewDecoder(4096, func(hf hpack.HeaderField) {}) - h2framer := http2.NewFramer(nil, c.headerStream) - - var lastStream protocol.StreamID - - for { - frame, err := h2framer.ReadFrame() - if err != nil { - c.headerErr = qerr.Error(qerr.HeadersStreamDataDecompressFailure, "cannot read frame") - break - } - lastStream = protocol.StreamID(frame.Header().StreamID) - hframe, ok := frame.(*http2.HeadersFrame) - if !ok { - c.headerErr = qerr.Error(qerr.InvalidHeadersStreamData, "not a headers frame") - break - } - mhframe := &http2.MetaHeadersFrame{HeadersFrame: hframe} - mhframe.Fields, err = decoder.DecodeFull(hframe.HeaderBlockFragment()) - if err != nil { - c.headerErr = qerr.Error(qerr.InvalidHeadersStreamData, "cannot read header fields") - break - } - - c.mutex.RLock() - responseChan, ok := c.responses[protocol.StreamID(hframe.StreamID)] - c.mutex.RUnlock() - if !ok { - c.headerErr = qerr.Error(qerr.InternalError, fmt.Sprintf("h2client BUG: response channel for stream %d not found", lastStream)) - break - } - - rsp, err := responseFromHeaders(mhframe) - if err != nil { - c.headerErr = qerr.Error(qerr.InternalError, err.Error()) - } - responseChan <- rsp - } - - // stop all running request - utils.Debugf("Error handling header stream %d: %s", lastStream, c.headerErr.Error()) - close(c.headerErrored) -} - -// Roundtrip executes a request and returns a response -func (c *client) RoundTrip(req *http.Request) (*http.Response, error) { - // TODO: add port to address, if it doesn't have one - if req.URL.Scheme != "https" { - return nil, errors.New("quic http2: unsupported scheme") - } - if authorityAddr("https", hostnameFromRequest(req)) != c.hostname { - return nil, fmt.Errorf("h2quic Client BUG: RoundTrip called for the wrong client (expected %s, got %s)", c.hostname, req.Host) - } - - c.dialOnce.Do(func() { - c.handshakeErr = c.dial() - }) - - if c.handshakeErr != nil { - return nil, c.handshakeErr - } - - hasBody := (req.Body != nil) - - responseChan := make(chan *http.Response) - dataStream, err := c.session.OpenStreamSync() - if err != nil { - _ = c.CloseWithError(err) - return nil, err - } - c.mutex.Lock() - c.responses[dataStream.StreamID()] = responseChan - c.mutex.Unlock() - - var requestedGzip bool - if !c.opts.DisableCompression && req.Header.Get("Accept-Encoding") == "" && req.Header.Get("Range") == "" && req.Method != "HEAD" { - requestedGzip = true - } - // TODO: add support for trailers - endStream := !hasBody - err = c.requestWriter.WriteRequest(req, dataStream.StreamID(), endStream, requestedGzip) - if err != nil { - _ = c.CloseWithError(err) - return nil, err - } - - resc := make(chan error, 1) - if hasBody { - go func() { - resc <- c.writeRequestBody(dataStream, req.Body) - }() - } - - var res *http.Response - - var receivedResponse bool - var bodySent bool - - if !hasBody { - bodySent = true - } - - for !(bodySent && receivedResponse) { - select { - case res = <-responseChan: - receivedResponse = true - c.mutex.Lock() - delete(c.responses, dataStream.StreamID()) - c.mutex.Unlock() - case err := <-resc: - bodySent = true - if err != nil { - return nil, err - } - case <-c.headerErrored: - // an error occured on the header stream - _ = c.CloseWithError(c.headerErr) - return nil, c.headerErr - } - } - - // TODO: correctly set this variable - var streamEnded bool - isHead := (req.Method == "HEAD") - - res = setLength(res, isHead, streamEnded) - - if streamEnded || isHead { - res.Body = noBody - } else { - res.Body = dataStream - if requestedGzip && res.Header.Get("Content-Encoding") == "gzip" { - res.Header.Del("Content-Encoding") - res.Header.Del("Content-Length") - res.ContentLength = -1 - res.Body = &gzipReader{body: res.Body} - res.Uncompressed = true - } - } - - res.Request = req - return res, nil -} - -func (c *client) writeRequestBody(dataStream quic.Stream, body io.ReadCloser) (err error) { - defer func() { - cerr := body.Close() - if err == nil { - // TODO: what to do with dataStream here? Maybe reset it? - err = cerr - } - }() - - _, err = io.Copy(dataStream, body) - if err != nil { - // TODO: what to do with dataStream here? Maybe reset it? - return err - } - return dataStream.Close() -} - -// Close closes the client -func (c *client) CloseWithError(e error) error { - if c.session == nil { - return nil - } - return c.session.Close(e) -} - -func (c *client) Close() error { - return c.CloseWithError(nil) -} - -// copied from net/transport.go - -// authorityAddr returns a given authority (a host/IP, or host:port / ip:port) -// and returns a host:port. The port 443 is added if needed. -func authorityAddr(scheme string, authority string) (addr string) { - host, port, err := net.SplitHostPort(authority) - if err != nil { // authority didn't have a port - port = "443" - if scheme == "http" { - port = "80" - } - host = authority - } - if a, err := idna.ToASCII(host); err == nil { - host = a - } - // IPv6 address literal, without a port: - if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") { - return host + ":" + port - } - return net.JoinHostPort(host, port) -} diff --git a/vendor/github.com/lucas-clemente/quic-go/h2quic/gzipreader.go b/vendor/github.com/lucas-clemente/quic-go/h2quic/gzipreader.go deleted file mode 100644 index 91c226b1ec5..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/h2quic/gzipreader.go +++ /dev/null @@ -1,35 +0,0 @@ -package h2quic - -// copied from net/transport.go - -// gzipReader wraps a response body so it can lazily -// call gzip.NewReader on the first call to Read -import ( - "compress/gzip" - "io" -) - -// call gzip.NewReader on the first call to Read -type gzipReader struct { - body io.ReadCloser // underlying Response.Body - zr *gzip.Reader // lazily-initialized gzip reader - zerr error // sticky error -} - -func (gz *gzipReader) Read(p []byte) (n int, err error) { - if gz.zerr != nil { - return 0, gz.zerr - } - if gz.zr == nil { - gz.zr, err = gzip.NewReader(gz.body) - if err != nil { - gz.zerr = err - return 0, err - } - } - return gz.zr.Read(p) -} - -func (gz *gzipReader) Close() error { - return gz.body.Close() -} diff --git a/vendor/github.com/lucas-clemente/quic-go/h2quic/request.go b/vendor/github.com/lucas-clemente/quic-go/h2quic/request.go deleted file mode 100644 index 911485ef248..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/h2quic/request.go +++ /dev/null @@ -1,80 +0,0 @@ -package h2quic - -import ( - "crypto/tls" - "errors" - "net/http" - "net/url" - "strconv" - "strings" - - "golang.org/x/net/http2/hpack" -) - -func requestFromHeaders(headers []hpack.HeaderField) (*http.Request, error) { - var path, authority, method, contentLengthStr string - httpHeaders := http.Header{} - - for _, h := range headers { - switch h.Name { - case ":path": - path = h.Value - case ":method": - method = h.Value - case ":authority": - authority = h.Value - case "content-length": - contentLengthStr = h.Value - default: - if !h.IsPseudo() { - httpHeaders.Add(h.Name, h.Value) - } - } - } - - // concatenate cookie headers, see https://tools.ietf.org/html/rfc6265#section-5.4 - if len(httpHeaders["Cookie"]) > 0 { - httpHeaders.Set("Cookie", strings.Join(httpHeaders["Cookie"], "; ")) - } - - if len(path) == 0 || len(authority) == 0 || len(method) == 0 { - return nil, errors.New(":path, :authority and :method must not be empty") - } - - u, err := url.Parse(path) - if err != nil { - return nil, err - } - - var contentLength int64 - if len(contentLengthStr) > 0 { - contentLength, err = strconv.ParseInt(contentLengthStr, 10, 64) - if err != nil { - return nil, err - } - } - - return &http.Request{ - Method: method, - URL: u, - Proto: "HTTP/2.0", - ProtoMajor: 2, - ProtoMinor: 0, - Header: httpHeaders, - Body: nil, - ContentLength: contentLength, - Host: authority, - RequestURI: path, - TLS: &tls.ConnectionState{}, - }, nil -} - -func hostnameFromRequest(req *http.Request) string { - if len(req.Host) > 0 { - return req.Host - } - if req.URL != nil { - return req.URL.Host - } - return "" -} diff --git a/vendor/github.com/lucas-clemente/quic-go/h2quic/request_body.go b/vendor/github.com/lucas-clemente/quic-go/h2quic/request_body.go deleted file mode 100644 index 2d4d5954347..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/h2quic/request_body.go +++ /dev/null @@ -1,29 +0,0 @@ -package h2quic - -import ( - "io" - - quic "github.com/lucas-clemente/quic-go" -) - -type requestBody struct { - requestRead bool - dataStream quic.Stream -} - -// make sure the requestBody can be used as a http.Request.Body -var _ io.ReadCloser = &requestBody{} - -func newRequestBody(stream quic.Stream) *requestBody { - return &requestBody{dataStream: stream} -} - -func (b *requestBody) Read(p []byte) (int, error) { - b.requestRead = true - return b.dataStream.Read(p) -} - -func (b *requestBody) Close() error { - // stream's Close() closes the write side, not the read side - return nil -} diff --git a/vendor/github.com/lucas-clemente/quic-go/h2quic/request_writer.go b/vendor/github.com/lucas-clemente/quic-go/h2quic/request_writer.go deleted file mode 100644 index dad591cc2d4..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/h2quic/request_writer.go +++ /dev/null @@ -1,201 +0,0 @@ -package h2quic - -import ( - "bytes" - "fmt" - "net/http" - "strconv" - "strings" - "sync" - - "golang.org/x/net/http2" - "golang.org/x/net/http2/hpack" - "golang.org/x/net/lex/httplex" - - quic "github.com/lucas-clemente/quic-go" - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" -) - -type requestWriter struct { - mutex sync.Mutex - headerStream quic.Stream - - henc *hpack.Encoder - hbuf bytes.Buffer // HPACK encoder writes into this -} - -const defaultUserAgent = "quic-go" - -func newRequestWriter(headerStream quic.Stream) *requestWriter { - rw := &requestWriter{ - headerStream: headerStream, - } - rw.henc = hpack.NewEncoder(&rw.hbuf) - return rw -} - -func (w *requestWriter) WriteRequest(req *http.Request, dataStreamID protocol.StreamID, endStream, requestGzip bool) error { - // TODO: add support for trailers - // TODO: add support for gzip compression - // TODO: write continuation frames, if the header frame is too long - - w.mutex.Lock() - defer w.mutex.Unlock() - - w.encodeHeaders(req, requestGzip, "", actualContentLength(req)) - h2framer := http2.NewFramer(w.headerStream, nil) - return h2framer.WriteHeaders(http2.HeadersFrameParam{ - StreamID: uint32(dataStreamID), - EndHeaders: true, - EndStream: endStream, - BlockFragment: w.hbuf.Bytes(), - Priority: http2.PriorityParam{Weight: 0xff}, - }) -} - -// the rest of this files is copied from http2.Transport -func (w *requestWriter) encodeHeaders(req *http.Request, addGzipHeader bool, trailers string, contentLength int64) ([]byte, error) { - w.hbuf.Reset() - - host := req.Host - if host == "" { - host = req.URL.Host - } - host, err := httplex.PunycodeHostPort(host) - if err != nil { - return nil, err - } - - var path string - if req.Method != "CONNECT" { - path = req.URL.RequestURI() - if !validPseudoPath(path) { - orig := path - path = strings.TrimPrefix(path, req.URL.Scheme+"://"+host) - if !validPseudoPath(path) { - if req.URL.Opaque != "" { - return nil, fmt.Errorf("invalid request :path %q from URL.Opaque = %q", orig, req.URL.Opaque) - } else { - return nil, fmt.Errorf("invalid request :path %q", orig) - } - } - } - } - - // Check for any invalid headers and return an error before we - // potentially pollute our hpack state. (We want to be able to - // continue to reuse the hpack encoder for future requests) - for k, vv := range req.Header { - if !httplex.ValidHeaderFieldName(k) { - return nil, fmt.Errorf("invalid HTTP header name %q", k) - } - for _, v := range vv { - if !httplex.ValidHeaderFieldValue(v) { - return nil, fmt.Errorf("invalid HTTP header value %q for header %q", v, k) - } - } - } - - // 8.1.2.3 Request Pseudo-Header Fields - // The :path pseudo-header field includes the path and query parts of the - // target URI (the path-absolute production and optionally a '?' character - // followed by the query production (see Sections 3.3 and 3.4 of - // [RFC3986]). - w.writeHeader(":authority", host) - w.writeHeader(":method", req.Method) - if req.Method != "CONNECT" { - w.writeHeader(":path", path) - w.writeHeader(":scheme", req.URL.Scheme) - } - if trailers != "" { - w.writeHeader("trailer", trailers) - } - - var didUA bool - for k, vv := range req.Header { - lowKey := strings.ToLower(k) - switch lowKey { - case "host", "content-length": - // Host is :authority, already sent. - // Content-Length is automatic, set below. - continue - case "connection", "proxy-connection", "transfer-encoding", "upgrade", "keep-alive": - // Per 8.1.2.2 Connection-Specific Header - // Fields, don't send connection-specific - // fields. We have already checked if any - // are error-worthy so just ignore the rest. - continue - case "user-agent": - // Match Go's http1 behavior: at most one - // User-Agent. If set to nil or empty string, - // then omit it. Otherwise if not mentioned, - // include the default (below). - didUA = true - if len(vv) < 1 { - continue - } - vv = vv[:1] - if vv[0] == "" { - continue - } - } - for _, v := range vv { - w.writeHeader(lowKey, v) - } - } - if shouldSendReqContentLength(req.Method, contentLength) { - w.writeHeader("content-length", strconv.FormatInt(contentLength, 10)) - } - if addGzipHeader { - w.writeHeader("accept-encoding", "gzip") - } - if !didUA { - w.writeHeader("user-agent", defaultUserAgent) - } - return w.hbuf.Bytes(), nil -} - -func (w *requestWriter) writeHeader(name, value string) { - utils.Debugf("http2: Transport encoding header %q = %q", name, value) - w.henc.WriteField(hpack.HeaderField{Name: name, Value: value}) -} - -// shouldSendReqContentLength reports whether the http2.Transport should send -// a "content-length" request header. This logic is basically a copy of the net/http -// transferWriter.shouldSendContentLength. -// The contentLength is the corrected contentLength (so 0 means actually 0, not unknown). -// -1 means unknown. -func shouldSendReqContentLength(method string, contentLength int64) bool { - if contentLength > 0 { - return true - } - if contentLength < 0 { - return false - } - // For zero bodies, whether we send a content-length depends on the method. - // It also kinda doesn't matter for http2 either way, with END_STREAM. - switch method { - case "POST", "PUT", "PATCH": - return true - default: - return false - } -} - -func validPseudoPath(v string) bool { - return (len(v) > 0 && v[0] == '/' && (len(v) == 1 || v[1] != '/')) || v == "*" -} - -// actualContentLength returns a sanitized version of -// req.ContentLength, where 0 actually means zero (not unknown) and -1 -// means unknown. -func actualContentLength(req *http.Request) int64 { - if req.Body == nil { - return 0 - } - if req.ContentLength != 0 { - return req.ContentLength - } - return -1 -} diff --git a/vendor/github.com/lucas-clemente/quic-go/h2quic/response.go b/vendor/github.com/lucas-clemente/quic-go/h2quic/response.go deleted file mode 100644 index 13efdf84930..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/h2quic/response.go +++ /dev/null @@ -1,111 +0,0 @@ -package h2quic - -import ( - "bytes" - "errors" - "io" - "io/ioutil" - "net/http" - "net/textproto" - "strconv" - "strings" - - "golang.org/x/net/http2" -) - -// copied from net/http2/transport.go - -var errResponseHeaderListSize = errors.New("http2: response header list larger than advertised limit") -var noBody io.ReadCloser = ioutil.NopCloser(bytes.NewReader(nil)) - -// from the handleResponse function -func responseFromHeaders(f *http2.MetaHeadersFrame) (*http.Response, error) { - if f.Truncated { - return nil, errResponseHeaderListSize - } - - status := f.PseudoValue("status") - if status == "" { - return nil, errors.New("missing status pseudo header") - } - statusCode, err := strconv.Atoi(status) - if err != nil { - return nil, errors.New("malformed non-numeric status pseudo header") - } - - if statusCode == 100 { - // TODO: handle this - - // traceGot100Continue(cs.trace) - // if cs.on100 != nil { - // cs.on100() // forces any write delay timer to fire - // } - // cs.pastHeaders = false // do it all again - // return nil, nil - } - - header := make(http.Header) - res := &http.Response{ - Proto: "HTTP/2.0", - ProtoMajor: 2, - Header: header, - StatusCode: statusCode, - Status: status + " " + http.StatusText(statusCode), - } - for _, hf := range f.RegularFields() { - key := http.CanonicalHeaderKey(hf.Name) - if key == "Trailer" { - t := res.Trailer - if t == nil { - t = make(http.Header) - res.Trailer = t - } - foreachHeaderElement(hf.Value, func(v string) { - t[http.CanonicalHeaderKey(v)] = nil - }) - } else { - header[key] = append(header[key], hf.Value) - } - } - - return res, nil -} - -// continuation of the handleResponse function -func setLength(res *http.Response, isHead, streamEnded bool) *http.Response { - if !streamEnded || isHead { - res.ContentLength = -1 - if clens := res.Header["Content-Length"]; len(clens) == 1 { - if clen64, err := strconv.ParseInt(clens[0], 10, 64); err == nil { - res.ContentLength = clen64 - } else { - // TODO: care? unlike http/1, it won't mess up our framing, so it's - // more safe smuggling-wise to ignore. - } - } else if len(clens) > 1 { - // TODO: care? unlike http/1, it won't mess up our framing, so it's - // more safe smuggling-wise to ignore. - } - } - return res -} - -// copied from net/http/server.go - -// foreachHeaderElement splits v according to the "#rule" construction -// in RFC 2616 section 2.1 and calls fn for each non-empty element. -func foreachHeaderElement(v string, fn func(string)) { - v = textproto.TrimString(v) - if v == "" { - return - } - if !strings.Contains(v, ",") { - fn(v) - return - } - for _, f := range strings.Split(v, ",") { - if f = textproto.TrimString(f); f != "" { - fn(f) - } - } -} diff --git a/vendor/github.com/lucas-clemente/quic-go/h2quic/response_writer.go b/vendor/github.com/lucas-clemente/quic-go/h2quic/response_writer.go deleted file mode 100644 index 246893468f0..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/h2quic/response_writer.go +++ /dev/null @@ -1,108 +0,0 @@ -package h2quic - -import ( - "bytes" - "net/http" - "strconv" - "strings" - "sync" - - quic "github.com/lucas-clemente/quic-go" - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" - "golang.org/x/net/http2" - "golang.org/x/net/http2/hpack" -) - -type responseWriter struct { - dataStreamID protocol.StreamID - dataStream quic.Stream - - headerStream quic.Stream - headerStreamMutex *sync.Mutex - - header http.Header - status int // status code passed to WriteHeader - headerWritten bool -} - -func newResponseWriter(headerStream quic.Stream, headerStreamMutex *sync.Mutex, dataStream quic.Stream, dataStreamID protocol.StreamID) *responseWriter { - return &responseWriter{ - header: http.Header{}, - headerStream: headerStream, - headerStreamMutex: headerStreamMutex, - dataStream: dataStream, - dataStreamID: dataStreamID, - } -} - -func (w *responseWriter) Header() http.Header { - return w.header -} - -func (w *responseWriter) WriteHeader(status int) { - if w.headerWritten { - return - } - w.headerWritten = true - w.status = status - - var headers bytes.Buffer - enc := hpack.NewEncoder(&headers) - enc.WriteField(hpack.HeaderField{Name: ":status", Value: strconv.Itoa(status)}) - - for k, v := range w.header { - for index := range v { - enc.WriteField(hpack.HeaderField{Name: strings.ToLower(k), Value: v[index]}) - } - } - - utils.Infof("Responding with %d", status) - w.headerStreamMutex.Lock() - defer w.headerStreamMutex.Unlock() - h2framer := http2.NewFramer(w.headerStream, nil) - err := h2framer.WriteHeaders(http2.HeadersFrameParam{ - StreamID: uint32(w.dataStreamID), - EndHeaders: true, - BlockFragment: headers.Bytes(), - }) - if err != nil { - utils.Errorf("could not write h2 header: %s", err.Error()) - } -} - -func (w *responseWriter) Write(p []byte) (int, error) { - if !w.headerWritten { - w.WriteHeader(200) - } - if !bodyAllowedForStatus(w.status) { - return 0, http.ErrBodyNotAllowed - } - return w.dataStream.Write(p) -} - -func (w *responseWriter) Flush() {} - -// TODO: Implement a functional CloseNotify method. -func (w *responseWriter) CloseNotify() <-chan bool { return make(<-chan bool) } - -// test that we implement http.Flusher -var _ http.Flusher = &responseWriter{} - -// test that we implement http.CloseNotifier -var _ http.CloseNotifier = &responseWriter{} - -// copied from http2/http2.go -// bodyAllowedForStatus reports whether a given response status code -// permits a body. See RFC 2616, section 4.4. -func bodyAllowedForStatus(status int) bool { - switch { - case status >= 100 && status <= 199: - return false - case status == 204: - return false - case status == 304: - return false - } - return true -} diff --git a/vendor/github.com/lucas-clemente/quic-go/h2quic/roundtrip.go b/vendor/github.com/lucas-clemente/quic-go/h2quic/roundtrip.go deleted file mode 100644 index 9ac5f1933ab..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/h2quic/roundtrip.go +++ /dev/null @@ -1,168 +0,0 @@ -package h2quic - -import ( - "crypto/tls" - "errors" - "fmt" - "io" - "net/http" - "strings" - "sync" - - quic "github.com/lucas-clemente/quic-go" - - "golang.org/x/net/lex/httplex" -) - -type roundTripCloser interface { - http.RoundTripper - io.Closer -} - -// RoundTripper implements the http.RoundTripper interface -type RoundTripper struct { - mutex sync.Mutex - - // DisableCompression, if true, prevents the Transport from - // requesting compression with an "Accept-Encoding: gzip" - // request header when the Request contains no existing - // Accept-Encoding value. If the Transport requests gzip on - // its own and gets a gzipped response, it's transparently - // decoded in the Response.Body. However, if the user - // explicitly requested gzip it is not automatically - // uncompressed. - DisableCompression bool - - // TLSClientConfig specifies the TLS configuration to use with - // tls.Client. If nil, the default configuration is used. - TLSClientConfig *tls.Config - - // QuicConfig is the quic.Config used for dialing new connections. - // If nil, reasonable default values will be used. - QuicConfig *quic.Config - - clients map[string]roundTripCloser -} - -// RoundTripOpt are options for the Transport.RoundTripOpt method. -type RoundTripOpt struct { - // OnlyCachedConn controls whether the RoundTripper may - // create a new QUIC connection. If set true and - // no cached connection is available, RoundTrip - // will return ErrNoCachedConn. - OnlyCachedConn bool -} - -var _ roundTripCloser = &RoundTripper{} - -// ErrNoCachedConn is returned when RoundTripper.OnlyCachedConn is set -var ErrNoCachedConn = errors.New("h2quic: no cached connection was available") - -// RoundTripOpt is like RoundTrip, but takes options. -func (r *RoundTripper) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) { - if req.URL == nil { - closeRequestBody(req) - return nil, errors.New("quic: nil Request.URL") - } - if req.URL.Host == "" { - closeRequestBody(req) - return nil, errors.New("quic: no Host in request URL") - } - if req.Header == nil { - closeRequestBody(req) - return nil, errors.New("quic: nil Request.Header") - } - - if req.URL.Scheme == "https" { - for k, vv := range req.Header { - if !httplex.ValidHeaderFieldName(k) { - return nil, fmt.Errorf("quic: invalid http header field name %q", k) - } - for _, v := range vv { - if !httplex.ValidHeaderFieldValue(v) { - return nil, fmt.Errorf("quic: invalid http header field value %q for key %v", v, k) - } - } - } - } else { - closeRequestBody(req) - return nil, fmt.Errorf("quic: unsupported protocol scheme: %s", req.URL.Scheme) - } - - if req.Method != "" && !validMethod(req.Method) { - closeRequestBody(req) - return nil, fmt.Errorf("quic: invalid method %q", req.Method) - } - - hostname := authorityAddr("https", hostnameFromRequest(req)) - cl, err := r.getClient(hostname, opt.OnlyCachedConn) - if err != nil { - return nil, err - } - return cl.RoundTrip(req) -} - -// RoundTrip does a round trip. -func (r *RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { - return r.RoundTripOpt(req, RoundTripOpt{}) -} - -func (r *RoundTripper) getClient(hostname string, onlyCached bool) (http.RoundTripper, error) { - r.mutex.Lock() - defer r.mutex.Unlock() - - if r.clients == nil { - r.clients = make(map[string]roundTripCloser) - } - - client, ok := r.clients[hostname] - if !ok { - if onlyCached { - return nil, ErrNoCachedConn - } - client = newClient(hostname, r.TLSClientConfig, &roundTripperOpts{DisableCompression: r.DisableCompression}, r.QuicConfig) - r.clients[hostname] = client - } - return client, nil -} - -// Close closes the QUIC connections that this RoundTripper has used -func (r *RoundTripper) Close() error { - r.mutex.Lock() - defer r.mutex.Unlock() - for _, client := range r.clients { - if err := client.Close(); err != nil { - return err - } - } - r.clients = nil - return nil -} - -func closeRequestBody(req *http.Request) { - if req.Body != nil { - req.Body.Close() - } -} - -func validMethod(method string) bool { - /* - Method = "OPTIONS" ; Section 9.2 - | "GET" ; Section 9.3 - | "HEAD" ; Section 9.4 - | "POST" ; Section 9.5 - | "PUT" ; Section 9.6 - | "DELETE" ; Section 9.7 - | "TRACE" ; Section 9.8 - | "CONNECT" ; Section 9.9 - | extension-method - extension-method = token - token = 1* - */ - return len(method) > 0 && strings.IndexFunc(method, isNotToken) == -1 -} - -// copied from net/http/http.go -func isNotToken(r rune) bool { - return !httplex.IsTokenRune(r) -} diff --git a/vendor/github.com/lucas-clemente/quic-go/h2quic/server.go b/vendor/github.com/lucas-clemente/quic-go/h2quic/server.go deleted file mode 100644 index 3647dc681b8..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/h2quic/server.go +++ /dev/null @@ -1,382 +0,0 @@ -package h2quic - -import ( - "crypto/tls" - "errors" - "fmt" - "net" - "net/http" - "runtime" - "strconv" - "sync" - "sync/atomic" - "time" - - quic "github.com/lucas-clemente/quic-go" - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" - "github.com/lucas-clemente/quic-go/qerr" - "golang.org/x/net/http2" - "golang.org/x/net/http2/hpack" -) - -type streamCreator interface { - quic.Session - GetOrOpenStream(protocol.StreamID) (quic.Stream, error) -} - -type remoteCloser interface { - CloseRemote(protocol.ByteCount) -} - -// allows mocking of quic.Listen and quic.ListenAddr -var ( - quicListen = quic.Listen - quicListenAddr = quic.ListenAddr -) - -// Server is a HTTP2 server listening for QUIC connections. -type Server struct { - *http.Server - - // By providing a quic.Config, it is possible to set parameters of the QUIC connection. - // If nil, it uses reasonable default values. - QuicConfig *quic.Config - - // Private flag for demo, do not use - CloseAfterFirstRequest bool - - port uint32 // used atomically - - listenerMutex sync.Mutex - listener quic.Listener - - supportedVersionsAsString string -} - -// ListenAndServe listens on the UDP address s.Addr and calls s.Handler to handle HTTP/2 requests on incoming connections. -func (s *Server) ListenAndServe() error { - if s.Server == nil { - return errors.New("use of h2quic.Server without http.Server") - } - return s.serveImpl(s.TLSConfig, nil) -} - -// ListenAndServeTLS listens on the UDP address s.Addr and calls s.Handler to handle HTTP/2 requests on incoming connections. -func (s *Server) ListenAndServeTLS(certFile, keyFile string) error { - var err error - certs := make([]tls.Certificate, 1) - certs[0], err = tls.LoadX509KeyPair(certFile, keyFile) - if err != nil { - return err - } - // We currently only use the cert-related stuff from tls.Config, - // so we don't need to make a full copy. - config := &tls.Config{ - Certificates: certs, - } - return s.serveImpl(config, nil) -} - -// Serve an existing UDP connection. -func (s *Server) Serve(conn net.PacketConn) error { - return s.serveImpl(s.TLSConfig, conn) -} - -func (s *Server) serveImpl(tlsConfig *tls.Config, conn net.PacketConn) error { - if s.Server == nil { - return errors.New("use of h2quic.Server without http.Server") - } - s.listenerMutex.Lock() - if s.listener != nil { - s.listenerMutex.Unlock() - return errors.New("ListenAndServe may only be called once") - } - - var ln quic.Listener - var err error - if conn == nil { - ln, err = quicListenAddr(s.Addr, tlsConfig, s.QuicConfig) - } else { - ln, err = quicListen(conn, tlsConfig, s.QuicConfig) - } - if err != nil { - s.listenerMutex.Unlock() - return err - } - s.listener = ln - s.listenerMutex.Unlock() - - for { - sess, err := ln.Accept() - if err != nil { - return err - } - go s.handleHeaderStream(sess.(streamCreator)) - } -} - -func (s *Server) handleHeaderStream(session streamCreator) { - stream, err := session.AcceptStream() - if err != nil { - session.Close(qerr.Error(qerr.InvalidHeadersStreamData, err.Error())) - return - } - if stream.StreamID() != 3 { - session.Close(qerr.Error(qerr.InternalError, "h2quic server BUG: header stream does not have stream ID 3")) - return - } - - hpackDecoder := hpack.NewDecoder(4096, nil) - h2framer := http2.NewFramer(nil, stream) - - go func() { - var headerStreamMutex sync.Mutex // Protects concurrent calls to Write() - for { - if err := s.handleRequest(session, stream, &headerStreamMutex, hpackDecoder, h2framer); err != nil { - // QuicErrors must originate from stream.Read() returning an error. - // In this case, the session has already logged the error, so we don't - // need to log it again. - if _, ok := err.(*qerr.QuicError); !ok { - utils.Errorf("error handling h2 request: %s", err.Error()) - } - session.Close(err) - return - } - } - }() -} - -func (s *Server) handleRequest(session streamCreator, headerStream quic.Stream, headerStreamMutex *sync.Mutex, hpackDecoder *hpack.Decoder, h2framer *http2.Framer) error { - h2frame, err := h2framer.ReadFrame() - if err != nil { - return qerr.Error(qerr.HeadersStreamDataDecompressFailure, "cannot read frame") - } - h2headersFrame, ok := h2frame.(*http2.HeadersFrame) - if !ok { - return qerr.Error(qerr.InvalidHeadersStreamData, "expected a header frame") - } - if !h2headersFrame.HeadersEnded() { - return errors.New("http2 header continuation not implemented") - } - headers, err := hpackDecoder.DecodeFull(h2headersFrame.HeaderBlockFragment()) - if err != nil { - utils.Errorf("invalid http2 headers encoding: %s", err.Error()) - return err - } - - req, err := requestFromHeaders(headers) - if err != nil { - return err - } - - req.RemoteAddr = session.RemoteAddr().String() - - if utils.Debug() { - utils.Infof("%s %s%s, on data stream %d", req.Method, req.Host, req.RequestURI, h2headersFrame.StreamID) - } else { - utils.Infof("%s %s%s", req.Method, req.Host, req.RequestURI) - } - - dataStream, err := session.GetOrOpenStream(protocol.StreamID(h2headersFrame.StreamID)) - if err != nil { - return err - } - // this can happen if the client immediately closes the data stream after sending the request and the runtime processes the reset before the request - if dataStream == nil { - return nil - } - - var streamEnded bool - if h2headersFrame.StreamEnded() { - dataStream.(remoteCloser).CloseRemote(0) - streamEnded = true - _, _ = dataStream.Read([]byte{0}) // read the eof - } - - reqBody := newRequestBody(dataStream) - req.Body = reqBody - - responseWriter := newResponseWriter(headerStream, headerStreamMutex, dataStream, protocol.StreamID(h2headersFrame.StreamID)) - - go func() { - handler := s.Handler - if handler == nil { - handler = http.DefaultServeMux - } - panicked := false - func() { - defer func() { - if p := recover(); p != nil { - // Copied from net/http/server.go - const size = 64 << 10 - buf := make([]byte, size) - buf = buf[:runtime.Stack(buf, false)] - utils.Errorf("http: panic serving: %v\n%s", p, buf) - panicked = true - } - }() - handler.ServeHTTP(responseWriter, req) - }() - if panicked { - responseWriter.WriteHeader(500) - } else { - responseWriter.WriteHeader(200) - } - if responseWriter.dataStream != nil { - if !streamEnded && !reqBody.requestRead { - responseWriter.dataStream.Reset(nil) - } - responseWriter.dataStream.Close() - } - if s.CloseAfterFirstRequest { - time.Sleep(100 * time.Millisecond) - session.Close(nil) - } - }() - - return nil -} - -// Close the server immediately, aborting requests and sending CONNECTION_CLOSE frames to connected clients. -// Close in combination with ListenAndServe() (instead of Serve()) may race if it is called before a UDP socket is established. -func (s *Server) Close() error { - s.listenerMutex.Lock() - defer s.listenerMutex.Unlock() - if s.listener != nil { - err := s.listener.Close() - s.listener = nil - return err - } - return nil -} - -// CloseGracefully shuts down the server gracefully. The server sends a GOAWAY frame first, then waits for either timeout to trigger, or for all running requests to complete. -// CloseGracefully in combination with ListenAndServe() (instead of Serve()) may race if it is called before a UDP socket is established. -func (s *Server) CloseGracefully(timeout time.Duration) error { - // TODO: implement - return nil -} - -// SetQuicHeaders can be used to set the proper headers that announce that this server supports QUIC. -// The values that are set depend on the port information from s.Server.Addr, and currently look like this (if Addr has port 443): -// Alt-Svc: quic=":443"; ma=2592000; v="33,32,31,30" -func (s *Server) SetQuicHeaders(hdr http.Header) error { - port := atomic.LoadUint32(&s.port) - - if port == 0 { - // Extract port from s.Server.Addr - _, portStr, err := net.SplitHostPort(s.Server.Addr) - if err != nil { - return err - } - portInt, err := net.LookupPort("tcp", portStr) - if err != nil { - return err - } - port = uint32(portInt) - atomic.StoreUint32(&s.port, port) - } - - if s.supportedVersionsAsString == "" { - for i, v := range protocol.SupportedVersions { - s.supportedVersionsAsString += strconv.Itoa(int(v)) - if i != len(protocol.SupportedVersions)-1 { - s.supportedVersionsAsString += "," - } - } - } - - hdr.Add("Alt-Svc", fmt.Sprintf(`quic=":%d"; ma=2592000; v="%s"`, port, s.supportedVersionsAsString)) - - return nil -} - -// ListenAndServeQUIC listens on the UDP network address addr and calls the -// handler for HTTP/2 requests on incoming connections. http.DefaultServeMux is -// used when handler is nil. -func ListenAndServeQUIC(addr, certFile, keyFile string, handler http.Handler) error { - server := &Server{ - Server: &http.Server{ - Addr: addr, - Handler: handler, - }, - } - return server.ListenAndServeTLS(certFile, keyFile) -} - -// ListenAndServe listens on the given network address for both, TLS and QUIC -// connetions in parallel. It returns if one of the two returns an error. -// http.DefaultServeMux is used when handler is nil. -// The correct Alt-Svc headers for QUIC are set. -func ListenAndServe(addr, certFile, keyFile string, handler http.Handler) error { - // Load certs - var err error - certs := make([]tls.Certificate, 1) - certs[0], err = tls.LoadX509KeyPair(certFile, keyFile) - if err != nil { - return err - } - // We currently only use the cert-related stuff from tls.Config, - // so we don't need to make a full copy. - config := &tls.Config{ - Certificates: certs, - } - - // Open the listeners - udpAddr, err := net.ResolveUDPAddr("udp", addr) - if err != nil { - return err - } - udpConn, err := net.ListenUDP("udp", udpAddr) - if err != nil { - return err - } - defer udpConn.Close() - - tcpAddr, err := net.ResolveTCPAddr("tcp", addr) - if err != nil { - return err - } - tcpConn, err := net.ListenTCP("tcp", tcpAddr) - if err != nil { - return err - } - defer tcpConn.Close() - - // Start the servers - httpServer := &http.Server{ - Addr: addr, - TLSConfig: config, - } - - quicServer := &Server{ - Server: httpServer, - } - - if handler == nil { - handler = http.DefaultServeMux - } - httpServer.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - quicServer.SetQuicHeaders(w.Header()) - handler.ServeHTTP(w, r) - }) - - hErr := make(chan error) - qErr := make(chan error) - go func() { - hErr <- httpServer.Serve(tcpConn) - }() - go func() { - qErr <- quicServer.Serve(udpConn) - }() - - select { - case err := <-hErr: - quicServer.Close() - return err - case err := <-qErr: - // Cannot close the HTTP server or wait for requests to complete properly :/ - return err - } -} diff --git a/vendor/github.com/lucas-clemente/quic-go/handshake/connection_parameters_manager.go b/vendor/github.com/lucas-clemente/quic-go/handshake/connection_parameters_manager.go deleted file mode 100644 index 1ad9a3a4110..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/handshake/connection_parameters_manager.go +++ /dev/null @@ -1,265 +0,0 @@ -package handshake - -import ( - "bytes" - "sync" - "time" - - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" - "github.com/lucas-clemente/quic-go/qerr" -) - -// ConnectionParametersManager negotiates and stores the connection parameters -// A ConnectionParametersManager can be used for a server as well as a client -// For the server: -// 1. call SetFromMap with the values received in the CHLO. This sets the corresponding values here, subject to negotiation -// 2. call GetHelloMap to get the values to send in the SHLO -// For the client: -// 1. call GetHelloMap to get the values to send in a CHLO -// 2. call SetFromMap with the values received in the SHLO -type ConnectionParametersManager interface { - SetFromMap(map[Tag][]byte) error - GetHelloMap() (map[Tag][]byte, error) - - GetSendStreamFlowControlWindow() protocol.ByteCount - GetSendConnectionFlowControlWindow() protocol.ByteCount - GetReceiveStreamFlowControlWindow() protocol.ByteCount - GetMaxReceiveStreamFlowControlWindow() protocol.ByteCount - GetReceiveConnectionFlowControlWindow() protocol.ByteCount - GetMaxReceiveConnectionFlowControlWindow() protocol.ByteCount - GetMaxOutgoingStreams() uint32 - GetMaxIncomingStreams() uint32 - GetIdleConnectionStateLifetime() time.Duration - TruncateConnectionID() bool -} - -type connectionParametersManager struct { - mutex sync.RWMutex - - version protocol.VersionNumber - perspective protocol.Perspective - - flowControlNegotiated bool - - truncateConnectionID bool - maxStreamsPerConnection uint32 - maxIncomingDynamicStreamsPerConnection uint32 - idleConnectionStateLifetime time.Duration - sendStreamFlowControlWindow protocol.ByteCount - sendConnectionFlowControlWindow protocol.ByteCount - receiveStreamFlowControlWindow protocol.ByteCount - receiveConnectionFlowControlWindow protocol.ByteCount - maxReceiveStreamFlowControlWindow protocol.ByteCount - maxReceiveConnectionFlowControlWindow protocol.ByteCount -} - -var _ ConnectionParametersManager = &connectionParametersManager{} - -// ErrMalformedTag is returned when the tag value cannot be read -var ( - ErrMalformedTag = qerr.Error(qerr.InvalidCryptoMessageParameter, "malformed Tag value") - ErrFlowControlRenegotiationNotSupported = qerr.Error(qerr.InvalidCryptoMessageParameter, "renegotiation of flow control parameters not supported") -) - -// NewConnectionParamatersManager creates a new connection parameters manager -func NewConnectionParamatersManager( - pers protocol.Perspective, v protocol.VersionNumber, - maxReceiveStreamFlowControlWindow protocol.ByteCount, maxReceiveConnectionFlowControlWindow protocol.ByteCount, -) ConnectionParametersManager { - h := &connectionParametersManager{ - perspective: pers, - version: v, - sendStreamFlowControlWindow: protocol.InitialStreamFlowControlWindow, // can only be changed by the client - sendConnectionFlowControlWindow: protocol.InitialConnectionFlowControlWindow, // can only be changed by the client - receiveStreamFlowControlWindow: protocol.ReceiveStreamFlowControlWindow, - receiveConnectionFlowControlWindow: protocol.ReceiveConnectionFlowControlWindow, - maxReceiveStreamFlowControlWindow: maxReceiveStreamFlowControlWindow, - maxReceiveConnectionFlowControlWindow: maxReceiveConnectionFlowControlWindow, - } - - if h.perspective == protocol.PerspectiveServer { - h.idleConnectionStateLifetime = protocol.DefaultIdleTimeout - h.maxStreamsPerConnection = protocol.MaxStreamsPerConnection // this is the value negotiated based on what the client sent - h.maxIncomingDynamicStreamsPerConnection = protocol.MaxStreamsPerConnection // "incoming" seen from the client's perspective - } else { - h.idleConnectionStateLifetime = protocol.MaxIdleTimeoutClient - h.maxStreamsPerConnection = protocol.MaxStreamsPerConnection // this is the value negotiated based on what the client sent - h.maxIncomingDynamicStreamsPerConnection = protocol.MaxStreamsPerConnection // "incoming" seen from the server's perspective - } - - return h -} - -// SetFromMap reads all params -func (h *connectionParametersManager) SetFromMap(params map[Tag][]byte) error { - h.mutex.Lock() - defer h.mutex.Unlock() - - if value, ok := params[TagTCID]; ok && h.perspective == protocol.PerspectiveServer { - clientValue, err := utils.ReadUint32(bytes.NewBuffer(value)) - if err != nil { - return ErrMalformedTag - } - h.truncateConnectionID = (clientValue == 0) - } - if value, ok := params[TagMSPC]; ok { - clientValue, err := utils.ReadUint32(bytes.NewBuffer(value)) - if err != nil { - return ErrMalformedTag - } - h.maxStreamsPerConnection = h.negotiateMaxStreamsPerConnection(clientValue) - } - if value, ok := params[TagMIDS]; ok { - clientValue, err := utils.ReadUint32(bytes.NewBuffer(value)) - if err != nil { - return ErrMalformedTag - } - h.maxIncomingDynamicStreamsPerConnection = h.negotiateMaxIncomingDynamicStreamsPerConnection(clientValue) - } - if value, ok := params[TagICSL]; ok { - clientValue, err := utils.ReadUint32(bytes.NewBuffer(value)) - if err != nil { - return ErrMalformedTag - } - h.idleConnectionStateLifetime = h.negotiateIdleConnectionStateLifetime(time.Duration(clientValue) * time.Second) - } - if value, ok := params[TagSFCW]; ok { - if h.flowControlNegotiated { - return ErrFlowControlRenegotiationNotSupported - } - sendStreamFlowControlWindow, err := utils.ReadUint32(bytes.NewBuffer(value)) - if err != nil { - return ErrMalformedTag - } - h.sendStreamFlowControlWindow = protocol.ByteCount(sendStreamFlowControlWindow) - } - if value, ok := params[TagCFCW]; ok { - if h.flowControlNegotiated { - return ErrFlowControlRenegotiationNotSupported - } - sendConnectionFlowControlWindow, err := utils.ReadUint32(bytes.NewBuffer(value)) - if err != nil { - return ErrMalformedTag - } - h.sendConnectionFlowControlWindow = protocol.ByteCount(sendConnectionFlowControlWindow) - } - - _, containsSFCW := params[TagSFCW] - _, containsCFCW := params[TagCFCW] - if containsCFCW || containsSFCW { - h.flowControlNegotiated = true - } - - return nil -} - -func (h *connectionParametersManager) negotiateMaxStreamsPerConnection(clientValue uint32) uint32 { - return utils.MinUint32(clientValue, protocol.MaxStreamsPerConnection) -} - -func (h *connectionParametersManager) negotiateMaxIncomingDynamicStreamsPerConnection(clientValue uint32) uint32 { - return utils.MinUint32(clientValue, protocol.MaxIncomingDynamicStreamsPerConnection) -} - -func (h *connectionParametersManager) negotiateIdleConnectionStateLifetime(clientValue time.Duration) time.Duration { - if h.perspective == protocol.PerspectiveServer { - return utils.MinDuration(clientValue, protocol.MaxIdleTimeoutServer) - } - return utils.MinDuration(clientValue, protocol.MaxIdleTimeoutClient) -} - -// GetHelloMap gets all parameters needed for the Hello message -func (h *connectionParametersManager) GetHelloMap() (map[Tag][]byte, error) { - sfcw := bytes.NewBuffer([]byte{}) - utils.WriteUint32(sfcw, uint32(h.GetReceiveStreamFlowControlWindow())) - cfcw := bytes.NewBuffer([]byte{}) - utils.WriteUint32(cfcw, uint32(h.GetReceiveConnectionFlowControlWindow())) - mspc := bytes.NewBuffer([]byte{}) - utils.WriteUint32(mspc, h.maxStreamsPerConnection) - mids := bytes.NewBuffer([]byte{}) - utils.WriteUint32(mids, protocol.MaxIncomingDynamicStreamsPerConnection) - icsl := bytes.NewBuffer([]byte{}) - utils.WriteUint32(icsl, uint32(h.GetIdleConnectionStateLifetime()/time.Second)) - - return map[Tag][]byte{ - TagICSL: icsl.Bytes(), - TagMSPC: mspc.Bytes(), - TagMIDS: mids.Bytes(), - TagCFCW: cfcw.Bytes(), - TagSFCW: sfcw.Bytes(), - }, nil -} - -// GetSendStreamFlowControlWindow gets the size of the stream-level flow control window for sending data -func (h *connectionParametersManager) GetSendStreamFlowControlWindow() protocol.ByteCount { - h.mutex.RLock() - defer h.mutex.RUnlock() - return h.sendStreamFlowControlWindow -} - -// GetSendConnectionFlowControlWindow gets the size of the stream-level flow control window for sending data -func (h *connectionParametersManager) GetSendConnectionFlowControlWindow() protocol.ByteCount { - h.mutex.RLock() - defer h.mutex.RUnlock() - return h.sendConnectionFlowControlWindow -} - -// GetReceiveStreamFlowControlWindow gets the size of the stream-level flow control window for receiving data -func (h *connectionParametersManager) GetReceiveStreamFlowControlWindow() protocol.ByteCount { - h.mutex.RLock() - defer h.mutex.RUnlock() - return h.receiveStreamFlowControlWindow -} - -// GetMaxReceiveStreamFlowControlWindow gets the maximum size of the stream-level flow control window for sending data -func (h *connectionParametersManager) GetMaxReceiveStreamFlowControlWindow() protocol.ByteCount { - return h.maxReceiveStreamFlowControlWindow -} - -// GetReceiveConnectionFlowControlWindow gets the size of the stream-level flow control window for receiving data -func (h *connectionParametersManager) GetReceiveConnectionFlowControlWindow() protocol.ByteCount { - h.mutex.RLock() - defer h.mutex.RUnlock() - return h.receiveConnectionFlowControlWindow -} - -// GetMaxReceiveConnectionFlowControlWindow gets the maximum size of the stream-level flow control window for sending data -func (h *connectionParametersManager) GetMaxReceiveConnectionFlowControlWindow() protocol.ByteCount { - return h.maxReceiveConnectionFlowControlWindow -} - -// GetMaxOutgoingStreams gets the maximum number of outgoing streams per connection -func (h *connectionParametersManager) GetMaxOutgoingStreams() uint32 { - h.mutex.RLock() - defer h.mutex.RUnlock() - - return h.maxIncomingDynamicStreamsPerConnection -} - -// GetMaxIncomingStreams get the maximum number of incoming streams per connection -func (h *connectionParametersManager) GetMaxIncomingStreams() uint32 { - h.mutex.RLock() - defer h.mutex.RUnlock() - - maxStreams := protocol.MaxIncomingDynamicStreamsPerConnection - return utils.MaxUint32(uint32(maxStreams)+protocol.MaxStreamsMinimumIncrement, uint32(float64(maxStreams)*protocol.MaxStreamsMultiplier)) -} - -// GetIdleConnectionStateLifetime gets the idle timeout -func (h *connectionParametersManager) GetIdleConnectionStateLifetime() time.Duration { - h.mutex.RLock() - defer h.mutex.RUnlock() - return h.idleConnectionStateLifetime -} - -// TruncateConnectionID determines if the client requests truncated ConnectionIDs -func (h *connectionParametersManager) TruncateConnectionID() bool { - if h.perspective == protocol.PerspectiveClient { - return false - } - - h.mutex.RLock() - defer h.mutex.RUnlock() - return h.truncateConnectionID -} diff --git a/vendor/github.com/lucas-clemente/quic-go/handshake/crypto_setup_client.go b/vendor/github.com/lucas-clemente/quic-go/handshake/crypto_setup_client.go deleted file mode 100644 index a8d8812977e..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/handshake/crypto_setup_client.go +++ /dev/null @@ -1,539 +0,0 @@ -package handshake - -import ( - "bytes" - "crypto/rand" - "crypto/tls" - "encoding/binary" - "errors" - "fmt" - "io" - "sync" - "time" - - "github.com/lucas-clemente/quic-go/crypto" - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" - "github.com/lucas-clemente/quic-go/qerr" -) - -type cryptoSetupClient struct { - mutex sync.RWMutex - - hostname string - connID protocol.ConnectionID - version protocol.VersionNumber - negotiatedVersions []protocol.VersionNumber - - cryptoStream io.ReadWriter - - serverConfig *serverConfigClient - - stk []byte - sno []byte - nonc []byte - proof []byte - chloForSignature []byte - lastSentCHLO []byte - certManager crypto.CertManager - - divNonceChan chan []byte - diversificationNonce []byte - - clientHelloCounter int - serverVerified bool // has the certificate chain and the proof already been verified - keyDerivation KeyDerivationFunction - keyExchange KeyExchangeFunction - - receivedSecurePacket bool - nullAEAD crypto.AEAD - secureAEAD crypto.AEAD - forwardSecureAEAD crypto.AEAD - aeadChanged chan<- protocol.EncryptionLevel - - params *TransportParameters - connectionParameters ConnectionParametersManager -} - -var _ CryptoSetup = &cryptoSetupClient{} - -var ( - errNoObitForClientNonce = errors.New("CryptoSetup BUG: No OBIT for client nonce available") - errClientNonceAlreadyExists = errors.New("CryptoSetup BUG: A client nonce was already generated") - errConflictingDiversificationNonces = errors.New("Received two different diversification nonces") -) - -// NewCryptoSetupClient creates a new CryptoSetup instance for a client -func NewCryptoSetupClient( - hostname string, - connID protocol.ConnectionID, - version protocol.VersionNumber, - cryptoStream io.ReadWriter, - tlsConfig *tls.Config, - connectionParameters ConnectionParametersManager, - aeadChanged chan<- protocol.EncryptionLevel, - params *TransportParameters, - negotiatedVersions []protocol.VersionNumber, -) (CryptoSetup, error) { - return &cryptoSetupClient{ - hostname: hostname, - connID: connID, - version: version, - cryptoStream: cryptoStream, - certManager: crypto.NewCertManager(tlsConfig), - connectionParameters: connectionParameters, - keyDerivation: crypto.DeriveKeysAESGCM, - keyExchange: getEphermalKEX, - nullAEAD: crypto.NewNullAEAD(protocol.PerspectiveClient, version), - aeadChanged: aeadChanged, - negotiatedVersions: negotiatedVersions, - divNonceChan: make(chan []byte), - params: params, - }, nil -} - -func (h *cryptoSetupClient) HandleCryptoStream() error { - messageChan := make(chan HandshakeMessage) - errorChan := make(chan error) - - go func() { - for { - message, err := ParseHandshakeMessage(h.cryptoStream) - if err != nil { - errorChan <- qerr.Error(qerr.HandshakeFailed, err.Error()) - return - } - messageChan <- message - } - }() - - for { - err := h.maybeUpgradeCrypto() - if err != nil { - return err - } - - h.mutex.RLock() - sendCHLO := h.secureAEAD == nil - h.mutex.RUnlock() - - if sendCHLO { - err = h.sendCHLO() - if err != nil { - return err - } - } - - var message HandshakeMessage - select { - case divNonce := <-h.divNonceChan: - if len(h.diversificationNonce) != 0 && !bytes.Equal(h.diversificationNonce, divNonce) { - return errConflictingDiversificationNonces - } - h.diversificationNonce = divNonce - // there's no message to process, but we should try upgrading the crypto again - continue - case message = <-messageChan: - case err = <-errorChan: - return err - } - - utils.Debugf("Got %s", message) - switch message.Tag { - case TagREJ: - err = h.handleREJMessage(message.Data) - case TagSHLO: - err = h.handleSHLOMessage(message.Data) - default: - return qerr.InvalidCryptoMessageType - } - if err != nil { - return err - } - } -} - -func (h *cryptoSetupClient) handleREJMessage(cryptoData map[Tag][]byte) error { - var err error - - if stk, ok := cryptoData[TagSTK]; ok { - h.stk = stk - } - - if sno, ok := cryptoData[TagSNO]; ok { - h.sno = sno - } - - // TODO: what happens if the server sends a different server config in two packets? - if scfg, ok := cryptoData[TagSCFG]; ok { - h.serverConfig, err = parseServerConfig(scfg) - if err != nil { - return err - } - - if h.serverConfig.IsExpired() { - return qerr.CryptoServerConfigExpired - } - - // now that we have a server config, we can use its OBIT value to generate a client nonce - if len(h.nonc) == 0 { - err = h.generateClientNonce() - if err != nil { - return err - } - } - } - - if proof, ok := cryptoData[TagPROF]; ok { - h.proof = proof - h.chloForSignature = h.lastSentCHLO - } - - if crt, ok := cryptoData[TagCERT]; ok { - err := h.certManager.SetData(crt) - if err != nil { - return qerr.Error(qerr.InvalidCryptoMessageParameter, "Certificate data invalid") - } - - err = h.certManager.Verify(h.hostname) - if err != nil { - utils.Infof("Certificate validation failed: %s", err.Error()) - return qerr.ProofInvalid - } - } - - if h.serverConfig != nil && len(h.proof) != 0 && h.certManager.GetLeafCert() != nil { - validProof := h.certManager.VerifyServerProof(h.proof, h.chloForSignature, h.serverConfig.Get()) - if !validProof { - utils.Infof("Server proof verification failed") - return qerr.ProofInvalid - } - - h.serverVerified = true - } - - return nil -} - -func (h *cryptoSetupClient) handleSHLOMessage(cryptoData map[Tag][]byte) error { - h.mutex.Lock() - defer h.mutex.Unlock() - - if !h.receivedSecurePacket { - return qerr.Error(qerr.CryptoEncryptionLevelIncorrect, "unencrypted SHLO message") - } - - if sno, ok := cryptoData[TagSNO]; ok { - h.sno = sno - } - - serverPubs, ok := cryptoData[TagPUBS] - if !ok { - return qerr.Error(qerr.CryptoMessageParameterNotFound, "PUBS") - } - - verTag, ok := cryptoData[TagVER] - if !ok { - return qerr.Error(qerr.InvalidCryptoMessageParameter, "server hello missing version list") - } - if !h.validateVersionList(verTag) { - return qerr.Error(qerr.VersionNegotiationMismatch, "Downgrade attack detected") - } - - nonce := append(h.nonc, h.sno...) - - ephermalSharedSecret, err := h.serverConfig.kex.CalculateSharedKey(serverPubs) - if err != nil { - return err - } - - leafCert := h.certManager.GetLeafCert() - - h.forwardSecureAEAD, err = h.keyDerivation( - true, - ephermalSharedSecret, - nonce, - h.connID, - h.lastSentCHLO, - h.serverConfig.Get(), - leafCert, - nil, - protocol.PerspectiveClient, - ) - if err != nil { - return err - } - - err = h.connectionParameters.SetFromMap(cryptoData) - if err != nil { - return qerr.InvalidCryptoMessageParameter - } - - h.aeadChanged <- protocol.EncryptionForwardSecure - close(h.aeadChanged) - - return nil -} - -func (h *cryptoSetupClient) validateVersionList(verTags []byte) bool { - if len(h.negotiatedVersions) == 0 { - return true - } - if len(verTags)%4 != 0 || len(verTags)/4 != len(h.negotiatedVersions) { - return false - } - - b := bytes.NewReader(verTags) - for _, negotiatedVersion := range h.negotiatedVersions { - verTag, err := utils.ReadUint32(b) - if err != nil { // should never occur, since the length was already checked - return false - } - ver := protocol.VersionTagToNumber(verTag) - if !protocol.IsSupportedVersion(protocol.SupportedVersions, ver) { - ver = protocol.VersionUnsupported - } - if ver != negotiatedVersion { - return false - } - } - return true -} - -func (h *cryptoSetupClient) Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, protocol.EncryptionLevel, error) { - h.mutex.RLock() - defer h.mutex.RUnlock() - - if h.forwardSecureAEAD != nil { - data, err := h.forwardSecureAEAD.Open(dst, src, packetNumber, associatedData) - if err == nil { - return data, protocol.EncryptionForwardSecure, nil - } - return nil, protocol.EncryptionUnspecified, err - } - - if h.secureAEAD != nil { - data, err := h.secureAEAD.Open(dst, src, packetNumber, associatedData) - if err == nil { - h.receivedSecurePacket = true - return data, protocol.EncryptionSecure, nil - } - if h.receivedSecurePacket { - return nil, protocol.EncryptionUnspecified, err - } - } - res, err := h.nullAEAD.Open(dst, src, packetNumber, associatedData) - if err != nil { - return nil, protocol.EncryptionUnspecified, err - } - return res, protocol.EncryptionUnencrypted, nil -} - -func (h *cryptoSetupClient) GetSealer() (protocol.EncryptionLevel, Sealer) { - h.mutex.RLock() - defer h.mutex.RUnlock() - if h.forwardSecureAEAD != nil { - return protocol.EncryptionForwardSecure, h.sealForwardSecure - } else if h.secureAEAD != nil { - return protocol.EncryptionSecure, h.sealSecure - } else { - return protocol.EncryptionUnencrypted, h.sealUnencrypted - } -} - -func (h *cryptoSetupClient) GetSealerForCryptoStream() (protocol.EncryptionLevel, Sealer) { - return protocol.EncryptionUnencrypted, h.sealUnencrypted -} - -func (h *cryptoSetupClient) GetSealerWithEncryptionLevel(encLevel protocol.EncryptionLevel) (Sealer, error) { - h.mutex.RLock() - defer h.mutex.RUnlock() - - switch encLevel { - case protocol.EncryptionUnencrypted: - return h.sealUnencrypted, nil - case protocol.EncryptionSecure: - if h.secureAEAD == nil { - return nil, errors.New("CryptoSetupClient: no secureAEAD") - } - return h.sealSecure, nil - case protocol.EncryptionForwardSecure: - if h.forwardSecureAEAD == nil { - return nil, errors.New("CryptoSetupClient: no forwardSecureAEAD") - } - return h.sealForwardSecure, nil - } - return nil, errors.New("CryptoSetupClient: no encryption level specified") -} - -func (h *cryptoSetupClient) sealUnencrypted(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte { - return h.nullAEAD.Seal(dst, src, packetNumber, associatedData) -} - -func (h *cryptoSetupClient) sealSecure(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte { - return h.secureAEAD.Seal(dst, src, packetNumber, associatedData) -} - -func (h *cryptoSetupClient) sealForwardSecure(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte { - return h.forwardSecureAEAD.Seal(dst, src, packetNumber, associatedData) -} - -func (h *cryptoSetupClient) DiversificationNonce() []byte { - panic("not needed for cryptoSetupClient") -} - -func (h *cryptoSetupClient) SetDiversificationNonce(data []byte) { - h.divNonceChan <- data -} - -func (h *cryptoSetupClient) sendCHLO() error { - h.clientHelloCounter++ - if h.clientHelloCounter > protocol.MaxClientHellos { - return qerr.Error(qerr.CryptoTooManyRejects, fmt.Sprintf("More than %d rejects", protocol.MaxClientHellos)) - } - - b := &bytes.Buffer{} - - tags, err := h.getTags() - if err != nil { - return err - } - h.addPadding(tags) - message := HandshakeMessage{ - Tag: TagCHLO, - Data: tags, - } - - utils.Debugf("Sending %s", message) - message.Write(b) - - _, err = h.cryptoStream.Write(b.Bytes()) - if err != nil { - return err - } - - h.lastSentCHLO = b.Bytes() - - return nil -} - -func (h *cryptoSetupClient) getTags() (map[Tag][]byte, error) { - tags, err := h.connectionParameters.GetHelloMap() - if err != nil { - return nil, err - } - tags[TagSNI] = []byte(h.hostname) - tags[TagPDMD] = []byte("X509") - - ccs := h.certManager.GetCommonCertificateHashes() - if len(ccs) > 0 { - tags[TagCCS] = ccs - } - - versionTag := make([]byte, 4) - binary.LittleEndian.PutUint32(versionTag, protocol.VersionNumberToTag(h.version)) - tags[TagVER] = versionTag - - if h.params.RequestConnectionIDTruncation { - tags[TagTCID] = []byte{0, 0, 0, 0} - } - if len(h.stk) > 0 { - tags[TagSTK] = h.stk - } - if len(h.sno) > 0 { - tags[TagSNO] = h.sno - } - - if h.serverConfig != nil { - tags[TagSCID] = h.serverConfig.ID - - leafCert := h.certManager.GetLeafCert() - if leafCert != nil { - certHash, _ := h.certManager.GetLeafCertHash() - xlct := make([]byte, 8) - binary.LittleEndian.PutUint64(xlct, certHash) - - tags[TagNONC] = h.nonc - tags[TagXLCT] = xlct - tags[TagKEXS] = []byte("C255") - tags[TagAEAD] = []byte("AESG") - tags[TagPUBS] = h.serverConfig.kex.PublicKey() // TODO: check if 3 bytes need to be prepended - } - } - - return tags, nil -} - -// add a TagPAD to a tagMap, such that the total size will be bigger than the ClientHelloMinimumSize -func (h *cryptoSetupClient) addPadding(tags map[Tag][]byte) { - var size int - for _, tag := range tags { - size += 8 + len(tag) // 4 bytes for the tag + 4 bytes for the offset + the length of the data - } - paddingSize := protocol.ClientHelloMinimumSize - size - if paddingSize > 0 { - tags[TagPAD] = bytes.Repeat([]byte{0}, paddingSize) - } -} - -func (h *cryptoSetupClient) maybeUpgradeCrypto() error { - if !h.serverVerified { - return nil - } - - h.mutex.Lock() - defer h.mutex.Unlock() - - leafCert := h.certManager.GetLeafCert() - if h.secureAEAD == nil && (h.serverConfig != nil && len(h.serverConfig.sharedSecret) > 0 && len(h.nonc) > 0 && len(leafCert) > 0 && len(h.diversificationNonce) > 0 && len(h.lastSentCHLO) > 0) { - var err error - var nonce []byte - if h.sno == nil { - nonce = h.nonc - } else { - nonce = append(h.nonc, h.sno...) - } - - h.secureAEAD, err = h.keyDerivation( - false, - h.serverConfig.sharedSecret, - nonce, - h.connID, - h.lastSentCHLO, - h.serverConfig.Get(), - leafCert, - h.diversificationNonce, - protocol.PerspectiveClient, - ) - if err != nil { - return err - } - - h.aeadChanged <- protocol.EncryptionSecure - } - - return nil -} - -func (h *cryptoSetupClient) generateClientNonce() error { - if len(h.nonc) > 0 { - return errClientNonceAlreadyExists - } - - nonc := make([]byte, 32) - binary.BigEndian.PutUint32(nonc, uint32(time.Now().Unix())) - - if len(h.serverConfig.obit) != 8 { - return errNoObitForClientNonce - } - - copy(nonc[4:12], h.serverConfig.obit) - - _, err := rand.Read(nonc[12:]) - if err != nil { - return err - } - - h.nonc = nonc - return nil -} diff --git a/vendor/github.com/lucas-clemente/quic-go/handshake/crypto_setup_server.go b/vendor/github.com/lucas-clemente/quic-go/handshake/crypto_setup_server.go deleted file mode 100644 index f639f51447a..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/handshake/crypto_setup_server.go +++ /dev/null @@ -1,470 +0,0 @@ -package handshake - -import ( - "bytes" - "crypto/rand" - "encoding/binary" - "errors" - "io" - "net" - "sync" - - "github.com/lucas-clemente/quic-go/crypto" - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" - "github.com/lucas-clemente/quic-go/qerr" -) - -// KeyDerivationFunction is used for key derivation -type KeyDerivationFunction func(forwardSecure bool, sharedSecret, nonces []byte, connID protocol.ConnectionID, chlo []byte, scfg []byte, cert []byte, divNonce []byte, pers protocol.Perspective) (crypto.AEAD, error) - -// KeyExchangeFunction is used to make a new KEX -type KeyExchangeFunction func() crypto.KeyExchange - -// The CryptoSetupServer handles all things crypto for the Session -type cryptoSetupServer struct { - connID protocol.ConnectionID - remoteAddr net.Addr - scfg *ServerConfig - stkGenerator *STKGenerator - diversificationNonce []byte - - version protocol.VersionNumber - supportedVersions []protocol.VersionNumber - - acceptSTKCallback func(net.Addr, *STK) bool - - nullAEAD crypto.AEAD - secureAEAD crypto.AEAD - forwardSecureAEAD crypto.AEAD - receivedForwardSecurePacket bool - sentSHLO bool - receivedSecurePacket bool - aeadChanged chan<- protocol.EncryptionLevel - - keyDerivation KeyDerivationFunction - keyExchange KeyExchangeFunction - - cryptoStream io.ReadWriter - - connectionParameters ConnectionParametersManager - - mutex sync.RWMutex -} - -var _ CryptoSetup = &cryptoSetupServer{} - -// ErrHOLExperiment is returned when the client sends the FHL2 tag in the CHLO -// this is an expiremnt implemented by Chrome in QUIC 36, which we don't support -// TODO: remove this when dropping support for QUIC 36 -var ErrHOLExperiment = qerr.Error(qerr.InvalidCryptoMessageParameter, "HOL experiment. Unsupported") - -// NewCryptoSetup creates a new CryptoSetup instance for a server -func NewCryptoSetup( - connID protocol.ConnectionID, - remoteAddr net.Addr, - version protocol.VersionNumber, - scfg *ServerConfig, - cryptoStream io.ReadWriter, - connectionParametersManager ConnectionParametersManager, - supportedVersions []protocol.VersionNumber, - acceptSTK func(net.Addr, *STK) bool, - aeadChanged chan<- protocol.EncryptionLevel, -) (CryptoSetup, error) { - stkGenerator, err := NewSTKGenerator() - if err != nil { - return nil, err - } - - return &cryptoSetupServer{ - connID: connID, - remoteAddr: remoteAddr, - version: version, - supportedVersions: supportedVersions, - scfg: scfg, - stkGenerator: stkGenerator, - keyDerivation: crypto.DeriveKeysAESGCM, - keyExchange: getEphermalKEX, - nullAEAD: crypto.NewNullAEAD(protocol.PerspectiveServer, version), - cryptoStream: cryptoStream, - connectionParameters: connectionParametersManager, - acceptSTKCallback: acceptSTK, - aeadChanged: aeadChanged, - }, nil -} - -// HandleCryptoStream reads and writes messages on the crypto stream -func (h *cryptoSetupServer) HandleCryptoStream() error { - for { - var chloData bytes.Buffer - message, err := ParseHandshakeMessage(io.TeeReader(h.cryptoStream, &chloData)) - if err != nil { - return qerr.HandshakeFailed - } - if message.Tag != TagCHLO { - return qerr.InvalidCryptoMessageType - } - - utils.Debugf("Got %s", message) - done, err := h.handleMessage(chloData.Bytes(), message.Data) - if err != nil { - return err - } - if done { - return nil - } - } -} - -func (h *cryptoSetupServer) handleMessage(chloData []byte, cryptoData map[Tag][]byte) (bool, error) { - if _, isHOLExperiment := cryptoData[TagFHL2]; isHOLExperiment { - return false, ErrHOLExperiment - } - - sniSlice, ok := cryptoData[TagSNI] - if !ok { - return false, qerr.Error(qerr.CryptoMessageParameterNotFound, "SNI required") - } - sni := string(sniSlice) - if sni == "" { - return false, qerr.Error(qerr.CryptoMessageParameterNotFound, "SNI required") - } - - // prevent version downgrade attacks - // see https://groups.google.com/a/chromium.org/forum/#!topic/proto-quic/N-de9j63tCk for a discussion and examples - verSlice, ok := cryptoData[TagVER] - if !ok { - return false, qerr.Error(qerr.InvalidCryptoMessageParameter, "client hello missing version tag") - } - if len(verSlice) != 4 { - return false, qerr.Error(qerr.InvalidCryptoMessageParameter, "incorrect version tag") - } - verTag := binary.LittleEndian.Uint32(verSlice) - ver := protocol.VersionTagToNumber(verTag) - // If the client's preferred version is not the version we are currently speaking, then the client went through a version negotiation. In this case, we need to make sure that we actually do not support this version and that it wasn't a downgrade attack. - if ver != h.version && protocol.IsSupportedVersion(h.supportedVersions, ver) { - return false, qerr.Error(qerr.VersionNegotiationMismatch, "Downgrade attack detected") - } - - var reply []byte - var err error - - certUncompressed, err := h.scfg.certChain.GetLeafCert(sni) - if err != nil { - return false, err - } - - if !h.isInchoateCHLO(cryptoData, certUncompressed) { - // We have a CHLO with a proper server config ID, do a 0-RTT handshake - reply, err = h.handleCHLO(sni, chloData, cryptoData) - if err != nil { - return false, err - } - _, err = h.cryptoStream.Write(reply) - if err != nil { - return false, err - } - return true, nil - } - - // We have an inchoate or non-matching CHLO, we now send a rejection - reply, err = h.handleInchoateCHLO(sni, chloData, cryptoData) - if err != nil { - return false, err - } - _, err = h.cryptoStream.Write(reply) - return false, err -} - -// Open a message -func (h *cryptoSetupServer) Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, protocol.EncryptionLevel, error) { - h.mutex.RLock() - defer h.mutex.RUnlock() - - if h.forwardSecureAEAD != nil { - res, err := h.forwardSecureAEAD.Open(dst, src, packetNumber, associatedData) - if err == nil { - if !h.receivedForwardSecurePacket { // this is the first forward secure packet we receive from the client - h.receivedForwardSecurePacket = true - close(h.aeadChanged) - } - return res, protocol.EncryptionForwardSecure, nil - } - if h.receivedForwardSecurePacket { - return nil, protocol.EncryptionUnspecified, err - } - } - if h.secureAEAD != nil { - res, err := h.secureAEAD.Open(dst, src, packetNumber, associatedData) - if err == nil { - h.receivedSecurePacket = true - return res, protocol.EncryptionSecure, nil - } - if h.receivedSecurePacket { - return nil, protocol.EncryptionUnspecified, err - } - } - res, err := h.nullAEAD.Open(dst, src, packetNumber, associatedData) - if err != nil { - return res, protocol.EncryptionUnspecified, err - } - return res, protocol.EncryptionUnencrypted, err -} - -func (h *cryptoSetupServer) GetSealer() (protocol.EncryptionLevel, Sealer) { - h.mutex.RLock() - defer h.mutex.RUnlock() - if h.forwardSecureAEAD != nil { - return protocol.EncryptionForwardSecure, h.sealForwardSecure - } - return protocol.EncryptionUnencrypted, h.sealUnencrypted -} - -func (h *cryptoSetupServer) GetSealerForCryptoStream() (protocol.EncryptionLevel, Sealer) { - h.mutex.RLock() - defer h.mutex.RUnlock() - if h.secureAEAD != nil { - return protocol.EncryptionSecure, h.sealSecure - } - return protocol.EncryptionUnencrypted, h.sealUnencrypted -} - -func (h *cryptoSetupServer) GetSealerWithEncryptionLevel(encLevel protocol.EncryptionLevel) (Sealer, error) { - h.mutex.RLock() - defer h.mutex.RUnlock() - - switch encLevel { - case protocol.EncryptionUnencrypted: - return h.sealUnencrypted, nil - case protocol.EncryptionSecure: - if h.secureAEAD == nil { - return nil, errors.New("CryptoSetupServer: no secureAEAD") - } - return h.sealSecure, nil - case protocol.EncryptionForwardSecure: - if h.forwardSecureAEAD == nil { - return nil, errors.New("CryptoSetupServer: no forwardSecureAEAD") - } - return h.sealForwardSecure, nil - } - return nil, errors.New("CryptoSetupServer: no encryption level specified") -} - -func (h *cryptoSetupServer) sealUnencrypted(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte { - return h.nullAEAD.Seal(dst, src, packetNumber, associatedData) -} - -func (h *cryptoSetupServer) sealSecure(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte { - return h.secureAEAD.Seal(dst, src, packetNumber, associatedData) -} - -func (h *cryptoSetupServer) sealForwardSecure(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte { - return h.forwardSecureAEAD.Seal(dst, src, packetNumber, associatedData) -} - -func (h *cryptoSetupServer) isInchoateCHLO(cryptoData map[Tag][]byte, cert []byte) bool { - if _, ok := cryptoData[TagPUBS]; !ok { - return true - } - scid, ok := cryptoData[TagSCID] - if !ok || !bytes.Equal(h.scfg.ID, scid) { - return true - } - xlctTag, ok := cryptoData[TagXLCT] - if !ok || len(xlctTag) != 8 { - return true - } - xlct := binary.LittleEndian.Uint64(xlctTag) - if crypto.HashCert(cert) != xlct { - return true - } - return !h.acceptSTK(cryptoData[TagSTK]) -} - -func (h *cryptoSetupServer) acceptSTK(token []byte) bool { - stk, err := h.stkGenerator.DecodeToken(token) - if err != nil { - utils.Debugf("STK invalid: %s", err.Error()) - return false - } - return h.acceptSTKCallback(h.remoteAddr, stk) -} - -func (h *cryptoSetupServer) handleInchoateCHLO(sni string, chlo []byte, cryptoData map[Tag][]byte) ([]byte, error) { - if len(chlo) < protocol.ClientHelloMinimumSize { - return nil, qerr.Error(qerr.CryptoInvalidValueLength, "CHLO too small") - } - - token, err := h.stkGenerator.NewToken(h.remoteAddr) - if err != nil { - return nil, err - } - - replyMap := map[Tag][]byte{ - TagSCFG: h.scfg.Get(), - TagSTK: token, - TagSVID: []byte("quic-go"), - } - - if h.acceptSTK(cryptoData[TagSTK]) { - proof, err := h.scfg.Sign(sni, chlo) - if err != nil { - return nil, err - } - - commonSetHashes := cryptoData[TagCCS] - cachedCertsHashes := cryptoData[TagCCRT] - - certCompressed, err := h.scfg.GetCertsCompressed(sni, commonSetHashes, cachedCertsHashes) - if err != nil { - return nil, err - } - // Token was valid, send more details - replyMap[TagPROF] = proof - replyMap[TagCERT] = certCompressed - } - - message := HandshakeMessage{ - Tag: TagREJ, - Data: replyMap, - } - - var serverReply bytes.Buffer - message.Write(&serverReply) - utils.Debugf("Sending %s", message) - return serverReply.Bytes(), nil -} - -func (h *cryptoSetupServer) handleCHLO(sni string, data []byte, cryptoData map[Tag][]byte) ([]byte, error) { - // We have a CHLO matching our server config, we can continue with the 0-RTT handshake - sharedSecret, err := h.scfg.kex.CalculateSharedKey(cryptoData[TagPUBS]) - if err != nil { - return nil, err - } - - h.mutex.Lock() - defer h.mutex.Unlock() - - certUncompressed, err := h.scfg.certChain.GetLeafCert(sni) - if err != nil { - return nil, err - } - - serverNonce := make([]byte, 32) - if _, err = rand.Read(serverNonce); err != nil { - return nil, err - } - - h.diversificationNonce = make([]byte, 32) - if _, err = rand.Read(h.diversificationNonce); err != nil { - return nil, err - } - - clientNonce := cryptoData[TagNONC] - err = h.validateClientNonce(clientNonce) - if err != nil { - return nil, err - } - - aead := cryptoData[TagAEAD] - if !bytes.Equal(aead, []byte("AESG")) { - return nil, qerr.Error(qerr.CryptoNoSupport, "Unsupported AEAD or KEXS") - } - - kexs := cryptoData[TagKEXS] - if !bytes.Equal(kexs, []byte("C255")) { - return nil, qerr.Error(qerr.CryptoNoSupport, "Unsupported AEAD or KEXS") - } - - h.secureAEAD, err = h.keyDerivation( - false, - sharedSecret, - clientNonce, - h.connID, - data, - h.scfg.Get(), - certUncompressed, - h.diversificationNonce, - protocol.PerspectiveServer, - ) - if err != nil { - return nil, err - } - - h.aeadChanged <- protocol.EncryptionSecure - - // Generate a new curve instance to derive the forward secure key - var fsNonce bytes.Buffer - fsNonce.Write(clientNonce) - fsNonce.Write(serverNonce) - ephermalKex := h.keyExchange() - ephermalSharedSecret, err := ephermalKex.CalculateSharedKey(cryptoData[TagPUBS]) - if err != nil { - return nil, err - } - - h.forwardSecureAEAD, err = h.keyDerivation( - true, - ephermalSharedSecret, - fsNonce.Bytes(), - h.connID, - data, - h.scfg.Get(), - certUncompressed, - nil, - protocol.PerspectiveServer, - ) - if err != nil { - return nil, err - } - - err = h.connectionParameters.SetFromMap(cryptoData) - if err != nil { - return nil, err - } - - replyMap, err := h.connectionParameters.GetHelloMap() - if err != nil { - return nil, err - } - // add crypto parameters - verTag := &bytes.Buffer{} - for _, v := range h.supportedVersions { - utils.WriteUint32(verTag, protocol.VersionNumberToTag(v)) - } - replyMap[TagPUBS] = ephermalKex.PublicKey() - replyMap[TagSNO] = serverNonce - replyMap[TagVER] = verTag.Bytes() - - // note that the SHLO *has* to fit into one packet - message := HandshakeMessage{ - Tag: TagSHLO, - Data: replyMap, - } - var reply bytes.Buffer - message.Write(&reply) - utils.Debugf("Sending %s", message) - - h.aeadChanged <- protocol.EncryptionForwardSecure - - return reply.Bytes(), nil -} - -// DiversificationNonce returns the diversification nonce -func (h *cryptoSetupServer) DiversificationNonce() []byte { - return h.diversificationNonce -} - -func (h *cryptoSetupServer) SetDiversificationNonce(data []byte) { - panic("not needed for cryptoSetupServer") -} - -func (h *cryptoSetupServer) validateClientNonce(nonce []byte) error { - if len(nonce) != 32 { - return qerr.Error(qerr.InvalidCryptoMessageParameter, "invalid client nonce length") - } - if !bytes.Equal(nonce[4:12], h.scfg.obit) { - return qerr.Error(qerr.InvalidCryptoMessageParameter, "OBIT not matching") - } - return nil -} diff --git a/vendor/github.com/lucas-clemente/quic-go/handshake/ephermal_cache.go b/vendor/github.com/lucas-clemente/quic-go/handshake/ephermal_cache.go deleted file mode 100644 index da6724f3d4f..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/handshake/ephermal_cache.go +++ /dev/null @@ -1,50 +0,0 @@ -package handshake - -import ( - "sync" - "time" - - "github.com/lucas-clemente/quic-go/crypto" - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" -) - -var ( - kexLifetime = protocol.EphermalKeyLifetime - kexCurrent crypto.KeyExchange - kexCurrentTime time.Time - kexMutex sync.RWMutex -) - -// getEphermalKEX returns the currently active KEX, which changes every protocol.EphermalKeyLifetime -// See the explanation from the QUIC crypto doc: -// -// A single connection is the usual scope for forward security, but the security -// difference between an ephemeral key used for a single connection, and one -// used for all connections for 60 seconds is negligible. Thus we can amortise -// the Diffie-Hellman key generation at the server over all the connections in a -// small time span. -func getEphermalKEX() (res crypto.KeyExchange) { - kexMutex.RLock() - res = kexCurrent - t := kexCurrentTime - kexMutex.RUnlock() - if res != nil && time.Since(t) < kexLifetime { - return res - } - - kexMutex.Lock() - defer kexMutex.Unlock() - // Check if still unfulfilled - if kexCurrent == nil || time.Since(kexCurrentTime) > kexLifetime { - kex, err := crypto.NewCurve25519KEX() - if err != nil { - utils.Errorf("could not set KEX: %s", err.Error()) - return kexCurrent - } - kexCurrent = kex - kexCurrentTime = time.Now() - return kexCurrent - } - return kexCurrent -} diff --git a/vendor/github.com/lucas-clemente/quic-go/handshake/handshake_message.go b/vendor/github.com/lucas-clemente/quic-go/handshake/handshake_message.go deleted file mode 100644 index 0744cbdcee3..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/handshake/handshake_message.go +++ /dev/null @@ -1,136 +0,0 @@ -package handshake - -import ( - "bytes" - "encoding/binary" - "fmt" - "io" - "sort" - - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" - "github.com/lucas-clemente/quic-go/qerr" -) - -// A HandshakeMessage is a handshake message -type HandshakeMessage struct { - Tag Tag - Data map[Tag][]byte -} - -var _ fmt.Stringer = &HandshakeMessage{} - -// ParseHandshakeMessage reads a crypto message -func ParseHandshakeMessage(r io.Reader) (HandshakeMessage, error) { - slice4 := make([]byte, 4) - - if _, err := io.ReadFull(r, slice4); err != nil { - return HandshakeMessage{}, err - } - messageTag := Tag(binary.LittleEndian.Uint32(slice4)) - - if _, err := io.ReadFull(r, slice4); err != nil { - return HandshakeMessage{}, err - } - nPairs := binary.LittleEndian.Uint32(slice4) - - if nPairs > protocol.CryptoMaxParams { - return HandshakeMessage{}, qerr.CryptoTooManyEntries - } - - index := make([]byte, nPairs*8) - if _, err := io.ReadFull(r, index); err != nil { - return HandshakeMessage{}, err - } - - resultMap := map[Tag][]byte{} - - var dataStart uint32 - for indexPos := 0; indexPos < int(nPairs)*8; indexPos += 8 { - tag := Tag(binary.LittleEndian.Uint32(index[indexPos : indexPos+4])) - dataEnd := binary.LittleEndian.Uint32(index[indexPos+4 : indexPos+8]) - - dataLen := dataEnd - dataStart - if dataLen > protocol.CryptoParameterMaxLength { - return HandshakeMessage{}, qerr.Error(qerr.CryptoInvalidValueLength, "value too long") - } - - data := make([]byte, dataLen) - if _, err := io.ReadFull(r, data); err != nil { - return HandshakeMessage{}, err - } - - resultMap[tag] = data - dataStart = dataEnd - } - - return HandshakeMessage{ - Tag: messageTag, - Data: resultMap}, nil -} - -// Write writes a crypto message -func (h HandshakeMessage) Write(b *bytes.Buffer) { - data := h.Data - utils.WriteUint32(b, uint32(h.Tag)) - utils.WriteUint16(b, uint16(len(data))) - utils.WriteUint16(b, 0) - - // Save current position in the buffer, so that we can update the index in-place later - indexStart := b.Len() - - indexData := make([]byte, 8*len(data)) - b.Write(indexData) // Will be updated later - - offset := uint32(0) - for i, t := range h.getTagsSorted() { - v := data[Tag(t)] - b.Write(v) - offset += uint32(len(v)) - binary.LittleEndian.PutUint32(indexData[i*8:], t) - binary.LittleEndian.PutUint32(indexData[i*8+4:], offset) - } - - // Now we write the index data for real - copy(b.Bytes()[indexStart:], indexData) -} - -func (h *HandshakeMessage) getTagsSorted() []uint32 { - tags := make([]uint32, len(h.Data)) - i := 0 - for t := range h.Data { - tags[i] = uint32(t) - i++ - } - sort.Sort(utils.Uint32Slice(tags)) - return tags -} - -func (h HandshakeMessage) String() string { - var pad string - res := tagToString(h.Tag) + ":\n" - for _, t := range h.getTagsSorted() { - tag := Tag(t) - if tag == TagPAD { - pad = fmt.Sprintf("\t%s: (%d bytes)\n", tagToString(tag), len(h.Data[tag])) - } else { - res += fmt.Sprintf("\t%s: %#v\n", tagToString(tag), string(h.Data[tag])) - } - } - - if len(pad) > 0 { - res += pad - } - return res -} - -func tagToString(tag Tag) string { - b := make([]byte, 4) - binary.LittleEndian.PutUint32(b, uint32(tag)) - for i := range b { - if b[i] == 0 { - b[i] = ' ' - } - } - return string(b) -} diff --git a/vendor/github.com/lucas-clemente/quic-go/handshake/interface.go b/vendor/github.com/lucas-clemente/quic-go/handshake/interface.go deleted file mode 100644 index 751aae1e555..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/handshake/interface.go +++ /dev/null @@ -1,24 +0,0 @@ -package handshake - -import "github.com/lucas-clemente/quic-go/protocol" - -// Sealer seals a packet -type Sealer func(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte - -// CryptoSetup is a crypto setup -type CryptoSetup interface { - Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, protocol.EncryptionLevel, error) - HandleCryptoStream() error - // TODO: clean up this interface - DiversificationNonce() []byte // only needed for cryptoSetupServer - SetDiversificationNonce([]byte) // only needed for cryptoSetupClient - - GetSealer() (protocol.EncryptionLevel, Sealer) - GetSealerWithEncryptionLevel(protocol.EncryptionLevel) (Sealer, error) - GetSealerForCryptoStream() (protocol.EncryptionLevel, Sealer) -} - -// TransportParameters are parameters sent to the peer during the handshake -type TransportParameters struct { - RequestConnectionIDTruncation bool -} diff --git a/vendor/github.com/lucas-clemente/quic-go/handshake/server_config.go b/vendor/github.com/lucas-clemente/quic-go/handshake/server_config.go deleted file mode 100644 index fce66efdfa3..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/handshake/server_config.go +++ /dev/null @@ -1,65 +0,0 @@ -package handshake - -import ( - "bytes" - "crypto/rand" - - "github.com/lucas-clemente/quic-go/crypto" -) - -// ServerConfig is a server config -type ServerConfig struct { - kex crypto.KeyExchange - certChain crypto.CertChain - ID []byte - obit []byte -} - -// NewServerConfig creates a new server config -func NewServerConfig(kex crypto.KeyExchange, certChain crypto.CertChain) (*ServerConfig, error) { - id := make([]byte, 16) - _, err := rand.Read(id) - if err != nil { - return nil, err - } - - obit := make([]byte, 8) - if _, err = rand.Read(obit); err != nil { - return nil, err - } - - return &ServerConfig{ - kex: kex, - certChain: certChain, - ID: id, - obit: obit, - }, nil -} - -// Get the server config binary representation -func (s *ServerConfig) Get() []byte { - var serverConfig bytes.Buffer - msg := HandshakeMessage{ - Tag: TagSCFG, - Data: map[Tag][]byte{ - TagSCID: s.ID, - TagKEXS: []byte("C255"), - TagAEAD: []byte("AESG"), - TagPUBS: append([]byte{0x20, 0x00, 0x00}, s.kex.PublicKey()...), - TagOBIT: s.obit, - TagEXPY: {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, - }, - } - msg.Write(&serverConfig) - return serverConfig.Bytes() -} - -// Sign the server config and CHLO with the server's keyData -func (s *ServerConfig) Sign(sni string, chlo []byte) ([]byte, error) { - return s.certChain.SignServerProof(sni, chlo, s.Get()) -} - -// GetCertsCompressed returns the certificate data -func (s *ServerConfig) GetCertsCompressed(sni string, commonSetHashes, compressedHashes []byte) ([]byte, error) { - return s.certChain.GetCertsCompressed(sni, commonSetHashes, compressedHashes) -} diff --git a/vendor/github.com/lucas-clemente/quic-go/handshake/server_config_client.go b/vendor/github.com/lucas-clemente/quic-go/handshake/server_config_client.go deleted file mode 100644 index 420141957f1..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/handshake/server_config_client.go +++ /dev/null @@ -1,180 +0,0 @@ -package handshake - -import ( - "bytes" - "encoding/binary" - "errors" - "math" - "time" - - "github.com/lucas-clemente/quic-go/crypto" - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/qerr" -) - -type serverConfigClient struct { - raw []byte - ID []byte - obit []byte - expiry time.Time - - kex crypto.KeyExchange - sharedSecret []byte -} - -var ( - errMessageNotServerConfig = errors.New("ServerConfig must have TagSCFG") -) - -// parseServerConfig parses a server config -func parseServerConfig(data []byte) (*serverConfigClient, error) { - message, err := ParseHandshakeMessage(bytes.NewReader(data)) - if err != nil { - return nil, err - } - if message.Tag != TagSCFG { - return nil, errMessageNotServerConfig - } - - scfg := &serverConfigClient{raw: data} - err = scfg.parseValues(message.Data) - if err != nil { - return nil, err - } - - return scfg, nil -} - -func (s *serverConfigClient) parseValues(tagMap map[Tag][]byte) error { - // SCID - scfgID, ok := tagMap[TagSCID] - if !ok { - return qerr.Error(qerr.CryptoMessageParameterNotFound, "SCID") - } - if len(scfgID) != 16 { - return qerr.Error(qerr.CryptoInvalidValueLength, "SCID") - } - s.ID = scfgID - - // KEXS - // TODO: setup Key Exchange - kexs, ok := tagMap[TagKEXS] - if !ok { - return qerr.Error(qerr.CryptoMessageParameterNotFound, "KEXS") - } - if len(kexs)%4 != 0 { - return qerr.Error(qerr.CryptoInvalidValueLength, "KEXS") - } - c255Foundat := -1 - - for i := 0; i < len(kexs)/4; i++ { - if bytes.Equal(kexs[4*i:4*i+4], []byte("C255")) { - c255Foundat = i - break - } - } - if c255Foundat < 0 { - return qerr.Error(qerr.CryptoNoSupport, "KEXS: Could not find C255, other key exchanges are not supported") - } - - // AEAD - aead, ok := tagMap[TagAEAD] - if !ok { - return qerr.Error(qerr.CryptoMessageParameterNotFound, "AEAD") - } - if len(aead)%4 != 0 { - return qerr.Error(qerr.CryptoInvalidValueLength, "AEAD") - } - var aesgFound bool - for i := 0; i < len(aead)/4; i++ { - if bytes.Equal(aead[4*i:4*i+4], []byte("AESG")) { - aesgFound = true - break - } - } - if !aesgFound { - return qerr.Error(qerr.CryptoNoSupport, "AEAD") - } - - // PUBS - pubs, ok := tagMap[TagPUBS] - if !ok { - return qerr.Error(qerr.CryptoMessageParameterNotFound, "PUBS") - } - - var pubs_kexs []struct{Length uint32; Value []byte} - var last_len uint32 - - for i := 0; i < len(pubs)-3; i += int(last_len)+3 { - // the PUBS value is always prepended by 3 byte little endian length field - - err := binary.Read(bytes.NewReader([]byte{pubs[i], pubs[i+1], pubs[i+2], 0x00}), binary.LittleEndian, &last_len); - if err != nil { - return qerr.Error(qerr.CryptoInvalidValueLength, "PUBS not decodable") - } - if last_len == 0 { - return qerr.Error(qerr.CryptoInvalidValueLength, "PUBS") - } - - if i+3+int(last_len) > len(pubs) { - return qerr.Error(qerr.CryptoInvalidValueLength, "PUBS") - } - - pubs_kexs = append(pubs_kexs, struct{Length uint32; Value []byte}{last_len, pubs[i+3:i+3+int(last_len)]}) - } - - if c255Foundat >= len(pubs_kexs) { - return qerr.Error(qerr.CryptoMessageParameterNotFound, "KEXS not in PUBS") - } - - if pubs_kexs[c255Foundat].Length != 32 { - return qerr.Error(qerr.CryptoInvalidValueLength, "PUBS") - } - - var err error - s.kex, err = crypto.NewCurve25519KEX() - if err != nil { - return err - } - - - s.sharedSecret, err = s.kex.CalculateSharedKey(pubs_kexs[c255Foundat].Value) - if err != nil { - return err - } - - // OBIT - obit, ok := tagMap[TagOBIT] - if !ok { - return qerr.Error(qerr.CryptoMessageParameterNotFound, "OBIT") - } - if len(obit) != 8 { - return qerr.Error(qerr.CryptoInvalidValueLength, "OBIT") - } - s.obit = obit - - // EXPY - expy, ok := tagMap[TagEXPY] - if !ok { - return qerr.Error(qerr.CryptoMessageParameterNotFound, "EXPY") - } - if len(expy) != 8 { - return qerr.Error(qerr.CryptoInvalidValueLength, "EXPY") - } - // make sure that the value doesn't overflow an int64 - // furthermore, values close to MaxInt64 are not a valid input to time.Unix, thus set MaxInt64/2 as the maximum value here - expyTimestamp := utils.MinUint64(binary.LittleEndian.Uint64(expy), math.MaxInt64/2) - s.expiry = time.Unix(int64(expyTimestamp), 0) - - // TODO: implement VER - - return nil -} - -func (s *serverConfigClient) IsExpired() bool { - return s.expiry.Before(time.Now()) -} - -func (s *serverConfigClient) Get() []byte { - return s.raw -} diff --git a/vendor/github.com/lucas-clemente/quic-go/handshake/stk_generator.go b/vendor/github.com/lucas-clemente/quic-go/handshake/stk_generator.go deleted file mode 100644 index c3caea3d226..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/handshake/stk_generator.go +++ /dev/null @@ -1,100 +0,0 @@ -package handshake - -import ( - "encoding/asn1" - "fmt" - "net" - "time" - - "github.com/lucas-clemente/quic-go/crypto" -) - -const ( - stkPrefixIP byte = iota - stkPrefixString -) - -// An STK is a source address token -type STK struct { - RemoteAddr string - SentTime time.Time -} - -// token is the struct that is used for ASN1 serialization and deserialization -type token struct { - Data []byte - Timestamp int64 -} - -// An STKGenerator generates STKs -type STKGenerator struct { - stkSource crypto.StkSource -} - -// NewSTKGenerator initializes a new STKGenerator -func NewSTKGenerator() (*STKGenerator, error) { - stkSource, err := crypto.NewStkSource() - if err != nil { - return nil, err - } - return &STKGenerator{ - stkSource: stkSource, - }, nil -} - -// NewToken generates a new STK token for a given source address -func (g *STKGenerator) NewToken(raddr net.Addr) ([]byte, error) { - data, err := asn1.Marshal(token{ - Data: encodeRemoteAddr(raddr), - Timestamp: time.Now().Unix(), - }) - if err != nil { - return nil, err - } - return g.stkSource.NewToken(data) -} - -// DecodeToken decodes an STK token -func (g *STKGenerator) DecodeToken(encrypted []byte) (*STK, error) { - // if the client didn't send any STK, DecodeToken will be called with a nil-slice - if len(encrypted) == 0 { - return nil, nil - } - - data, err := g.stkSource.DecodeToken(encrypted) - if err != nil { - return nil, err - } - t := &token{} - rest, err := asn1.Unmarshal(data, t) - if err != nil { - return nil, err - } - if len(rest) != 0 { - return nil, fmt.Errorf("rest when unpacking token: %d", len(rest)) - } - return &STK{ - RemoteAddr: decodeRemoteAddr(t.Data), - SentTime: time.Unix(t.Timestamp, 0), - }, nil -} - -// encodeRemoteAddr encodes a remote address such that it can be saved in the STK -func encodeRemoteAddr(remoteAddr net.Addr) []byte { - if udpAddr, ok := remoteAddr.(*net.UDPAddr); ok { - return append([]byte{stkPrefixIP}, udpAddr.IP...) - } - return append([]byte{stkPrefixString}, []byte(remoteAddr.String())...) -} - -// decodeRemoteAddr decodes the remote address saved in the STK -func decodeRemoteAddr(data []byte) string { - // data will never be empty for an STK that we generated. Check it to be on the safe side - if len(data) == 0 { - return "" - } - if data[0] == stkPrefixIP { - return net.IP(data[1:]).String() - } - return string(data[1:]) -} diff --git a/vendor/github.com/lucas-clemente/quic-go/handshake/tags.go b/vendor/github.com/lucas-clemente/quic-go/handshake/tags.go deleted file mode 100644 index 2b3783f6481..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/handshake/tags.go +++ /dev/null @@ -1,94 +0,0 @@ -package handshake - -// A Tag in the QUIC crypto -type Tag uint32 - -const ( - // TagCHLO is a client hello - TagCHLO Tag = 'C' + 'H'<<8 + 'L'<<16 + 'O'<<24 - // TagREJ is a server hello rejection - TagREJ Tag = 'R' + 'E'<<8 + 'J'<<16 - // TagSCFG is a server config - TagSCFG Tag = 'S' + 'C'<<8 + 'F'<<16 + 'G'<<24 - - // TagPAD is padding - TagPAD Tag = 'P' + 'A'<<8 + 'D'<<16 - // TagSNI is the server name indication - TagSNI Tag = 'S' + 'N'<<8 + 'I'<<16 - // TagVER is the QUIC version - TagVER Tag = 'V' + 'E'<<8 + 'R'<<16 - // TagCCS are the hashes of the common certificate sets - TagCCS Tag = 'C' + 'C'<<8 + 'S'<<16 - // TagCCRT are the hashes of the cached certificates - TagCCRT Tag = 'C' + 'C'<<8 + 'R'<<16 + 'T'<<24 - // TagMSPC is max streams per connection - TagMSPC Tag = 'M' + 'S'<<8 + 'P'<<16 + 'C'<<24 - // TagMIDS is max incoming dyanamic streams - TagMIDS Tag = 'M' + 'I'<<8 + 'D'<<16 + 'S'<<24 - // TagUAID is the user agent ID - TagUAID Tag = 'U' + 'A'<<8 + 'I'<<16 + 'D'<<24 - // TagSVID is the server ID (unofficial tag by us :) - TagSVID Tag = 'S' + 'V'<<8 + 'I'<<16 + 'D'<<24 - // TagTCID is truncation of the connection ID - TagTCID Tag = 'T' + 'C'<<8 + 'I'<<16 + 'D'<<24 - // TagPDMD is the proof demand - TagPDMD Tag = 'P' + 'D'<<8 + 'M'<<16 + 'D'<<24 - // TagSRBF is the socket receive buffer - TagSRBF Tag = 'S' + 'R'<<8 + 'B'<<16 + 'F'<<24 - // TagICSL is the idle connection state lifetime - TagICSL Tag = 'I' + 'C'<<8 + 'S'<<16 + 'L'<<24 - // TagNONP is the client proof nonce - TagNONP Tag = 'N' + 'O'<<8 + 'N'<<16 + 'P'<<24 - // TagSCLS is the silently close timeout - TagSCLS Tag = 'S' + 'C'<<8 + 'L'<<16 + 'S'<<24 - // TagCSCT is the signed cert timestamp (RFC6962) of leaf cert - TagCSCT Tag = 'C' + 'S'<<8 + 'C'<<16 + 'T'<<24 - // TagCOPT are the connection options - TagCOPT Tag = 'C' + 'O'<<8 + 'P'<<16 + 'T'<<24 - // TagCFCW is the initial session/connection flow control receive window - TagCFCW Tag = 'C' + 'F'<<8 + 'C'<<16 + 'W'<<24 - // TagSFCW is the initial stream flow control receive window. - TagSFCW Tag = 'S' + 'F'<<8 + 'C'<<16 + 'W'<<24 - - // TagFHL2 forces head of line blocking. - // Chrome experiment (see https://codereview.chromium.org/2115033002) - // unsupported by quic-go - TagFHL2 Tag = 'F' + 'H'<<8 + 'L'<<16 + '2'<<24 - - // TagSTK is the source-address token - TagSTK Tag = 'S' + 'T'<<8 + 'K'<<16 - // TagSNO is the server nonce - TagSNO Tag = 'S' + 'N'<<8 + 'O'<<16 - // TagPROF is the server proof - TagPROF Tag = 'P' + 'R'<<8 + 'O'<<16 + 'F'<<24 - - // TagNONC is the client nonce - TagNONC Tag = 'N' + 'O'<<8 + 'N'<<16 + 'C'<<24 - // TagXLCT is the expected leaf certificate - TagXLCT Tag = 'X' + 'L'<<8 + 'C'<<16 + 'T'<<24 - - // TagSCID is the server config ID - TagSCID Tag = 'S' + 'C'<<8 + 'I'<<16 + 'D'<<24 - // TagKEXS is the list of key exchange algos - TagKEXS Tag = 'K' + 'E'<<8 + 'X'<<16 + 'S'<<24 - // TagAEAD is the list of AEAD algos - TagAEAD Tag = 'A' + 'E'<<8 + 'A'<<16 + 'D'<<24 - // TagPUBS is the public value for the KEX - TagPUBS Tag = 'P' + 'U'<<8 + 'B'<<16 + 'S'<<24 - // TagOBIT is the client orbit - TagOBIT Tag = 'O' + 'B'<<8 + 'I'<<16 + 'T'<<24 - // TagEXPY is the server config expiry - TagEXPY Tag = 'E' + 'X'<<8 + 'P'<<16 + 'Y'<<24 - // TagCERT is the CERT data - TagCERT Tag = 0xff545243 - - // TagSHLO is the server hello - TagSHLO Tag = 'S' + 'H'<<8 + 'L'<<16 + 'O'<<24 - - // TagPRST is the public reset tag - TagPRST Tag = 'P' + 'R'<<8 + 'S'<<16 + 'T'<<24 - // TagRSEQ is the public reset rejected packet number - TagRSEQ Tag = 'R' + 'S'<<8 + 'E'<<16 + 'Q'<<24 - // TagRNON is the public reset nonce - TagRNON Tag = 'R' + 'N'<<8 + 'O'<<16 + 'N'<<24 -) diff --git a/vendor/github.com/lucas-clemente/quic-go/integrationtests/data_manager.go b/vendor/github.com/lucas-clemente/quic-go/integrationtests/data_manager.go deleted file mode 100644 index 627782a06f1..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/integrationtests/data_manager.go +++ /dev/null @@ -1,32 +0,0 @@ -package integrationtests - -import ( - "crypto/md5" - "math/rand" - "time" -) - -type dataManager struct { - data []byte - md5 []byte -} - -func (m *dataManager) GenerateData(len int) error { - m.data = make([]byte, len) - r := rand.New(rand.NewSource(int64(time.Now().Nanosecond()))) - _, err := r.Read(m.data) - if err != nil { - return err - } - sum := md5.Sum(m.data) - m.md5 = sum[:] - return nil -} - -func (m *dataManager) GetData() []byte { - return m.data -} - -func (m *dataManager) GetMD5() []byte { - return m.md5 -} diff --git a/vendor/github.com/lucas-clemente/quic-go/integrationtests/handshake/rtt.go b/vendor/github.com/lucas-clemente/quic-go/integrationtests/handshake/rtt.go deleted file mode 100644 index d998caf5ad0..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/integrationtests/handshake/rtt.go +++ /dev/null @@ -1,133 +0,0 @@ -package handshaketests - -import ( - "crypto/tls" - "fmt" - "net" - "time" - - quic "github.com/lucas-clemente/quic-go" - "github.com/lucas-clemente/quic-go/integrationtests/proxy" - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" - "github.com/lucas-clemente/quic-go/qerr" - - "github.com/lucas-clemente/quic-go/testdata" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -var _ = Describe("Handshake integration tets", func() { - var ( - proxy *quicproxy.QuicProxy - server quic.Listener - serverConfig *quic.Config - testStartedAt time.Time - ) - rtt := 400 * time.Millisecond - - BeforeEach(func() { - serverConfig = &quic.Config{} - }) - - AfterEach(func() { - Expect(proxy.Close()).To(Succeed()) - Expect(server.Close()).To(Succeed()) - }) - - runServerAndProxy := func() { - var err error - // start the server - server, err = quic.ListenAddr("localhost:0", testdata.GetTLSConfig(), serverConfig) - Expect(err).ToNot(HaveOccurred()) - // start the proxy - proxy, err = quicproxy.NewQuicProxy("localhost:0", quicproxy.Opts{ - RemoteAddr: server.Addr().String(), - DelayPacket: func(_ quicproxy.Direction, _ protocol.PacketNumber) time.Duration { return rtt / 2 }, - }) - Expect(err).ToNot(HaveOccurred()) - - testStartedAt = time.Now() - - go func() { - for { - _, _ = server.Accept() - } - }() - } - - expectDurationInRTTs := func(num int) { - testDuration := time.Since(testStartedAt) - expectedDuration := time.Duration(num) * rtt - Expect(testDuration).To(SatisfyAll( - BeNumerically(">=", expectedDuration), - BeNumerically("<", expectedDuration+rtt), - )) - } - - It("fails when there's no matching version, after 1 RTT", func() { - Expect(len(protocol.SupportedVersions)).To(BeNumerically(">", 1)) - serverConfig.Versions = protocol.SupportedVersions[:1] - runServerAndProxy() - clientConfig := &quic.Config{ - Versions: protocol.SupportedVersions[1:2], - } - _, err := quic.DialAddr(proxy.LocalAddr().String(), nil, clientConfig) - Expect(err).To(HaveOccurred()) - Expect(err.(qerr.ErrorCode)).To(Equal(qerr.InvalidVersion)) - expectDurationInRTTs(1) - }) - - // 1 RTT for verifying the source address - // 1 RTT to become secure - // 1 RTT to become forward-secure - It("is forward-secure after 3 RTTs", func() { - runServerAndProxy() - _, err := quic.DialAddr(proxy.LocalAddr().String(), &tls.Config{InsecureSkipVerify: true}, nil) - Expect(err).ToNot(HaveOccurred()) - expectDurationInRTTs(3) - }) - - // 1 RTT for verifying the source address - // 1 RTT to become secure - // TODO (marten-seemann): enable this test (see #625) - PIt("is secure after 2 RTTs", func() { - utils.SetLogLevel(utils.LogLevelDebug) - runServerAndProxy() - _, err := quic.DialAddrNonFWSecure(proxy.LocalAddr().String(), &tls.Config{InsecureSkipVerify: true}, nil) - fmt.Println("#### is non fw secure ###") - Expect(err).ToNot(HaveOccurred()) - expectDurationInRTTs(2) - }) - - It("is forward-secure after 2 RTTs when the server doesn't require an STK", func() { - serverConfig.AcceptSTK = func(_ net.Addr, _ *quic.STK) bool { - return true - } - runServerAndProxy() - _, err := quic.DialAddr(proxy.LocalAddr().String(), &tls.Config{InsecureSkipVerify: true}, nil) - Expect(err).ToNot(HaveOccurred()) - expectDurationInRTTs(2) - }) - - It("doesn't complete the handshake when the server never accepts the STK", func() { - serverConfig.AcceptSTK = func(_ net.Addr, _ *quic.STK) bool { - return false - } - runServerAndProxy() - _, err := quic.DialAddr(proxy.LocalAddr().String(), &tls.Config{InsecureSkipVerify: true}, nil) - Expect(err).To(HaveOccurred()) - Expect(err.(*qerr.QuicError).ErrorCode).To(Equal(qerr.CryptoTooManyRejects)) - }) - - It("doesn't complete the handshake when the handshake timeout is too short", func() { - serverConfig.HandshakeTimeout = 2 * rtt - runServerAndProxy() - _, err := quic.DialAddr(proxy.LocalAddr().String(), &tls.Config{InsecureSkipVerify: true}, nil) - Expect(err).To(HaveOccurred()) - Expect(err.(*qerr.QuicError).ErrorCode).To(Equal(qerr.HandshakeTimeout)) - // 2 RTTs during the timeout - // plus 1 RTT: the timer starts 0.5 RTTs after sending the first packet, and the CONNECTION_CLOSE needs another 0.5 RTTs to reach the client - expectDurationInRTTs(3) - }) -}) diff --git a/vendor/github.com/lucas-clemente/quic-go/integrationtests/integration.go b/vendor/github.com/lucas-clemente/quic-go/integrationtests/integration.go deleted file mode 100644 index c94303b8c32..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/integrationtests/integration.go +++ /dev/null @@ -1 +0,0 @@ -package integrationtests diff --git a/vendor/github.com/lucas-clemente/quic-go/integrationtests/proxy/proxy.go b/vendor/github.com/lucas-clemente/quic-go/integrationtests/proxy/proxy.go deleted file mode 100644 index 70be88892dd..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/integrationtests/proxy/proxy.go +++ /dev/null @@ -1,227 +0,0 @@ -package quicproxy - -import ( - "bytes" - "net" - "sync" - "sync/atomic" - "time" - - "github.com/lucas-clemente/quic-go" - "github.com/lucas-clemente/quic-go/protocol" -) - -// Connection is a UDP connection -type connection struct { - ClientAddr *net.UDPAddr // Address of the client - ServerConn *net.UDPConn // UDP connection to server - - incomingPacketCounter uint64 - outgoingPacketCounter uint64 -} - -// Direction is the direction a packet is sent. -type Direction int - -const ( - // DirectionIncoming is the direction from the client to the server. - DirectionIncoming Direction = iota - // DirectionOutgoing is the direction from the server to the client. - DirectionOutgoing -) - -// DropCallback is a callback that determines which packet gets dropped. -type DropCallback func(Direction, protocol.PacketNumber) bool - -// NoDropper doesn't drop packets. -var NoDropper DropCallback = func(Direction, protocol.PacketNumber) bool { - return false -} - -// DelayCallback is a callback that determines how much delay to apply to a packet. -type DelayCallback func(Direction, protocol.PacketNumber) time.Duration - -// NoDelay doesn't apply a delay. -var NoDelay DelayCallback = func(Direction, protocol.PacketNumber) time.Duration { - return 0 -} - -// Opts are proxy options. -type Opts struct { - // The address this proxy proxies packets to. - RemoteAddr string - // DropPacket determines whether a packet gets dropped. - DropPacket DropCallback - // DelayPacket determines how long a packet gets delayed. This allows - // simulating a connection with non-zero RTTs. - // Note that the RTT is the sum of the delay for the incoming and the outgoing packet. - DelayPacket DelayCallback -} - -// QuicProxy is a QUIC proxy that can drop and delay packets. -type QuicProxy struct { - mutex sync.Mutex - - conn *net.UDPConn - serverAddr *net.UDPAddr - - dropPacket DropCallback - delayPacket DelayCallback - - // Mapping from client addresses (as host:port) to connection - clientDict map[string]*connection -} - -// NewQuicProxy creates a new UDP proxy -func NewQuicProxy(local string, opts Opts) (*QuicProxy, error) { - laddr, err := net.ResolveUDPAddr("udp", local) - if err != nil { - return nil, err - } - conn, err := net.ListenUDP("udp", laddr) - if err != nil { - return nil, err - } - raddr, err := net.ResolveUDPAddr("udp", opts.RemoteAddr) - if err != nil { - return nil, err - } - - packetDropper := NoDropper - if opts.DropPacket != nil { - packetDropper = opts.DropPacket - } - - packetDelayer := NoDelay - if opts.DelayPacket != nil { - packetDelayer = opts.DelayPacket - } - - p := QuicProxy{ - clientDict: make(map[string]*connection), - conn: conn, - serverAddr: raddr, - dropPacket: packetDropper, - delayPacket: packetDelayer, - } - - go p.runProxy() - return &p, nil -} - -// Close stops the UDP Proxy -func (p *QuicProxy) Close() error { - return p.conn.Close() -} - -// LocalAddr is the address the proxy is listening on. -func (p *QuicProxy) LocalAddr() net.Addr { - return p.conn.LocalAddr() -} - -func (p *QuicProxy) LocalPort() int { - return p.conn.LocalAddr().(*net.UDPAddr).Port -} - -func (p *QuicProxy) newConnection(cliAddr *net.UDPAddr) (*connection, error) { - srvudp, err := net.DialUDP("udp", nil, p.serverAddr) - if err != nil { - return nil, err - } - return &connection{ - ClientAddr: cliAddr, - ServerConn: srvudp, - }, nil -} - -// runProxy listens on the proxy address and handles incoming packets. -func (p *QuicProxy) runProxy() error { - for { - buffer := make([]byte, protocol.MaxPacketSize) - n, cliaddr, err := p.conn.ReadFromUDP(buffer) - if err != nil { - return err - } - raw := buffer[0:n] - - saddr := cliaddr.String() - p.mutex.Lock() - conn, ok := p.clientDict[saddr] - - if !ok { - conn, err = p.newConnection(cliaddr) - if err != nil { - p.mutex.Unlock() - return err - } - p.clientDict[saddr] = conn - go p.runConnection(conn) - } - p.mutex.Unlock() - - atomic.AddUint64(&conn.incomingPacketCounter, 1) - - r := bytes.NewReader(raw) - hdr, err := quic.ParsePublicHeader(r, protocol.PerspectiveClient) - if err != nil { - return err - } - - if p.dropPacket(DirectionIncoming, hdr.PacketNumber) { - continue - } - - // Send the packet to the server - delay := p.delayPacket(DirectionIncoming, hdr.PacketNumber) - if delay != 0 { - time.AfterFunc(delay, func() { - // TODO: handle error - _, _ = conn.ServerConn.Write(raw) - }) - } else { - _, err := conn.ServerConn.Write(raw) - if err != nil { - return err - } - } - } -} - -// runConnection handles packets from server to a single client -func (p *QuicProxy) runConnection(conn *connection) error { - for { - buffer := make([]byte, protocol.MaxPacketSize) - n, err := conn.ServerConn.Read(buffer) - if err != nil { - return err - } - raw := buffer[0:n] - - // TODO: Switch back to using the public header once Chrome properly sets the type byte. - // r := bytes.NewReader(raw) - // , err := quic.ParsePublicHeader(r, protocol.PerspectiveServer) - // if err != nil { - // return err - // } - - v := atomic.AddUint64(&conn.outgoingPacketCounter, 1) - - packetNumber := protocol.PacketNumber(v) - if p.dropPacket(DirectionOutgoing, packetNumber) { - continue - } - - delay := p.delayPacket(DirectionOutgoing, packetNumber) - if delay != 0 { - time.AfterFunc(delay, func() { - // TODO: handle error - _, _ = p.conn.WriteToUDP(raw, conn.ClientAddr) - }) - } else { - _, err := p.conn.WriteToUDP(raw, conn.ClientAddr) - if err != nil { - return err - } - } - } -} diff --git a/vendor/github.com/lucas-clemente/quic-go/interface.go b/vendor/github.com/lucas-clemente/quic-go/interface.go deleted file mode 100644 index c4b7ed30f6c..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/interface.go +++ /dev/null @@ -1,121 +0,0 @@ -package quic - -import ( - "io" - "net" - "time" - - "github.com/lucas-clemente/quic-go/protocol" -) - -// Stream is the interface implemented by QUIC streams -type Stream interface { - // Read reads data from the stream. - // Read can be made to time out and return a net.Error with Timeout() == true - // after a fixed time limit; see SetDeadline and SetReadDeadline. - io.Reader - // Write writes data to the stream. - // Write can be made to time out and return a net.Error with Timeout() == true - // after a fixed time limit; see SetDeadline and SetWriteDeadline. - io.Writer - io.Closer - StreamID() protocol.StreamID - // Reset closes the stream with an error. - Reset(error) - // SetReadDeadline sets the deadline for future Read calls and - // any currently-blocked Read call. - // A zero value for t means Read will not time out. - SetReadDeadline(t time.Time) error - // SetWriteDeadline sets the deadline for future Write calls - // and any currently-blocked Write call. - // Even if write times out, it may return n > 0, indicating that - // some of the data was successfully written. - // A zero value for t means Write will not time out. - SetWriteDeadline(t time.Time) error - // SetDeadline sets the read and write deadlines associated - // with the connection. It is equivalent to calling both - // SetReadDeadline and SetWriteDeadline. - SetDeadline(t time.Time) error -} - -// A Session is a QUIC connection between two peers. -type Session interface { - // AcceptStream returns the next stream opened by the peer, blocking until one is available. - // Since stream 1 is reserved for the crypto stream, the first stream is either 2 (for a client) or 3 (for a server). - AcceptStream() (Stream, error) - // OpenStream opens a new QUIC stream, returning a special error when the peeer's concurrent stream limit is reached. - // New streams always have the smallest possible stream ID. - // TODO: Enable testing for the special error - OpenStream() (Stream, error) - // OpenStreamSync opens a new QUIC stream, blocking until the peer's concurrent stream limit allows a new stream to be opened. - // It always picks the smallest possible stream ID. - OpenStreamSync() (Stream, error) - // LocalAddr returns the local address. - LocalAddr() net.Addr - // RemoteAddr returns the address of the peer. - RemoteAddr() net.Addr - // Close closes the connection. The error will be sent to the remote peer in a CONNECTION_CLOSE frame. An error value of nil is allowed and will cause a normal PeerGoingAway to be sent. - Close(error) error - // WaitUntilClosed() blocks until the session is closed. - // Warning: This API should not be considered stable and might change soon. - WaitUntilClosed() -} - -// A NonFWSession is a QUIC connection between two peers half-way through the handshake. -// The communication is encrypted, but not yet forward secure. -type NonFWSession interface { - Session - WaitUntilHandshakeComplete() error -} - -// An STK is a Source Address token. -// It is issued by the server and sent to the client. For the client, it is an opaque blob. -// The client can send the STK in subsequent handshakes to prove ownership of its IP address. -type STK struct { - // The remote address this token was issued for. - // If the server is run on a net.UDPConn, this is the string representation of the IP address (net.IP.String()) - // Otherwise, this is the string representation of the net.Addr (net.Addr.String()) - remoteAddr string - // The time that the STK was issued (resolution 1 second) - sentTime time.Time -} - -// Config contains all configuration data needed for a QUIC server or client. -// More config parameters (such as timeouts) will be added soon, see e.g. https://github.com/lucas-clemente/quic-go/issues/441. -type Config struct { - // The QUIC versions that can be negotiated. - // If not set, it uses all versions available. - // Warning: This API should not be considered stable and will change soon. - Versions []protocol.VersionNumber - // Ask the server to truncate the connection ID sent in the Public Header. - // This saves 8 bytes in the Public Header in every packet. However, if the IP address of the server changes, the connection cannot be migrated. - // Currently only valid for the client. - RequestConnectionIDTruncation bool - // HandshakeTimeout is the maximum duration that the cryptographic handshake may take. - // If the timeout is exceeded, the connection is closed. - // If this value is zero, the timeout is set to 10 seconds. - HandshakeTimeout time.Duration - // AcceptSTK determines if an STK is accepted. - // It is called with stk = nil if the client didn't send an STK. - // If not set, it verifies that the address matches, and that the STK was issued within the last 24 hours. - // This option is only valid for the server. - AcceptSTK func(clientAddr net.Addr, stk *STK) bool - // MaxReceiveStreamFlowControlWindow is the maximum stream-level flow control window for receiving data. - // If this value is zero, it will default to 1 MB for the server and 6 MB for the client. - MaxReceiveStreamFlowControlWindow protocol.ByteCount - // MaxReceiveConnectionFlowControlWindow is the connection-level flow control window for receiving data. - // If this value is zero, it will default to 1.5 MB for the server and 15 MB for the client. - MaxReceiveConnectionFlowControlWindow protocol.ByteCount - // KeepAlive defines whether this peer will periodically send PING frames to keep the connection alive. - KeepAlive bool -} - -// A Listener for incoming QUIC connections -type Listener interface { - // Close the server, sending CONNECTION_CLOSE frames to each peer. - Close() error - // Addr returns the local network addr that the server is listening on. - Addr() net.Addr - // Accept returns new sessions. It should be called in a loop. - Accept() (Session, error) -} diff --git a/vendor/github.com/lucas-clemente/quic-go/internal/mocks/cpm.go b/vendor/github.com/lucas-clemente/quic-go/internal/mocks/cpm.go deleted file mode 100644 index 686928f63f0..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/internal/mocks/cpm.go +++ /dev/null @@ -1,153 +0,0 @@ -// Automatically generated by MockGen. DO NOT EDIT! -// Source: github.com/lucas-clemente/quic-go/handshake (interfaces: ConnectionParametersManager) - -package mocks - -import ( - gomock "github.com/golang/mock/gomock" - handshake "github.com/lucas-clemente/quic-go/handshake" - protocol "github.com/lucas-clemente/quic-go/protocol" - time "time" -) - -// Mock of ConnectionParametersManager interface -type MockConnectionParametersManager struct { - ctrl *gomock.Controller - recorder *_MockConnectionParametersManagerRecorder -} - -// Recorder for MockConnectionParametersManager (not exported) -type _MockConnectionParametersManagerRecorder struct { - mock *MockConnectionParametersManager -} - -func NewMockConnectionParametersManager(ctrl *gomock.Controller) *MockConnectionParametersManager { - mock := &MockConnectionParametersManager{ctrl: ctrl} - mock.recorder = &_MockConnectionParametersManagerRecorder{mock} - return mock -} - -func (_m *MockConnectionParametersManager) EXPECT() *_MockConnectionParametersManagerRecorder { - return _m.recorder -} - -func (_m *MockConnectionParametersManager) GetHelloMap() (map[handshake.Tag][]byte, error) { - ret := _m.ctrl.Call(_m, "GetHelloMap") - ret0, _ := ret[0].(map[handshake.Tag][]byte) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -func (_mr *_MockConnectionParametersManagerRecorder) GetHelloMap() *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "GetHelloMap") -} - -func (_m *MockConnectionParametersManager) GetIdleConnectionStateLifetime() time.Duration { - ret := _m.ctrl.Call(_m, "GetIdleConnectionStateLifetime") - ret0, _ := ret[0].(time.Duration) - return ret0 -} - -func (_mr *_MockConnectionParametersManagerRecorder) GetIdleConnectionStateLifetime() *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "GetIdleConnectionStateLifetime") -} - -func (_m *MockConnectionParametersManager) GetMaxIncomingStreams() uint32 { - ret := _m.ctrl.Call(_m, "GetMaxIncomingStreams") - ret0, _ := ret[0].(uint32) - return ret0 -} - -func (_mr *_MockConnectionParametersManagerRecorder) GetMaxIncomingStreams() *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "GetMaxIncomingStreams") -} - -func (_m *MockConnectionParametersManager) GetMaxOutgoingStreams() uint32 { - ret := _m.ctrl.Call(_m, "GetMaxOutgoingStreams") - ret0, _ := ret[0].(uint32) - return ret0 -} - -func (_mr *_MockConnectionParametersManagerRecorder) GetMaxOutgoingStreams() *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "GetMaxOutgoingStreams") -} - -func (_m *MockConnectionParametersManager) GetMaxReceiveConnectionFlowControlWindow() protocol.ByteCount { - ret := _m.ctrl.Call(_m, "GetMaxReceiveConnectionFlowControlWindow") - ret0, _ := ret[0].(protocol.ByteCount) - return ret0 -} - -func (_mr *_MockConnectionParametersManagerRecorder) GetMaxReceiveConnectionFlowControlWindow() *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "GetMaxReceiveConnectionFlowControlWindow") -} - -func (_m *MockConnectionParametersManager) GetMaxReceiveStreamFlowControlWindow() protocol.ByteCount { - ret := _m.ctrl.Call(_m, "GetMaxReceiveStreamFlowControlWindow") - ret0, _ := ret[0].(protocol.ByteCount) - return ret0 -} - -func (_mr *_MockConnectionParametersManagerRecorder) GetMaxReceiveStreamFlowControlWindow() *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "GetMaxReceiveStreamFlowControlWindow") -} - -func (_m *MockConnectionParametersManager) GetReceiveConnectionFlowControlWindow() protocol.ByteCount { - ret := _m.ctrl.Call(_m, "GetReceiveConnectionFlowControlWindow") - ret0, _ := ret[0].(protocol.ByteCount) - return ret0 -} - -func (_mr *_MockConnectionParametersManagerRecorder) GetReceiveConnectionFlowControlWindow() *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "GetReceiveConnectionFlowControlWindow") -} - -func (_m *MockConnectionParametersManager) GetReceiveStreamFlowControlWindow() protocol.ByteCount { - ret := _m.ctrl.Call(_m, "GetReceiveStreamFlowControlWindow") - ret0, _ := ret[0].(protocol.ByteCount) - return ret0 -} - -func (_mr *_MockConnectionParametersManagerRecorder) GetReceiveStreamFlowControlWindow() *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "GetReceiveStreamFlowControlWindow") -} - -func (_m *MockConnectionParametersManager) GetSendConnectionFlowControlWindow() protocol.ByteCount { - ret := _m.ctrl.Call(_m, "GetSendConnectionFlowControlWindow") - ret0, _ := ret[0].(protocol.ByteCount) - return ret0 -} - -func (_mr *_MockConnectionParametersManagerRecorder) GetSendConnectionFlowControlWindow() *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "GetSendConnectionFlowControlWindow") -} - -func (_m *MockConnectionParametersManager) GetSendStreamFlowControlWindow() protocol.ByteCount { - ret := _m.ctrl.Call(_m, "GetSendStreamFlowControlWindow") - ret0, _ := ret[0].(protocol.ByteCount) - return ret0 -} - -func (_mr *_MockConnectionParametersManagerRecorder) GetSendStreamFlowControlWindow() *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "GetSendStreamFlowControlWindow") -} - -func (_m *MockConnectionParametersManager) SetFromMap(_param0 map[handshake.Tag][]byte) error { - ret := _m.ctrl.Call(_m, "SetFromMap", _param0) - ret0, _ := ret[0].(error) - return ret0 -} - -func (_mr *_MockConnectionParametersManagerRecorder) SetFromMap(arg0 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "SetFromMap", arg0) -} - -func (_m *MockConnectionParametersManager) TruncateConnectionID() bool { - ret := _m.ctrl.Call(_m, "TruncateConnectionID") - ret0, _ := ret[0].(bool) - return ret0 -} - -func (_mr *_MockConnectionParametersManagerRecorder) TruncateConnectionID() *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "TruncateConnectionID") -} diff --git a/vendor/github.com/lucas-clemente/quic-go/internal/mocks/gen.go b/vendor/github.com/lucas-clemente/quic-go/internal/mocks/gen.go deleted file mode 100644 index e778447693e..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/internal/mocks/gen.go +++ /dev/null @@ -1,4 +0,0 @@ -package mocks - -//go:generate mockgen -destination mocks_fc/flow_control_manager.go -package mocks_fc github.com/lucas-clemente/quic-go/flowcontrol FlowControlManager -//go:generate mockgen -destination cpm.go -package mocks github.com/lucas-clemente/quic-go/handshake ConnectionParametersManager diff --git a/vendor/github.com/lucas-clemente/quic-go/internal/mocks/mocks_fc/flow_control_manager.go b/vendor/github.com/lucas-clemente/quic-go/internal/mocks/mocks_fc/flow_control_manager.go deleted file mode 100644 index d18bf48fc57..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/internal/mocks/mocks_fc/flow_control_manager.go +++ /dev/null @@ -1,140 +0,0 @@ -// Automatically generated by MockGen. DO NOT EDIT! -// Source: github.com/lucas-clemente/quic-go/flowcontrol (interfaces: FlowControlManager) - -package mocks_fc - -import ( - gomock "github.com/golang/mock/gomock" - flowcontrol "github.com/lucas-clemente/quic-go/flowcontrol" - protocol "github.com/lucas-clemente/quic-go/protocol" -) - -// Mock of FlowControlManager interface -type MockFlowControlManager struct { - ctrl *gomock.Controller - recorder *_MockFlowControlManagerRecorder -} - -// Recorder for MockFlowControlManager (not exported) -type _MockFlowControlManagerRecorder struct { - mock *MockFlowControlManager -} - -func NewMockFlowControlManager(ctrl *gomock.Controller) *MockFlowControlManager { - mock := &MockFlowControlManager{ctrl: ctrl} - mock.recorder = &_MockFlowControlManagerRecorder{mock} - return mock -} - -func (_m *MockFlowControlManager) EXPECT() *_MockFlowControlManagerRecorder { - return _m.recorder -} - -func (_m *MockFlowControlManager) AddBytesRead(_param0 protocol.StreamID, _param1 protocol.ByteCount) error { - ret := _m.ctrl.Call(_m, "AddBytesRead", _param0, _param1) - ret0, _ := ret[0].(error) - return ret0 -} - -func (_mr *_MockFlowControlManagerRecorder) AddBytesRead(arg0, arg1 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "AddBytesRead", arg0, arg1) -} - -func (_m *MockFlowControlManager) AddBytesSent(_param0 protocol.StreamID, _param1 protocol.ByteCount) error { - ret := _m.ctrl.Call(_m, "AddBytesSent", _param0, _param1) - ret0, _ := ret[0].(error) - return ret0 -} - -func (_mr *_MockFlowControlManagerRecorder) AddBytesSent(arg0, arg1 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "AddBytesSent", arg0, arg1) -} - -func (_m *MockFlowControlManager) GetReceiveWindow(_param0 protocol.StreamID) (protocol.ByteCount, error) { - ret := _m.ctrl.Call(_m, "GetReceiveWindow", _param0) - ret0, _ := ret[0].(protocol.ByteCount) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -func (_mr *_MockFlowControlManagerRecorder) GetReceiveWindow(arg0 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "GetReceiveWindow", arg0) -} - -func (_m *MockFlowControlManager) GetWindowUpdates() []flowcontrol.WindowUpdate { - ret := _m.ctrl.Call(_m, "GetWindowUpdates") - ret0, _ := ret[0].([]flowcontrol.WindowUpdate) - return ret0 -} - -func (_mr *_MockFlowControlManagerRecorder) GetWindowUpdates() *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "GetWindowUpdates") -} - -func (_m *MockFlowControlManager) NewStream(_param0 protocol.StreamID, _param1 bool) { - _m.ctrl.Call(_m, "NewStream", _param0, _param1) -} - -func (_mr *_MockFlowControlManagerRecorder) NewStream(arg0, arg1 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "NewStream", arg0, arg1) -} - -func (_m *MockFlowControlManager) RemainingConnectionWindowSize() protocol.ByteCount { - ret := _m.ctrl.Call(_m, "RemainingConnectionWindowSize") - ret0, _ := ret[0].(protocol.ByteCount) - return ret0 -} - -func (_mr *_MockFlowControlManagerRecorder) RemainingConnectionWindowSize() *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "RemainingConnectionWindowSize") -} - -func (_m *MockFlowControlManager) RemoveStream(_param0 protocol.StreamID) { - _m.ctrl.Call(_m, "RemoveStream", _param0) -} - -func (_mr *_MockFlowControlManagerRecorder) RemoveStream(arg0 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "RemoveStream", arg0) -} - -func (_m *MockFlowControlManager) ResetStream(_param0 protocol.StreamID, _param1 protocol.ByteCount) error { - ret := _m.ctrl.Call(_m, "ResetStream", _param0, _param1) - ret0, _ := ret[0].(error) - return ret0 -} - -func (_mr *_MockFlowControlManagerRecorder) ResetStream(arg0, arg1 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "ResetStream", arg0, arg1) -} - -func (_m *MockFlowControlManager) SendWindowSize(_param0 protocol.StreamID) (protocol.ByteCount, error) { - ret := _m.ctrl.Call(_m, "SendWindowSize", _param0) - ret0, _ := ret[0].(protocol.ByteCount) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -func (_mr *_MockFlowControlManagerRecorder) SendWindowSize(arg0 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "SendWindowSize", arg0) -} - -func (_m *MockFlowControlManager) UpdateHighestReceived(_param0 protocol.StreamID, _param1 protocol.ByteCount) error { - ret := _m.ctrl.Call(_m, "UpdateHighestReceived", _param0, _param1) - ret0, _ := ret[0].(error) - return ret0 -} - -func (_mr *_MockFlowControlManagerRecorder) UpdateHighestReceived(arg0, arg1 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "UpdateHighestReceived", arg0, arg1) -} - -func (_m *MockFlowControlManager) UpdateWindow(_param0 protocol.StreamID, _param1 protocol.ByteCount) (bool, error) { - ret := _m.ctrl.Call(_m, "UpdateWindow", _param0, _param1) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -func (_mr *_MockFlowControlManagerRecorder) UpdateWindow(arg0, arg1 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "UpdateWindow", arg0, arg1) -} diff --git a/vendor/github.com/lucas-clemente/quic-go/internal/utils/atomic_bool.go b/vendor/github.com/lucas-clemente/quic-go/internal/utils/atomic_bool.go deleted file mode 100644 index cf4642504e0..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/internal/utils/atomic_bool.go +++ /dev/null @@ -1,22 +0,0 @@ -package utils - -import "sync/atomic" - -// An AtomicBool is an atomic bool -type AtomicBool struct { - v int32 -} - -// Set sets the value -func (a *AtomicBool) Set(value bool) { - var n int32 - if value { - n = 1 - } - atomic.StoreInt32(&a.v, n) -} - -// Get gets the value -func (a *AtomicBool) Get() bool { - return atomic.LoadInt32(&a.v) != 0 -} diff --git a/vendor/github.com/lucas-clemente/quic-go/internal/utils/byteinterval_linkedlist.go b/vendor/github.com/lucas-clemente/quic-go/internal/utils/byteinterval_linkedlist.go deleted file mode 100644 index 545fc203877..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/internal/utils/byteinterval_linkedlist.go +++ /dev/null @@ -1,214 +0,0 @@ -// Generated by: main -// TypeWriter: linkedlist -// Directive: +gen on ByteInterval - -package utils - -// List is a modification of http://golang.org/pkg/container/list/ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// ByteIntervalElement is an element of a linked list. -type ByteIntervalElement struct { - // Next and previous pointers in the doubly-linked list of elements. - // To simplify the implementation, internally a list l is implemented - // as a ring, such that &l.root is both the next element of the last - // list element (l.Back()) and the previous element of the first list - // element (l.Front()). - next, prev *ByteIntervalElement - - // The list to which this element belongs. - list *ByteIntervalList - - // The value stored with this element. - Value ByteInterval -} - -// Next returns the next list element or nil. -func (e *ByteIntervalElement) Next() *ByteIntervalElement { - if p := e.next; e.list != nil && p != &e.list.root { - return p - } - return nil -} - -// Prev returns the previous list element or nil. -func (e *ByteIntervalElement) Prev() *ByteIntervalElement { - if p := e.prev; e.list != nil && p != &e.list.root { - return p - } - return nil -} - -// ByteIntervalList represents a doubly linked list. -// The zero value for ByteIntervalList is an empty list ready to use. -type ByteIntervalList struct { - root ByteIntervalElement // sentinel list element, only &root, root.prev, and root.next are used - len int // current list length excluding (this) sentinel element -} - -// Init initializes or clears list l. -func (l *ByteIntervalList) Init() *ByteIntervalList { - l.root.next = &l.root - l.root.prev = &l.root - l.len = 0 - return l -} - -// NewByteIntervalList returns an initialized list. -func NewByteIntervalList() *ByteIntervalList { return new(ByteIntervalList).Init() } - -// Len returns the number of elements of list l. -// The complexity is O(1). -func (l *ByteIntervalList) Len() int { return l.len } - -// Front returns the first element of list l or nil. -func (l *ByteIntervalList) Front() *ByteIntervalElement { - if l.len == 0 { - return nil - } - return l.root.next -} - -// Back returns the last element of list l or nil. -func (l *ByteIntervalList) Back() *ByteIntervalElement { - if l.len == 0 { - return nil - } - return l.root.prev -} - -// lazyInit lazily initializes a zero ByteIntervalList value. -func (l *ByteIntervalList) lazyInit() { - if l.root.next == nil { - l.Init() - } -} - -// insert inserts e after at, increments l.len, and returns e. -func (l *ByteIntervalList) insert(e, at *ByteIntervalElement) *ByteIntervalElement { - n := at.next - at.next = e - e.prev = at - e.next = n - n.prev = e - e.list = l - l.len++ - return e -} - -// insertValue is a convenience wrapper for insert(&ByteIntervalElement{Value: v}, at). -func (l *ByteIntervalList) insertValue(v ByteInterval, at *ByteIntervalElement) *ByteIntervalElement { - return l.insert(&ByteIntervalElement{Value: v}, at) -} - -// remove removes e from its list, decrements l.len, and returns e. -func (l *ByteIntervalList) remove(e *ByteIntervalElement) *ByteIntervalElement { - e.prev.next = e.next - e.next.prev = e.prev - e.next = nil // avoid memory leaks - e.prev = nil // avoid memory leaks - e.list = nil - l.len-- - return e -} - -// Remove removes e from l if e is an element of list l. -// It returns the element value e.Value. -func (l *ByteIntervalList) Remove(e *ByteIntervalElement) ByteInterval { - if e.list == l { - // if e.list == l, l must have been initialized when e was inserted - // in l or l == nil (e is a zero ByteIntervalElement) and l.remove will crash - l.remove(e) - } - return e.Value -} - -// PushFront inserts a new element e with value v at the front of list l and returns e. -func (l *ByteIntervalList) PushFront(v ByteInterval) *ByteIntervalElement { - l.lazyInit() - return l.insertValue(v, &l.root) -} - -// PushBack inserts a new element e with value v at the back of list l and returns e. -func (l *ByteIntervalList) PushBack(v ByteInterval) *ByteIntervalElement { - l.lazyInit() - return l.insertValue(v, l.root.prev) -} - -// InsertBefore inserts a new element e with value v immediately before mark and returns e. -// If mark is not an element of l, the list is not modified. -func (l *ByteIntervalList) InsertBefore(v ByteInterval, mark *ByteIntervalElement) *ByteIntervalElement { - if mark.list != l { - return nil - } - // see comment in ByteIntervalList.Remove about initialization of l - return l.insertValue(v, mark.prev) -} - -// InsertAfter inserts a new element e with value v immediately after mark and returns e. -// If mark is not an element of l, the list is not modified. -func (l *ByteIntervalList) InsertAfter(v ByteInterval, mark *ByteIntervalElement) *ByteIntervalElement { - if mark.list != l { - return nil - } - // see comment in ByteIntervalList.Remove about initialization of l - return l.insertValue(v, mark) -} - -// MoveToFront moves element e to the front of list l. -// If e is not an element of l, the list is not modified. -func (l *ByteIntervalList) MoveToFront(e *ByteIntervalElement) { - if e.list != l || l.root.next == e { - return - } - // see comment in ByteIntervalList.Remove about initialization of l - l.insert(l.remove(e), &l.root) -} - -// MoveToBack moves element e to the back of list l. -// If e is not an element of l, the list is not modified. -func (l *ByteIntervalList) MoveToBack(e *ByteIntervalElement) { - if e.list != l || l.root.prev == e { - return - } - // see comment in ByteIntervalList.Remove about initialization of l - l.insert(l.remove(e), l.root.prev) -} - -// MoveBefore moves element e to its new position before mark. -// If e or mark is not an element of l, or e == mark, the list is not modified. -func (l *ByteIntervalList) MoveBefore(e, mark *ByteIntervalElement) { - if e.list != l || e == mark || mark.list != l { - return - } - l.insert(l.remove(e), mark.prev) -} - -// MoveAfter moves element e to its new position after mark. -// If e is not an element of l, or e == mark, the list is not modified. -func (l *ByteIntervalList) MoveAfter(e, mark *ByteIntervalElement) { - if e.list != l || e == mark || mark.list != l { - return - } - l.insert(l.remove(e), mark) -} - -// PushBackList inserts a copy of an other list at the back of list l. -// The lists l and other may be the same. -func (l *ByteIntervalList) PushBackList(other *ByteIntervalList) { - l.lazyInit() - for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() { - l.insertValue(e.Value, l.root.prev) - } -} - -// PushFrontList inserts a copy of an other list at the front of list l. -// The lists l and other may be the same. -func (l *ByteIntervalList) PushFrontList(other *ByteIntervalList) { - l.lazyInit() - for i, e := other.Len(), other.Back(); i > 0; i, e = i-1, e.Prev() { - l.insertValue(e.Value, &l.root) - } -} diff --git a/vendor/github.com/lucas-clemente/quic-go/internal/utils/connection_id.go b/vendor/github.com/lucas-clemente/quic-go/internal/utils/connection_id.go deleted file mode 100644 index c2252e6edb2..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/internal/utils/connection_id.go +++ /dev/null @@ -1,18 +0,0 @@ -package utils - -import ( - "crypto/rand" - "encoding/binary" - - "github.com/lucas-clemente/quic-go/protocol" -) - -// GenerateConnectionID generates a connection ID using cryptographic random -func GenerateConnectionID() (protocol.ConnectionID, error) { - b := make([]byte, 8) - _, err := rand.Read(b) - if err != nil { - return 0, err - } - return protocol.ConnectionID(binary.LittleEndian.Uint64(b)), nil -} diff --git a/vendor/github.com/lucas-clemente/quic-go/internal/utils/float16.go b/vendor/github.com/lucas-clemente/quic-go/internal/utils/float16.go deleted file mode 100644 index 8abdb51d84e..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/internal/utils/float16.go +++ /dev/null @@ -1,86 +0,0 @@ -package utils - -import ( - "bytes" - "io" - "math" -) - -// We define an unsigned 16-bit floating point value, inspired by IEEE floats -// (http://en.wikipedia.org/wiki/Half_precision_floating-point_format), -// with 5-bit exponent (bias 1), 11-bit mantissa (effective 12 with hidden -// bit) and denormals, but without signs, transfinites or fractions. Wire format -// 16 bits (little-endian byte order) are split into exponent (high 5) and -// mantissa (low 11) and decoded as: -// uint64_t value; -// if (exponent == 0) value = mantissa; -// else value = (mantissa | 1 << 11) << (exponent - 1) -const uFloat16ExponentBits = 5 -const uFloat16MaxExponent = (1 << uFloat16ExponentBits) - 2 // 30 -const uFloat16MantissaBits = 16 - uFloat16ExponentBits // 11 -const uFloat16MantissaEffectiveBits = uFloat16MantissaBits + 1 // 12 -const uFloat16MaxValue = ((uint64(1) << uFloat16MantissaEffectiveBits) - 1) << uFloat16MaxExponent // 0x3FFC0000000 - -// ReadUfloat16 reads a float in the QUIC-float16 format and returns its uint64 representation -func ReadUfloat16(b io.ByteReader) (uint64, error) { - val, err := ReadUint16(b) - if err != nil { - return 0, err - } - - res := uint64(val) - - if res < (1 << uFloat16MantissaEffectiveBits) { - // Fast path: either the value is denormalized (no hidden bit), or - // normalized (hidden bit set, exponent offset by one) with exponent zero. - // Zero exponent offset by one sets the bit exactly where the hidden bit is. - // So in both cases the value encodes itself. - return res, nil - } - - exponent := val >> uFloat16MantissaBits // No sign extend on uint! - // After the fast pass, the exponent is at least one (offset by one). - // Un-offset the exponent. - exponent-- - // Here we need to clear the exponent and set the hidden bit. We have already - // decremented the exponent, so when we subtract it, it leaves behind the - // hidden bit. - res -= uint64(exponent) << uFloat16MantissaBits - res <<= exponent - return res, nil -} - -// WriteUfloat16 writes a float in the QUIC-float16 format from its uint64 representation -func WriteUfloat16(b *bytes.Buffer, value uint64) { - var result uint16 - if value < (uint64(1) << uFloat16MantissaEffectiveBits) { - // Fast path: either the value is denormalized, or has exponent zero. - // Both cases are represented by the value itself. - result = uint16(value) - } else if value >= uFloat16MaxValue { - // Value is out of range; clamp it to the maximum representable. - result = math.MaxUint16 - } else { - // The highest bit is between position 13 and 42 (zero-based), which - // corresponds to exponent 1-30. In the output, mantissa is from 0 to 10, - // hidden bit is 11 and exponent is 11 to 15. Shift the highest bit to 11 - // and count the shifts. - exponent := uint16(0) - for offset := uint16(16); offset > 0; offset /= 2 { - // Right-shift the value until the highest bit is in position 11. - // For offset of 16, 8, 4, 2 and 1 (binary search over 1-30), - // shift if the bit is at or above 11 + offset. - if value >= (uint64(1) << (uFloat16MantissaBits + offset)) { - exponent += offset - value >>= offset - } - } - - // Hidden bit (position 11) is set. We should remove it and increment the - // exponent. Equivalently, we just add it to the exponent. - // This hides the bit. - result = (uint16(value) + (exponent << uFloat16MantissaBits)) - } - - WriteUint16(b, result) -} diff --git a/vendor/github.com/lucas-clemente/quic-go/internal/utils/host.go b/vendor/github.com/lucas-clemente/quic-go/internal/utils/host.go deleted file mode 100644 index a1d6453b0c2..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/internal/utils/host.go +++ /dev/null @@ -1,27 +0,0 @@ -package utils - -import ( - "net/url" - "strings" -) - -// HostnameFromAddr determines the hostname in an address string -func HostnameFromAddr(addr string) (string, error) { - p, err := url.Parse(addr) - if err != nil { - return "", err - } - h := p.Host - - // copied from https://golang.org/src/net/http/transport.go - if hasPort(h) { - h = h[:strings.LastIndex(h, ":")] - } - - return h, nil -} - -// copied from https://golang.org/src/net/http/http.go -func hasPort(s string) bool { - return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") -} diff --git a/vendor/github.com/lucas-clemente/quic-go/internal/utils/log.go b/vendor/github.com/lucas-clemente/quic-go/internal/utils/log.go deleted file mode 100644 index 9128510e63d..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/internal/utils/log.go +++ /dev/null @@ -1,94 +0,0 @@ -package utils - -import ( - "fmt" - "log" - "os" - "time" -) - -// LogLevel of quic-go -type LogLevel uint8 - -const logEnv = "QUIC_GO_LOG_LEVEL" - -const ( - // LogLevelNothing disables - LogLevelNothing LogLevel = iota - // LogLevelError enables err logs - LogLevelError - // LogLevelInfo enables info logs (e.g. packets) - LogLevelInfo - // LogLevelDebug enables debug logs (e.g. packet contents) - LogLevelDebug -) - -var ( - logLevel = LogLevelNothing - timeFormat = "" -) - -// SetLogLevel sets the log level -func SetLogLevel(level LogLevel) { - logLevel = level -} - -// SetLogTimeFormat sets the format of the timestamp -// an empty string disables the logging of timestamps -func SetLogTimeFormat(format string) { - log.SetFlags(0) // disable timestamp logging done by the log package - timeFormat = format -} - -// Debugf logs something -func Debugf(format string, args ...interface{}) { - if logLevel == LogLevelDebug { - logMessage(format, args...) - } -} - -// Infof logs something -func Infof(format string, args ...interface{}) { - if logLevel >= LogLevelInfo { - logMessage(format, args...) - } -} - -// Errorf logs something -func Errorf(format string, args ...interface{}) { - if logLevel >= LogLevelError { - logMessage(format, args...) - } -} - -func logMessage(format string, args ...interface{}) { - if len(timeFormat) > 0 { - log.Printf(time.Now().Format(timeFormat)+" "+format, args...) - } else { - log.Printf(format, args...) - } -} - -// Debug returns true if the log level is LogLevelDebug -func Debug() bool { - return logLevel == LogLevelDebug -} - -func init() { - readLoggingEnv() -} - -func readLoggingEnv() { - switch os.Getenv(logEnv) { - case "": - return - case "DEBUG": - logLevel = LogLevelDebug - case "INFO": - logLevel = LogLevelInfo - case "ERROR": - logLevel = LogLevelError - default: - fmt.Fprintln(os.Stderr, "invalid quic-go log level, see https://github.com/lucas-clemente/quic-go/wiki/Logging") - } -} diff --git a/vendor/github.com/lucas-clemente/quic-go/internal/utils/minmax.go b/vendor/github.com/lucas-clemente/quic-go/internal/utils/minmax.go deleted file mode 100644 index 6e23df5a50d..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/internal/utils/minmax.go +++ /dev/null @@ -1,131 +0,0 @@ -package utils - -import ( - "math" - "time" - - "github.com/lucas-clemente/quic-go/protocol" -) - -// InfDuration is a duration of infinite length -const InfDuration = time.Duration(math.MaxInt64) - -// Max returns the maximum of two Ints -func Max(a, b int) int { - if a < b { - return b - } - return a -} - -// MaxUint32 returns the maximum of two uint32 -func MaxUint32(a, b uint32) uint32 { - if a < b { - return b - } - return a -} - -// MaxUint64 returns the maximum of two uint64 -func MaxUint64(a, b uint64) uint64 { - if a < b { - return b - } - return a -} - -// MinUint64 returns the maximum of two uint64 -func MinUint64(a, b uint64) uint64 { - if a < b { - return a - } - return b -} - -// Min returns the minimum of two Ints -func Min(a, b int) int { - if a < b { - return a - } - return b -} - -// MinUint32 returns the maximum of two uint32 -func MinUint32(a, b uint32) uint32 { - if a < b { - return a - } - return b -} - -// MinInt64 returns the minimum of two int64 -func MinInt64(a, b int64) int64 { - if a < b { - return a - } - return b -} - -// MaxInt64 returns the minimum of two int64 -func MaxInt64(a, b int64) int64 { - if a > b { - return a - } - return b -} - -// MinByteCount returns the minimum of two ByteCounts -func MinByteCount(a, b protocol.ByteCount) protocol.ByteCount { - if a < b { - return a - } - return b -} - -// MaxDuration returns the max duration -func MaxDuration(a, b time.Duration) time.Duration { - if a > b { - return a - } - return b -} - -// MinDuration returns the minimum duration -func MinDuration(a, b time.Duration) time.Duration { - if a > b { - return b - } - return a -} - -// AbsDuration returns the absolute value of a time duration -func AbsDuration(d time.Duration) time.Duration { - if d >= 0 { - return d - } - return -d -} - -// MinTime returns the earlier time -func MinTime(a, b time.Time) time.Time { - if a.After(b) { - return b - } - return a -} - -// MaxPacketNumber returns the max packet number -func MaxPacketNumber(a, b protocol.PacketNumber) protocol.PacketNumber { - if a > b { - return a - } - return b -} - -// MinPacketNumber returns the min packet number -func MinPacketNumber(a, b protocol.PacketNumber) protocol.PacketNumber { - if a < b { - return a - } - return b -} diff --git a/vendor/github.com/lucas-clemente/quic-go/internal/utils/packet_interval.go b/vendor/github.com/lucas-clemente/quic-go/internal/utils/packet_interval.go deleted file mode 100644 index 09800b6b672..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/internal/utils/packet_interval.go +++ /dev/null @@ -1,10 +0,0 @@ -package utils - -import "github.com/lucas-clemente/quic-go/protocol" - -// PacketInterval is an interval from one PacketNumber to the other -// +gen linkedlist -type PacketInterval struct { - Start protocol.PacketNumber - End protocol.PacketNumber -} diff --git a/vendor/github.com/lucas-clemente/quic-go/internal/utils/packetinterval_linkedlist.go b/vendor/github.com/lucas-clemente/quic-go/internal/utils/packetinterval_linkedlist.go deleted file mode 100644 index e3431d68c23..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/internal/utils/packetinterval_linkedlist.go +++ /dev/null @@ -1,214 +0,0 @@ -// Generated by: main -// TypeWriter: linkedlist -// Directive: +gen on PacketInterval - -package utils - -// List is a modification of http://golang.org/pkg/container/list/ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// PacketIntervalElement is an element of a linked list. -type PacketIntervalElement struct { - // Next and previous pointers in the doubly-linked list of elements. - // To simplify the implementation, internally a list l is implemented - // as a ring, such that &l.root is both the next element of the last - // list element (l.Back()) and the previous element of the first list - // element (l.Front()). - next, prev *PacketIntervalElement - - // The list to which this element belongs. - list *PacketIntervalList - - // The value stored with this element. - Value PacketInterval -} - -// Next returns the next list element or nil. -func (e *PacketIntervalElement) Next() *PacketIntervalElement { - if p := e.next; e.list != nil && p != &e.list.root { - return p - } - return nil -} - -// Prev returns the previous list element or nil. -func (e *PacketIntervalElement) Prev() *PacketIntervalElement { - if p := e.prev; e.list != nil && p != &e.list.root { - return p - } - return nil -} - -// PacketIntervalList represents a doubly linked list. -// The zero value for PacketIntervalList is an empty list ready to use. -type PacketIntervalList struct { - root PacketIntervalElement // sentinel list element, only &root, root.prev, and root.next are used - len int // current list length excluding (this) sentinel element -} - -// Init initializes or clears list l. -func (l *PacketIntervalList) Init() *PacketIntervalList { - l.root.next = &l.root - l.root.prev = &l.root - l.len = 0 - return l -} - -// NewPacketIntervalList returns an initialized list. -func NewPacketIntervalList() *PacketIntervalList { return new(PacketIntervalList).Init() } - -// Len returns the number of elements of list l. -// The complexity is O(1). -func (l *PacketIntervalList) Len() int { return l.len } - -// Front returns the first element of list l or nil. -func (l *PacketIntervalList) Front() *PacketIntervalElement { - if l.len == 0 { - return nil - } - return l.root.next -} - -// Back returns the last element of list l or nil. -func (l *PacketIntervalList) Back() *PacketIntervalElement { - if l.len == 0 { - return nil - } - return l.root.prev -} - -// lazyInit lazily initializes a zero PacketIntervalList value. -func (l *PacketIntervalList) lazyInit() { - if l.root.next == nil { - l.Init() - } -} - -// insert inserts e after at, increments l.len, and returns e. -func (l *PacketIntervalList) insert(e, at *PacketIntervalElement) *PacketIntervalElement { - n := at.next - at.next = e - e.prev = at - e.next = n - n.prev = e - e.list = l - l.len++ - return e -} - -// insertValue is a convenience wrapper for insert(&PacketIntervalElement{Value: v}, at). -func (l *PacketIntervalList) insertValue(v PacketInterval, at *PacketIntervalElement) *PacketIntervalElement { - return l.insert(&PacketIntervalElement{Value: v}, at) -} - -// remove removes e from its list, decrements l.len, and returns e. -func (l *PacketIntervalList) remove(e *PacketIntervalElement) *PacketIntervalElement { - e.prev.next = e.next - e.next.prev = e.prev - e.next = nil // avoid memory leaks - e.prev = nil // avoid memory leaks - e.list = nil - l.len-- - return e -} - -// Remove removes e from l if e is an element of list l. -// It returns the element value e.Value. -func (l *PacketIntervalList) Remove(e *PacketIntervalElement) PacketInterval { - if e.list == l { - // if e.list == l, l must have been initialized when e was inserted - // in l or l == nil (e is a zero PacketIntervalElement) and l.remove will crash - l.remove(e) - } - return e.Value -} - -// PushFront inserts a new element e with value v at the front of list l and returns e. -func (l *PacketIntervalList) PushFront(v PacketInterval) *PacketIntervalElement { - l.lazyInit() - return l.insertValue(v, &l.root) -} - -// PushBack inserts a new element e with value v at the back of list l and returns e. -func (l *PacketIntervalList) PushBack(v PacketInterval) *PacketIntervalElement { - l.lazyInit() - return l.insertValue(v, l.root.prev) -} - -// InsertBefore inserts a new element e with value v immediately before mark and returns e. -// If mark is not an element of l, the list is not modified. -func (l *PacketIntervalList) InsertBefore(v PacketInterval, mark *PacketIntervalElement) *PacketIntervalElement { - if mark.list != l { - return nil - } - // see comment in PacketIntervalList.Remove about initialization of l - return l.insertValue(v, mark.prev) -} - -// InsertAfter inserts a new element e with value v immediately after mark and returns e. -// If mark is not an element of l, the list is not modified. -func (l *PacketIntervalList) InsertAfter(v PacketInterval, mark *PacketIntervalElement) *PacketIntervalElement { - if mark.list != l { - return nil - } - // see comment in PacketIntervalList.Remove about initialization of l - return l.insertValue(v, mark) -} - -// MoveToFront moves element e to the front of list l. -// If e is not an element of l, the list is not modified. -func (l *PacketIntervalList) MoveToFront(e *PacketIntervalElement) { - if e.list != l || l.root.next == e { - return - } - // see comment in PacketIntervalList.Remove about initialization of l - l.insert(l.remove(e), &l.root) -} - -// MoveToBack moves element e to the back of list l. -// If e is not an element of l, the list is not modified. -func (l *PacketIntervalList) MoveToBack(e *PacketIntervalElement) { - if e.list != l || l.root.prev == e { - return - } - // see comment in PacketIntervalList.Remove about initialization of l - l.insert(l.remove(e), l.root.prev) -} - -// MoveBefore moves element e to its new position before mark. -// If e or mark is not an element of l, or e == mark, the list is not modified. -func (l *PacketIntervalList) MoveBefore(e, mark *PacketIntervalElement) { - if e.list != l || e == mark || mark.list != l { - return - } - l.insert(l.remove(e), mark.prev) -} - -// MoveAfter moves element e to its new position after mark. -// If e is not an element of l, or e == mark, the list is not modified. -func (l *PacketIntervalList) MoveAfter(e, mark *PacketIntervalElement) { - if e.list != l || e == mark || mark.list != l { - return - } - l.insert(l.remove(e), mark) -} - -// PushBackList inserts a copy of an other list at the back of list l. -// The lists l and other may be the same. -func (l *PacketIntervalList) PushBackList(other *PacketIntervalList) { - l.lazyInit() - for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() { - l.insertValue(e.Value, l.root.prev) - } -} - -// PushFrontList inserts a copy of an other list at the front of list l. -// The lists l and other may be the same. -func (l *PacketIntervalList) PushFrontList(other *PacketIntervalList) { - l.lazyInit() - for i, e := other.Len(), other.Back(); i > 0; i, e = i-1, e.Prev() { - l.insertValue(e.Value, &l.root) - } -} diff --git a/vendor/github.com/lucas-clemente/quic-go/internal/utils/streamframe_interval.go b/vendor/github.com/lucas-clemente/quic-go/internal/utils/streamframe_interval.go deleted file mode 100644 index c918b62eb09..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/internal/utils/streamframe_interval.go +++ /dev/null @@ -1,10 +0,0 @@ -package utils - -import "github.com/lucas-clemente/quic-go/protocol" - -// ByteInterval is an interval from one ByteCount to the other -// +gen linkedlist -type ByteInterval struct { - Start protocol.ByteCount - End protocol.ByteCount -} diff --git a/vendor/github.com/lucas-clemente/quic-go/internal/utils/timer.go b/vendor/github.com/lucas-clemente/quic-go/internal/utils/timer.go deleted file mode 100644 index 695ad3e752c..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/internal/utils/timer.go +++ /dev/null @@ -1,43 +0,0 @@ -package utils - -import "time" - -// A Timer wrapper that behaves correctly when resetting -type Timer struct { - t *time.Timer - read bool - deadline time.Time -} - -// NewTimer creates a new timer that is not set -func NewTimer() *Timer { - return &Timer{t: time.NewTimer(0)} -} - -// Chan returns the channel of the wrapped timer -func (t *Timer) Chan() <-chan time.Time { - return t.t.C -} - -// Reset the timer, no matter whether the value was read or not -func (t *Timer) Reset(deadline time.Time) { - if deadline.Equal(t.deadline) { - // No need to reset the timer - return - } - - // We need to drain the timer if the value from its channel was not read yet. - // See https://groups.google.com/forum/#!topic/golang-dev/c9UUfASVPoU - if !t.t.Stop() && !t.read { - <-t.t.C - } - t.t.Reset(deadline.Sub(time.Now())) - - t.read = false - t.deadline = deadline -} - -// SetRead should be called after the value from the chan was read -func (t *Timer) SetRead() { - t.read = true -} diff --git a/vendor/github.com/lucas-clemente/quic-go/internal/utils/utils.go b/vendor/github.com/lucas-clemente/quic-go/internal/utils/utils.go deleted file mode 100644 index f6c4e03b7e1..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/internal/utils/utils.go +++ /dev/null @@ -1,136 +0,0 @@ -package utils - -import ( - "bytes" - "io" -) - -// ReadUintN reads N bytes -func ReadUintN(b io.ByteReader, length uint8) (uint64, error) { - var res uint64 - for i := uint8(0); i < length; i++ { - bt, err := b.ReadByte() - if err != nil { - return 0, err - } - res ^= uint64(bt) << (i * 8) - } - return res, nil -} - -// ReadUint64 reads a uint64 -func ReadUint64(b io.ByteReader) (uint64, error) { - var b1, b2, b3, b4, b5, b6, b7, b8 uint8 - var err error - if b1, err = b.ReadByte(); err != nil { - return 0, err - } - if b2, err = b.ReadByte(); err != nil { - return 0, err - } - if b3, err = b.ReadByte(); err != nil { - return 0, err - } - if b4, err = b.ReadByte(); err != nil { - return 0, err - } - if b5, err = b.ReadByte(); err != nil { - return 0, err - } - if b6, err = b.ReadByte(); err != nil { - return 0, err - } - if b7, err = b.ReadByte(); err != nil { - return 0, err - } - if b8, err = b.ReadByte(); err != nil { - return 0, err - } - return uint64(b1) + uint64(b2)<<8 + uint64(b3)<<16 + uint64(b4)<<24 + uint64(b5)<<32 + uint64(b6)<<40 + uint64(b7)<<48 + uint64(b8)<<56, nil -} - -// ReadUint32 reads a uint32 -func ReadUint32(b io.ByteReader) (uint32, error) { - var b1, b2, b3, b4 uint8 - var err error - if b1, err = b.ReadByte(); err != nil { - return 0, err - } - if b2, err = b.ReadByte(); err != nil { - return 0, err - } - if b3, err = b.ReadByte(); err != nil { - return 0, err - } - if b4, err = b.ReadByte(); err != nil { - return 0, err - } - return uint32(b1) + uint32(b2)<<8 + uint32(b3)<<16 + uint32(b4)<<24, nil -} - -// ReadUint16 reads a uint16 -func ReadUint16(b io.ByteReader) (uint16, error) { - var b1, b2 uint8 - var err error - if b1, err = b.ReadByte(); err != nil { - return 0, err - } - if b2, err = b.ReadByte(); err != nil { - return 0, err - } - return uint16(b1) + uint16(b2)<<8, nil -} - -// WriteUint64 writes a uint64 -func WriteUint64(b *bytes.Buffer, i uint64) { - b.Write([]byte{ - uint8(i), uint8(i >> 8), uint8(i >> 16), uint8(i >> 24), - uint8(i >> 32), uint8(i >> 40), uint8(i >> 48), uint8(i >> 56), - }) -} - -// WriteUint56 writes 56 bit of a uint64 -func WriteUint56(b *bytes.Buffer, i uint64) { - b.Write([]byte{ - uint8(i), uint8(i >> 8), uint8(i >> 16), uint8(i >> 24), - uint8(i >> 32), uint8(i >> 40), uint8(i >> 48), - }) -} - -// WriteUint48 writes 48 bit of a uint64 -func WriteUint48(b *bytes.Buffer, i uint64) { - b.Write([]byte{ - uint8(i), uint8(i >> 8), uint8(i >> 16), uint8(i >> 24), - uint8(i >> 32), uint8(i >> 40), - }) -} - -// WriteUint40 writes 40 bit of a uint64 -func WriteUint40(b *bytes.Buffer, i uint64) { - b.Write([]byte{ - uint8(i), uint8(i >> 8), uint8(i >> 16), - uint8(i >> 24), uint8(i >> 32), - }) -} - -// WriteUint32 writes a uint32 -func WriteUint32(b *bytes.Buffer, i uint32) { - b.Write([]byte{uint8(i), uint8(i >> 8), uint8(i >> 16), uint8(i >> 24)}) -} - -// WriteUint24 writes 24 bit of a uint32 -func WriteUint24(b *bytes.Buffer, i uint32) { - b.Write([]byte{uint8(i), uint8(i >> 8), uint8(i >> 16)}) -} - -// WriteUint16 writes a uint16 -func WriteUint16(b *bytes.Buffer, i uint16) { - b.Write([]byte{uint8(i), uint8(i >> 8)}) -} - -// Uint32Slice attaches the methods of sort.Interface to []uint32, sorting in increasing order. -type Uint32Slice []uint32 - -func (s Uint32Slice) Len() int { return len(s) } -func (s Uint32Slice) Less(i, j int) bool { return s[i] < s[j] } -func (s Uint32Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } diff --git a/vendor/github.com/lucas-clemente/quic-go/packet_number_generator.go b/vendor/github.com/lucas-clemente/quic-go/packet_number_generator.go deleted file mode 100644 index 71ca9a3c46a..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/packet_number_generator.go +++ /dev/null @@ -1,69 +0,0 @@ -package quic - -import ( - "crypto/rand" - "math" - - "github.com/lucas-clemente/quic-go/protocol" -) - -// The packetNumberGenerator generates the packet number for the next packet -// it randomly skips a packet number every averagePeriod packets (on average) -// it is guarantued to never skip two consecutive packet numbers -type packetNumberGenerator struct { - averagePeriod protocol.PacketNumber - - next protocol.PacketNumber - nextToSkip protocol.PacketNumber -} - -func newPacketNumberGenerator(averagePeriod protocol.PacketNumber) *packetNumberGenerator { - return &packetNumberGenerator{ - next: 1, - averagePeriod: averagePeriod, - } -} - -func (p *packetNumberGenerator) Peek() protocol.PacketNumber { - return p.next -} - -func (p *packetNumberGenerator) Pop() protocol.PacketNumber { - next := p.next - - // generate a new packet number for the next packet - p.next++ - - if p.next == p.nextToSkip { - p.next++ - p.generateNewSkip() - } - - return next -} - -func (p *packetNumberGenerator) generateNewSkip() error { - num, err := p.getRandomNumber() - if err != nil { - return err - } - - skip := protocol.PacketNumber(num) * (p.averagePeriod - 1) / (math.MaxUint16 / 2) - // make sure that there are never two consecutive packet numbers that are skipped - p.nextToSkip = p.next + 2 + skip - - return nil -} - -// getRandomNumber() generates a cryptographically secure random number between 0 and MaxUint16 (= 65535) -// The expectation value is 65535/2 -func (p *packetNumberGenerator) getRandomNumber() (uint16, error) { - b := make([]byte, 2) - _, err := rand.Read(b) - if err != nil { - return 0, err - } - - num := uint16(b[0])<<8 + uint16(b[1]) - return num, nil -} diff --git a/vendor/github.com/lucas-clemente/quic-go/packet_packer.go b/vendor/github.com/lucas-clemente/quic-go/packet_packer.go deleted file mode 100644 index 28c29ace0a3..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/packet_packer.go +++ /dev/null @@ -1,331 +0,0 @@ -package quic - -import ( - "bytes" - "errors" - "fmt" - - "github.com/lucas-clemente/quic-go/ackhandler" - "github.com/lucas-clemente/quic-go/frames" - "github.com/lucas-clemente/quic-go/handshake" - "github.com/lucas-clemente/quic-go/protocol" -) - -type packedPacket struct { - number protocol.PacketNumber - raw []byte - frames []frames.Frame - encryptionLevel protocol.EncryptionLevel -} - -type packetPacker struct { - connectionID protocol.ConnectionID - perspective protocol.Perspective - version protocol.VersionNumber - cryptoSetup handshake.CryptoSetup - - packetNumberGenerator *packetNumberGenerator - connectionParameters handshake.ConnectionParametersManager - streamFramer *streamFramer - - controlFrames []frames.Frame - stopWaiting *frames.StopWaitingFrame - ackFrame *frames.AckFrame - leastUnacked protocol.PacketNumber -} - -func newPacketPacker(connectionID protocol.ConnectionID, - cryptoSetup handshake.CryptoSetup, - connectionParameters handshake.ConnectionParametersManager, - streamFramer *streamFramer, - perspective protocol.Perspective, - version protocol.VersionNumber, -) *packetPacker { - return &packetPacker{ - cryptoSetup: cryptoSetup, - connectionID: connectionID, - connectionParameters: connectionParameters, - perspective: perspective, - version: version, - streamFramer: streamFramer, - packetNumberGenerator: newPacketNumberGenerator(protocol.SkipPacketAveragePeriodLength), - } -} - -// PackConnectionClose packs a packet that ONLY contains a ConnectionCloseFrame -func (p *packetPacker) PackConnectionClose(ccf *frames.ConnectionCloseFrame) (*packedPacket, error) { - frames := []frames.Frame{ccf} - encLevel, sealer := p.cryptoSetup.GetSealer() - ph := p.getPublicHeader(encLevel) - raw, err := p.writeAndSealPacket(ph, frames, sealer) - return &packedPacket{ - number: ph.PacketNumber, - raw: raw, - frames: frames, - encryptionLevel: encLevel, - }, err -} - -func (p *packetPacker) PackAckPacket() (*packedPacket, error) { - if p.ackFrame == nil { - return nil, errors.New("packet packer BUG: no ack frame queued") - } - encLevel, sealer := p.cryptoSetup.GetSealer() - ph := p.getPublicHeader(encLevel) - frames := []frames.Frame{p.ackFrame} - if p.stopWaiting != nil { - p.stopWaiting.PacketNumber = ph.PacketNumber - p.stopWaiting.PacketNumberLen = ph.PacketNumberLen - frames = append(frames, p.stopWaiting) - p.stopWaiting = nil - } - p.ackFrame = nil - raw, err := p.writeAndSealPacket(ph, frames, sealer) - return &packedPacket{ - number: ph.PacketNumber, - raw: raw, - frames: frames, - encryptionLevel: encLevel, - }, err -} - -// PackHandshakeRetransmission retransmits a handshake packet, that was sent with less than forward-secure encryption -func (p *packetPacker) PackHandshakeRetransmission(packet *ackhandler.Packet) (*packedPacket, error) { - if packet.EncryptionLevel == protocol.EncryptionForwardSecure { - return nil, errors.New("PacketPacker BUG: forward-secure encrypted handshake packets don't need special treatment") - } - sealer, err := p.cryptoSetup.GetSealerWithEncryptionLevel(packet.EncryptionLevel) - if err != nil { - return nil, err - } - if p.stopWaiting == nil { - return nil, errors.New("PacketPacker BUG: Handshake retransmissions must contain a StopWaitingFrame") - } - ph := p.getPublicHeader(packet.EncryptionLevel) - p.stopWaiting.PacketNumber = ph.PacketNumber - p.stopWaiting.PacketNumberLen = ph.PacketNumberLen - frames := append([]frames.Frame{p.stopWaiting}, packet.Frames...) - p.stopWaiting = nil - raw, err := p.writeAndSealPacket(ph, frames, sealer) - return &packedPacket{ - number: ph.PacketNumber, - raw: raw, - frames: frames, - encryptionLevel: packet.EncryptionLevel, - }, err -} - -// PackPacket packs a new packet -// the other controlFrames are sent in the next packet, but might be queued and sent in the next packet if the packet would overflow MaxPacketSize otherwise -func (p *packetPacker) PackPacket() (*packedPacket, error) { - if p.streamFramer.HasCryptoStreamFrame() { - return p.packCryptoPacket() - } - - encLevel, sealer := p.cryptoSetup.GetSealer() - - publicHeader := p.getPublicHeader(encLevel) - publicHeaderLength, err := publicHeader.GetLength(p.perspective) - if err != nil { - return nil, err - } - if p.stopWaiting != nil { - p.stopWaiting.PacketNumber = publicHeader.PacketNumber - p.stopWaiting.PacketNumberLen = publicHeader.PacketNumberLen - } - - maxSize := protocol.MaxFrameAndPublicHeaderSize - publicHeaderLength - payloadFrames, err := p.composeNextPacket(maxSize, p.canSendData(encLevel)) - if err != nil { - return nil, err - } - - // Check if we have enough frames to send - if len(payloadFrames) == 0 { - return nil, nil - } - // Don't send out packets that only contain a StopWaitingFrame - if len(payloadFrames) == 1 && p.stopWaiting != nil { - return nil, nil - } - p.stopWaiting = nil - p.ackFrame = nil - - raw, err := p.writeAndSealPacket(publicHeader, payloadFrames, sealer) - if err != nil { - return nil, err - } - return &packedPacket{ - number: publicHeader.PacketNumber, - raw: raw, - frames: payloadFrames, - encryptionLevel: encLevel, - }, nil -} - -func (p *packetPacker) packCryptoPacket() (*packedPacket, error) { - encLevel, sealer := p.cryptoSetup.GetSealerForCryptoStream() - publicHeader := p.getPublicHeader(encLevel) - publicHeaderLength, err := publicHeader.GetLength(p.perspective) - if err != nil { - return nil, err - } - maxLen := protocol.MaxFrameAndPublicHeaderSize - protocol.NonForwardSecurePacketSizeReduction - publicHeaderLength - frames := []frames.Frame{p.streamFramer.PopCryptoStreamFrame(maxLen)} - raw, err := p.writeAndSealPacket(publicHeader, frames, sealer) - if err != nil { - return nil, err - } - return &packedPacket{ - number: publicHeader.PacketNumber, - raw: raw, - frames: frames, - encryptionLevel: encLevel, - }, nil -} - -func (p *packetPacker) composeNextPacket( - maxFrameSize protocol.ByteCount, - canSendStreamFrames bool, -) ([]frames.Frame, error) { - var payloadLength protocol.ByteCount - var payloadFrames []frames.Frame - - // STOP_WAITING and ACK will always fit - if p.stopWaiting != nil { - payloadFrames = append(payloadFrames, p.stopWaiting) - l, err := p.stopWaiting.MinLength(p.version) - if err != nil { - return nil, err - } - payloadLength += l - } - if p.ackFrame != nil { - payloadFrames = append(payloadFrames, p.ackFrame) - l, err := p.ackFrame.MinLength(p.version) - if err != nil { - return nil, err - } - payloadLength += l - } - - for len(p.controlFrames) > 0 { - frame := p.controlFrames[len(p.controlFrames)-1] - minLength, err := frame.MinLength(p.version) - if err != nil { - return nil, err - } - if payloadLength+minLength > maxFrameSize { - break - } - payloadFrames = append(payloadFrames, frame) - payloadLength += minLength - p.controlFrames = p.controlFrames[:len(p.controlFrames)-1] - } - - if payloadLength > maxFrameSize { - return nil, fmt.Errorf("Packet Packer BUG: packet payload (%d) too large (%d)", payloadLength, maxFrameSize) - } - - if !canSendStreamFrames { - return payloadFrames, nil - } - - // temporarily increase the maxFrameSize by 2 bytes - // this leads to a properly sized packet in all cases, since we do all the packet length calculations with StreamFrames that have the DataLen set - // however, for the last StreamFrame in the packet, we can omit the DataLen, thus saving 2 bytes and yielding a packet of exactly the correct size - maxFrameSize += 2 - - fs := p.streamFramer.PopStreamFrames(maxFrameSize - payloadLength) - if len(fs) != 0 { - fs[len(fs)-1].DataLenPresent = false - } - - // TODO: Simplify - for _, f := range fs { - payloadFrames = append(payloadFrames, f) - } - - for b := p.streamFramer.PopBlockedFrame(); b != nil; b = p.streamFramer.PopBlockedFrame() { - p.controlFrames = append(p.controlFrames, b) - } - - return payloadFrames, nil -} - -func (p *packetPacker) QueueControlFrame(frame frames.Frame) { - switch f := frame.(type) { - case *frames.StopWaitingFrame: - p.stopWaiting = f - case *frames.AckFrame: - p.ackFrame = f - default: - p.controlFrames = append(p.controlFrames, f) - } -} - -func (p *packetPacker) getPublicHeader(encLevel protocol.EncryptionLevel) *PublicHeader { - pnum := p.packetNumberGenerator.Peek() - packetNumberLen := protocol.GetPacketNumberLengthForPublicHeader(pnum, p.leastUnacked) - publicHeader := &PublicHeader{ - ConnectionID: p.connectionID, - PacketNumber: pnum, - PacketNumberLen: packetNumberLen, - TruncateConnectionID: p.connectionParameters.TruncateConnectionID(), - } - - if p.perspective == protocol.PerspectiveServer && encLevel == protocol.EncryptionSecure { - publicHeader.DiversificationNonce = p.cryptoSetup.DiversificationNonce() - } - if p.perspective == protocol.PerspectiveClient && encLevel != protocol.EncryptionForwardSecure { - publicHeader.VersionFlag = true - publicHeader.VersionNumber = p.version - } - - return publicHeader -} - -func (p *packetPacker) writeAndSealPacket( - publicHeader *PublicHeader, - payloadFrames []frames.Frame, - sealer handshake.Sealer, -) ([]byte, error) { - raw := getPacketBuffer() - buffer := bytes.NewBuffer(raw) - - if err := publicHeader.Write(buffer, p.version, p.perspective); err != nil { - return nil, err - } - payloadStartIndex := buffer.Len() - for _, frame := range payloadFrames { - err := frame.Write(buffer, p.version) - if err != nil { - return nil, err - } - } - if protocol.ByteCount(buffer.Len()+12) > protocol.MaxPacketSize { - return nil, errors.New("PacketPacker BUG: packet too large") - } - - raw = raw[0:buffer.Len()] - _ = sealer(raw[payloadStartIndex:payloadStartIndex], raw[payloadStartIndex:], publicHeader.PacketNumber, raw[:payloadStartIndex]) - raw = raw[0 : buffer.Len()+12] - - num := p.packetNumberGenerator.Pop() - if num != publicHeader.PacketNumber { - return nil, errors.New("packetPacker BUG: Peeked and Popped packet numbers do not match") - } - - return raw, nil -} - -func (p *packetPacker) canSendData(encLevel protocol.EncryptionLevel) bool { - if p.perspective == protocol.PerspectiveClient { - return encLevel >= protocol.EncryptionSecure - } - return encLevel == protocol.EncryptionForwardSecure -} - -func (p *packetPacker) SetLeastUnacked(leastUnacked protocol.PacketNumber) { - p.leastUnacked = leastUnacked -} diff --git a/vendor/github.com/lucas-clemente/quic-go/packet_unpacker.go b/vendor/github.com/lucas-clemente/quic-go/packet_unpacker.go deleted file mode 100644 index c92e6a53fb5..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/packet_unpacker.go +++ /dev/null @@ -1,119 +0,0 @@ -package quic - -import ( - "bytes" - "errors" - "fmt" - - "github.com/lucas-clemente/quic-go/frames" - "github.com/lucas-clemente/quic-go/protocol" - "github.com/lucas-clemente/quic-go/qerr" -) - -type unpackedPacket struct { - encryptionLevel protocol.EncryptionLevel - frames []frames.Frame -} - -type quicAEAD interface { - Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, protocol.EncryptionLevel, error) -} - -type packetUnpacker struct { - version protocol.VersionNumber - aead quicAEAD -} - -func (u *packetUnpacker) Unpack(publicHeaderBinary []byte, hdr *PublicHeader, data []byte) (*unpackedPacket, error) { - buf := getPacketBuffer() - defer putPacketBuffer(buf) - decrypted, encryptionLevel, err := u.aead.Open(buf, data, hdr.PacketNumber, publicHeaderBinary) - if err != nil { - // Wrap err in quicError so that public reset is sent by session - return nil, qerr.Error(qerr.DecryptionFailure, err.Error()) - } - r := bytes.NewReader(decrypted) - - if r.Len() == 0 { - return nil, qerr.MissingPayload - } - - fs := make([]frames.Frame, 0, 2) - - // Read all frames in the packet - for r.Len() > 0 { - typeByte, _ := r.ReadByte() - if typeByte == 0x0 { // PADDING frame - continue - } - r.UnreadByte() - - var frame frames.Frame - if typeByte&0x80 == 0x80 { - frame, err = frames.ParseStreamFrame(r) - if err != nil { - err = qerr.Error(qerr.InvalidStreamData, err.Error()) - } else { - streamID := frame.(*frames.StreamFrame).StreamID - if streamID != 1 && encryptionLevel <= protocol.EncryptionUnencrypted { - err = qerr.Error(qerr.UnencryptedStreamData, fmt.Sprintf("received unencrypted stream data on stream %d", streamID)) - } - } - } else if typeByte&0xc0 == 0x40 { - frame, err = frames.ParseAckFrame(r, u.version) - if err != nil { - err = qerr.Error(qerr.InvalidAckData, err.Error()) - } - } else if typeByte&0xe0 == 0x20 { - err = errors.New("unimplemented: CONGESTION_FEEDBACK") - } else { - switch typeByte { - case 0x01: - frame, err = frames.ParseRstStreamFrame(r) - if err != nil { - err = qerr.Error(qerr.InvalidRstStreamData, err.Error()) - } - case 0x02: - frame, err = frames.ParseConnectionCloseFrame(r) - if err != nil { - err = qerr.Error(qerr.InvalidConnectionCloseData, err.Error()) - } - case 0x03: - frame, err = frames.ParseGoawayFrame(r) - if err != nil { - err = qerr.Error(qerr.InvalidGoawayData, err.Error()) - } - case 0x04: - frame, err = frames.ParseWindowUpdateFrame(r) - if err != nil { - err = qerr.Error(qerr.InvalidWindowUpdateData, err.Error()) - } - case 0x05: - frame, err = frames.ParseBlockedFrame(r) - if err != nil { - err = qerr.Error(qerr.InvalidBlockedData, err.Error()) - } - case 0x06: - frame, err = frames.ParseStopWaitingFrame(r, hdr.PacketNumber, hdr.PacketNumberLen, u.version) - if err != nil { - err = qerr.Error(qerr.InvalidStopWaitingData, err.Error()) - } - case 0x07: - frame, err = frames.ParsePingFrame(r) - default: - err = qerr.Error(qerr.InvalidFrameData, fmt.Sprintf("unknown type byte 0x%x", typeByte)) - } - } - if err != nil { - return nil, err - } - if frame != nil { - fs = append(fs, frame) - } - } - - return &unpackedPacket{ - encryptionLevel: encryptionLevel, - frames: fs, - }, nil -} diff --git a/vendor/github.com/lucas-clemente/quic-go/protocol/encryption_level.go b/vendor/github.com/lucas-clemente/quic-go/protocol/encryption_level.go deleted file mode 100644 index 19480b12717..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/protocol/encryption_level.go +++ /dev/null @@ -1,28 +0,0 @@ -package protocol - -// EncryptionLevel is the encryption level -// Default value is Unencrypted -type EncryptionLevel int - -const ( - // EncryptionUnspecified is a not specified encryption level - EncryptionUnspecified EncryptionLevel = iota - // EncryptionUnencrypted is not encrypted - EncryptionUnencrypted - // EncryptionSecure is encrypted, but not forward secure - EncryptionSecure - // EncryptionForwardSecure is forward secure - EncryptionForwardSecure -) - -func (e EncryptionLevel) String() string { - switch e { - case EncryptionUnencrypted: - return "unencrypted" - case EncryptionSecure: - return "encrypted (not forward-secure)" - case EncryptionForwardSecure: - return "forward-secure" - } - return "unknown" -} diff --git a/vendor/github.com/lucas-clemente/quic-go/protocol/packet_number.go b/vendor/github.com/lucas-clemente/quic-go/protocol/packet_number.go deleted file mode 100644 index c4f468ad5fc..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/protocol/packet_number.go +++ /dev/null @@ -1,56 +0,0 @@ -package protocol - -// InferPacketNumber calculates the packet number based on the received packet number, its length and the last seen packet number -func InferPacketNumber(packetNumberLength PacketNumberLen, lastPacketNumber PacketNumber, wirePacketNumber PacketNumber) PacketNumber { - epochDelta := PacketNumber(1) << (uint8(packetNumberLength) * 8) - epoch := lastPacketNumber & ^(epochDelta - 1) - prevEpochBegin := epoch - epochDelta - nextEpochBegin := epoch + epochDelta - return closestTo( - lastPacketNumber+1, - epoch+wirePacketNumber, - closestTo(lastPacketNumber+1, prevEpochBegin+wirePacketNumber, nextEpochBegin+wirePacketNumber), - ) -} - -func closestTo(target, a, b PacketNumber) PacketNumber { - if delta(target, a) < delta(target, b) { - return a - } - return b -} - -func delta(a, b PacketNumber) PacketNumber { - if a < b { - return b - a - } - return a - b -} - -// GetPacketNumberLengthForPublicHeader gets the length of the packet number for the public header -// it never chooses a PacketNumberLen of 1 byte, since this is too short under certain circumstances -func GetPacketNumberLengthForPublicHeader(packetNumber PacketNumber, leastUnacked PacketNumber) PacketNumberLen { - diff := uint64(packetNumber - leastUnacked) - if diff < (2 << (uint8(PacketNumberLen2)*8 - 2)) { - return PacketNumberLen2 - } - if diff < (2 << (uint8(PacketNumberLen4)*8 - 2)) { - return PacketNumberLen4 - } - // we do not check if there are less than 2^46 packets in flight, since flow control and congestion control will limit this number *a lot* sooner - return PacketNumberLen6 -} - -// GetPacketNumberLength gets the minimum length needed to fully represent the packet number -func GetPacketNumberLength(packetNumber PacketNumber) PacketNumberLen { - if packetNumber < (1 << (uint8(PacketNumberLen1) * 8)) { - return PacketNumberLen1 - } - if packetNumber < (1 << (uint8(PacketNumberLen2) * 8)) { - return PacketNumberLen2 - } - if packetNumber < (1 << (uint8(PacketNumberLen4) * 8)) { - return PacketNumberLen4 - } - return PacketNumberLen6 -} diff --git a/vendor/github.com/lucas-clemente/quic-go/protocol/perspective.go b/vendor/github.com/lucas-clemente/quic-go/protocol/perspective.go deleted file mode 100644 index 6aa3b70c3b9..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/protocol/perspective.go +++ /dev/null @@ -1,10 +0,0 @@ -package protocol - -// Perspective determines if we're acting as a server or a client -type Perspective int - -// the perspectives -const ( - PerspectiveServer Perspective = 1 - PerspectiveClient Perspective = 2 -) diff --git a/vendor/github.com/lucas-clemente/quic-go/protocol/protocol.go b/vendor/github.com/lucas-clemente/quic-go/protocol/protocol.go deleted file mode 100644 index cf9cf056f96..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/protocol/protocol.go +++ /dev/null @@ -1,59 +0,0 @@ -package protocol - -import "math" - -// A PacketNumber in QUIC -type PacketNumber uint64 - -// PacketNumberLen is the length of the packet number in bytes -type PacketNumberLen uint8 - -const ( - // PacketNumberLenInvalid is the default value and not a valid length for a packet number - PacketNumberLenInvalid PacketNumberLen = 0 - // PacketNumberLen1 is a packet number length of 1 byte - PacketNumberLen1 PacketNumberLen = 1 - // PacketNumberLen2 is a packet number length of 2 bytes - PacketNumberLen2 PacketNumberLen = 2 - // PacketNumberLen4 is a packet number length of 4 bytes - PacketNumberLen4 PacketNumberLen = 4 - // PacketNumberLen6 is a packet number length of 6 bytes - PacketNumberLen6 PacketNumberLen = 6 -) - -// A ConnectionID in QUIC -type ConnectionID uint64 - -// A StreamID in QUIC -type StreamID uint32 - -// A ByteCount in QUIC -type ByteCount uint64 - -// MaxByteCount is the maximum value of a ByteCount -const MaxByteCount = ByteCount(math.MaxUint64) - -// MaxReceivePacketSize maximum packet size of any QUIC packet, based on -// ethernet's max size, minus the IP and UDP headers. IPv6 has a 40 byte header, -// UDP adds an additional 8 bytes. This is a total overhead of 48 bytes. -// Ethernet's max packet size is 1500 bytes, 1500 - 48 = 1452. -const MaxReceivePacketSize ByteCount = 1452 - -// DefaultTCPMSS is the default maximum packet size used in the Linux TCP implementation. -// Used in QUIC for congestion window computations in bytes. -const DefaultTCPMSS ByteCount = 1460 - -// InitialStreamFlowControlWindow is the initial stream-level flow control window for sending -const InitialStreamFlowControlWindow ByteCount = (1 << 14) // 16 kB - -// InitialConnectionFlowControlWindow is the initial connection-level flow control window for sending -const InitialConnectionFlowControlWindow ByteCount = (1 << 14) // 16 kB - -// ClientHelloMinimumSize is the minimum size the server expects an inchoate CHLO to have. -const ClientHelloMinimumSize = 1024 - -// MaxClientHellos is the maximum number of times we'll send a client hello -// The value 3 accounts for: -// * one failure due to an incorrect or missing source-address token -// * one failure due the server's certificate chain being unavailible and the server being unwilling to send it without a valid source-address token -const MaxClientHellos = 3 diff --git a/vendor/github.com/lucas-clemente/quic-go/protocol/server_parameters.go b/vendor/github.com/lucas-clemente/quic-go/protocol/server_parameters.go deleted file mode 100644 index 8e632cc1350..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/protocol/server_parameters.go +++ /dev/null @@ -1,139 +0,0 @@ -package protocol - -import "time" - -// MaxPacketSize is the maximum packet size, including the public header, that we use for sending packets -// This is the value used by Chromium for a QUIC packet sent using IPv6 (for IPv4 it would be 1370) -const MaxPacketSize ByteCount = 1350 - -// MaxFrameAndPublicHeaderSize is the maximum size of a QUIC frame plus PublicHeader -const MaxFrameAndPublicHeaderSize = MaxPacketSize - 12 /*crypto signature*/ - -// NonForwardSecurePacketSizeReduction is the number of bytes a non forward-secure packet has to be smaller than a forward-secure packet -// This makes sure that those packets can always be retransmitted without splitting the contained StreamFrames -const NonForwardSecurePacketSizeReduction = 50 - -// DefaultMaxCongestionWindow is the default for the max congestion window -const DefaultMaxCongestionWindow = 1000 - -// InitialCongestionWindow is the initial congestion window in QUIC packets -const InitialCongestionWindow = 32 - -// MaxUndecryptablePackets limits the number of undecryptable packets that a -// session queues for later until it sends a public reset. -const MaxUndecryptablePackets = 10 - -// PublicResetTimeout is the time to wait before sending a Public Reset when receiving too many undecryptable packets during the handshake -// This timeout allows the Go scheduler to switch to the Go rountine that reads the crypto stream and to escalate the crypto -const PublicResetTimeout = 500 * time.Millisecond - -// AckSendDelay is the maximum delay that can be applied to an ACK for a retransmittable packet -// This is the value Chromium is using -const AckSendDelay = 25 * time.Millisecond - -// ReceiveStreamFlowControlWindow is the stream-level flow control window for receiving data -// This is the value that Google servers are using -const ReceiveStreamFlowControlWindow ByteCount = (1 << 10) * 32 // 32 kB - -// ReceiveConnectionFlowControlWindow is the connection-level flow control window for receiving data -// This is the value that Google servers are using -const ReceiveConnectionFlowControlWindow ByteCount = (1 << 10) * 48 // 48 kB - -// DefaultMaxReceiveStreamFlowControlWindowServer is the default maximum stream-level flow control window for receiving data, for the server -// This is the value that Google servers are using -const DefaultMaxReceiveStreamFlowControlWindowServer ByteCount = 1 * (1 << 20) // 1 MB - -// DefaultMaxReceiveConnectionFlowControlWindowServer is the default connection-level flow control window for receiving data, for the server -// This is the value that Google servers are using -const DefaultMaxReceiveConnectionFlowControlWindowServer ByteCount = 1.5 * (1 << 20) // 1.5 MB - -// DefaultMaxReceiveStreamFlowControlWindowClient is the default maximum stream-level flow control window for receiving data, for the client -// This is the value that Chromium is using -const DefaultMaxReceiveStreamFlowControlWindowClient ByteCount = 6 * (1 << 20) // 6 MB - -// DefaultMaxReceiveConnectionFlowControlWindowClient is the default connection-level flow control window for receiving data, for the client -// This is the value that Google servers are using -const DefaultMaxReceiveConnectionFlowControlWindowClient ByteCount = 15 * (1 << 20) // 15 MB - -// ConnectionFlowControlMultiplier determines how much larger the connection flow control windows needs to be relative to any stream's flow control window -// This is the value that Chromium is using -const ConnectionFlowControlMultiplier = 1.5 - -// MaxStreamsPerConnection is the maximum value accepted for the number of streams per connection -const MaxStreamsPerConnection = 100 - -// MaxIncomingDynamicStreamsPerConnection is the maximum value accepted for the incoming number of dynamic streams per connection -const MaxIncomingDynamicStreamsPerConnection = 100 - -// MaxStreamsMultiplier is the slack the client is allowed for the maximum number of streams per connection, needed e.g. when packets are out of order or dropped. The minimum of this procentual increase and the absolute increment specified by MaxStreamsMinimumIncrement is used. -const MaxStreamsMultiplier = 1.1 - -// MaxStreamsMinimumIncrement is the slack the client is allowed for the maximum number of streams per connection, needed e.g. when packets are out of order or dropped. The minimum of this absolute increment and the procentual increase specified by MaxStreamsMultiplier is used. -const MaxStreamsMinimumIncrement = 10 - -// MaxNewStreamIDDelta is the maximum difference between and a newly opened Stream and the highest StreamID that a client has ever opened -// note that the number of streams is half this value, since the client can only open streams with open StreamID -const MaxNewStreamIDDelta = 4 * MaxStreamsPerConnection - -// MaxSessionUnprocessedPackets is the max number of packets stored in each session that are not yet processed. -const MaxSessionUnprocessedPackets = DefaultMaxCongestionWindow - -// SkipPacketAveragePeriodLength is the average period length in which one packet number is skipped to prevent an Optimistic ACK attack -const SkipPacketAveragePeriodLength PacketNumber = 500 - -// MaxTrackedSkippedPackets is the maximum number of skipped packet numbers the SentPacketHandler keep track of for Optimistic ACK attack mitigation -const MaxTrackedSkippedPackets = 10 - -// STKExpiryTime is the valid time of a source address token -const STKExpiryTime = 24 * time.Hour - -// MaxTrackedSentPackets is maximum number of sent packets saved for either later retransmission or entropy calculation -const MaxTrackedSentPackets = 2 * DefaultMaxCongestionWindow - -// MaxTrackedReceivedPackets is the maximum number of received packets saved for doing the entropy calculations -const MaxTrackedReceivedPackets = 2 * DefaultMaxCongestionWindow - -// MaxTrackedReceivedAckRanges is the maximum number of ACK ranges tracked -const MaxTrackedReceivedAckRanges = DefaultMaxCongestionWindow - -// MaxPacketsReceivedBeforeAckSend is the number of packets that can be received before an ACK frame is sent -const MaxPacketsReceivedBeforeAckSend = 20 - -// RetransmittablePacketsBeforeAck is the number of retransmittable that an ACK is sent for -const RetransmittablePacketsBeforeAck = 2 - -// MaxStreamFrameSorterGaps is the maximum number of gaps between received StreamFrames -// prevents DoS attacks against the streamFrameSorter -const MaxStreamFrameSorterGaps = 1000 - -// CryptoMaxParams is the upper limit for the number of parameters in a crypto message. -// Value taken from Chrome. -const CryptoMaxParams = 128 - -// CryptoParameterMaxLength is the upper limit for the length of a parameter in a crypto message. -const CryptoParameterMaxLength = 4000 - -// EphermalKeyLifetime is the lifetime of the ephermal key during the handshake, see handshake.getEphermalKEX. -const EphermalKeyLifetime = time.Minute - -// InitialIdleTimeout is the timeout before the handshake succeeds. -const InitialIdleTimeout = 5 * time.Second - -// DefaultIdleTimeout is the default idle timeout, for the server -const DefaultIdleTimeout = 30 * time.Second - -// MaxIdleTimeoutServer is the maximum idle timeout that can be negotiated, for the server -const MaxIdleTimeoutServer = 1 * time.Minute - -// MaxIdleTimeoutClient is the idle timeout that the client suggests to the server -const MaxIdleTimeoutClient = 2 * time.Minute - -// DefaultHandshakeTimeout is the default timeout for a connection until the crypto handshake succeeds. -const DefaultHandshakeTimeout = 10 * time.Second - -// ClosedSessionDeleteTimeout the server ignores packets arriving on a connection that is already closed -// after this time all information about the old connection will be deleted -const ClosedSessionDeleteTimeout = time.Minute - -// NumCachedCertificates is the number of cached compressed certificate chains, each taking ~1K space -const NumCachedCertificates = 128 diff --git a/vendor/github.com/lucas-clemente/quic-go/protocol/version.go b/vendor/github.com/lucas-clemente/quic-go/protocol/version.go deleted file mode 100644 index 388162e27a2..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/protocol/version.go +++ /dev/null @@ -1,55 +0,0 @@ -package protocol - -// VersionNumber is a version number as int -type VersionNumber int - -// The version numbers, making grepping easier -const ( - Version35 VersionNumber = 35 + iota - Version36 - Version37 - VersionWhatever VersionNumber = 0 // for when the version doesn't matter - VersionUnsupported VersionNumber = -1 -) - -// SupportedVersions lists the versions that the server supports -// must be in sorted descending order -var SupportedVersions = []VersionNumber{ - Version37, Version36, Version35, -} - -// VersionNumberToTag maps version numbers ('32') to tags ('Q032') -func VersionNumberToTag(vn VersionNumber) uint32 { - v := uint32(vn) - return 'Q' + ((v/100%10)+'0')<<8 + ((v/10%10)+'0')<<16 + ((v%10)+'0')<<24 -} - -// VersionTagToNumber is built from VersionNumberToTag in init() -func VersionTagToNumber(v uint32) VersionNumber { - return VersionNumber(((v>>8)&0xff-'0')*100 + ((v>>16)&0xff-'0')*10 + ((v>>24)&0xff - '0')) -} - -// IsSupportedVersion returns true if the server supports this version -func IsSupportedVersion(supported []VersionNumber, v VersionNumber) bool { - for _, t := range supported { - if t == v { - return true - } - } - return false -} - -// ChooseSupportedVersion finds the best version in the overlap of ours and theirs -// ours is a slice of versions that we support, sorted by our preference (descending) -// theirs is a slice of versions offered by the peer. The order does not matter -// if no suitable version is found, it returns VersionUnsupported -func ChooseSupportedVersion(ours, theirs []VersionNumber) VersionNumber { - for _, ourVer := range ours { - for _, theirVer := range theirs { - if ourVer == theirVer { - return ourVer - } - } - } - return VersionUnsupported -} diff --git a/vendor/github.com/lucas-clemente/quic-go/public_header.go b/vendor/github.com/lucas-clemente/quic-go/public_header.go deleted file mode 100644 index 59ddc6cb433..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/public_header.go +++ /dev/null @@ -1,261 +0,0 @@ -package quic - -import ( - "bytes" - "errors" - - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" - "github.com/lucas-clemente/quic-go/qerr" -) - -var ( - errPacketNumberLenNotSet = errors.New("PublicHeader: PacketNumberLen not set") - errResetAndVersionFlagSet = errors.New("PublicHeader: Reset Flag and Version Flag should not be set at the same time") - errReceivedTruncatedConnectionID = qerr.Error(qerr.InvalidPacketHeader, "receiving packets with truncated ConnectionID is not supported") - errInvalidConnectionID = qerr.Error(qerr.InvalidPacketHeader, "connection ID cannot be 0") - errGetLengthNotForVersionNegotiation = errors.New("PublicHeader: GetLength cannot be called for VersionNegotiation packets") -) - -// The PublicHeader of a QUIC packet. Warning: This struct should not be considered stable and will change soon. -type PublicHeader struct { - Raw []byte - ConnectionID protocol.ConnectionID - VersionFlag bool - ResetFlag bool - TruncateConnectionID bool - PacketNumberLen protocol.PacketNumberLen - PacketNumber protocol.PacketNumber - VersionNumber protocol.VersionNumber // VersionNumber sent by the client - SupportedVersions []protocol.VersionNumber // VersionNumbers sent by the server - DiversificationNonce []byte -} - -// Write writes a public header. Warning: This API should not be considered stable and will change soon. -func (h *PublicHeader) Write(b *bytes.Buffer, version protocol.VersionNumber, pers protocol.Perspective) error { - publicFlagByte := uint8(0x00) - - if h.VersionFlag && h.ResetFlag { - return errResetAndVersionFlagSet - } - - if h.VersionFlag { - publicFlagByte |= 0x01 - } - if h.ResetFlag { - publicFlagByte |= 0x02 - } - if !h.TruncateConnectionID { - publicFlagByte |= 0x08 - } - - if len(h.DiversificationNonce) > 0 { - if len(h.DiversificationNonce) != 32 { - return errors.New("invalid diversification nonce length") - } - publicFlagByte |= 0x04 - } - - // only set PacketNumberLen bits if a packet number will be written - if h.hasPacketNumber(pers) { - switch h.PacketNumberLen { - case protocol.PacketNumberLen1: - publicFlagByte |= 0x00 - case protocol.PacketNumberLen2: - publicFlagByte |= 0x10 - case protocol.PacketNumberLen4: - publicFlagByte |= 0x20 - case protocol.PacketNumberLen6: - publicFlagByte |= 0x30 - } - } - - b.WriteByte(publicFlagByte) - - if !h.TruncateConnectionID { - utils.WriteUint64(b, uint64(h.ConnectionID)) - } - - if h.VersionFlag && pers == protocol.PerspectiveClient { - utils.WriteUint32(b, protocol.VersionNumberToTag(h.VersionNumber)) - } - - if len(h.DiversificationNonce) > 0 { - b.Write(h.DiversificationNonce) - } - - // if we're a server, and the VersionFlag is set, we must not include anything else in the packet - if !h.hasPacketNumber(pers) { - return nil - } - - if h.PacketNumberLen != protocol.PacketNumberLen1 && h.PacketNumberLen != protocol.PacketNumberLen2 && h.PacketNumberLen != protocol.PacketNumberLen4 && h.PacketNumberLen != protocol.PacketNumberLen6 { - return errPacketNumberLenNotSet - } - - switch h.PacketNumberLen { - case protocol.PacketNumberLen1: - b.WriteByte(uint8(h.PacketNumber)) - case protocol.PacketNumberLen2: - utils.WriteUint16(b, uint16(h.PacketNumber)) - case protocol.PacketNumberLen4: - utils.WriteUint32(b, uint32(h.PacketNumber)) - case protocol.PacketNumberLen6: - utils.WriteUint48(b, uint64(h.PacketNumber)) - default: - return errPacketNumberLenNotSet - } - - return nil -} - -// ParsePublicHeader parses a QUIC packet's public header. -// The packetSentBy is the perspective of the peer that sent this PublicHeader, i.e. if we're the server, packetSentBy should be PerspectiveClient. -// Warning: This API should not be considered stable and will change soon. -func ParsePublicHeader(b *bytes.Reader, packetSentBy protocol.Perspective) (*PublicHeader, error) { - header := &PublicHeader{} - - // First byte - publicFlagByte, err := b.ReadByte() - if err != nil { - return nil, err - } - header.VersionFlag = publicFlagByte&0x01 > 0 - header.ResetFlag = publicFlagByte&0x02 > 0 - - // TODO: activate this check once Chrome sends the correct value - // see https://github.com/lucas-clemente/quic-go/issues/232 - // if publicFlagByte&0x04 > 0 { - // return nil, errors.New("diversification nonces should only be sent by servers") - // } - - header.TruncateConnectionID = publicFlagByte&0x08 == 0 - if header.TruncateConnectionID && packetSentBy == protocol.PerspectiveClient { - return nil, errReceivedTruncatedConnectionID - } - - if header.hasPacketNumber(packetSentBy) { - switch publicFlagByte & 0x30 { - case 0x30: - header.PacketNumberLen = protocol.PacketNumberLen6 - case 0x20: - header.PacketNumberLen = protocol.PacketNumberLen4 - case 0x10: - header.PacketNumberLen = protocol.PacketNumberLen2 - case 0x00: - header.PacketNumberLen = protocol.PacketNumberLen1 - } - } - - // Connection ID - if !header.TruncateConnectionID { - var connID uint64 - connID, err = utils.ReadUint64(b) - if err != nil { - return nil, err - } - header.ConnectionID = protocol.ConnectionID(connID) - if header.ConnectionID == 0 { - return nil, errInvalidConnectionID - } - } - - if packetSentBy == protocol.PerspectiveServer && publicFlagByte&0x04 > 0 { - // TODO: remove the if once the Google servers send the correct value - // assume that a packet doesn't contain a diversification nonce if the version flag or the reset flag is set, no matter what the public flag says - // see https://github.com/lucas-clemente/quic-go/issues/232 - if !header.VersionFlag && !header.ResetFlag { - header.DiversificationNonce = make([]byte, 32) - // this Read can never return an EOF for a valid packet, since the diversification nonce is followed by the packet number - _, err = b.Read(header.DiversificationNonce) - if err != nil { - return nil, err - } - } - } - - // Version (optional) - if !header.ResetFlag { - if header.VersionFlag { - if packetSentBy == protocol.PerspectiveClient { - var versionTag uint32 - versionTag, err = utils.ReadUint32(b) - if err != nil { - return nil, err - } - header.VersionNumber = protocol.VersionTagToNumber(versionTag) - } else { // parse the version negotiaton packet - if b.Len()%4 != 0 { - return nil, qerr.InvalidVersionNegotiationPacket - } - header.SupportedVersions = make([]protocol.VersionNumber, 0) - for { - var versionTag uint32 - versionTag, err = utils.ReadUint32(b) - if err != nil { - break - } - v := protocol.VersionTagToNumber(versionTag) - header.SupportedVersions = append(header.SupportedVersions, v) - } - } - } - } - - // Packet number - if header.hasPacketNumber(packetSentBy) { - packetNumber, err := utils.ReadUintN(b, uint8(header.PacketNumberLen)) - if err != nil { - return nil, err - } - header.PacketNumber = protocol.PacketNumber(packetNumber) - } - - return header, nil -} - -// GetLength gets the length of the publicHeader in bytes. -// It can only be called for regular packets. -func (h *PublicHeader) GetLength(pers protocol.Perspective) (protocol.ByteCount, error) { - if h.VersionFlag && h.ResetFlag { - return 0, errResetAndVersionFlagSet - } - - if h.VersionFlag && pers == protocol.PerspectiveServer { - return 0, errGetLengthNotForVersionNegotiation - } - - length := protocol.ByteCount(1) // 1 byte for public flags - - if h.hasPacketNumber(pers) { - if h.PacketNumberLen != protocol.PacketNumberLen1 && h.PacketNumberLen != protocol.PacketNumberLen2 && h.PacketNumberLen != protocol.PacketNumberLen4 && h.PacketNumberLen != protocol.PacketNumberLen6 { - return 0, errPacketNumberLenNotSet - } - length += protocol.ByteCount(h.PacketNumberLen) - } - - if !h.TruncateConnectionID { - length += 8 // 8 bytes for the connection ID - } - - // Version Number in packets sent by the client - if h.VersionFlag { - length += 4 - } - - length += protocol.ByteCount(len(h.DiversificationNonce)) - - return length, nil -} - -// hasPacketNumber determines if this PublicHeader will contain a packet number -// this depends on the ResetFlag, the VersionFlag and who sent the packet -func (h *PublicHeader) hasPacketNumber(packetSentBy protocol.Perspective) bool { - if h.ResetFlag { - return false - } - if h.VersionFlag && packetSentBy == protocol.PerspectiveServer { - return false - } - return true -} diff --git a/vendor/github.com/lucas-clemente/quic-go/public_reset.go b/vendor/github.com/lucas-clemente/quic-go/public_reset.go deleted file mode 100644 index 958db9cc4e9..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/public_reset.go +++ /dev/null @@ -1,62 +0,0 @@ -package quic - -import ( - "bytes" - "encoding/binary" - "errors" - - "github.com/lucas-clemente/quic-go/handshake" - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" -) - -type publicReset struct { - rejectedPacketNumber protocol.PacketNumber - nonce uint64 -} - -func writePublicReset(connectionID protocol.ConnectionID, rejectedPacketNumber protocol.PacketNumber, nonceProof uint64) []byte { - b := &bytes.Buffer{} - b.WriteByte(0x0a) - utils.WriteUint64(b, uint64(connectionID)) - utils.WriteUint32(b, uint32(handshake.TagPRST)) - utils.WriteUint32(b, 2) - utils.WriteUint32(b, uint32(handshake.TagRNON)) - utils.WriteUint32(b, 8) - utils.WriteUint32(b, uint32(handshake.TagRSEQ)) - utils.WriteUint32(b, 16) - utils.WriteUint64(b, nonceProof) - utils.WriteUint64(b, uint64(rejectedPacketNumber)) - return b.Bytes() -} - -func parsePublicReset(r *bytes.Reader) (*publicReset, error) { - pr := publicReset{} - msg, err := handshake.ParseHandshakeMessage(r) - if err != nil { - return nil, err - } - if msg.Tag != handshake.TagPRST { - return nil, errors.New("wrong public reset tag") - } - - rseq, ok := msg.Data[handshake.TagRSEQ] - if !ok { - return nil, errors.New("RSEQ missing") - } - if len(rseq) != 8 { - return nil, errors.New("invalid RSEQ tag") - } - pr.rejectedPacketNumber = protocol.PacketNumber(binary.LittleEndian.Uint64(rseq)) - - rnon, ok := msg.Data[handshake.TagRNON] - if !ok { - return nil, errors.New("RNON missing") - } - if len(rnon) != 8 { - return nil, errors.New("invalid RNON tag") - } - pr.nonce = binary.LittleEndian.Uint64(rnon) - - return &pr, nil -} diff --git a/vendor/github.com/lucas-clemente/quic-go/qerr/error_codes.go b/vendor/github.com/lucas-clemente/quic-go/qerr/error_codes.go deleted file mode 100644 index f3e6dd9cbc4..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/qerr/error_codes.go +++ /dev/null @@ -1,193 +0,0 @@ -package qerr - -// The error codes defined by QUIC -// Remember to run `go generate ./...` whenever the error codes change. -//go:generate stringer -type=ErrorCode -const ( - InternalError ErrorCode = 1 - // There were data frames after the a fin or reset. - StreamDataAfterTermination ErrorCode = 2 - // Control frame is malformed. - InvalidPacketHeader ErrorCode = 3 - // Frame data is malformed. - InvalidFrameData ErrorCode = 4 - // The packet contained no payload. - MissingPayload ErrorCode = 48 - // FEC data is malformed. - InvalidFecData ErrorCode = 5 - // STREAM frame data is malformed. - InvalidStreamData ErrorCode = 46 - // STREAM frame data overlaps with buffered data. - OverlappingStreamData ErrorCode = 87 - // Received STREAM frame data is not encrypted. - UnencryptedStreamData ErrorCode = 61 - // Attempt to send unencrypted STREAM frame. - AttemptToSendUnencryptedStreamData ErrorCode = 88 - // FEC frame data is not encrypted. - UnencryptedFecData ErrorCode = 77 - // RST_STREAM frame data is malformed. - InvalidRstStreamData ErrorCode = 6 - // CONNECTION_CLOSE frame data is malformed. - InvalidConnectionCloseData ErrorCode = 7 - // GOAWAY frame data is malformed. - InvalidGoawayData ErrorCode = 8 - // WINDOW_UPDATE frame data is malformed. - InvalidWindowUpdateData ErrorCode = 57 - // BLOCKED frame data is malformed. - InvalidBlockedData ErrorCode = 58 - // STOP_WAITING frame data is malformed. - InvalidStopWaitingData ErrorCode = 60 - // PATH_CLOSE frame data is malformed. - InvalidPathCloseData ErrorCode = 78 - // ACK frame data is malformed. - InvalidAckData ErrorCode = 9 - - // Version negotiation packet is malformed. - InvalidVersionNegotiationPacket ErrorCode = 10 - // Public RST packet is malformed. - InvalidPublicRstPacket ErrorCode = 11 - // There was an error decrypting. - DecryptionFailure ErrorCode = 12 - // There was an error encrypting. - EncryptionFailure ErrorCode = 13 - // The packet exceeded kMaxPacketSize. - PacketTooLarge ErrorCode = 14 - // The peer is going away. May be a client or server. - PeerGoingAway ErrorCode = 16 - // A stream ID was invalid. - InvalidStreamID ErrorCode = 17 - // A priority was invalid. - InvalidPriority ErrorCode = 49 - // Too many streams already open. - TooManyOpenStreams ErrorCode = 18 - // The peer created too many available streams. - TooManyAvailableStreams ErrorCode = 76 - // Received public reset for this connection. - PublicReset ErrorCode = 19 - // Invalid protocol version. - InvalidVersion ErrorCode = 20 - - // The Header ID for a stream was too far from the previous. - InvalidHeaderID ErrorCode = 22 - // Negotiable parameter received during handshake had invalid value. - InvalidNegotiatedValue ErrorCode = 23 - // There was an error decompressing data. - DecompressionFailure ErrorCode = 24 - // The connection timed out due to no network activity. - NetworkIdleTimeout ErrorCode = 25 - // The connection timed out waiting for the handshake to complete. - HandshakeTimeout ErrorCode = 67 - // There was an error encountered migrating addresses. - ErrorMigratingAddress ErrorCode = 26 - // There was an error encountered migrating port only. - ErrorMigratingPort ErrorCode = 86 - // There was an error while writing to the socket. - PacketWriteError ErrorCode = 27 - // There was an error while reading from the socket. - PacketReadError ErrorCode = 51 - // We received a STREAM_FRAME with no data and no fin flag set. - EmptyStreamFrameNoFin ErrorCode = 50 - // We received invalid data on the headers stream. - InvalidHeadersStreamData ErrorCode = 56 - // Invalid data on the headers stream received because of decompression - // failure. - HeadersStreamDataDecompressFailure ErrorCode = 97 - // The peer received too much data, violating flow control. - FlowControlReceivedTooMuchData ErrorCode = 59 - // The peer sent too much data, violating flow control. - FlowControlSentTooMuchData ErrorCode = 63 - // The peer received an invalid flow control window. - FlowControlInvalidWindow ErrorCode = 64 - // The connection has been IP pooled into an existing connection. - ConnectionIPPooled ErrorCode = 62 - // The connection has too many outstanding sent packets. - TooManyOutstandingSentPackets ErrorCode = 68 - // The connection has too many outstanding received packets. - TooManyOutstandingReceivedPackets ErrorCode = 69 - // The quic connection has been cancelled. - ConnectionCancelled ErrorCode = 70 - // Disabled QUIC because of high packet loss rate. - BadPacketLossRate ErrorCode = 71 - // Disabled QUIC because of too many PUBLIC_RESETs post handshake. - PublicResetsPostHandshake ErrorCode = 73 - // Disabled QUIC because of too many timeouts with streams open. - TimeoutsWithOpenStreams ErrorCode = 74 - // Closed because we failed to serialize a packet. - FailedToSerializePacket ErrorCode = 75 - // QUIC timed out after too many RTOs. - TooManyRtos ErrorCode = 85 - - // Crypto errors. - - // Hanshake failed. - HandshakeFailed ErrorCode = 28 - // Handshake message contained out of order tags. - CryptoTagsOutOfOrder ErrorCode = 29 - // Handshake message contained too many entries. - CryptoTooManyEntries ErrorCode = 30 - // Handshake message contained an invalid value length. - CryptoInvalidValueLength ErrorCode = 31 - // A crypto message was received after the handshake was complete. - CryptoMessageAfterHandshakeComplete ErrorCode = 32 - // A crypto message was received with an illegal message tag. - InvalidCryptoMessageType ErrorCode = 33 - // A crypto message was received with an illegal parameter. - InvalidCryptoMessageParameter ErrorCode = 34 - // An invalid channel id signature was supplied. - InvalidChannelIDSignature ErrorCode = 52 - // A crypto message was received with a mandatory parameter missing. - CryptoMessageParameterNotFound ErrorCode = 35 - // A crypto message was received with a parameter that has no overlap - // with the local parameter. - CryptoMessageParameterNoOverlap ErrorCode = 36 - // A crypto message was received that contained a parameter with too few - // values. - CryptoMessageIndexNotFound ErrorCode = 37 - // An internal error occurred in crypto processing. - CryptoInternalError ErrorCode = 38 - // A crypto handshake message specified an unsupported version. - CryptoVersionNotSupported ErrorCode = 39 - // A crypto handshake message resulted in a stateless reject. - CryptoHandshakeStatelessReject ErrorCode = 72 - // There was no intersection between the crypto primitives supported by the - // peer and ourselves. - CryptoNoSupport ErrorCode = 40 - // The server rejected our client hello messages too many times. - CryptoTooManyRejects ErrorCode = 41 - // The client rejected the server's certificate chain or signature. - ProofInvalid ErrorCode = 42 - // A crypto message was received with a duplicate tag. - CryptoDuplicateTag ErrorCode = 43 - // A crypto message was received with the wrong encryption level (i.e. it - // should have been encrypted but was not.) - CryptoEncryptionLevelIncorrect ErrorCode = 44 - // The server config for a server has expired. - CryptoServerConfigExpired ErrorCode = 45 - // We failed to setup the symmetric keys for a connection. - CryptoSymmetricKeySetupFailed ErrorCode = 53 - // A handshake message arrived, but we are still validating the - // previous handshake message. - CryptoMessageWhileValidatingClientHello ErrorCode = 54 - // A server config update arrived before the handshake is complete. - CryptoUpdateBeforeHandshakeComplete ErrorCode = 65 - // This connection involved a version negotiation which appears to have been - // tampered with. - VersionNegotiationMismatch ErrorCode = 55 - - // Multipath is not enabled, but a packet with multipath flag on is received. - BadMultipathFlag ErrorCode = 79 - - // IP address changed causing connection close. - IPAddressChanged ErrorCode = 80 - - // Connection migration errors. - // Network changed, but connection had no migratable streams. - ConnectionMigrationNoMigratableStreams ErrorCode = 81 - // Connection changed networks too many times. - ConnectionMigrationTooManyChanges ErrorCode = 82 - // Connection migration was attempted, but there was no new network to - // migrate to. - ConnectionMigrationNoNewNetwork ErrorCode = 83 - // Network changed, but connection had one or more non-migratable streams. - ConnectionMigrationNonMigratableStream ErrorCode = 84 -) diff --git a/vendor/github.com/lucas-clemente/quic-go/qerr/errorcode_string.go b/vendor/github.com/lucas-clemente/quic-go/qerr/errorcode_string.go deleted file mode 100644 index 5a8e0240e0d..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/qerr/errorcode_string.go +++ /dev/null @@ -1,47 +0,0 @@ -// Code generated by "stringer -type=ErrorCode"; DO NOT EDIT - -package qerr - -import "fmt" - -const ( - _ErrorCode_name_0 = "InternalErrorStreamDataAfterTerminationInvalidPacketHeaderInvalidFrameDataInvalidFecDataInvalidRstStreamDataInvalidConnectionCloseDataInvalidGoawayDataInvalidAckDataInvalidVersionNegotiationPacketInvalidPublicRstPacketDecryptionFailureEncryptionFailurePacketTooLarge" - _ErrorCode_name_1 = "PeerGoingAwayInvalidStreamIDTooManyOpenStreamsPublicResetInvalidVersion" - _ErrorCode_name_2 = "InvalidHeaderIDInvalidNegotiatedValueDecompressionFailureNetworkIdleTimeoutErrorMigratingAddressPacketWriteErrorHandshakeFailedCryptoTagsOutOfOrderCryptoTooManyEntriesCryptoInvalidValueLengthCryptoMessageAfterHandshakeCompleteInvalidCryptoMessageTypeInvalidCryptoMessageParameterCryptoMessageParameterNotFoundCryptoMessageParameterNoOverlapCryptoMessageIndexNotFoundCryptoInternalErrorCryptoVersionNotSupportedCryptoNoSupportCryptoTooManyRejectsProofInvalidCryptoDuplicateTagCryptoEncryptionLevelIncorrectCryptoServerConfigExpiredInvalidStreamData" - _ErrorCode_name_3 = "MissingPayloadInvalidPriorityEmptyStreamFrameNoFinPacketReadErrorInvalidChannelIDSignatureCryptoSymmetricKeySetupFailedCryptoMessageWhileValidatingClientHelloVersionNegotiationMismatchInvalidHeadersStreamDataInvalidWindowUpdateDataInvalidBlockedDataFlowControlReceivedTooMuchDataInvalidStopWaitingDataUnencryptedStreamDataConnectionIPPooledFlowControlSentTooMuchDataFlowControlInvalidWindowCryptoUpdateBeforeHandshakeComplete" - _ErrorCode_name_4 = "HandshakeTimeoutTooManyOutstandingSentPacketsTooManyOutstandingReceivedPacketsConnectionCancelledBadPacketLossRateCryptoHandshakeStatelessRejectPublicResetsPostHandshakeTimeoutsWithOpenStreamsFailedToSerializePacketTooManyAvailableStreamsUnencryptedFecDataInvalidPathCloseDataBadMultipathFlagIPAddressChangedConnectionMigrationNoMigratableStreamsConnectionMigrationTooManyChangesConnectionMigrationNoNewNetworkConnectionMigrationNonMigratableStreamTooManyRtosErrorMigratingPortOverlappingStreamDataAttemptToSendUnencryptedStreamData" - _ErrorCode_name_5 = "HeadersStreamDataDecompressFailure" -) - -var ( - _ErrorCode_index_0 = [...]uint16{0, 13, 39, 58, 74, 88, 108, 134, 151, 165, 196, 218, 235, 252, 266} - _ErrorCode_index_1 = [...]uint8{0, 13, 28, 46, 57, 71} - _ErrorCode_index_2 = [...]uint16{0, 15, 37, 57, 75, 96, 112, 127, 147, 167, 191, 226, 250, 279, 309, 340, 366, 385, 410, 425, 445, 457, 475, 505, 530, 547} - _ErrorCode_index_3 = [...]uint16{0, 14, 29, 50, 65, 90, 119, 158, 184, 208, 231, 249, 279, 301, 322, 340, 366, 390, 425} - _ErrorCode_index_4 = [...]uint16{0, 16, 45, 78, 97, 114, 144, 169, 192, 215, 238, 256, 276, 292, 308, 346, 379, 410, 448, 459, 477, 498, 532} - _ErrorCode_index_5 = [...]uint8{0, 34} -) - -func (i ErrorCode) String() string { - switch { - case 1 <= i && i <= 14: - i -= 1 - return _ErrorCode_name_0[_ErrorCode_index_0[i]:_ErrorCode_index_0[i+1]] - case 16 <= i && i <= 20: - i -= 16 - return _ErrorCode_name_1[_ErrorCode_index_1[i]:_ErrorCode_index_1[i+1]] - case 22 <= i && i <= 46: - i -= 22 - return _ErrorCode_name_2[_ErrorCode_index_2[i]:_ErrorCode_index_2[i+1]] - case 48 <= i && i <= 65: - i -= 48 - return _ErrorCode_name_3[_ErrorCode_index_3[i]:_ErrorCode_index_3[i+1]] - case 67 <= i && i <= 88: - i -= 67 - return _ErrorCode_name_4[_ErrorCode_index_4[i]:_ErrorCode_index_4[i+1]] - case i == 97: - return _ErrorCode_name_5 - default: - return fmt.Sprintf("ErrorCode(%d)", i) - } -} diff --git a/vendor/github.com/lucas-clemente/quic-go/qerr/quic_error.go b/vendor/github.com/lucas-clemente/quic-go/qerr/quic_error.go deleted file mode 100644 index 9e1956fdcbe..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/qerr/quic_error.go +++ /dev/null @@ -1,55 +0,0 @@ -package qerr - -import ( - "fmt" - - "github.com/lucas-clemente/quic-go/internal/utils" -) - -// ErrorCode can be used as a normal error without reason. -type ErrorCode uint32 - -func (e ErrorCode) Error() string { - return e.String() -} - -// A QuicError consists of an error code plus a error reason -type QuicError struct { - ErrorCode ErrorCode - ErrorMessage string -} - -// Error creates a new QuicError instance -func Error(errorCode ErrorCode, errorMessage string) *QuicError { - return &QuicError{ - ErrorCode: errorCode, - ErrorMessage: errorMessage, - } -} - -func (e *QuicError) Error() string { - return fmt.Sprintf("%s: %s", e.ErrorCode.String(), e.ErrorMessage) -} - -func (e *QuicError) Timeout() bool { - switch e.ErrorCode { - case NetworkIdleTimeout, - HandshakeTimeout, - TimeoutsWithOpenStreams: - return true - } - return false -} - -// ToQuicError converts an arbitrary error to a QuicError. It leaves QuicErrors -// unchanged, and properly handles `ErrorCode`s. -func ToQuicError(err error) *QuicError { - switch e := err.(type) { - case *QuicError: - return e - case ErrorCode: - return Error(e, "") - } - utils.Errorf("Internal error: %v", err) - return Error(InternalError, err.Error()) -} diff --git a/vendor/github.com/lucas-clemente/quic-go/server.go b/vendor/github.com/lucas-clemente/quic-go/server.go deleted file mode 100644 index 76f07bab336..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/server.go +++ /dev/null @@ -1,338 +0,0 @@ -package quic - -import ( - "bytes" - "crypto/tls" - "errors" - "net" - "sync" - "time" - - "github.com/lucas-clemente/quic-go/crypto" - "github.com/lucas-clemente/quic-go/handshake" - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" - "github.com/lucas-clemente/quic-go/qerr" -) - -// packetHandler handles packets -type packetHandler interface { - Session - handlePacket(*receivedPacket) - run() error - closeRemote(error) -} - -// A Listener of QUIC -type server struct { - tlsConf *tls.Config - config *Config - - conn net.PacketConn - - certChain crypto.CertChain - scfg *handshake.ServerConfig - - sessions map[protocol.ConnectionID]packetHandler - sessionsMutex sync.RWMutex - deleteClosedSessionsAfter time.Duration - - serverError error - sessionQueue chan Session - errorChan chan struct{} - - newSession func(conn connection, v protocol.VersionNumber, connectionID protocol.ConnectionID, sCfg *handshake.ServerConfig, tlsConf *tls.Config, config *Config) (packetHandler, <-chan handshakeEvent, error) -} - -var _ Listener = &server{} - -// ListenAddr creates a QUIC server listening on a given address. -// The listener is not active until Serve() is called. -// The tls.Config must not be nil, the quic.Config may be nil. -func ListenAddr(addr string, tlsConf *tls.Config, config *Config) (Listener, error) { - udpAddr, err := net.ResolveUDPAddr("udp", addr) - if err != nil { - return nil, err - } - conn, err := net.ListenUDP("udp", udpAddr) - if err != nil { - return nil, err - } - return Listen(conn, tlsConf, config) -} - -// Listen listens for QUIC connections on a given net.PacketConn. -// The listener is not active until Serve() is called. -// The tls.Config must not be nil, the quic.Config may be nil. -func Listen(conn net.PacketConn, tlsConf *tls.Config, config *Config) (Listener, error) { - certChain := crypto.NewCertChain(tlsConf) - kex, err := crypto.NewCurve25519KEX() - if err != nil { - return nil, err - } - scfg, err := handshake.NewServerConfig(kex, certChain) - if err != nil { - return nil, err - } - - s := &server{ - conn: conn, - tlsConf: tlsConf, - config: populateServerConfig(config), - certChain: certChain, - scfg: scfg, - sessions: map[protocol.ConnectionID]packetHandler{}, - newSession: newSession, - deleteClosedSessionsAfter: protocol.ClosedSessionDeleteTimeout, - sessionQueue: make(chan Session, 5), - errorChan: make(chan struct{}), - } - go s.serve() - return s, nil -} - -var defaultAcceptSTK = func(clientAddr net.Addr, stk *STK) bool { - if stk == nil { - return false - } - if time.Now().After(stk.sentTime.Add(protocol.STKExpiryTime)) { - return false - } - var sourceAddr string - if udpAddr, ok := clientAddr.(*net.UDPAddr); ok { - sourceAddr = udpAddr.IP.String() - } else { - sourceAddr = clientAddr.String() - } - return sourceAddr == stk.remoteAddr -} - -// populateServerConfig populates fields in the quic.Config with their default values, if none are set -// it may be called with nil -func populateServerConfig(config *Config) *Config { - if config == nil { - config = &Config{} - } - versions := config.Versions - if len(versions) == 0 { - versions = protocol.SupportedVersions - } - - vsa := defaultAcceptSTK - if config.AcceptSTK != nil { - vsa = config.AcceptSTK - } - - handshakeTimeout := protocol.DefaultHandshakeTimeout - if config.HandshakeTimeout != 0 { - handshakeTimeout = config.HandshakeTimeout - } - - maxReceiveStreamFlowControlWindow := config.MaxReceiveStreamFlowControlWindow - if maxReceiveStreamFlowControlWindow == 0 { - maxReceiveStreamFlowControlWindow = protocol.DefaultMaxReceiveStreamFlowControlWindowServer - } - maxReceiveConnectionFlowControlWindow := config.MaxReceiveConnectionFlowControlWindow - if maxReceiveConnectionFlowControlWindow == 0 { - maxReceiveConnectionFlowControlWindow = protocol.DefaultMaxReceiveConnectionFlowControlWindowServer - } - - return &Config{ - Versions: versions, - HandshakeTimeout: handshakeTimeout, - AcceptSTK: vsa, - MaxReceiveStreamFlowControlWindow: maxReceiveStreamFlowControlWindow, - MaxReceiveConnectionFlowControlWindow: maxReceiveConnectionFlowControlWindow, - } -} - -// serve listens on an existing PacketConn -func (s *server) serve() { - for { - data := getPacketBuffer() - data = data[:protocol.MaxReceivePacketSize] - // The packet size should not exceed protocol.MaxReceivePacketSize bytes - // If it does, we only read a truncated packet, which will then end up undecryptable - n, remoteAddr, err := s.conn.ReadFrom(data) - if err != nil { - s.serverError = err - close(s.errorChan) - _ = s.Close() - return - } - data = data[:n] - if err := s.handlePacket(s.conn, remoteAddr, data); err != nil { - utils.Errorf("error handling packet: %s", err.Error()) - } - } -} - -// Accept returns newly openend sessions -func (s *server) Accept() (Session, error) { - var sess Session - select { - case sess = <-s.sessionQueue: - return sess, nil - case <-s.errorChan: - return nil, s.serverError - } -} - -// Close the server -func (s *server) Close() error { - s.sessionsMutex.Lock() - for _, session := range s.sessions { - if session != nil { - s.sessionsMutex.Unlock() - _ = session.Close(nil) - s.sessionsMutex.Lock() - } - } - s.sessionsMutex.Unlock() - - if s.conn == nil { - return nil - } - return s.conn.Close() -} - -// Addr returns the server's network address -func (s *server) Addr() net.Addr { - return s.conn.LocalAddr() -} - -func (s *server) handlePacket(pconn net.PacketConn, remoteAddr net.Addr, packet []byte) error { - rcvTime := time.Now() - - r := bytes.NewReader(packet) - hdr, err := ParsePublicHeader(r, protocol.PerspectiveClient) - if err != nil { - return qerr.Error(qerr.InvalidPacketHeader, err.Error()) - } - hdr.Raw = packet[:len(packet)-r.Len()] - - s.sessionsMutex.RLock() - session, ok := s.sessions[hdr.ConnectionID] - s.sessionsMutex.RUnlock() - - // ignore all Public Reset packets - if hdr.ResetFlag { - if ok { - var pr *publicReset - pr, err = parsePublicReset(r) - if err != nil { - utils.Infof("Received a Public Reset for connection %x. An error occurred parsing the packet.") - } else { - utils.Infof("Received a Public Reset for connection %x, rejected packet number: 0x%x.", hdr.ConnectionID, pr.rejectedPacketNumber) - } - } else { - utils.Infof("Received Public Reset for unknown connection %x.", hdr.ConnectionID) - } - return nil - } - - // a session is only created once the client sent a supported version - // if we receive a packet for a connection that already has session, it's probably an old packet that was sent by the client before the version was negotiated - // it is safe to drop it - if ok && hdr.VersionFlag && !protocol.IsSupportedVersion(s.config.Versions, hdr.VersionNumber) { - return nil - } - - // Send Version Negotiation Packet if the client is speaking a different protocol version - if hdr.VersionFlag && !protocol.IsSupportedVersion(s.config.Versions, hdr.VersionNumber) { - // drop packets that are too small to be valid first packets - if len(packet) < protocol.ClientHelloMinimumSize+len(hdr.Raw) { - return errors.New("dropping small packet with unknown version") - } - utils.Infof("Client offered version %d, sending VersionNegotiationPacket", hdr.VersionNumber) - _, err = pconn.WriteTo(composeVersionNegotiation(hdr.ConnectionID, s.config.Versions), remoteAddr) - return err - } - - if !ok { - if !hdr.VersionFlag { - _, err = pconn.WriteTo(writePublicReset(hdr.ConnectionID, hdr.PacketNumber, 0), remoteAddr) - return err - } - version := hdr.VersionNumber - if !protocol.IsSupportedVersion(s.config.Versions, version) { - return errors.New("Server BUG: negotiated version not supported") - } - - utils.Infof("Serving new connection: %x, version %d from %v", hdr.ConnectionID, version, remoteAddr) - var handshakeChan <-chan handshakeEvent - session, handshakeChan, err = s.newSession( - &conn{pconn: pconn, currentAddr: remoteAddr}, - version, - hdr.ConnectionID, - s.scfg, - s.tlsConf, - s.config, - ) - if err != nil { - return err - } - s.sessionsMutex.Lock() - s.sessions[hdr.ConnectionID] = session - s.sessionsMutex.Unlock() - - go func() { - // session.run() returns as soon as the session is closed - _ = session.run() - s.removeConnection(hdr.ConnectionID) - }() - - go func() { - for { - ev := <-handshakeChan - if ev.err != nil { - return - } - if ev.encLevel == protocol.EncryptionForwardSecure { - break - } - } - s.sessionQueue <- session - }() - } - if session == nil { - // Late packet for closed session - return nil - } - session.handlePacket(&receivedPacket{ - remoteAddr: remoteAddr, - publicHeader: hdr, - data: packet[len(packet)-r.Len():], - rcvTime: rcvTime, - }) - return nil -} - -func (s *server) removeConnection(id protocol.ConnectionID) { - s.sessionsMutex.Lock() - s.sessions[id] = nil - s.sessionsMutex.Unlock() - - time.AfterFunc(s.deleteClosedSessionsAfter, func() { - s.sessionsMutex.Lock() - delete(s.sessions, id) - s.sessionsMutex.Unlock() - }) -} - -func composeVersionNegotiation(connectionID protocol.ConnectionID, versions []protocol.VersionNumber) []byte { - fullReply := &bytes.Buffer{} - responsePublicHeader := PublicHeader{ - ConnectionID: connectionID, - PacketNumber: 1, - VersionFlag: true, - } - err := responsePublicHeader.Write(fullReply, protocol.VersionWhatever, protocol.PerspectiveServer) - if err != nil { - utils.Errorf("error composing version negotiation packet: %s", err.Error()) - } - for _, v := range versions { - utils.WriteUint32(fullReply, protocol.VersionNumberToTag(v)) - } - return fullReply.Bytes() -} diff --git a/vendor/github.com/lucas-clemente/quic-go/session.go b/vendor/github.com/lucas-clemente/quic-go/session.go deleted file mode 100644 index 376aa7fceac..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/session.go +++ /dev/null @@ -1,838 +0,0 @@ -package quic - -import ( - "crypto/tls" - "errors" - "fmt" - "net" - "sync" - "time" - - "github.com/lucas-clemente/quic-go/ackhandler" - "github.com/lucas-clemente/quic-go/congestion" - "github.com/lucas-clemente/quic-go/flowcontrol" - "github.com/lucas-clemente/quic-go/frames" - "github.com/lucas-clemente/quic-go/handshake" - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" - "github.com/lucas-clemente/quic-go/qerr" -) - -type unpacker interface { - Unpack(publicHeaderBinary []byte, hdr *PublicHeader, data []byte) (*unpackedPacket, error) -} - -type receivedPacket struct { - remoteAddr net.Addr - publicHeader *PublicHeader - data []byte - rcvTime time.Time -} - -var ( - errRstStreamOnInvalidStream = errors.New("RST_STREAM received for unknown stream") - errWindowUpdateOnClosedStream = errors.New("WINDOW_UPDATE received for an already closed stream") -) - -var ( - newCryptoSetup = handshake.NewCryptoSetup - newCryptoSetupClient = handshake.NewCryptoSetupClient -) - -type handshakeEvent struct { - encLevel protocol.EncryptionLevel - err error -} - -type closeError struct { - err error - remote bool -} - -// A Session is a QUIC session -type session struct { - connectionID protocol.ConnectionID - perspective protocol.Perspective - version protocol.VersionNumber - tlsConf *tls.Config - config *Config - - conn connection - - streamsMap *streamsMap - - rttStats *congestion.RTTStats - - sentPacketHandler ackhandler.SentPacketHandler - receivedPacketHandler ackhandler.ReceivedPacketHandler - streamFramer *streamFramer - - flowControlManager flowcontrol.FlowControlManager - - unpacker unpacker - packer *packetPacker - - cryptoSetup handshake.CryptoSetup - - receivedPackets chan *receivedPacket - sendingScheduled chan struct{} - // closeChan is used to notify the run loop that it should terminate. - closeChan chan closeError - // runClosed is closed once the run loop exits - // it is used to block Close() and WaitUntilClosed() - runClosed chan struct{} - closeOnce sync.Once - - // when we receive too many undecryptable packets during the handshake, we send a Public reset - // but only after a time of protocol.PublicResetTimeout has passed - undecryptablePackets []*receivedPacket - receivedTooManyUndecrytablePacketsTime time.Time - - // this channel is passed to the CryptoSetup and receives the current encryption level - // it is closed as soon as the handshake is complete - aeadChanged <-chan protocol.EncryptionLevel - handshakeComplete bool - // will be closed as soon as the handshake completes, and receive any error that might occur until then - // it is used to block WaitUntilHandshakeComplete() - handshakeCompleteChan chan error - // handshakeChan receives handshake events and is closed as soon the handshake completes - // the receiving end of this channel is passed to the creator of the session - // it receives at most 3 handshake events: 2 when the encryption level changes, and one error - handshakeChan chan<- handshakeEvent - - connectionParameters handshake.ConnectionParametersManager - - lastRcvdPacketNumber protocol.PacketNumber - // Used to calculate the next packet number from the truncated wire - // representation, and sent back in public reset packets - largestRcvdPacketNumber protocol.PacketNumber - - sessionCreationTime time.Time - lastNetworkActivityTime time.Time - - timer *utils.Timer - // keepAlivePingSent stores whether a Ping frame was sent to the peer or not - // it is reset as soon as we receive a packet from the peer - keepAlivePingSent bool -} - -var _ Session = &session{} - -// newSession makes a new session -func newSession( - conn connection, - v protocol.VersionNumber, - connectionID protocol.ConnectionID, - sCfg *handshake.ServerConfig, - tlsConf *tls.Config, - config *Config, -) (packetHandler, <-chan handshakeEvent, error) { - s := &session{ - conn: conn, - connectionID: connectionID, - perspective: protocol.PerspectiveServer, - version: v, - config: config, - } - return s.setup(sCfg, "", nil) -} - -// declare this as a variable, such that we can it mock it in the tests -var newClientSession = func( - conn connection, - hostname string, - v protocol.VersionNumber, - connectionID protocol.ConnectionID, - tlsConf *tls.Config, - config *Config, - negotiatedVersions []protocol.VersionNumber, -) (packetHandler, <-chan handshakeEvent, error) { - s := &session{ - conn: conn, - connectionID: connectionID, - perspective: protocol.PerspectiveClient, - version: v, - tlsConf: tlsConf, - config: config, - } - return s.setup(nil, hostname, negotiatedVersions) -} - -func (s *session) setup( - scfg *handshake.ServerConfig, - hostname string, - negotiatedVersions []protocol.VersionNumber, -) (packetHandler, <-chan handshakeEvent, error) { - aeadChanged := make(chan protocol.EncryptionLevel, 2) - s.aeadChanged = aeadChanged - handshakeChan := make(chan handshakeEvent, 3) - s.handshakeChan = handshakeChan - s.runClosed = make(chan struct{}) - s.handshakeCompleteChan = make(chan error, 1) - s.receivedPackets = make(chan *receivedPacket, protocol.MaxSessionUnprocessedPackets) - s.closeChan = make(chan closeError, 1) - s.sendingScheduled = make(chan struct{}, 1) - s.undecryptablePackets = make([]*receivedPacket, 0, protocol.MaxUndecryptablePackets) - - s.timer = utils.NewTimer() - now := time.Now() - s.lastNetworkActivityTime = now - s.sessionCreationTime = now - - s.rttStats = &congestion.RTTStats{} - s.connectionParameters = handshake.NewConnectionParamatersManager(s.perspective, s.version, - s.config.MaxReceiveStreamFlowControlWindow, s.config.MaxReceiveConnectionFlowControlWindow) - s.sentPacketHandler = ackhandler.NewSentPacketHandler(s.rttStats) - s.flowControlManager = flowcontrol.NewFlowControlManager(s.connectionParameters, s.rttStats) - s.receivedPacketHandler = ackhandler.NewReceivedPacketHandler() - s.streamsMap = newStreamsMap(s.newStream, s.perspective, s.connectionParameters) - s.streamFramer = newStreamFramer(s.streamsMap, s.flowControlManager) - - var err error - if s.perspective == protocol.PerspectiveServer { - cryptoStream, _ := s.GetOrOpenStream(1) - _, _ = s.AcceptStream() // don't expose the crypto stream - verifySourceAddr := func(clientAddr net.Addr, hstk *handshake.STK) bool { - var stk *STK - if hstk != nil { - stk = &STK{remoteAddr: hstk.RemoteAddr, sentTime: hstk.SentTime} - } - return s.config.AcceptSTK(clientAddr, stk) - } - s.cryptoSetup, err = newCryptoSetup( - s.connectionID, - s.conn.RemoteAddr(), - s.version, - scfg, - cryptoStream, - s.connectionParameters, - s.config.Versions, - verifySourceAddr, - aeadChanged, - ) - } else { - cryptoStream, _ := s.OpenStream() - s.cryptoSetup, err = newCryptoSetupClient( - hostname, - s.connectionID, - s.version, - cryptoStream, - s.tlsConf, - s.connectionParameters, - aeadChanged, - &handshake.TransportParameters{RequestConnectionIDTruncation: s.config.RequestConnectionIDTruncation}, - negotiatedVersions, - ) - } - if err != nil { - return nil, nil, err - } - - s.packer = newPacketPacker(s.connectionID, - s.cryptoSetup, - s.connectionParameters, - s.streamFramer, - s.perspective, - s.version, - ) - s.unpacker = &packetUnpacker{aead: s.cryptoSetup, version: s.version} - - return s, handshakeChan, nil -} - -// run the session main loop -func (s *session) run() error { - // Start the crypto stream handler - go func() { - if err := s.cryptoSetup.HandleCryptoStream(); err != nil { - s.Close(err) - } - }() - - var closeErr closeError - aeadChanged := s.aeadChanged - -runLoop: - for { - // Close immediately if requested - select { - case closeErr = <-s.closeChan: - break runLoop - default: - } - - s.maybeResetTimer() - - select { - case closeErr = <-s.closeChan: - break runLoop - case <-s.timer.Chan(): - s.timer.SetRead() - // We do all the interesting stuff after the switch statement, so - // nothing to see here. - case <-s.sendingScheduled: - // We do all the interesting stuff after the switch statement, so - // nothing to see here. - case p := <-s.receivedPackets: - err := s.handlePacketImpl(p) - if err != nil { - if qErr, ok := err.(*qerr.QuicError); ok && qErr.ErrorCode == qerr.DecryptionFailure { - s.tryQueueingUndecryptablePacket(p) - continue - } - s.closeLocal(err) - continue - } - // This is a bit unclean, but works properly, since the packet always - // begins with the public header and we never copy it. - putPacketBuffer(p.publicHeader.Raw) - case l, ok := <-aeadChanged: - if !ok { // the aeadChanged chan was closed. This means that the handshake is completed. - s.handshakeComplete = true - aeadChanged = nil // prevent this case from ever being selected again - close(s.handshakeChan) - close(s.handshakeCompleteChan) - } else { - s.tryDecryptingQueuedPackets() - s.handshakeChan <- handshakeEvent{encLevel: l} - } - } - - now := time.Now() - if timeout := s.sentPacketHandler.GetAlarmTimeout(); !timeout.IsZero() && timeout.Before(now) { - // This could cause packets to be retransmitted, so check it before trying - // to send packets. - s.sentPacketHandler.OnAlarm() - } - - if s.config.KeepAlive && s.handshakeComplete && time.Since(s.lastNetworkActivityTime) >= s.idleTimeout()/2 { - // send the PING frame since there is no activity in the session - s.packer.QueueControlFrame(&frames.PingFrame{}) - s.keepAlivePingSent = true - } - - if err := s.sendPacket(); err != nil { - s.closeLocal(err) - } - if !s.receivedTooManyUndecrytablePacketsTime.IsZero() && s.receivedTooManyUndecrytablePacketsTime.Add(protocol.PublicResetTimeout).Before(now) && len(s.undecryptablePackets) != 0 { - s.closeLocal(qerr.Error(qerr.DecryptionFailure, "too many undecryptable packets received")) - } - if now.Sub(s.lastNetworkActivityTime) >= s.idleTimeout() { - s.closeLocal(qerr.Error(qerr.NetworkIdleTimeout, "No recent network activity.")) - } - if !s.handshakeComplete && now.Sub(s.sessionCreationTime) >= s.config.HandshakeTimeout { - s.closeLocal(qerr.Error(qerr.HandshakeTimeout, "Crypto handshake did not complete in time.")) - } - s.garbageCollectStreams() - } - - // only send the error the handshakeChan when the handshake is not completed yet - // otherwise this chan will already be closed - if !s.handshakeComplete { - s.handshakeCompleteChan <- closeErr.err - s.handshakeChan <- handshakeEvent{err: closeErr.err} - } - s.handleCloseError(closeErr) - close(s.runClosed) - return closeErr.err -} - -func (s *session) WaitUntilClosed() { - <-s.runClosed -} - -func (s *session) maybeResetTimer() { - var deadline time.Time - if s.config.KeepAlive && s.handshakeComplete && !s.keepAlivePingSent { - deadline = s.lastNetworkActivityTime.Add(s.idleTimeout() / 2) - } else { - deadline = s.lastNetworkActivityTime.Add(s.idleTimeout()) - } - - if ackAlarm := s.receivedPacketHandler.GetAlarmTimeout(); !ackAlarm.IsZero() { - deadline = utils.MinTime(deadline, ackAlarm) - } - if lossTime := s.sentPacketHandler.GetAlarmTimeout(); !lossTime.IsZero() { - deadline = utils.MinTime(deadline, lossTime) - } - if !s.handshakeComplete { - handshakeDeadline := s.sessionCreationTime.Add(s.config.HandshakeTimeout) - deadline = utils.MinTime(deadline, handshakeDeadline) - } - if !s.receivedTooManyUndecrytablePacketsTime.IsZero() { - deadline = utils.MinTime(deadline, s.receivedTooManyUndecrytablePacketsTime.Add(protocol.PublicResetTimeout)) - } - - s.timer.Reset(deadline) -} - -func (s *session) idleTimeout() time.Duration { - if s.handshakeComplete { - return s.connectionParameters.GetIdleConnectionStateLifetime() - } - return protocol.InitialIdleTimeout -} - -func (s *session) handlePacketImpl(p *receivedPacket) error { - if s.perspective == protocol.PerspectiveClient { - diversificationNonce := p.publicHeader.DiversificationNonce - if len(diversificationNonce) > 0 { - s.cryptoSetup.SetDiversificationNonce(diversificationNonce) - } - } - - if p.rcvTime.IsZero() { - // To simplify testing - p.rcvTime = time.Now() - } - - s.lastNetworkActivityTime = p.rcvTime - s.keepAlivePingSent = false - hdr := p.publicHeader - data := p.data - - // Calculate packet number - hdr.PacketNumber = protocol.InferPacketNumber( - hdr.PacketNumberLen, - s.largestRcvdPacketNumber, - hdr.PacketNumber, - ) - - packet, err := s.unpacker.Unpack(hdr.Raw, hdr, data) - if utils.Debug() { - if err != nil { - utils.Debugf("<- Reading packet 0x%x (%d bytes) for connection %x", hdr.PacketNumber, len(data)+len(hdr.Raw), hdr.ConnectionID) - } else { - utils.Debugf("<- Reading packet 0x%x (%d bytes) for connection %x, %s", hdr.PacketNumber, len(data)+len(hdr.Raw), hdr.ConnectionID, packet.encryptionLevel) - } - } - // if the decryption failed, this might be a packet sent by an attacker - // don't update the remote address - if quicErr, ok := err.(*qerr.QuicError); ok && quicErr.ErrorCode == qerr.DecryptionFailure { - return err - } - if s.perspective == protocol.PerspectiveServer { - // update the remote address, even if unpacking failed for any other reason than a decryption error - s.conn.SetCurrentRemoteAddr(p.remoteAddr) - } - if err != nil { - return err - } - - s.lastRcvdPacketNumber = hdr.PacketNumber - // Only do this after decrypting, so we are sure the packet is not attacker-controlled - s.largestRcvdPacketNumber = utils.MaxPacketNumber(s.largestRcvdPacketNumber, hdr.PacketNumber) - - isRetransmittable := ackhandler.HasRetransmittableFrames(packet.frames) - if err = s.receivedPacketHandler.ReceivedPacket(hdr.PacketNumber, isRetransmittable); err != nil { - return err - } - - return s.handleFrames(packet.frames) -} - -func (s *session) handleFrames(fs []frames.Frame) error { - for _, ff := range fs { - var err error - frames.LogFrame(ff, false) - switch frame := ff.(type) { - case *frames.StreamFrame: - err = s.handleStreamFrame(frame) - case *frames.AckFrame: - err = s.handleAckFrame(frame) - case *frames.ConnectionCloseFrame: - s.closeRemote(qerr.Error(frame.ErrorCode, frame.ReasonPhrase)) - case *frames.GoawayFrame: - err = errors.New("unimplemented: handling GOAWAY frames") - case *frames.StopWaitingFrame: - err = s.receivedPacketHandler.ReceivedStopWaiting(frame) - case *frames.RstStreamFrame: - err = s.handleRstStreamFrame(frame) - case *frames.WindowUpdateFrame: - err = s.handleWindowUpdateFrame(frame) - case *frames.BlockedFrame: - case *frames.PingFrame: - default: - return errors.New("Session BUG: unexpected frame type") - } - - if err != nil { - switch err { - case ackhandler.ErrDuplicateOrOutOfOrderAck: - // Can happen e.g. when packets thought missing arrive late - case errRstStreamOnInvalidStream: - // Can happen when RST_STREAMs arrive early or late (?) - utils.Errorf("Ignoring error in session: %s", err.Error()) - case errWindowUpdateOnClosedStream: - // Can happen when we already sent the last StreamFrame with the FinBit, but the client already sent a WindowUpdate for this Stream - default: - return err - } - } - } - return nil -} - -// handlePacket is called by the server with a new packet -func (s *session) handlePacket(p *receivedPacket) { - // Discard packets once the amount of queued packets is larger than - // the channel size, protocol.MaxSessionUnprocessedPackets - select { - case s.receivedPackets <- p: - default: - } -} - -func (s *session) handleStreamFrame(frame *frames.StreamFrame) error { - str, err := s.streamsMap.GetOrOpenStream(frame.StreamID) - if err != nil { - return err - } - if str == nil { - // Stream is closed and already garbage collected - // ignore this StreamFrame - return nil - } - return str.AddStreamFrame(frame) -} - -func (s *session) handleWindowUpdateFrame(frame *frames.WindowUpdateFrame) error { - if frame.StreamID != 0 { - str, err := s.streamsMap.GetOrOpenStream(frame.StreamID) - if err != nil { - return err - } - if str == nil { - return errWindowUpdateOnClosedStream - } - } - _, err := s.flowControlManager.UpdateWindow(frame.StreamID, frame.ByteOffset) - return err -} - -func (s *session) handleRstStreamFrame(frame *frames.RstStreamFrame) error { - str, err := s.streamsMap.GetOrOpenStream(frame.StreamID) - if err != nil { - return err - } - if str == nil { - return errRstStreamOnInvalidStream - } - - str.RegisterRemoteError(fmt.Errorf("RST_STREAM received with code %d", frame.ErrorCode)) - return s.flowControlManager.ResetStream(frame.StreamID, frame.ByteOffset) -} - -func (s *session) handleAckFrame(frame *frames.AckFrame) error { - return s.sentPacketHandler.ReceivedAck(frame, s.lastRcvdPacketNumber, s.lastNetworkActivityTime) -} - -func (s *session) closeLocal(e error) { - s.closeOnce.Do(func() { - s.closeChan <- closeError{err: e, remote: false} - }) -} - -func (s *session) closeRemote(e error) { - s.closeOnce.Do(func() { - s.closeChan <- closeError{err: e, remote: true} - }) -} - -// Close the connection. If err is nil it will be set to qerr.PeerGoingAway. -// It waits until the run loop has stopped before returning -func (s *session) Close(e error) error { - s.closeLocal(e) - <-s.runClosed - return nil -} - -func (s *session) handleCloseError(closeErr closeError) error { - if closeErr.err == nil { - closeErr.err = qerr.PeerGoingAway - } - - var quicErr *qerr.QuicError - var ok bool - if quicErr, ok = closeErr.err.(*qerr.QuicError); !ok { - quicErr = qerr.ToQuicError(closeErr.err) - } - // Don't log 'normal' reasons - if quicErr.ErrorCode == qerr.PeerGoingAway || quicErr.ErrorCode == qerr.NetworkIdleTimeout { - utils.Infof("Closing connection %x", s.connectionID) - } else { - utils.Errorf("Closing session with error: %s", closeErr.err.Error()) - } - - s.streamsMap.CloseWithError(quicErr) - - if closeErr.err == errCloseSessionForNewVersion { - return nil - } - - // If this is a remote close we're done here - if closeErr.remote { - return nil - } - - if quicErr.ErrorCode == qerr.DecryptionFailure || quicErr == handshake.ErrHOLExperiment { - return s.sendPublicReset(s.lastRcvdPacketNumber) - } - return s.sendConnectionClose(quicErr) -} - -func (s *session) sendPacket() error { - s.packer.SetLeastUnacked(s.sentPacketHandler.GetLeastUnacked()) - - // Get WindowUpdate frames - // this call triggers the flow controller to increase the flow control windows, if necessary - windowUpdateFrames := s.getWindowUpdateFrames() - for _, wuf := range windowUpdateFrames { - s.packer.QueueControlFrame(wuf) - } - - ack := s.receivedPacketHandler.GetAckFrame() - if ack != nil { - s.packer.QueueControlFrame(ack) - } - - // Repeatedly try sending until we don't have any more data, or run out of the congestion window - for { - if !s.sentPacketHandler.SendingAllowed() { - if ack == nil { - return nil - } - // If we aren't allowed to send, at least try sending an ACK frame - swf := s.sentPacketHandler.GetStopWaitingFrame(false) - if swf != nil { - s.packer.QueueControlFrame(swf) - } - packet, err := s.packer.PackAckPacket() - if err != nil { - return err - } - return s.sendPackedPacket(packet) - } - - // check for retransmissions first - for { - retransmitPacket := s.sentPacketHandler.DequeuePacketForRetransmission() - if retransmitPacket == nil { - break - } - - if retransmitPacket.EncryptionLevel != protocol.EncryptionForwardSecure { - if s.handshakeComplete { - // Don't retransmit handshake packets when the handshake is complete - continue - } - utils.Debugf("\tDequeueing handshake retransmission for packet 0x%x", retransmitPacket.PacketNumber) - s.packer.QueueControlFrame(s.sentPacketHandler.GetStopWaitingFrame(true)) - packet, err := s.packer.PackHandshakeRetransmission(retransmitPacket) - if err != nil { - return err - } - if err = s.sendPackedPacket(packet); err != nil { - return err - } - } else { - utils.Debugf("\tDequeueing retransmission for packet 0x%x", retransmitPacket.PacketNumber) - // resend the frames that were in the packet - for _, frame := range retransmitPacket.GetFramesForRetransmission() { - switch f := frame.(type) { - case *frames.StreamFrame: - s.streamFramer.AddFrameForRetransmission(f) - case *frames.WindowUpdateFrame: - // only retransmit WindowUpdates if the stream is not yet closed and the we haven't sent another WindowUpdate with a higher ByteOffset for the stream - currentOffset, err := s.flowControlManager.GetReceiveWindow(f.StreamID) - if err == nil && f.ByteOffset >= currentOffset { - s.packer.QueueControlFrame(f) - } - default: - s.packer.QueueControlFrame(frame) - } - } - } - } - - hasRetransmission := s.streamFramer.HasFramesForRetransmission() - if ack != nil || hasRetransmission { - swf := s.sentPacketHandler.GetStopWaitingFrame(hasRetransmission) - if swf != nil { - s.packer.QueueControlFrame(swf) - } - } - packet, err := s.packer.PackPacket() - if err != nil || packet == nil { - return err - } - if err = s.sendPackedPacket(packet); err != nil { - return err - } - - // send every window update twice - for _, f := range windowUpdateFrames { - s.packer.QueueControlFrame(f) - } - windowUpdateFrames = nil - ack = nil - } -} - -func (s *session) sendPackedPacket(packet *packedPacket) error { - defer putPacketBuffer(packet.raw) - err := s.sentPacketHandler.SentPacket(&ackhandler.Packet{ - PacketNumber: packet.number, - Frames: packet.frames, - Length: protocol.ByteCount(len(packet.raw)), - EncryptionLevel: packet.encryptionLevel, - }) - if err != nil { - return err - } - s.logPacket(packet) - return s.conn.Write(packet.raw) -} - -func (s *session) sendConnectionClose(quicErr *qerr.QuicError) error { - s.packer.SetLeastUnacked(s.sentPacketHandler.GetLeastUnacked()) - packet, err := s.packer.PackConnectionClose(&frames.ConnectionCloseFrame{ - ErrorCode: quicErr.ErrorCode, - ReasonPhrase: quicErr.ErrorMessage, - }) - if err != nil { - return err - } - s.logPacket(packet) - return s.conn.Write(packet.raw) -} - -func (s *session) logPacket(packet *packedPacket) { - if !utils.Debug() { - // We don't need to allocate the slices for calling the format functions - return - } - utils.Debugf("-> Sending packet 0x%x (%d bytes) for connection %x, %s", packet.number, len(packet.raw), s.connectionID, packet.encryptionLevel) - for _, frame := range packet.frames { - frames.LogFrame(frame, true) - } -} - -// GetOrOpenStream either returns an existing stream, a newly opened stream, or nil if a stream with the provided ID is already closed. -// Newly opened streams should only originate from the client. To open a stream from the server, OpenStream should be used. -func (s *session) GetOrOpenStream(id protocol.StreamID) (Stream, error) { - str, err := s.streamsMap.GetOrOpenStream(id) - if str != nil { - return str, err - } - // make sure to return an actual nil value here, not an Stream with value nil - return nil, err -} - -// AcceptStream returns the next stream openend by the peer -func (s *session) AcceptStream() (Stream, error) { - return s.streamsMap.AcceptStream() -} - -// OpenStream opens a stream -func (s *session) OpenStream() (Stream, error) { - return s.streamsMap.OpenStream() -} - -func (s *session) OpenStreamSync() (Stream, error) { - return s.streamsMap.OpenStreamSync() -} - -func (s *session) WaitUntilHandshakeComplete() error { - return <-s.handshakeCompleteChan -} - -func (s *session) queueResetStreamFrame(id protocol.StreamID, offset protocol.ByteCount) { - s.packer.QueueControlFrame(&frames.RstStreamFrame{ - StreamID: id, - ByteOffset: offset, - }) - s.scheduleSending() -} - -func (s *session) newStream(id protocol.StreamID) *stream { - // TODO: find a better solution for determining which streams contribute to connection level flow control - if id == 1 || id == 3 { - s.flowControlManager.NewStream(id, false) - } else { - s.flowControlManager.NewStream(id, true) - } - return newStream(id, s.scheduleSending, s.queueResetStreamFrame, s.flowControlManager) -} - -// garbageCollectStreams goes through all streams and removes EOF'ed streams -// from the streams map. -func (s *session) garbageCollectStreams() { - s.streamsMap.Iterate(func(str *stream) (bool, error) { - id := str.StreamID() - if str.finished() { - err := s.streamsMap.RemoveStream(id) - if err != nil { - return false, err - } - s.flowControlManager.RemoveStream(id) - } - return true, nil - }) -} - -func (s *session) sendPublicReset(rejectedPacketNumber protocol.PacketNumber) error { - utils.Infof("Sending public reset for connection %x, packet number %d", s.connectionID, rejectedPacketNumber) - return s.conn.Write(writePublicReset(s.connectionID, rejectedPacketNumber, 0)) -} - -// scheduleSending signals that we have data for sending -func (s *session) scheduleSending() { - select { - case s.sendingScheduled <- struct{}{}: - default: - } -} - -func (s *session) tryQueueingUndecryptablePacket(p *receivedPacket) { - if s.handshakeComplete { - utils.Debugf("Received undecryptable packet from %s after the handshake: %#v, %d bytes data", p.remoteAddr.String(), p.publicHeader, len(p.data)) - return - } - if len(s.undecryptablePackets)+1 > protocol.MaxUndecryptablePackets { - // if this is the first time the undecryptablePackets runs full, start the timer to send a Public Reset - if s.receivedTooManyUndecrytablePacketsTime.IsZero() { - s.receivedTooManyUndecrytablePacketsTime = time.Now() - s.maybeResetTimer() - } - utils.Infof("Dropping undecrytable packet 0x%x (undecryptable packet queue full)", p.publicHeader.PacketNumber) - return - } - utils.Infof("Queueing packet 0x%x for later decryption", p.publicHeader.PacketNumber) - s.undecryptablePackets = append(s.undecryptablePackets, p) -} - -func (s *session) tryDecryptingQueuedPackets() { - for _, p := range s.undecryptablePackets { - s.handlePacket(p) - } - s.undecryptablePackets = s.undecryptablePackets[:0] -} - -func (s *session) getWindowUpdateFrames() []*frames.WindowUpdateFrame { - updates := s.flowControlManager.GetWindowUpdates() - res := make([]*frames.WindowUpdateFrame, len(updates)) - for i, u := range updates { - res[i] = &frames.WindowUpdateFrame{StreamID: u.StreamID, ByteOffset: u.Offset} - } - return res -} - -func (s *session) LocalAddr() net.Addr { - return s.conn.LocalAddr() -} - -// RemoteAddr returns the net.Addr of the client -func (s *session) RemoteAddr() net.Addr { - return s.conn.RemoteAddr() -} diff --git a/vendor/github.com/lucas-clemente/quic-go/stream.go b/vendor/github.com/lucas-clemente/quic-go/stream.go deleted file mode 100644 index 6a3d4a06f70..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/stream.go +++ /dev/null @@ -1,414 +0,0 @@ -package quic - -import ( - "fmt" - "io" - "net" - "sync" - "time" - - "github.com/lucas-clemente/quic-go/flowcontrol" - "github.com/lucas-clemente/quic-go/frames" - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" -) - -// A Stream assembles the data from StreamFrames and provides a super-convenient Read-Interface -// -// Read() and Write() may be called concurrently, but multiple calls to Read() or Write() individually must be synchronized manually. -type stream struct { - mutex sync.Mutex - - streamID protocol.StreamID - onData func() - // onReset is a callback that should send a RST_STREAM - onReset func(protocol.StreamID, protocol.ByteCount) - - readPosInFrame int - writeOffset protocol.ByteCount - readOffset protocol.ByteCount - - // Once set, the errors must not be changed! - err error - - // cancelled is set when Cancel() is called - cancelled utils.AtomicBool - // finishedReading is set once we read a frame with a FinBit - finishedReading utils.AtomicBool - // finisedWriting is set once Close() is called - finishedWriting utils.AtomicBool - // resetLocally is set if Reset() is called - resetLocally utils.AtomicBool - // resetRemotely is set if RegisterRemoteError() is called - resetRemotely utils.AtomicBool - - frameQueue *streamFrameSorter - readChan chan struct{} - readDeadline time.Time - - dataForWriting []byte - finSent utils.AtomicBool - rstSent utils.AtomicBool - writeChan chan struct{} - writeDeadline time.Time - - flowControlManager flowcontrol.FlowControlManager -} - -type deadlineError struct{} - -func (deadlineError) Error() string { return "deadline exceeded" } -func (deadlineError) Temporary() bool { return true } -func (deadlineError) Timeout() bool { return true } - -var errDeadline net.Error = &deadlineError{} - -// newStream creates a new Stream -func newStream(StreamID protocol.StreamID, - onData func(), - onReset func(protocol.StreamID, protocol.ByteCount), - flowControlManager flowcontrol.FlowControlManager) *stream { - return &stream{ - onData: onData, - onReset: onReset, - streamID: StreamID, - flowControlManager: flowControlManager, - frameQueue: newStreamFrameSorter(), - readChan: make(chan struct{}, 1), - writeChan: make(chan struct{}, 1), - } -} - -// Read implements io.Reader. It is not thread safe! -func (s *stream) Read(p []byte) (int, error) { - s.mutex.Lock() - err := s.err - s.mutex.Unlock() - if s.cancelled.Get() || s.resetLocally.Get() { - return 0, err - } - if s.finishedReading.Get() { - return 0, io.EOF - } - - bytesRead := 0 - for bytesRead < len(p) { - s.mutex.Lock() - frame := s.frameQueue.Head() - if frame == nil && bytesRead > 0 { - err = s.err - s.mutex.Unlock() - return bytesRead, err - } - - var err error - for { - // Stop waiting on errors - if s.resetLocally.Get() || s.cancelled.Get() { - err = s.err - break - } - - deadline := s.readDeadline - if !deadline.IsZero() && !time.Now().Before(deadline) { - err = errDeadline - break - } - - if frame != nil { - s.readPosInFrame = int(s.readOffset - frame.Offset) - break - } - - s.mutex.Unlock() - if deadline.IsZero() { - <-s.readChan - } else { - select { - case <-s.readChan: - case <-time.After(deadline.Sub(time.Now())): - } - } - s.mutex.Lock() - frame = s.frameQueue.Head() - } - s.mutex.Unlock() - - if err != nil { - return bytesRead, err - } - - m := utils.Min(len(p)-bytesRead, int(frame.DataLen())-s.readPosInFrame) - - if bytesRead > len(p) { - return bytesRead, fmt.Errorf("BUG: bytesRead (%d) > len(p) (%d) in stream.Read", bytesRead, len(p)) - } - if s.readPosInFrame > int(frame.DataLen()) { - return bytesRead, fmt.Errorf("BUG: readPosInFrame (%d) > frame.DataLen (%d) in stream.Read", s.readPosInFrame, frame.DataLen()) - } - copy(p[bytesRead:], frame.Data[s.readPosInFrame:]) - - s.readPosInFrame += m - bytesRead += m - s.readOffset += protocol.ByteCount(m) - - // when a RST_STREAM was received, the was already informed about the final byteOffset for this stream - if !s.resetRemotely.Get() { - s.flowControlManager.AddBytesRead(s.streamID, protocol.ByteCount(m)) - } - s.onData() // so that a possible WINDOW_UPDATE is sent - - if s.readPosInFrame >= int(frame.DataLen()) { - fin := frame.FinBit - s.mutex.Lock() - s.frameQueue.Pop() - s.mutex.Unlock() - if fin { - s.finishedReading.Set(true) - return bytesRead, io.EOF - } - } - } - - return bytesRead, nil -} - -func (s *stream) Write(p []byte) (int, error) { - s.mutex.Lock() - defer s.mutex.Unlock() - - if s.resetLocally.Get() || s.err != nil { - return 0, s.err - } - if len(p) == 0 { - return 0, nil - } - - s.dataForWriting = make([]byte, len(p)) - copy(s.dataForWriting, p) - s.onData() - - var err error - for { - deadline := s.writeDeadline - if !deadline.IsZero() && !time.Now().Before(deadline) { - err = errDeadline - break - } - if s.dataForWriting == nil || s.err != nil { - break - } - - s.mutex.Unlock() - if deadline.IsZero() { - <-s.writeChan - } else { - select { - case <-s.writeChan: - case <-time.After(deadline.Sub(time.Now())): - } - } - s.mutex.Lock() - } - - if err != nil { - return 0, err - } - if s.err != nil { - return len(p) - len(s.dataForWriting), s.err - } - return len(p), nil -} - -func (s *stream) lenOfDataForWriting() protocol.ByteCount { - s.mutex.Lock() - var l protocol.ByteCount - if s.err == nil { - l = protocol.ByteCount(len(s.dataForWriting)) - } - s.mutex.Unlock() - return l -} - -func (s *stream) getDataForWriting(maxBytes protocol.ByteCount) []byte { - s.mutex.Lock() - defer s.mutex.Unlock() - - if s.err != nil || s.dataForWriting == nil { - return nil - } - - var ret []byte - if protocol.ByteCount(len(s.dataForWriting)) > maxBytes { - ret = s.dataForWriting[:maxBytes] - s.dataForWriting = s.dataForWriting[maxBytes:] - } else { - ret = s.dataForWriting - s.dataForWriting = nil - s.signalWrite() - } - s.writeOffset += protocol.ByteCount(len(ret)) - return ret -} - -// Close implements io.Closer -func (s *stream) Close() error { - s.finishedWriting.Set(true) - s.onData() - return nil -} - -func (s *stream) shouldSendReset() bool { - if s.rstSent.Get() { - return false - } - return (s.resetLocally.Get() || s.resetRemotely.Get()) && !s.finishedWriteAndSentFin() -} - -func (s *stream) shouldSendFin() bool { - s.mutex.Lock() - res := s.finishedWriting.Get() && !s.finSent.Get() && s.err == nil && s.dataForWriting == nil - s.mutex.Unlock() - return res -} - -func (s *stream) sentFin() { - s.finSent.Set(true) -} - -// AddStreamFrame adds a new stream frame -func (s *stream) AddStreamFrame(frame *frames.StreamFrame) error { - maxOffset := frame.Offset + frame.DataLen() - err := s.flowControlManager.UpdateHighestReceived(s.streamID, maxOffset) - if err != nil { - return err - } - - s.mutex.Lock() - defer s.mutex.Unlock() - err = s.frameQueue.Push(frame) - if err != nil && err != errDuplicateStreamData { - return err - } - s.signalRead() - return nil -} - -// signalRead performs a non-blocking send on the readChan -func (s *stream) signalRead() { - select { - case s.readChan <- struct{}{}: - default: - } -} - -// signalRead performs a non-blocking send on the writeChan -func (s *stream) signalWrite() { - select { - case s.writeChan <- struct{}{}: - default: - } -} - -func (s *stream) SetReadDeadline(t time.Time) error { - s.mutex.Lock() - oldDeadline := s.readDeadline - s.readDeadline = t - s.mutex.Unlock() - // if the new deadline is before the currently set deadline, wake up Read() - if t.Before(oldDeadline) { - s.signalRead() - } - return nil -} - -func (s *stream) SetWriteDeadline(t time.Time) error { - s.mutex.Lock() - oldDeadline := s.writeDeadline - s.writeDeadline = t - s.mutex.Unlock() - if t.Before(oldDeadline) { - s.signalWrite() - } - return nil -} - -func (s *stream) SetDeadline(t time.Time) error { - _ = s.SetReadDeadline(t) // SetReadDeadline never errors - _ = s.SetWriteDeadline(t) // SetWriteDeadline never errors - return nil -} - -// CloseRemote makes the stream receive a "virtual" FIN stream frame at a given offset -func (s *stream) CloseRemote(offset protocol.ByteCount) { - s.AddStreamFrame(&frames.StreamFrame{FinBit: true, Offset: offset}) -} - -// Cancel is called by session to indicate that an error occurred -// The stream should will be closed immediately -func (s *stream) Cancel(err error) { - s.mutex.Lock() - s.cancelled.Set(true) - // errors must not be changed! - if s.err == nil { - s.err = err - s.signalRead() - s.signalWrite() - } - s.mutex.Unlock() -} - -// resets the stream locally -func (s *stream) Reset(err error) { - if s.resetLocally.Get() { - return - } - s.mutex.Lock() - s.resetLocally.Set(true) - // errors must not be changed! - if s.err == nil { - s.err = err - s.signalRead() - s.signalWrite() - } - if s.shouldSendReset() { - s.onReset(s.streamID, s.writeOffset) - s.rstSent.Set(true) - } - s.mutex.Unlock() -} - -// resets the stream remotely -func (s *stream) RegisterRemoteError(err error) { - if s.resetRemotely.Get() { - return - } - s.mutex.Lock() - s.resetRemotely.Set(true) - // errors must not be changed! - if s.err == nil { - s.err = err - s.signalWrite() - } - if s.shouldSendReset() { - s.onReset(s.streamID, s.writeOffset) - s.rstSent.Set(true) - } - s.mutex.Unlock() -} - -func (s *stream) finishedWriteAndSentFin() bool { - return s.finishedWriting.Get() && s.finSent.Get() -} - -func (s *stream) finished() bool { - return s.cancelled.Get() || - (s.finishedReading.Get() && s.finishedWriteAndSentFin()) || - (s.resetRemotely.Get() && s.rstSent.Get()) || - (s.finishedReading.Get() && s.rstSent.Get()) || - (s.finishedWriteAndSentFin() && s.resetRemotely.Get()) -} - -func (s *stream) StreamID() protocol.StreamID { - return s.streamID -} diff --git a/vendor/github.com/lucas-clemente/quic-go/stream_frame_sorter.go b/vendor/github.com/lucas-clemente/quic-go/stream_frame_sorter.go deleted file mode 100644 index 4a50150e29c..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/stream_frame_sorter.go +++ /dev/null @@ -1,161 +0,0 @@ -package quic - -import ( - "errors" - - "github.com/lucas-clemente/quic-go/frames" - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" -) - -type streamFrameSorter struct { - queuedFrames map[protocol.ByteCount]*frames.StreamFrame - readPosition protocol.ByteCount - gaps *utils.ByteIntervalList -} - -var ( - errTooManyGapsInReceivedStreamData = errors.New("Too many gaps in received StreamFrame data") - errDuplicateStreamData = errors.New("Duplicate Stream Data") - errEmptyStreamData = errors.New("Stream Data empty") -) - -func newStreamFrameSorter() *streamFrameSorter { - s := streamFrameSorter{ - gaps: utils.NewByteIntervalList(), - queuedFrames: make(map[protocol.ByteCount]*frames.StreamFrame), - } - s.gaps.PushFront(utils.ByteInterval{Start: 0, End: protocol.MaxByteCount}) - return &s -} - -func (s *streamFrameSorter) Push(frame *frames.StreamFrame) error { - if frame.DataLen() == 0 { - if frame.FinBit { - s.queuedFrames[frame.Offset] = frame - return nil - } - return errEmptyStreamData - } - - var wasCut bool - if oldFrame, ok := s.queuedFrames[frame.Offset]; ok { - if frame.DataLen() <= oldFrame.DataLen() { - return errDuplicateStreamData - } - frame.Data = frame.Data[oldFrame.DataLen():] - frame.Offset += oldFrame.DataLen() - wasCut = true - } - - start := frame.Offset - end := frame.Offset + frame.DataLen() - - // skip all gaps that are before this stream frame - var gap *utils.ByteIntervalElement - for gap = s.gaps.Front(); gap != nil; gap = gap.Next() { - // the frame is a duplicate. Ignore it - if end <= gap.Value.Start { - return errDuplicateStreamData - } - if end > gap.Value.Start && start <= gap.Value.End { - break - } - } - - if gap == nil { - return errors.New("StreamFrameSorter BUG: no gap found") - } - - if start < gap.Value.Start { - add := gap.Value.Start - start - frame.Offset += add - start += add - frame.Data = frame.Data[add:] - wasCut = true - } - - // find the highest gaps whose Start lies before the end of the frame - endGap := gap - for end >= endGap.Value.End { - nextEndGap := endGap.Next() - if nextEndGap == nil { - return errors.New("StreamFrameSorter BUG: no end gap found") - } - if endGap != gap { - s.gaps.Remove(endGap) - } - if end <= nextEndGap.Value.Start { - break - } - // delete queued frames completely covered by the current frame - delete(s.queuedFrames, endGap.Value.End) - endGap = nextEndGap - } - - if end > endGap.Value.End { - cutLen := end - endGap.Value.End - len := frame.DataLen() - cutLen - end -= cutLen - frame.Data = frame.Data[:len] - wasCut = true - } - - if start == gap.Value.Start { - if end >= gap.Value.End { - // the frame completely fills this gap - // delete the gap - s.gaps.Remove(gap) - } - if end < endGap.Value.End { - // the frame covers the beginning of the gap - // adjust the Start value to shrink the gap - endGap.Value.Start = end - } - } else if end == endGap.Value.End { - // the frame covers the end of the gap - // adjust the End value to shrink the gap - gap.Value.End = start - } else { - if gap == endGap { - // the frame lies within the current gap, splitting it into two - // insert a new gap and adjust the current one - intv := utils.ByteInterval{Start: end, End: gap.Value.End} - s.gaps.InsertAfter(intv, gap) - gap.Value.End = start - } else { - gap.Value.End = start - endGap.Value.Start = end - } - } - - if s.gaps.Len() > protocol.MaxStreamFrameSorterGaps { - return errTooManyGapsInReceivedStreamData - } - - if wasCut { - data := make([]byte, frame.DataLen()) - copy(data, frame.Data) - frame.Data = data - } - - s.queuedFrames[frame.Offset] = frame - return nil -} - -func (s *streamFrameSorter) Pop() *frames.StreamFrame { - frame := s.Head() - if frame != nil { - s.readPosition += frame.DataLen() - delete(s.queuedFrames, frame.Offset) - } - return frame -} - -func (s *streamFrameSorter) Head() *frames.StreamFrame { - frame, ok := s.queuedFrames[s.readPosition] - if ok { - return frame - } - return nil -} diff --git a/vendor/github.com/lucas-clemente/quic-go/stream_framer.go b/vendor/github.com/lucas-clemente/quic-go/stream_framer.go deleted file mode 100644 index 20f82e3e23c..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/stream_framer.go +++ /dev/null @@ -1,189 +0,0 @@ -package quic - -import ( - "github.com/lucas-clemente/quic-go/flowcontrol" - "github.com/lucas-clemente/quic-go/frames" - "github.com/lucas-clemente/quic-go/internal/utils" - "github.com/lucas-clemente/quic-go/protocol" -) - -type streamFramer struct { - streamsMap *streamsMap - - flowControlManager flowcontrol.FlowControlManager - - retransmissionQueue []*frames.StreamFrame - blockedFrameQueue []*frames.BlockedFrame -} - -func newStreamFramer(streamsMap *streamsMap, flowControlManager flowcontrol.FlowControlManager) *streamFramer { - return &streamFramer{ - streamsMap: streamsMap, - flowControlManager: flowControlManager, - } -} - -func (f *streamFramer) AddFrameForRetransmission(frame *frames.StreamFrame) { - f.retransmissionQueue = append(f.retransmissionQueue, frame) -} - -func (f *streamFramer) PopStreamFrames(maxLen protocol.ByteCount) []*frames.StreamFrame { - fs, currentLen := f.maybePopFramesForRetransmission(maxLen) - return append(fs, f.maybePopNormalFrames(maxLen-currentLen)...) -} - -func (f *streamFramer) PopBlockedFrame() *frames.BlockedFrame { - if len(f.blockedFrameQueue) == 0 { - return nil - } - frame := f.blockedFrameQueue[0] - f.blockedFrameQueue = f.blockedFrameQueue[1:] - return frame -} - -func (f *streamFramer) HasFramesForRetransmission() bool { - return len(f.retransmissionQueue) > 0 -} - -func (f *streamFramer) HasCryptoStreamFrame() bool { - // TODO(#657): Flow control - cs, _ := f.streamsMap.GetOrOpenStream(1) - return cs.lenOfDataForWriting() > 0 -} - -// TODO(lclemente): This is somewhat duplicate with the normal path for generating frames. -// TODO(#657): Flow control -func (f *streamFramer) PopCryptoStreamFrame(maxLen protocol.ByteCount) *frames.StreamFrame { - if !f.HasCryptoStreamFrame() { - return nil - } - cs, _ := f.streamsMap.GetOrOpenStream(1) - frame := &frames.StreamFrame{ - StreamID: 1, - Offset: cs.writeOffset, - } - frameHeaderBytes, _ := frame.MinLength(protocol.VersionWhatever) // can never error - frame.Data = cs.getDataForWriting(maxLen - frameHeaderBytes) - return frame -} - -func (f *streamFramer) maybePopFramesForRetransmission(maxLen protocol.ByteCount) (res []*frames.StreamFrame, currentLen protocol.ByteCount) { - for len(f.retransmissionQueue) > 0 { - frame := f.retransmissionQueue[0] - frame.DataLenPresent = true - - frameHeaderLen, _ := frame.MinLength(protocol.VersionWhatever) // can never error - if currentLen+frameHeaderLen >= maxLen { - break - } - - currentLen += frameHeaderLen - - splitFrame := maybeSplitOffFrame(frame, maxLen-currentLen) - if splitFrame != nil { // StreamFrame was split - res = append(res, splitFrame) - currentLen += splitFrame.DataLen() - break - } - - f.retransmissionQueue = f.retransmissionQueue[1:] - res = append(res, frame) - currentLen += frame.DataLen() - } - return -} - -func (f *streamFramer) maybePopNormalFrames(maxBytes protocol.ByteCount) (res []*frames.StreamFrame) { - frame := &frames.StreamFrame{DataLenPresent: true} - var currentLen protocol.ByteCount - - fn := func(s *stream) (bool, error) { - if s == nil || s.streamID == 1 /* crypto stream is handled separately */ { - return true, nil - } - - frame.StreamID = s.streamID - // not perfect, but thread-safe since writeOffset is only written when getting data - frame.Offset = s.writeOffset - frameHeaderBytes, _ := frame.MinLength(protocol.VersionWhatever) // can never error - if currentLen+frameHeaderBytes > maxBytes { - return false, nil // theoretically, we could find another stream that fits, but this is quite unlikely, so we stop here - } - maxLen := maxBytes - currentLen - frameHeaderBytes - - var sendWindowSize protocol.ByteCount - lenStreamData := s.lenOfDataForWriting() - if lenStreamData != 0 { - sendWindowSize, _ = f.flowControlManager.SendWindowSize(s.streamID) - maxLen = utils.MinByteCount(maxLen, sendWindowSize) - } - - if maxLen == 0 { - return true, nil - } - - var data []byte - if lenStreamData != 0 { - // Only getDataForWriting() if we didn't have data earlier, so that we - // don't send without FC approval (if a Write() raced). - data = s.getDataForWriting(maxLen) - } - - // This is unlikely, but check it nonetheless, the scheduler might have jumped in. Seems to happen in ~20% of cases in the tests. - shouldSendFin := s.shouldSendFin() - if data == nil && !shouldSendFin { - return true, nil - } - - if shouldSendFin { - frame.FinBit = true - s.sentFin() - } - - frame.Data = data - f.flowControlManager.AddBytesSent(s.streamID, protocol.ByteCount(len(data))) - - // Finally, check if we are now FC blocked and should queue a BLOCKED frame - if f.flowControlManager.RemainingConnectionWindowSize() == 0 { - // We are now connection-level FC blocked - f.blockedFrameQueue = append(f.blockedFrameQueue, &frames.BlockedFrame{StreamID: 0}) - } else if !frame.FinBit && sendWindowSize-frame.DataLen() == 0 { - // We are now stream-level FC blocked - f.blockedFrameQueue = append(f.blockedFrameQueue, &frames.BlockedFrame{StreamID: s.StreamID()}) - } - - res = append(res, frame) - currentLen += frameHeaderBytes + frame.DataLen() - - if currentLen == maxBytes { - return false, nil - } - - frame = &frames.StreamFrame{DataLenPresent: true} - return true, nil - } - - f.streamsMap.RoundRobinIterate(fn) - - return -} - -// maybeSplitOffFrame removes the first n bytes and returns them as a separate frame. If n >= len(frame), nil is returned and nothing is modified. -func maybeSplitOffFrame(frame *frames.StreamFrame, n protocol.ByteCount) *frames.StreamFrame { - if n >= frame.DataLen() { - return nil - } - - defer func() { - frame.Data = frame.Data[n:] - frame.Offset += n - }() - - return &frames.StreamFrame{ - FinBit: false, - StreamID: frame.StreamID, - Offset: frame.Offset, - Data: frame.Data[:n], - DataLenPresent: frame.DataLenPresent, - } -} diff --git a/vendor/github.com/lucas-clemente/quic-go/streams_map.go b/vendor/github.com/lucas-clemente/quic-go/streams_map.go deleted file mode 100644 index 74be17e0823..00000000000 --- a/vendor/github.com/lucas-clemente/quic-go/streams_map.go +++ /dev/null @@ -1,333 +0,0 @@ -package quic - -import ( - "errors" - "fmt" - "sync" - - "github.com/lucas-clemente/quic-go/handshake" - "github.com/lucas-clemente/quic-go/protocol" - "github.com/lucas-clemente/quic-go/qerr" -) - -type streamsMap struct { - mutex sync.RWMutex - - perspective protocol.Perspective - connectionParameters handshake.ConnectionParametersManager - - streams map[protocol.StreamID]*stream - // needed for round-robin scheduling - openStreams []protocol.StreamID - roundRobinIndex uint32 - - nextStream protocol.StreamID // StreamID of the next Stream that will be returned by OpenStream() - highestStreamOpenedByPeer protocol.StreamID - nextStreamOrErrCond sync.Cond - openStreamOrErrCond sync.Cond - - closeErr error - nextStreamToAccept protocol.StreamID - - newStream newStreamLambda - - numOutgoingStreams uint32 - numIncomingStreams uint32 -} - -type streamLambda func(*stream) (bool, error) -type newStreamLambda func(protocol.StreamID) *stream - -var ( - errMapAccess = errors.New("streamsMap: Error accessing the streams map") -) - -func newStreamsMap(newStream newStreamLambda, pers protocol.Perspective, connectionParameters handshake.ConnectionParametersManager) *streamsMap { - sm := streamsMap{ - perspective: pers, - streams: map[protocol.StreamID]*stream{}, - openStreams: make([]protocol.StreamID, 0), - newStream: newStream, - connectionParameters: connectionParameters, - } - sm.nextStreamOrErrCond.L = &sm.mutex - sm.openStreamOrErrCond.L = &sm.mutex - - if pers == protocol.PerspectiveClient { - sm.nextStream = 1 - sm.nextStreamToAccept = 2 - } else { - sm.nextStream = 2 - sm.nextStreamToAccept = 1 - } - - return &sm -} - -// GetOrOpenStream either returns an existing stream, a newly opened stream, or nil if a stream with the provided ID is already closed. -// Newly opened streams should only originate from the client. To open a stream from the server, OpenStream should be used. -func (m *streamsMap) GetOrOpenStream(id protocol.StreamID) (*stream, error) { - m.mutex.RLock() - s, ok := m.streams[id] - m.mutex.RUnlock() - if ok { - return s, nil // s may be nil - } - - // ... we don't have an existing stream - m.mutex.Lock() - defer m.mutex.Unlock() - // We need to check whether another invocation has already created a stream (between RUnlock() and Lock()). - s, ok = m.streams[id] - if ok { - return s, nil - } - - if m.perspective == protocol.PerspectiveServer { - if id%2 == 0 { - if id <= m.nextStream { // this is a server-side stream that we already opened. Must have been closed already - return nil, nil - } - return nil, qerr.Error(qerr.InvalidStreamID, fmt.Sprintf("attempted to open stream %d from client-side", id)) - } - if id <= m.highestStreamOpenedByPeer { // this is a client-side stream that doesn't exist anymore. Must have been closed already - return nil, nil - } - } - if m.perspective == protocol.PerspectiveClient { - if id%2 == 1 { - if id <= m.nextStream { // this is a client-side stream that we already opened. - return nil, nil - } - return nil, qerr.Error(qerr.InvalidStreamID, fmt.Sprintf("attempted to open stream %d from server-side", id)) - } - if id <= m.highestStreamOpenedByPeer { // this is a server-side stream that doesn't exist anymore. Must have been closed already - return nil, nil - } - } - - // sid is the next stream that will be opened - sid := m.highestStreamOpenedByPeer + 2 - // if there is no stream opened yet, and this is the server, stream 1 should be openend - if sid == 2 && m.perspective == protocol.PerspectiveServer { - sid = 1 - } - - for ; sid <= id; sid += 2 { - _, err := m.openRemoteStream(sid) - if err != nil { - return nil, err - } - } - - m.nextStreamOrErrCond.Broadcast() - return m.streams[id], nil -} - -func (m *streamsMap) openRemoteStream(id protocol.StreamID) (*stream, error) { - if m.numIncomingStreams >= m.connectionParameters.GetMaxIncomingStreams() { - return nil, qerr.TooManyOpenStreams - } - if id+protocol.MaxNewStreamIDDelta < m.highestStreamOpenedByPeer { - return nil, qerr.Error(qerr.InvalidStreamID, fmt.Sprintf("attempted to open stream %d, which is a lot smaller than the highest opened stream, %d", id, m.highestStreamOpenedByPeer)) - } - - if m.perspective == protocol.PerspectiveServer { - m.numIncomingStreams++ - } else { - m.numOutgoingStreams++ - } - - if id > m.highestStreamOpenedByPeer { - m.highestStreamOpenedByPeer = id - } - - s := m.newStream(id) - m.putStream(s) - return s, nil -} - -func (m *streamsMap) openStreamImpl() (*stream, error) { - id := m.nextStream - if m.numOutgoingStreams >= m.connectionParameters.GetMaxOutgoingStreams() { - return nil, qerr.TooManyOpenStreams - } - - if m.perspective == protocol.PerspectiveServer { - m.numOutgoingStreams++ - } else { - m.numIncomingStreams++ - } - - m.nextStream += 2 - s := m.newStream(id) - m.putStream(s) - return s, nil -} - -// OpenStream opens the next available stream -func (m *streamsMap) OpenStream() (*stream, error) { - m.mutex.Lock() - defer m.mutex.Unlock() - - if m.closeErr != nil { - return nil, m.closeErr - } - return m.openStreamImpl() -} - -func (m *streamsMap) OpenStreamSync() (*stream, error) { - m.mutex.Lock() - defer m.mutex.Unlock() - - for { - if m.closeErr != nil { - return nil, m.closeErr - } - str, err := m.openStreamImpl() - if err == nil { - return str, err - } - if err != nil && err != qerr.TooManyOpenStreams { - return nil, err - } - m.openStreamOrErrCond.Wait() - } -} - -// AcceptStream returns the next stream opened by the peer -// it blocks until a new stream is opened -func (m *streamsMap) AcceptStream() (*stream, error) { - m.mutex.Lock() - defer m.mutex.Unlock() - var str *stream - for { - var ok bool - if m.closeErr != nil { - return nil, m.closeErr - } - str, ok = m.streams[m.nextStreamToAccept] - if ok { - break - } - m.nextStreamOrErrCond.Wait() - } - m.nextStreamToAccept += 2 - return str, nil -} - -func (m *streamsMap) Iterate(fn streamLambda) error { - m.mutex.Lock() - defer m.mutex.Unlock() - - openStreams := append([]protocol.StreamID{}, m.openStreams...) - - for _, streamID := range openStreams { - cont, err := m.iterateFunc(streamID, fn) - if err != nil { - return err - } - if !cont { - break - } - } - return nil -} - -// RoundRobinIterate executes the streamLambda for every open stream, until the streamLambda returns false -// It uses a round-robin-like scheduling to ensure that every stream is considered fairly -// It prioritizes the crypto- and the header-stream (StreamIDs 1 and 3) -func (m *streamsMap) RoundRobinIterate(fn streamLambda) error { - m.mutex.Lock() - defer m.mutex.Unlock() - - numStreams := uint32(len(m.streams)) - startIndex := m.roundRobinIndex - - for _, i := range []protocol.StreamID{1, 3} { - cont, err := m.iterateFunc(i, fn) - if err != nil && err != errMapAccess { - return err - } - if !cont { - return nil - } - } - - for i := uint32(0); i < numStreams; i++ { - streamID := m.openStreams[(i+startIndex)%numStreams] - if streamID == 1 || streamID == 3 { - continue - } - - cont, err := m.iterateFunc(streamID, fn) - if err != nil { - return err - } - m.roundRobinIndex = (m.roundRobinIndex + 1) % numStreams - if !cont { - break - } - } - return nil -} - -func (m *streamsMap) iterateFunc(streamID protocol.StreamID, fn streamLambda) (bool, error) { - str, ok := m.streams[streamID] - if !ok { - return true, errMapAccess - } - return fn(str) -} - -func (m *streamsMap) putStream(s *stream) error { - id := s.StreamID() - if _, ok := m.streams[id]; ok { - return fmt.Errorf("a stream with ID %d already exists", id) - } - - m.streams[id] = s - m.openStreams = append(m.openStreams, id) - return nil -} - -// Attention: this function must only be called if a mutex has been acquired previously -func (m *streamsMap) RemoveStream(id protocol.StreamID) error { - s, ok := m.streams[id] - if !ok || s == nil { - return fmt.Errorf("attempted to remove non-existing stream: %d", id) - } - - if id%2 == 0 { - m.numOutgoingStreams-- - } else { - m.numIncomingStreams-- - } - - for i, s := range m.openStreams { - if s == id { - // delete the streamID from the openStreams slice - m.openStreams = m.openStreams[:i+copy(m.openStreams[i:], m.openStreams[i+1:])] - // adjust round-robin index, if necessary - if uint32(i) < m.roundRobinIndex { - m.roundRobinIndex-- - } - break - } - } - - delete(m.streams, id) - m.openStreamOrErrCond.Signal() - return nil -} - -func (m *streamsMap) CloseWithError(err error) { - m.mutex.Lock() - defer m.mutex.Unlock() - m.closeErr = err - m.nextStreamOrErrCond.Broadcast() - m.openStreamOrErrCond.Broadcast() - for _, s := range m.openStreams { - m.streams[s].Cancel(err) - } -} diff --git a/vendor/github.com/miekg/dns/COPYRIGHT b/vendor/github.com/miekg/dns/COPYRIGHT deleted file mode 100644 index 35702b10e87..00000000000 --- a/vendor/github.com/miekg/dns/COPYRIGHT +++ /dev/null @@ -1,9 +0,0 @@ -Copyright 2009 The Go Authors. All rights reserved. Use of this source code -is governed by a BSD-style license that can be found in the LICENSE file. -Extensions of the original work are copyright (c) 2011 Miek Gieben - -Copyright 2011 Miek Gieben. All rights reserved. Use of this source code is -governed by a BSD-style license that can be found in the LICENSE file. - -Copyright 2014 CloudFlare. All rights reserved. Use of this source code is -governed by a BSD-style license that can be found in the LICENSE file. diff --git a/vendor/github.com/miekg/dns/LICENSE b/vendor/github.com/miekg/dns/LICENSE deleted file mode 100644 index 5763fa7fe5d..00000000000 --- a/vendor/github.com/miekg/dns/LICENSE +++ /dev/null @@ -1,32 +0,0 @@ -Extensions of the original work are copyright (c) 2011 Miek Gieben - -As this is fork of the official Go code the same license applies: - -Copyright (c) 2009 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - diff --git a/vendor/github.com/miekg/dns/client.go b/vendor/github.com/miekg/dns/client.go deleted file mode 100644 index 1c14a19d893..00000000000 --- a/vendor/github.com/miekg/dns/client.go +++ /dev/null @@ -1,531 +0,0 @@ -package dns - -// A client implementation. - -import ( - "bytes" - "context" - "crypto/tls" - "encoding/binary" - "io" - "net" - "time" -) - -const dnsTimeout time.Duration = 2 * time.Second -const tcpIdleTimeout time.Duration = 8 * time.Second - -// A Conn represents a connection to a DNS server. -type Conn struct { - net.Conn // a net.Conn holding the connection - UDPSize uint16 // minimum receive buffer for UDP messages - TsigSecret map[string]string // secret(s) for Tsig map[], zonename must be fully qualified - rtt time.Duration - t time.Time - tsigRequestMAC string -} - -// A Client defines parameters for a DNS client. -type Client struct { - Net string // if "tcp" or "tcp-tls" (DNS over TLS) a TCP query will be initiated, otherwise an UDP one (default is "" for UDP) - UDPSize uint16 // minimum receive buffer for UDP messages - TLSConfig *tls.Config // TLS connection configuration - Timeout time.Duration // a cumulative timeout for dial, write and read, defaults to 0 (disabled) - overrides DialTimeout, ReadTimeout and WriteTimeout when non-zero - DialTimeout time.Duration // net.DialTimeout, defaults to 2 seconds - overridden by Timeout when that value is non-zero - ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero - WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero - TsigSecret map[string]string // secret(s) for Tsig map[], zonename must be fully qualified - SingleInflight bool // if true suppress multiple outstanding queries for the same Qname, Qtype and Qclass - group singleflight -} - -// Exchange performs a synchronous UDP query. It sends the message m to the address -// contained in a and waits for a reply. Exchange does not retry a failed query, nor -// will it fall back to TCP in case of truncation. -// See client.Exchange for more information on setting larger buffer sizes. -func Exchange(m *Msg, a string) (r *Msg, err error) { - var co *Conn - co, err = DialTimeout("udp", a, dnsTimeout) - if err != nil { - return nil, err - } - - defer co.Close() - - opt := m.IsEdns0() - // If EDNS0 is used use that for size. - if opt != nil && opt.UDPSize() >= MinMsgSize { - co.UDPSize = opt.UDPSize() - } - - co.SetWriteDeadline(time.Now().Add(dnsTimeout)) - if err = co.WriteMsg(m); err != nil { - return nil, err - } - - co.SetReadDeadline(time.Now().Add(dnsTimeout)) - r, err = co.ReadMsg() - if err == nil && r.Id != m.Id { - err = ErrId - } - return r, err -} - -// ExchangeContext performs a synchronous UDP query, like Exchange. It -// additionally obeys deadlines from the passed Context. -func ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, err error) { - // Combine context deadline with built-in timeout. Context chooses whichever - // is sooner. - timeoutCtx, cancel := context.WithTimeout(ctx, dnsTimeout) - defer cancel() - deadline, _ := timeoutCtx.Deadline() - - co := new(Conn) - dialer := net.Dialer{} - co.Conn, err = dialer.DialContext(timeoutCtx, "udp", a) - if err != nil { - return nil, err - } - - defer co.Conn.Close() - - opt := m.IsEdns0() - // If EDNS0 is used use that for size. - if opt != nil && opt.UDPSize() >= MinMsgSize { - co.UDPSize = opt.UDPSize() - } - - co.SetWriteDeadline(deadline) - if err = co.WriteMsg(m); err != nil { - return nil, err - } - - co.SetReadDeadline(deadline) - r, err = co.ReadMsg() - if err == nil && r.Id != m.Id { - err = ErrId - } - return r, err -} - -// ExchangeConn performs a synchronous query. It sends the message m via the connection -// c and waits for a reply. The connection c is not closed by ExchangeConn. -// This function is going away, but can easily be mimicked: -// -// co := &dns.Conn{Conn: c} // c is your net.Conn -// co.WriteMsg(m) -// in, _ := co.ReadMsg() -// co.Close() -// -func ExchangeConn(c net.Conn, m *Msg) (r *Msg, err error) { - println("dns: this function is deprecated") - co := new(Conn) - co.Conn = c - if err = co.WriteMsg(m); err != nil { - return nil, err - } - r, err = co.ReadMsg() - if err == nil && r.Id != m.Id { - err = ErrId - } - return r, err -} - -// Exchange performs a synchronous query. It sends the message m to the address -// contained in a and waits for a reply. Basic use pattern with a *dns.Client: -// -// c := new(dns.Client) -// in, rtt, err := c.Exchange(message, "127.0.0.1:53") -// -// Exchange does not retry a failed query, nor will it fall back to TCP in -// case of truncation. -// It is up to the caller to create a message that allows for larger responses to be -// returned. Specifically this means adding an EDNS0 OPT RR that will advertise a larger -// buffer, see SetEdns0. Messages without an OPT RR will fallback to the historic limit -// of 512 bytes. -func (c *Client) Exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err error) { - return c.ExchangeContext(context.Background(), m, a) -} - -// ExchangeContext acts like Exchange, but honors the deadline on the provided -// context, if present. If there is both a context deadline and a configured -// timeout on the client, the earliest of the two takes effect. -func (c *Client) ExchangeContext(ctx context.Context, m *Msg, a string) ( - r *Msg, - rtt time.Duration, - err error) { - if !c.SingleInflight { - return c.exchange(ctx, m, a) - } - // This adds a bunch of garbage, TODO(miek). - t := "nop" - if t1, ok := TypeToString[m.Question[0].Qtype]; ok { - t = t1 - } - cl := "nop" - if cl1, ok := ClassToString[m.Question[0].Qclass]; ok { - cl = cl1 - } - r, rtt, err, shared := c.group.Do(m.Question[0].Name+t+cl, func() (*Msg, time.Duration, error) { - return c.exchange(ctx, m, a) - }) - if r != nil && shared { - r = r.Copy() - } - if err != nil { - return r, rtt, err - } - return r, rtt, nil -} - -func (c *Client) dialTimeout() time.Duration { - if c.Timeout != 0 { - return c.Timeout - } - if c.DialTimeout != 0 { - return c.DialTimeout - } - return dnsTimeout -} - -func (c *Client) readTimeout() time.Duration { - if c.ReadTimeout != 0 { - return c.ReadTimeout - } - return dnsTimeout -} - -func (c *Client) writeTimeout() time.Duration { - if c.WriteTimeout != 0 { - return c.WriteTimeout - } - return dnsTimeout -} - -func (c *Client) exchange(ctx context.Context, m *Msg, a string) (r *Msg, rtt time.Duration, err error) { - var co *Conn - network := "udp" - tls := false - - switch c.Net { - case "tcp-tls": - network = "tcp" - tls = true - case "tcp4-tls": - network = "tcp4" - tls = true - case "tcp6-tls": - network = "tcp6" - tls = true - default: - if c.Net != "" { - network = c.Net - } - } - - var deadline time.Time - if c.Timeout != 0 { - deadline = time.Now().Add(c.Timeout) - } - - dialDeadline := deadlineOrTimeoutOrCtx(ctx, deadline, c.dialTimeout()) - dialTimeout := dialDeadline.Sub(time.Now()) - - if tls { - co, err = DialTimeoutWithTLS(network, a, c.TLSConfig, dialTimeout) - } else { - co, err = DialTimeout(network, a, dialTimeout) - } - - if err != nil { - return nil, 0, err - } - defer co.Close() - - opt := m.IsEdns0() - // If EDNS0 is used use that for size. - if opt != nil && opt.UDPSize() >= MinMsgSize { - co.UDPSize = opt.UDPSize() - } - // Otherwise use the client's configured UDP size. - if opt == nil && c.UDPSize >= MinMsgSize { - co.UDPSize = c.UDPSize - } - - co.TsigSecret = c.TsigSecret - co.SetWriteDeadline(deadlineOrTimeoutOrCtx(ctx, deadline, c.writeTimeout())) - if err = co.WriteMsg(m); err != nil { - return nil, 0, err - } - - co.SetReadDeadline(deadlineOrTimeoutOrCtx(ctx, deadline, c.readTimeout())) - r, err = co.ReadMsg() - if err == nil && r.Id != m.Id { - err = ErrId - } - return r, co.rtt, err -} - -// ReadMsg reads a message from the connection co. -// If the received message contains a TSIG record the transaction -// signature is verified. -func (co *Conn) ReadMsg() (*Msg, error) { - p, err := co.ReadMsgHeader(nil) - if err != nil { - return nil, err - } - - m := new(Msg) - if err := m.Unpack(p); err != nil { - // If ErrTruncated was returned, we still want to allow the user to use - // the message, but naively they can just check err if they don't want - // to use a truncated message - if err == ErrTruncated { - return m, err - } - return nil, err - } - if t := m.IsTsig(); t != nil { - if _, ok := co.TsigSecret[t.Hdr.Name]; !ok { - return m, ErrSecret - } - // Need to work on the original message p, as that was used to calculate the tsig. - err = TsigVerify(p, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false) - } - return m, err -} - -// ReadMsgHeader reads a DNS message, parses and populates hdr (when hdr is not nil). -// Returns message as a byte slice to be parsed with Msg.Unpack later on. -// Note that error handling on the message body is not possible as only the header is parsed. -func (co *Conn) ReadMsgHeader(hdr *Header) ([]byte, error) { - var ( - p []byte - n int - err error - ) - - switch t := co.Conn.(type) { - case *net.TCPConn, *tls.Conn: - r := t.(io.Reader) - - // First two bytes specify the length of the entire message. - l, err := tcpMsgLen(r) - if err != nil { - return nil, err - } - p = make([]byte, l) - n, err = tcpRead(r, p) - co.rtt = time.Since(co.t) - default: - if co.UDPSize > MinMsgSize { - p = make([]byte, co.UDPSize) - } else { - p = make([]byte, MinMsgSize) - } - n, err = co.Read(p) - co.rtt = time.Since(co.t) - } - - if err != nil { - return nil, err - } else if n < headerSize { - return nil, ErrShortRead - } - - p = p[:n] - if hdr != nil { - dh, _, err := unpackMsgHdr(p, 0) - if err != nil { - return nil, err - } - *hdr = dh - } - return p, err -} - -// tcpMsgLen is a helper func to read first two bytes of stream as uint16 packet length. -func tcpMsgLen(t io.Reader) (int, error) { - p := []byte{0, 0} - n, err := t.Read(p) - if err != nil { - return 0, err - } - - // As seen with my local router/switch, retursn 1 byte on the above read, - // resulting a a ShortRead. Just write it out (instead of loop) and read the - // other byte. - if n == 1 { - n1, err := t.Read(p[1:]) - if err != nil { - return 0, err - } - n += n1 - } - - if n != 2 { - return 0, ErrShortRead - } - l := binary.BigEndian.Uint16(p) - if l == 0 { - return 0, ErrShortRead - } - return int(l), nil -} - -// tcpRead calls TCPConn.Read enough times to fill allocated buffer. -func tcpRead(t io.Reader, p []byte) (int, error) { - n, err := t.Read(p) - if err != nil { - return n, err - } - for n < len(p) { - j, err := t.Read(p[n:]) - if err != nil { - return n, err - } - n += j - } - return n, err -} - -// Read implements the net.Conn read method. -func (co *Conn) Read(p []byte) (n int, err error) { - if co.Conn == nil { - return 0, ErrConnEmpty - } - if len(p) < 2 { - return 0, io.ErrShortBuffer - } - switch t := co.Conn.(type) { - case *net.TCPConn, *tls.Conn: - r := t.(io.Reader) - - l, err := tcpMsgLen(r) - if err != nil { - return 0, err - } - if l > len(p) { - return int(l), io.ErrShortBuffer - } - return tcpRead(r, p[:l]) - } - // UDP connection - n, err = co.Conn.Read(p) - if err != nil { - return n, err - } - return n, err -} - -// WriteMsg sends a message through the connection co. -// If the message m contains a TSIG record the transaction -// signature is calculated. -func (co *Conn) WriteMsg(m *Msg) (err error) { - var out []byte - if t := m.IsTsig(); t != nil { - mac := "" - if _, ok := co.TsigSecret[t.Hdr.Name]; !ok { - return ErrSecret - } - out, mac, err = TsigGenerate(m, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false) - // Set for the next read, although only used in zone transfers - co.tsigRequestMAC = mac - } else { - out, err = m.Pack() - } - if err != nil { - return err - } - co.t = time.Now() - if _, err = co.Write(out); err != nil { - return err - } - return nil -} - -// Write implements the net.Conn Write method. -func (co *Conn) Write(p []byte) (n int, err error) { - switch t := co.Conn.(type) { - case *net.TCPConn, *tls.Conn: - w := t.(io.Writer) - - lp := len(p) - if lp < 2 { - return 0, io.ErrShortBuffer - } - if lp > MaxMsgSize { - return 0, &Error{err: "message too large"} - } - l := make([]byte, 2, lp+2) - binary.BigEndian.PutUint16(l, uint16(lp)) - p = append(l, p...) - n, err := io.Copy(w, bytes.NewReader(p)) - return int(n), err - } - n, err = co.Conn.Write(p) - return n, err -} - -// Dial connects to the address on the named network. -func Dial(network, address string) (conn *Conn, err error) { - conn = new(Conn) - conn.Conn, err = net.Dial(network, address) - if err != nil { - return nil, err - } - return conn, nil -} - -// DialTimeout acts like Dial but takes a timeout. -func DialTimeout(network, address string, timeout time.Duration) (conn *Conn, err error) { - conn = new(Conn) - conn.Conn, err = net.DialTimeout(network, address, timeout) - if err != nil { - return nil, err - } - return conn, nil -} - -// DialWithTLS connects to the address on the named network with TLS. -func DialWithTLS(network, address string, tlsConfig *tls.Config) (conn *Conn, err error) { - conn = new(Conn) - conn.Conn, err = tls.Dial(network, address, tlsConfig) - if err != nil { - return nil, err - } - return conn, nil -} - -// DialTimeoutWithTLS acts like DialWithTLS but takes a timeout. -func DialTimeoutWithTLS(network, address string, tlsConfig *tls.Config, timeout time.Duration) (conn *Conn, err error) { - var dialer net.Dialer - dialer.Timeout = timeout - - conn = new(Conn) - conn.Conn, err = tls.DialWithDialer(&dialer, network, address, tlsConfig) - if err != nil { - return nil, err - } - return conn, nil -} - -// deadlineOrTimeout chooses between the provided deadline and timeout -// by always preferring the deadline so long as it's non-zero (regardless -// of which is bigger), and returns the equivalent deadline value. -func deadlineOrTimeout(deadline time.Time, timeout time.Duration) time.Time { - if deadline.IsZero() { - return time.Now().Add(timeout) - } - return deadline -} - -// deadlineOrTimeoutOrCtx returns the earliest of: a context deadline, or the -// output of deadlineOrtimeout. -func deadlineOrTimeoutOrCtx(ctx context.Context, deadline time.Time, timeout time.Duration) time.Time { - result := deadlineOrTimeout(deadline, timeout) - if ctxDeadline, ok := ctx.Deadline(); ok && ctxDeadline.Before(result) { - result = ctxDeadline - } - return result -} diff --git a/vendor/github.com/miekg/dns/clientconfig.go b/vendor/github.com/miekg/dns/clientconfig.go deleted file mode 100644 index 0a1f5a92c57..00000000000 --- a/vendor/github.com/miekg/dns/clientconfig.go +++ /dev/null @@ -1,131 +0,0 @@ -package dns - -import ( - "bufio" - "os" - "strconv" - "strings" -) - -// ClientConfig wraps the contents of the /etc/resolv.conf file. -type ClientConfig struct { - Servers []string // servers to use - Search []string // suffixes to append to local name - Port string // what port to use - Ndots int // number of dots in name to trigger absolute lookup - Timeout int // seconds before giving up on packet - Attempts int // lost packets before giving up on server, not used in the package dns -} - -// ClientConfigFromFile parses a resolv.conf(5) like file and returns -// a *ClientConfig. -func ClientConfigFromFile(resolvconf string) (*ClientConfig, error) { - file, err := os.Open(resolvconf) - if err != nil { - return nil, err - } - defer file.Close() - c := new(ClientConfig) - scanner := bufio.NewScanner(file) - c.Servers = make([]string, 0) - c.Search = make([]string, 0) - c.Port = "53" - c.Ndots = 1 - c.Timeout = 5 - c.Attempts = 2 - - for scanner.Scan() { - if err := scanner.Err(); err != nil { - return nil, err - } - line := scanner.Text() - f := strings.Fields(line) - if len(f) < 1 { - continue - } - switch f[0] { - case "nameserver": // add one name server - if len(f) > 1 { - // One more check: make sure server name is - // just an IP address. Otherwise we need DNS - // to look it up. - name := f[1] - c.Servers = append(c.Servers, name) - } - - case "domain": // set search path to just this domain - if len(f) > 1 { - c.Search = make([]string, 1) - c.Search[0] = f[1] - } else { - c.Search = make([]string, 0) - } - - case "search": // set search path to given servers - c.Search = make([]string, len(f)-1) - for i := 0; i < len(c.Search); i++ { - c.Search[i] = f[i+1] - } - - case "options": // magic options - for i := 1; i < len(f); i++ { - s := f[i] - switch { - case len(s) >= 6 && s[:6] == "ndots:": - n, _ := strconv.Atoi(s[6:]) - if n < 1 { - n = 1 - } - c.Ndots = n - case len(s) >= 8 && s[:8] == "timeout:": - n, _ := strconv.Atoi(s[8:]) - if n < 1 { - n = 1 - } - c.Timeout = n - case len(s) >= 8 && s[:9] == "attempts:": - n, _ := strconv.Atoi(s[9:]) - if n < 1 { - n = 1 - } - c.Attempts = n - case s == "rotate": - /* not imp */ - } - } - } - } - return c, nil -} - -// NameList returns all of the names that should be queried based on the -// config. It is based off of go's net/dns name building, but it does not -// check the length of the resulting names. -func (c *ClientConfig) NameList(name string) []string { - // if this domain is already fully qualified, no append needed. - if IsFqdn(name) { - return []string{name} - } - - // Check to see if the name has more labels than Ndots. Do this before making - // the domain fully qualified. - hasNdots := CountLabel(name) > c.Ndots - // Make the domain fully qualified. - name = Fqdn(name) - - // Make a list of names based off search. - names := []string{} - - // If name has enough dots, try that first. - if hasNdots { - names = append(names, name) - } - for _, s := range c.Search { - names = append(names, Fqdn(name+s)) - } - // If we didn't have enough dots, try after suffixes. - if !hasNdots { - names = append(names, name) - } - return names -} diff --git a/vendor/github.com/miekg/dns/compress_generate.go b/vendor/github.com/miekg/dns/compress_generate.go deleted file mode 100644 index 1a301e9f391..00000000000 --- a/vendor/github.com/miekg/dns/compress_generate.go +++ /dev/null @@ -1,184 +0,0 @@ -//+build ignore - -// compression_generate.go is meant to run with go generate. It will use -// go/{importer,types} to track down all the RR struct types. Then for each type -// it will look to see if there are (compressible) names, if so it will add that -// type to compressionLenHelperType and comressionLenSearchType which "fake" the -// compression so that Len() is fast. -package main - -import ( - "bytes" - "fmt" - "go/format" - "go/importer" - "go/types" - "log" - "os" -) - -var packageHdr = ` -// *** DO NOT MODIFY *** -// AUTOGENERATED BY go generate from compress_generate.go - -package dns - -` - -// getTypeStruct will take a type and the package scope, and return the -// (innermost) struct if the type is considered a RR type (currently defined as -// those structs beginning with a RR_Header, could be redefined as implementing -// the RR interface). The bool return value indicates if embedded structs were -// resolved. -func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) { - st, ok := t.Underlying().(*types.Struct) - if !ok { - return nil, false - } - if st.Field(0).Type() == scope.Lookup("RR_Header").Type() { - return st, false - } - if st.Field(0).Anonymous() { - st, _ := getTypeStruct(st.Field(0).Type(), scope) - return st, true - } - return nil, false -} - -func main() { - // Import and type-check the package - pkg, err := importer.Default().Import("github.com/miekg/dns") - fatalIfErr(err) - scope := pkg.Scope() - - domainTypes := map[string]bool{} // Types that have a domain name in them (either comressible or not). - cdomainTypes := map[string]bool{} // Types that have a compressible domain name in them (subset of domainType) - for _, name := range scope.Names() { - o := scope.Lookup(name) - if o == nil || !o.Exported() { - continue - } - st, _ := getTypeStruct(o.Type(), scope) - if st == nil { - continue - } - if name == "PrivateRR" { - continue - } - - if scope.Lookup("Type"+o.Name()) == nil && o.Name() != "RFC3597" { - log.Fatalf("Constant Type%s does not exist.", o.Name()) - } - - for i := 1; i < st.NumFields(); i++ { - if _, ok := st.Field(i).Type().(*types.Slice); ok { - if st.Tag(i) == `dns:"domain-name"` { - domainTypes[o.Name()] = true - } - if st.Tag(i) == `dns:"cdomain-name"` { - cdomainTypes[o.Name()] = true - domainTypes[o.Name()] = true - } - continue - } - - switch { - case st.Tag(i) == `dns:"domain-name"`: - domainTypes[o.Name()] = true - case st.Tag(i) == `dns:"cdomain-name"`: - cdomainTypes[o.Name()] = true - domainTypes[o.Name()] = true - } - } - } - - b := &bytes.Buffer{} - b.WriteString(packageHdr) - - // compressionLenHelperType - all types that have domain-name/cdomain-name can be used for compressing names - - fmt.Fprint(b, "func compressionLenHelperType(c map[string]int, r RR) {\n") - fmt.Fprint(b, "switch x := r.(type) {\n") - for name, _ := range domainTypes { - o := scope.Lookup(name) - st, _ := getTypeStruct(o.Type(), scope) - - fmt.Fprintf(b, "case *%s:\n", name) - for i := 1; i < st.NumFields(); i++ { - out := func(s string) { fmt.Fprintf(b, "compressionLenHelper(c, x.%s)\n", st.Field(i).Name()) } - - if _, ok := st.Field(i).Type().(*types.Slice); ok { - switch st.Tag(i) { - case `dns:"domain-name"`: - fallthrough - case `dns:"cdomain-name"`: - // For HIP we need to slice over the elements in this slice. - fmt.Fprintf(b, `for i := range x.%s { - compressionLenHelper(c, x.%s[i]) - } -`, st.Field(i).Name(), st.Field(i).Name()) - } - continue - } - - switch { - case st.Tag(i) == `dns:"cdomain-name"`: - fallthrough - case st.Tag(i) == `dns:"domain-name"`: - out(st.Field(i).Name()) - } - } - } - fmt.Fprintln(b, "}\n}\n\n") - - // compressionLenSearchType - search cdomain-tags types for compressible names. - - fmt.Fprint(b, "func compressionLenSearchType(c map[string]int, r RR) (int, bool) {\n") - fmt.Fprint(b, "switch x := r.(type) {\n") - for name, _ := range cdomainTypes { - o := scope.Lookup(name) - st, _ := getTypeStruct(o.Type(), scope) - - fmt.Fprintf(b, "case *%s:\n", name) - j := 1 - for i := 1; i < st.NumFields(); i++ { - out := func(s string, j int) { - fmt.Fprintf(b, "k%d, ok%d := compressionLenSearch(c, x.%s)\n", j, j, st.Field(i).Name()) - } - - // There are no slice types with names that can be compressed. - - switch { - case st.Tag(i) == `dns:"cdomain-name"`: - out(st.Field(i).Name(), j) - j++ - } - } - k := "k1" - ok := "ok1" - for i := 2; i < j; i++ { - k += fmt.Sprintf(" + k%d", i) - ok += fmt.Sprintf(" && ok%d", i) - } - fmt.Fprintf(b, "return %s, %s\n", k, ok) - } - fmt.Fprintln(b, "}\nreturn 0, false\n}\n\n") - - // gofmt - res, err := format.Source(b.Bytes()) - if err != nil { - b.WriteTo(os.Stderr) - log.Fatal(err) - } - - f, err := os.Create("zcompress.go") - fatalIfErr(err) - defer f.Close() - f.Write(res) -} - -func fatalIfErr(err error) { - if err != nil { - log.Fatal(err) - } -} diff --git a/vendor/github.com/miekg/dns/dane.go b/vendor/github.com/miekg/dns/dane.go deleted file mode 100644 index 8c4a14ef190..00000000000 --- a/vendor/github.com/miekg/dns/dane.go +++ /dev/null @@ -1,43 +0,0 @@ -package dns - -import ( - "crypto/sha256" - "crypto/sha512" - "crypto/x509" - "encoding/hex" - "errors" -) - -// CertificateToDANE converts a certificate to a hex string as used in the TLSA or SMIMEA records. -func CertificateToDANE(selector, matchingType uint8, cert *x509.Certificate) (string, error) { - switch matchingType { - case 0: - switch selector { - case 0: - return hex.EncodeToString(cert.Raw), nil - case 1: - return hex.EncodeToString(cert.RawSubjectPublicKeyInfo), nil - } - case 1: - h := sha256.New() - switch selector { - case 0: - h.Write(cert.Raw) - return hex.EncodeToString(h.Sum(nil)), nil - case 1: - h.Write(cert.RawSubjectPublicKeyInfo) - return hex.EncodeToString(h.Sum(nil)), nil - } - case 2: - h := sha512.New() - switch selector { - case 0: - h.Write(cert.Raw) - return hex.EncodeToString(h.Sum(nil)), nil - case 1: - h.Write(cert.RawSubjectPublicKeyInfo) - return hex.EncodeToString(h.Sum(nil)), nil - } - } - return "", errors.New("dns: bad MatchingType or Selector") -} diff --git a/vendor/github.com/miekg/dns/defaults.go b/vendor/github.com/miekg/dns/defaults.go deleted file mode 100644 index c34890eecc7..00000000000 --- a/vendor/github.com/miekg/dns/defaults.go +++ /dev/null @@ -1,285 +0,0 @@ -package dns - -import ( - "errors" - "net" - "strconv" -) - -const hexDigit = "0123456789abcdef" - -// Everything is assumed in ClassINET. - -// SetReply creates a reply message from a request message. -func (dns *Msg) SetReply(request *Msg) *Msg { - dns.Id = request.Id - dns.Response = true - dns.Opcode = request.Opcode - if dns.Opcode == OpcodeQuery { - dns.RecursionDesired = request.RecursionDesired // Copy rd bit - dns.CheckingDisabled = request.CheckingDisabled // Copy cd bit - } - dns.Rcode = RcodeSuccess - if len(request.Question) > 0 { - dns.Question = make([]Question, 1) - dns.Question[0] = request.Question[0] - } - return dns -} - -// SetQuestion creates a question message, it sets the Question -// section, generates an Id and sets the RecursionDesired (RD) -// bit to true. -func (dns *Msg) SetQuestion(z string, t uint16) *Msg { - dns.Id = Id() - dns.RecursionDesired = true - dns.Question = make([]Question, 1) - dns.Question[0] = Question{z, t, ClassINET} - return dns -} - -// SetNotify creates a notify message, it sets the Question -// section, generates an Id and sets the Authoritative (AA) -// bit to true. -func (dns *Msg) SetNotify(z string) *Msg { - dns.Opcode = OpcodeNotify - dns.Authoritative = true - dns.Id = Id() - dns.Question = make([]Question, 1) - dns.Question[0] = Question{z, TypeSOA, ClassINET} - return dns -} - -// SetRcode creates an error message suitable for the request. -func (dns *Msg) SetRcode(request *Msg, rcode int) *Msg { - dns.SetReply(request) - dns.Rcode = rcode - return dns -} - -// SetRcodeFormatError creates a message with FormError set. -func (dns *Msg) SetRcodeFormatError(request *Msg) *Msg { - dns.Rcode = RcodeFormatError - dns.Opcode = OpcodeQuery - dns.Response = true - dns.Authoritative = false - dns.Id = request.Id - return dns -} - -// SetUpdate makes the message a dynamic update message. It -// sets the ZONE section to: z, TypeSOA, ClassINET. -func (dns *Msg) SetUpdate(z string) *Msg { - dns.Id = Id() - dns.Response = false - dns.Opcode = OpcodeUpdate - dns.Compress = false // BIND9 cannot handle compression - dns.Question = make([]Question, 1) - dns.Question[0] = Question{z, TypeSOA, ClassINET} - return dns -} - -// SetIxfr creates message for requesting an IXFR. -func (dns *Msg) SetIxfr(z string, serial uint32, ns, mbox string) *Msg { - dns.Id = Id() - dns.Question = make([]Question, 1) - dns.Ns = make([]RR, 1) - s := new(SOA) - s.Hdr = RR_Header{z, TypeSOA, ClassINET, defaultTtl, 0} - s.Serial = serial - s.Ns = ns - s.Mbox = mbox - dns.Question[0] = Question{z, TypeIXFR, ClassINET} - dns.Ns[0] = s - return dns -} - -// SetAxfr creates message for requesting an AXFR. -func (dns *Msg) SetAxfr(z string) *Msg { - dns.Id = Id() - dns.Question = make([]Question, 1) - dns.Question[0] = Question{z, TypeAXFR, ClassINET} - return dns -} - -// SetTsig appends a TSIG RR to the message. -// This is only a skeleton TSIG RR that is added as the last RR in the -// additional section. The Tsig is calculated when the message is being send. -func (dns *Msg) SetTsig(z, algo string, fudge uint16, timesigned int64) *Msg { - t := new(TSIG) - t.Hdr = RR_Header{z, TypeTSIG, ClassANY, 0, 0} - t.Algorithm = algo - t.Fudge = fudge - t.TimeSigned = uint64(timesigned) - t.OrigId = dns.Id - dns.Extra = append(dns.Extra, t) - return dns -} - -// SetEdns0 appends a EDNS0 OPT RR to the message. -// TSIG should always the last RR in a message. -func (dns *Msg) SetEdns0(udpsize uint16, do bool) *Msg { - e := new(OPT) - e.Hdr.Name = "." - e.Hdr.Rrtype = TypeOPT - e.SetUDPSize(udpsize) - if do { - e.SetDo() - } - dns.Extra = append(dns.Extra, e) - return dns -} - -// IsTsig checks if the message has a TSIG record as the last record -// in the additional section. It returns the TSIG record found or nil. -func (dns *Msg) IsTsig() *TSIG { - if len(dns.Extra) > 0 { - if dns.Extra[len(dns.Extra)-1].Header().Rrtype == TypeTSIG { - return dns.Extra[len(dns.Extra)-1].(*TSIG) - } - } - return nil -} - -// IsEdns0 checks if the message has a EDNS0 (OPT) record, any EDNS0 -// record in the additional section will do. It returns the OPT record -// found or nil. -func (dns *Msg) IsEdns0() *OPT { - // EDNS0 is at the end of the additional section, start there. - // We might want to change this to *only* look at the last two - // records. So we see TSIG and/or OPT - this a slightly bigger - // change though. - for i := len(dns.Extra) - 1; i >= 0; i-- { - if dns.Extra[i].Header().Rrtype == TypeOPT { - return dns.Extra[i].(*OPT) - } - } - return nil -} - -// IsDomainName checks if s is a valid domain name, it returns the number of -// labels and true, when a domain name is valid. Note that non fully qualified -// domain name is considered valid, in this case the last label is counted in -// the number of labels. When false is returned the number of labels is not -// defined. Also note that this function is extremely liberal; almost any -// string is a valid domain name as the DNS is 8 bit protocol. It checks if each -// label fits in 63 characters, but there is no length check for the entire -// string s. I.e. a domain name longer than 255 characters is considered valid. -func IsDomainName(s string) (labels int, ok bool) { - _, labels, err := packDomainName(s, nil, 0, nil, false) - return labels, err == nil -} - -// IsSubDomain checks if child is indeed a child of the parent. If child and parent -// are the same domain true is returned as well. -func IsSubDomain(parent, child string) bool { - // Entire child is contained in parent - return CompareDomainName(parent, child) == CountLabel(parent) -} - -// IsMsg sanity checks buf and returns an error if it isn't a valid DNS packet. -// The checking is performed on the binary payload. -func IsMsg(buf []byte) error { - // Header - if len(buf) < 12 { - return errors.New("dns: bad message header") - } - // Header: Opcode - // TODO(miek): more checks here, e.g. check all header bits. - return nil -} - -// IsFqdn checks if a domain name is fully qualified. -func IsFqdn(s string) bool { - l := len(s) - if l == 0 { - return false - } - return s[l-1] == '.' -} - -// IsRRset checks if a set of RRs is a valid RRset as defined by RFC 2181. -// This means the RRs need to have the same type, name, and class. Returns true -// if the RR set is valid, otherwise false. -func IsRRset(rrset []RR) bool { - if len(rrset) == 0 { - return false - } - if len(rrset) == 1 { - return true - } - rrHeader := rrset[0].Header() - rrType := rrHeader.Rrtype - rrClass := rrHeader.Class - rrName := rrHeader.Name - - for _, rr := range rrset[1:] { - curRRHeader := rr.Header() - if curRRHeader.Rrtype != rrType || curRRHeader.Class != rrClass || curRRHeader.Name != rrName { - // Mismatch between the records, so this is not a valid rrset for - //signing/verifying - return false - } - } - - return true -} - -// Fqdn return the fully qualified domain name from s. -// If s is already fully qualified, it behaves as the identity function. -func Fqdn(s string) string { - if IsFqdn(s) { - return s - } - return s + "." -} - -// Copied from the official Go code. - -// ReverseAddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP -// address suitable for reverse DNS (PTR) record lookups or an error if it fails -// to parse the IP address. -func ReverseAddr(addr string) (arpa string, err error) { - ip := net.ParseIP(addr) - if ip == nil { - return "", &Error{err: "unrecognized address: " + addr} - } - if ip.To4() != nil { - return strconv.Itoa(int(ip[15])) + "." + strconv.Itoa(int(ip[14])) + "." + strconv.Itoa(int(ip[13])) + "." + - strconv.Itoa(int(ip[12])) + ".in-addr.arpa.", nil - } - // Must be IPv6 - buf := make([]byte, 0, len(ip)*4+len("ip6.arpa.")) - // Add it, in reverse, to the buffer - for i := len(ip) - 1; i >= 0; i-- { - v := ip[i] - buf = append(buf, hexDigit[v&0xF]) - buf = append(buf, '.') - buf = append(buf, hexDigit[v>>4]) - buf = append(buf, '.') - } - // Append "ip6.arpa." and return (buf already has the final .) - buf = append(buf, "ip6.arpa."...) - return string(buf), nil -} - -// String returns the string representation for the type t. -func (t Type) String() string { - if t1, ok := TypeToString[uint16(t)]; ok { - return t1 - } - return "TYPE" + strconv.Itoa(int(t)) -} - -// String returns the string representation for the class c. -func (c Class) String() string { - if c1, ok := ClassToString[uint16(c)]; ok { - return c1 - } - return "CLASS" + strconv.Itoa(int(c)) -} - -// String returns the string representation for the name n. -func (n Name) String() string { - return sprintName(string(n)) -} diff --git a/vendor/github.com/miekg/dns/dns.go b/vendor/github.com/miekg/dns/dns.go deleted file mode 100644 index b3292287ce7..00000000000 --- a/vendor/github.com/miekg/dns/dns.go +++ /dev/null @@ -1,104 +0,0 @@ -package dns - -import "strconv" - -const ( - year68 = 1 << 31 // For RFC1982 (Serial Arithmetic) calculations in 32 bits. - defaultTtl = 3600 // Default internal TTL. - - DefaultMsgSize = 4096 // DefaultMsgSize is the standard default for messages larger than 512 bytes. - MinMsgSize = 512 // MinMsgSize is the minimal size of a DNS packet. - MaxMsgSize = 65535 // MaxMsgSize is the largest possible DNS packet. -) - -// Error represents a DNS error. -type Error struct{ err string } - -func (e *Error) Error() string { - if e == nil { - return "dns: " - } - return "dns: " + e.err -} - -// An RR represents a resource record. -type RR interface { - // Header returns the header of an resource record. The header contains - // everything up to the rdata. - Header() *RR_Header - // String returns the text representation of the resource record. - String() string - - // copy returns a copy of the RR - copy() RR - // len returns the length (in octets) of the uncompressed RR in wire format. - len() int - // pack packs an RR into wire format. - pack([]byte, int, map[string]int, bool) (int, error) -} - -// RR_Header is the header all DNS resource records share. -type RR_Header struct { - Name string `dns:"cdomain-name"` - Rrtype uint16 - Class uint16 - Ttl uint32 - Rdlength uint16 // Length of data after header. -} - -// Header returns itself. This is here to make RR_Header implements the RR interface. -func (h *RR_Header) Header() *RR_Header { return h } - -// Just to implement the RR interface. -func (h *RR_Header) copy() RR { return nil } - -func (h *RR_Header) copyHeader() *RR_Header { - r := new(RR_Header) - r.Name = h.Name - r.Rrtype = h.Rrtype - r.Class = h.Class - r.Ttl = h.Ttl - r.Rdlength = h.Rdlength - return r -} - -func (h *RR_Header) String() string { - var s string - - if h.Rrtype == TypeOPT { - s = ";" - // and maybe other things - } - - s += sprintName(h.Name) + "\t" - s += strconv.FormatInt(int64(h.Ttl), 10) + "\t" - s += Class(h.Class).String() + "\t" - s += Type(h.Rrtype).String() + "\t" - return s -} - -func (h *RR_Header) len() int { - l := len(h.Name) + 1 - l += 10 // rrtype(2) + class(2) + ttl(4) + rdlength(2) - return l -} - -// ToRFC3597 converts a known RR to the unknown RR representation from RFC 3597. -func (rr *RFC3597) ToRFC3597(r RR) error { - buf := make([]byte, r.len()*2) - off, err := PackRR(r, buf, 0, nil, false) - if err != nil { - return err - } - buf = buf[:off] - if int(r.Header().Rdlength) > off { - return ErrBuf - } - - rfc3597, _, err := unpackRFC3597(*r.Header(), buf, off-int(r.Header().Rdlength)) - if err != nil { - return err - } - *rr = *rfc3597.(*RFC3597) - return nil -} diff --git a/vendor/github.com/miekg/dns/dnssec.go b/vendor/github.com/miekg/dns/dnssec.go deleted file mode 100644 index 3bd55388d81..00000000000 --- a/vendor/github.com/miekg/dns/dnssec.go +++ /dev/null @@ -1,720 +0,0 @@ -package dns - -import ( - "bytes" - "crypto" - "crypto/dsa" - "crypto/ecdsa" - "crypto/elliptic" - _ "crypto/md5" - "crypto/rand" - "crypto/rsa" - _ "crypto/sha1" - _ "crypto/sha256" - _ "crypto/sha512" - "encoding/asn1" - "encoding/binary" - "encoding/hex" - "math/big" - "sort" - "strings" - "time" -) - -// DNSSEC encryption algorithm codes. -const ( - _ uint8 = iota - RSAMD5 - DH - DSA - _ // Skip 4, RFC 6725, section 2.1 - RSASHA1 - DSANSEC3SHA1 - RSASHA1NSEC3SHA1 - RSASHA256 - _ // Skip 9, RFC 6725, section 2.1 - RSASHA512 - _ // Skip 11, RFC 6725, section 2.1 - ECCGOST - ECDSAP256SHA256 - ECDSAP384SHA384 - INDIRECT uint8 = 252 - PRIVATEDNS uint8 = 253 // Private (experimental keys) - PRIVATEOID uint8 = 254 -) - -// AlgorithmToString is a map of algorithm IDs to algorithm names. -var AlgorithmToString = map[uint8]string{ - RSAMD5: "RSAMD5", - DH: "DH", - DSA: "DSA", - RSASHA1: "RSASHA1", - DSANSEC3SHA1: "DSA-NSEC3-SHA1", - RSASHA1NSEC3SHA1: "RSASHA1-NSEC3-SHA1", - RSASHA256: "RSASHA256", - RSASHA512: "RSASHA512", - ECCGOST: "ECC-GOST", - ECDSAP256SHA256: "ECDSAP256SHA256", - ECDSAP384SHA384: "ECDSAP384SHA384", - INDIRECT: "INDIRECT", - PRIVATEDNS: "PRIVATEDNS", - PRIVATEOID: "PRIVATEOID", -} - -// StringToAlgorithm is the reverse of AlgorithmToString. -var StringToAlgorithm = reverseInt8(AlgorithmToString) - -// AlgorithmToHash is a map of algorithm crypto hash IDs to crypto.Hash's. -var AlgorithmToHash = map[uint8]crypto.Hash{ - RSAMD5: crypto.MD5, // Deprecated in RFC 6725 - RSASHA1: crypto.SHA1, - RSASHA1NSEC3SHA1: crypto.SHA1, - RSASHA256: crypto.SHA256, - ECDSAP256SHA256: crypto.SHA256, - ECDSAP384SHA384: crypto.SHA384, - RSASHA512: crypto.SHA512, -} - -// DNSSEC hashing algorithm codes. -const ( - _ uint8 = iota - SHA1 // RFC 4034 - SHA256 // RFC 4509 - GOST94 // RFC 5933 - SHA384 // Experimental - SHA512 // Experimental -) - -// HashToString is a map of hash IDs to names. -var HashToString = map[uint8]string{ - SHA1: "SHA1", - SHA256: "SHA256", - GOST94: "GOST94", - SHA384: "SHA384", - SHA512: "SHA512", -} - -// StringToHash is a map of names to hash IDs. -var StringToHash = reverseInt8(HashToString) - -// DNSKEY flag values. -const ( - SEP = 1 - REVOKE = 1 << 7 - ZONE = 1 << 8 -) - -// The RRSIG needs to be converted to wireformat with some of the rdata (the signature) missing. -type rrsigWireFmt struct { - TypeCovered uint16 - Algorithm uint8 - Labels uint8 - OrigTtl uint32 - Expiration uint32 - Inception uint32 - KeyTag uint16 - SignerName string `dns:"domain-name"` - /* No Signature */ -} - -// Used for converting DNSKEY's rdata to wirefmt. -type dnskeyWireFmt struct { - Flags uint16 - Protocol uint8 - Algorithm uint8 - PublicKey string `dns:"base64"` - /* Nothing is left out */ -} - -func divRoundUp(a, b int) int { - return (a + b - 1) / b -} - -// KeyTag calculates the keytag (or key-id) of the DNSKEY. -func (k *DNSKEY) KeyTag() uint16 { - if k == nil { - return 0 - } - var keytag int - switch k.Algorithm { - case RSAMD5: - // Look at the bottom two bytes of the modules, which the last - // item in the pubkey. We could do this faster by looking directly - // at the base64 values. But I'm lazy. - modulus, _ := fromBase64([]byte(k.PublicKey)) - if len(modulus) > 1 { - x := binary.BigEndian.Uint16(modulus[len(modulus)-2:]) - keytag = int(x) - } - default: - keywire := new(dnskeyWireFmt) - keywire.Flags = k.Flags - keywire.Protocol = k.Protocol - keywire.Algorithm = k.Algorithm - keywire.PublicKey = k.PublicKey - wire := make([]byte, DefaultMsgSize) - n, err := packKeyWire(keywire, wire) - if err != nil { - return 0 - } - wire = wire[:n] - for i, v := range wire { - if i&1 != 0 { - keytag += int(v) // must be larger than uint32 - } else { - keytag += int(v) << 8 - } - } - keytag += (keytag >> 16) & 0xFFFF - keytag &= 0xFFFF - } - return uint16(keytag) -} - -// ToDS converts a DNSKEY record to a DS record. -func (k *DNSKEY) ToDS(h uint8) *DS { - if k == nil { - return nil - } - ds := new(DS) - ds.Hdr.Name = k.Hdr.Name - ds.Hdr.Class = k.Hdr.Class - ds.Hdr.Rrtype = TypeDS - ds.Hdr.Ttl = k.Hdr.Ttl - ds.Algorithm = k.Algorithm - ds.DigestType = h - ds.KeyTag = k.KeyTag() - - keywire := new(dnskeyWireFmt) - keywire.Flags = k.Flags - keywire.Protocol = k.Protocol - keywire.Algorithm = k.Algorithm - keywire.PublicKey = k.PublicKey - wire := make([]byte, DefaultMsgSize) - n, err := packKeyWire(keywire, wire) - if err != nil { - return nil - } - wire = wire[:n] - - owner := make([]byte, 255) - off, err1 := PackDomainName(strings.ToLower(k.Hdr.Name), owner, 0, nil, false) - if err1 != nil { - return nil - } - owner = owner[:off] - // RFC4034: - // digest = digest_algorithm( DNSKEY owner name | DNSKEY RDATA); - // "|" denotes concatenation - // DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key. - - var hash crypto.Hash - switch h { - case SHA1: - hash = crypto.SHA1 - case SHA256: - hash = crypto.SHA256 - case SHA384: - hash = crypto.SHA384 - case SHA512: - hash = crypto.SHA512 - default: - return nil - } - - s := hash.New() - s.Write(owner) - s.Write(wire) - ds.Digest = hex.EncodeToString(s.Sum(nil)) - return ds -} - -// ToCDNSKEY converts a DNSKEY record to a CDNSKEY record. -func (k *DNSKEY) ToCDNSKEY() *CDNSKEY { - c := &CDNSKEY{DNSKEY: *k} - c.Hdr = *k.Hdr.copyHeader() - c.Hdr.Rrtype = TypeCDNSKEY - return c -} - -// ToCDS converts a DS record to a CDS record. -func (d *DS) ToCDS() *CDS { - c := &CDS{DS: *d} - c.Hdr = *d.Hdr.copyHeader() - c.Hdr.Rrtype = TypeCDS - return c -} - -// Sign signs an RRSet. The signature needs to be filled in with the values: -// Inception, Expiration, KeyTag, SignerName and Algorithm. The rest is copied -// from the RRset. Sign returns a non-nill error when the signing went OK. -// There is no check if RRSet is a proper (RFC 2181) RRSet. If OrigTTL is non -// zero, it is used as-is, otherwise the TTL of the RRset is used as the -// OrigTTL. -func (rr *RRSIG) Sign(k crypto.Signer, rrset []RR) error { - if k == nil { - return ErrPrivKey - } - // s.Inception and s.Expiration may be 0 (rollover etc.), the rest must be set - if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 { - return ErrKey - } - - rr.Hdr.Rrtype = TypeRRSIG - rr.Hdr.Name = rrset[0].Header().Name - rr.Hdr.Class = rrset[0].Header().Class - if rr.OrigTtl == 0 { // If set don't override - rr.OrigTtl = rrset[0].Header().Ttl - } - rr.TypeCovered = rrset[0].Header().Rrtype - rr.Labels = uint8(CountLabel(rrset[0].Header().Name)) - - if strings.HasPrefix(rrset[0].Header().Name, "*") { - rr.Labels-- // wildcard, remove from label count - } - - sigwire := new(rrsigWireFmt) - sigwire.TypeCovered = rr.TypeCovered - sigwire.Algorithm = rr.Algorithm - sigwire.Labels = rr.Labels - sigwire.OrigTtl = rr.OrigTtl - sigwire.Expiration = rr.Expiration - sigwire.Inception = rr.Inception - sigwire.KeyTag = rr.KeyTag - // For signing, lowercase this name - sigwire.SignerName = strings.ToLower(rr.SignerName) - - // Create the desired binary blob - signdata := make([]byte, DefaultMsgSize) - n, err := packSigWire(sigwire, signdata) - if err != nil { - return err - } - signdata = signdata[:n] - wire, err := rawSignatureData(rrset, rr) - if err != nil { - return err - } - - hash, ok := AlgorithmToHash[rr.Algorithm] - if !ok { - return ErrAlg - } - - h := hash.New() - h.Write(signdata) - h.Write(wire) - - signature, err := sign(k, h.Sum(nil), hash, rr.Algorithm) - if err != nil { - return err - } - - rr.Signature = toBase64(signature) - - return nil -} - -func sign(k crypto.Signer, hashed []byte, hash crypto.Hash, alg uint8) ([]byte, error) { - signature, err := k.Sign(rand.Reader, hashed, hash) - if err != nil { - return nil, err - } - - switch alg { - case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512: - return signature, nil - - case ECDSAP256SHA256, ECDSAP384SHA384: - ecdsaSignature := &struct { - R, S *big.Int - }{} - if _, err := asn1.Unmarshal(signature, ecdsaSignature); err != nil { - return nil, err - } - - var intlen int - switch alg { - case ECDSAP256SHA256: - intlen = 32 - case ECDSAP384SHA384: - intlen = 48 - } - - signature := intToBytes(ecdsaSignature.R, intlen) - signature = append(signature, intToBytes(ecdsaSignature.S, intlen)...) - return signature, nil - - // There is no defined interface for what a DSA backed crypto.Signer returns - case DSA, DSANSEC3SHA1: - // t := divRoundUp(divRoundUp(p.PublicKey.Y.BitLen(), 8)-64, 8) - // signature := []byte{byte(t)} - // signature = append(signature, intToBytes(r1, 20)...) - // signature = append(signature, intToBytes(s1, 20)...) - // rr.Signature = signature - } - - return nil, ErrAlg -} - -// Verify validates an RRSet with the signature and key. This is only the -// cryptographic test, the signature validity period must be checked separately. -// This function copies the rdata of some RRs (to lowercase domain names) for the validation to work. -func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error { - // First the easy checks - if !IsRRset(rrset) { - return ErrRRset - } - if rr.KeyTag != k.KeyTag() { - return ErrKey - } - if rr.Hdr.Class != k.Hdr.Class { - return ErrKey - } - if rr.Algorithm != k.Algorithm { - return ErrKey - } - if strings.ToLower(rr.SignerName) != strings.ToLower(k.Hdr.Name) { - return ErrKey - } - if k.Protocol != 3 { - return ErrKey - } - - // IsRRset checked that we have at least one RR and that the RRs in - // the set have consistent type, class, and name. Also check that type and - // class matches the RRSIG record. - if rrset[0].Header().Class != rr.Hdr.Class { - return ErrRRset - } - if rrset[0].Header().Rrtype != rr.TypeCovered { - return ErrRRset - } - - // RFC 4035 5.3.2. Reconstructing the Signed Data - // Copy the sig, except the rrsig data - sigwire := new(rrsigWireFmt) - sigwire.TypeCovered = rr.TypeCovered - sigwire.Algorithm = rr.Algorithm - sigwire.Labels = rr.Labels - sigwire.OrigTtl = rr.OrigTtl - sigwire.Expiration = rr.Expiration - sigwire.Inception = rr.Inception - sigwire.KeyTag = rr.KeyTag - sigwire.SignerName = strings.ToLower(rr.SignerName) - // Create the desired binary blob - signeddata := make([]byte, DefaultMsgSize) - n, err := packSigWire(sigwire, signeddata) - if err != nil { - return err - } - signeddata = signeddata[:n] - wire, err := rawSignatureData(rrset, rr) - if err != nil { - return err - } - - sigbuf := rr.sigBuf() // Get the binary signature data - if rr.Algorithm == PRIVATEDNS { // PRIVATEOID - // TODO(miek) - // remove the domain name and assume its ours? - } - - hash, ok := AlgorithmToHash[rr.Algorithm] - if !ok { - return ErrAlg - } - - switch rr.Algorithm { - case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512, RSAMD5: - // TODO(mg): this can be done quicker, ie. cache the pubkey data somewhere?? - pubkey := k.publicKeyRSA() // Get the key - if pubkey == nil { - return ErrKey - } - - h := hash.New() - h.Write(signeddata) - h.Write(wire) - return rsa.VerifyPKCS1v15(pubkey, hash, h.Sum(nil), sigbuf) - - case ECDSAP256SHA256, ECDSAP384SHA384: - pubkey := k.publicKeyECDSA() - if pubkey == nil { - return ErrKey - } - - // Split sigbuf into the r and s coordinates - r := new(big.Int).SetBytes(sigbuf[:len(sigbuf)/2]) - s := new(big.Int).SetBytes(sigbuf[len(sigbuf)/2:]) - - h := hash.New() - h.Write(signeddata) - h.Write(wire) - if ecdsa.Verify(pubkey, h.Sum(nil), r, s) { - return nil - } - return ErrSig - - default: - return ErrAlg - } -} - -// ValidityPeriod uses RFC1982 serial arithmetic to calculate -// if a signature period is valid. If t is the zero time, the -// current time is taken other t is. Returns true if the signature -// is valid at the given time, otherwise returns false. -func (rr *RRSIG) ValidityPeriod(t time.Time) bool { - var utc int64 - if t.IsZero() { - utc = time.Now().UTC().Unix() - } else { - utc = t.UTC().Unix() - } - modi := (int64(rr.Inception) - utc) / year68 - mode := (int64(rr.Expiration) - utc) / year68 - ti := int64(rr.Inception) + (modi * year68) - te := int64(rr.Expiration) + (mode * year68) - return ti <= utc && utc <= te -} - -// Return the signatures base64 encodedig sigdata as a byte slice. -func (rr *RRSIG) sigBuf() []byte { - sigbuf, err := fromBase64([]byte(rr.Signature)) - if err != nil { - return nil - } - return sigbuf -} - -// publicKeyRSA returns the RSA public key from a DNSKEY record. -func (k *DNSKEY) publicKeyRSA() *rsa.PublicKey { - keybuf, err := fromBase64([]byte(k.PublicKey)) - if err != nil { - return nil - } - - // RFC 2537/3110, section 2. RSA Public KEY Resource Records - // Length is in the 0th byte, unless its zero, then it - // it in bytes 1 and 2 and its a 16 bit number - explen := uint16(keybuf[0]) - keyoff := 1 - if explen == 0 { - explen = uint16(keybuf[1])<<8 | uint16(keybuf[2]) - keyoff = 3 - } - pubkey := new(rsa.PublicKey) - - pubkey.N = big.NewInt(0) - shift := uint64((explen - 1) * 8) - expo := uint64(0) - for i := int(explen - 1); i > 0; i-- { - expo += uint64(keybuf[keyoff+i]) << shift - shift -= 8 - } - // Remainder - expo += uint64(keybuf[keyoff]) - if expo > (2<<31)+1 { - // Larger expo than supported. - // println("dns: F5 primes (or larger) are not supported") - return nil - } - pubkey.E = int(expo) - - pubkey.N.SetBytes(keybuf[keyoff+int(explen):]) - return pubkey -} - -// publicKeyECDSA returns the Curve public key from the DNSKEY record. -func (k *DNSKEY) publicKeyECDSA() *ecdsa.PublicKey { - keybuf, err := fromBase64([]byte(k.PublicKey)) - if err != nil { - return nil - } - pubkey := new(ecdsa.PublicKey) - switch k.Algorithm { - case ECDSAP256SHA256: - pubkey.Curve = elliptic.P256() - if len(keybuf) != 64 { - // wrongly encoded key - return nil - } - case ECDSAP384SHA384: - pubkey.Curve = elliptic.P384() - if len(keybuf) != 96 { - // Wrongly encoded key - return nil - } - } - pubkey.X = big.NewInt(0) - pubkey.X.SetBytes(keybuf[:len(keybuf)/2]) - pubkey.Y = big.NewInt(0) - pubkey.Y.SetBytes(keybuf[len(keybuf)/2:]) - return pubkey -} - -func (k *DNSKEY) publicKeyDSA() *dsa.PublicKey { - keybuf, err := fromBase64([]byte(k.PublicKey)) - if err != nil { - return nil - } - if len(keybuf) < 22 { - return nil - } - t, keybuf := int(keybuf[0]), keybuf[1:] - size := 64 + t*8 - q, keybuf := keybuf[:20], keybuf[20:] - if len(keybuf) != 3*size { - return nil - } - p, keybuf := keybuf[:size], keybuf[size:] - g, y := keybuf[:size], keybuf[size:] - pubkey := new(dsa.PublicKey) - pubkey.Parameters.Q = big.NewInt(0).SetBytes(q) - pubkey.Parameters.P = big.NewInt(0).SetBytes(p) - pubkey.Parameters.G = big.NewInt(0).SetBytes(g) - pubkey.Y = big.NewInt(0).SetBytes(y) - return pubkey -} - -type wireSlice [][]byte - -func (p wireSlice) Len() int { return len(p) } -func (p wireSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } -func (p wireSlice) Less(i, j int) bool { - _, ioff, _ := UnpackDomainName(p[i], 0) - _, joff, _ := UnpackDomainName(p[j], 0) - return bytes.Compare(p[i][ioff+10:], p[j][joff+10:]) < 0 -} - -// Return the raw signature data. -func rawSignatureData(rrset []RR, s *RRSIG) (buf []byte, err error) { - wires := make(wireSlice, len(rrset)) - for i, r := range rrset { - r1 := r.copy() - r1.Header().Ttl = s.OrigTtl - labels := SplitDomainName(r1.Header().Name) - // 6.2. Canonical RR Form. (4) - wildcards - if len(labels) > int(s.Labels) { - // Wildcard - r1.Header().Name = "*." + strings.Join(labels[len(labels)-int(s.Labels):], ".") + "." - } - // RFC 4034: 6.2. Canonical RR Form. (2) - domain name to lowercase - r1.Header().Name = strings.ToLower(r1.Header().Name) - // 6.2. Canonical RR Form. (3) - domain rdata to lowercase. - // NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR, - // HINFO, MINFO, MX, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX, - // SRV, DNAME, A6 - // - // RFC 6840 - Clarifications and Implementation Notes for DNS Security (DNSSEC): - // Section 6.2 of [RFC4034] also erroneously lists HINFO as a record - // that needs conversion to lowercase, and twice at that. Since HINFO - // records contain no domain names, they are not subject to case - // conversion. - switch x := r1.(type) { - case *NS: - x.Ns = strings.ToLower(x.Ns) - case *CNAME: - x.Target = strings.ToLower(x.Target) - case *SOA: - x.Ns = strings.ToLower(x.Ns) - x.Mbox = strings.ToLower(x.Mbox) - case *MB: - x.Mb = strings.ToLower(x.Mb) - case *MG: - x.Mg = strings.ToLower(x.Mg) - case *MR: - x.Mr = strings.ToLower(x.Mr) - case *PTR: - x.Ptr = strings.ToLower(x.Ptr) - case *MINFO: - x.Rmail = strings.ToLower(x.Rmail) - x.Email = strings.ToLower(x.Email) - case *MX: - x.Mx = strings.ToLower(x.Mx) - case *NAPTR: - x.Replacement = strings.ToLower(x.Replacement) - case *KX: - x.Exchanger = strings.ToLower(x.Exchanger) - case *SRV: - x.Target = strings.ToLower(x.Target) - case *DNAME: - x.Target = strings.ToLower(x.Target) - } - // 6.2. Canonical RR Form. (5) - origTTL - wire := make([]byte, r1.len()+1) // +1 to be safe(r) - off, err1 := PackRR(r1, wire, 0, nil, false) - if err1 != nil { - return nil, err1 - } - wire = wire[:off] - wires[i] = wire - } - sort.Sort(wires) - for i, wire := range wires { - if i > 0 && bytes.Equal(wire, wires[i-1]) { - continue - } - buf = append(buf, wire...) - } - return buf, nil -} - -func packSigWire(sw *rrsigWireFmt, msg []byte) (int, error) { - // copied from zmsg.go RRSIG packing - off, err := packUint16(sw.TypeCovered, msg, 0) - if err != nil { - return off, err - } - off, err = packUint8(sw.Algorithm, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(sw.Labels, msg, off) - if err != nil { - return off, err - } - off, err = packUint32(sw.OrigTtl, msg, off) - if err != nil { - return off, err - } - off, err = packUint32(sw.Expiration, msg, off) - if err != nil { - return off, err - } - off, err = packUint32(sw.Inception, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(sw.KeyTag, msg, off) - if err != nil { - return off, err - } - off, err = PackDomainName(sw.SignerName, msg, off, nil, false) - if err != nil { - return off, err - } - return off, nil -} - -func packKeyWire(dw *dnskeyWireFmt, msg []byte) (int, error) { - // copied from zmsg.go DNSKEY packing - off, err := packUint16(dw.Flags, msg, 0) - if err != nil { - return off, err - } - off, err = packUint8(dw.Protocol, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(dw.Algorithm, msg, off) - if err != nil { - return off, err - } - off, err = packStringBase64(dw.PublicKey, msg, off) - if err != nil { - return off, err - } - return off, nil -} diff --git a/vendor/github.com/miekg/dns/dnssec_keygen.go b/vendor/github.com/miekg/dns/dnssec_keygen.go deleted file mode 100644 index 5e4b7741a69..00000000000 --- a/vendor/github.com/miekg/dns/dnssec_keygen.go +++ /dev/null @@ -1,156 +0,0 @@ -package dns - -import ( - "crypto" - "crypto/dsa" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/rsa" - "math/big" -) - -// Generate generates a DNSKEY of the given bit size. -// The public part is put inside the DNSKEY record. -// The Algorithm in the key must be set as this will define -// what kind of DNSKEY will be generated. -// The ECDSA algorithms imply a fixed keysize, in that case -// bits should be set to the size of the algorithm. -func (k *DNSKEY) Generate(bits int) (crypto.PrivateKey, error) { - switch k.Algorithm { - case DSA, DSANSEC3SHA1: - if bits != 1024 { - return nil, ErrKeySize - } - case RSAMD5, RSASHA1, RSASHA256, RSASHA1NSEC3SHA1: - if bits < 512 || bits > 4096 { - return nil, ErrKeySize - } - case RSASHA512: - if bits < 1024 || bits > 4096 { - return nil, ErrKeySize - } - case ECDSAP256SHA256: - if bits != 256 { - return nil, ErrKeySize - } - case ECDSAP384SHA384: - if bits != 384 { - return nil, ErrKeySize - } - } - - switch k.Algorithm { - case DSA, DSANSEC3SHA1: - params := new(dsa.Parameters) - if err := dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160); err != nil { - return nil, err - } - priv := new(dsa.PrivateKey) - priv.PublicKey.Parameters = *params - err := dsa.GenerateKey(priv, rand.Reader) - if err != nil { - return nil, err - } - k.setPublicKeyDSA(params.Q, params.P, params.G, priv.PublicKey.Y) - return priv, nil - case RSAMD5, RSASHA1, RSASHA256, RSASHA512, RSASHA1NSEC3SHA1: - priv, err := rsa.GenerateKey(rand.Reader, bits) - if err != nil { - return nil, err - } - k.setPublicKeyRSA(priv.PublicKey.E, priv.PublicKey.N) - return priv, nil - case ECDSAP256SHA256, ECDSAP384SHA384: - var c elliptic.Curve - switch k.Algorithm { - case ECDSAP256SHA256: - c = elliptic.P256() - case ECDSAP384SHA384: - c = elliptic.P384() - } - priv, err := ecdsa.GenerateKey(c, rand.Reader) - if err != nil { - return nil, err - } - k.setPublicKeyECDSA(priv.PublicKey.X, priv.PublicKey.Y) - return priv, nil - default: - return nil, ErrAlg - } -} - -// Set the public key (the value E and N) -func (k *DNSKEY) setPublicKeyRSA(_E int, _N *big.Int) bool { - if _E == 0 || _N == nil { - return false - } - buf := exponentToBuf(_E) - buf = append(buf, _N.Bytes()...) - k.PublicKey = toBase64(buf) - return true -} - -// Set the public key for Elliptic Curves -func (k *DNSKEY) setPublicKeyECDSA(_X, _Y *big.Int) bool { - if _X == nil || _Y == nil { - return false - } - var intlen int - switch k.Algorithm { - case ECDSAP256SHA256: - intlen = 32 - case ECDSAP384SHA384: - intlen = 48 - } - k.PublicKey = toBase64(curveToBuf(_X, _Y, intlen)) - return true -} - -// Set the public key for DSA -func (k *DNSKEY) setPublicKeyDSA(_Q, _P, _G, _Y *big.Int) bool { - if _Q == nil || _P == nil || _G == nil || _Y == nil { - return false - } - buf := dsaToBuf(_Q, _P, _G, _Y) - k.PublicKey = toBase64(buf) - return true -} - -// Set the public key (the values E and N) for RSA -// RFC 3110: Section 2. RSA Public KEY Resource Records -func exponentToBuf(_E int) []byte { - var buf []byte - i := big.NewInt(int64(_E)).Bytes() - if len(i) < 256 { - buf = make([]byte, 1, 1+len(i)) - buf[0] = uint8(len(i)) - } else { - buf = make([]byte, 3, 3+len(i)) - buf[0] = 0 - buf[1] = uint8(len(i) >> 8) - buf[2] = uint8(len(i)) - } - buf = append(buf, i...) - return buf -} - -// Set the public key for X and Y for Curve. The two -// values are just concatenated. -func curveToBuf(_X, _Y *big.Int, intlen int) []byte { - buf := intToBytes(_X, intlen) - buf = append(buf, intToBytes(_Y, intlen)...) - return buf -} - -// Set the public key for X and Y for Curve. The two -// values are just concatenated. -func dsaToBuf(_Q, _P, _G, _Y *big.Int) []byte { - t := divRoundUp(divRoundUp(_G.BitLen(), 8)-64, 8) - buf := []byte{byte(t)} - buf = append(buf, intToBytes(_Q, 20)...) - buf = append(buf, intToBytes(_P, 64+t*8)...) - buf = append(buf, intToBytes(_G, 64+t*8)...) - buf = append(buf, intToBytes(_Y, 64+t*8)...) - return buf -} diff --git a/vendor/github.com/miekg/dns/dnssec_keyscan.go b/vendor/github.com/miekg/dns/dnssec_keyscan.go deleted file mode 100644 index 4f8d830b858..00000000000 --- a/vendor/github.com/miekg/dns/dnssec_keyscan.go +++ /dev/null @@ -1,249 +0,0 @@ -package dns - -import ( - "crypto" - "crypto/dsa" - "crypto/ecdsa" - "crypto/rsa" - "io" - "math/big" - "strconv" - "strings" -) - -// NewPrivateKey returns a PrivateKey by parsing the string s. -// s should be in the same form of the BIND private key files. -func (k *DNSKEY) NewPrivateKey(s string) (crypto.PrivateKey, error) { - if s == "" || s[len(s)-1] != '\n' { // We need a closing newline - return k.ReadPrivateKey(strings.NewReader(s+"\n"), "") - } - return k.ReadPrivateKey(strings.NewReader(s), "") -} - -// ReadPrivateKey reads a private key from the io.Reader q. The string file is -// only used in error reporting. -// The public key must be known, because some cryptographic algorithms embed -// the public inside the privatekey. -func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, error) { - m, err := parseKey(q, file) - if m == nil { - return nil, err - } - if _, ok := m["private-key-format"]; !ok { - return nil, ErrPrivKey - } - if m["private-key-format"] != "v1.2" && m["private-key-format"] != "v1.3" { - return nil, ErrPrivKey - } - // TODO(mg): check if the pubkey matches the private key - algo, err := strconv.ParseUint(strings.SplitN(m["algorithm"], " ", 2)[0], 10, 8) - if err != nil { - return nil, ErrPrivKey - } - switch uint8(algo) { - case DSA: - priv, err := readPrivateKeyDSA(m) - if err != nil { - return nil, err - } - pub := k.publicKeyDSA() - if pub == nil { - return nil, ErrKey - } - priv.PublicKey = *pub - return priv, nil - case RSAMD5: - fallthrough - case RSASHA1: - fallthrough - case RSASHA1NSEC3SHA1: - fallthrough - case RSASHA256: - fallthrough - case RSASHA512: - priv, err := readPrivateKeyRSA(m) - if err != nil { - return nil, err - } - pub := k.publicKeyRSA() - if pub == nil { - return nil, ErrKey - } - priv.PublicKey = *pub - return priv, nil - case ECCGOST: - return nil, ErrPrivKey - case ECDSAP256SHA256: - fallthrough - case ECDSAP384SHA384: - priv, err := readPrivateKeyECDSA(m) - if err != nil { - return nil, err - } - pub := k.publicKeyECDSA() - if pub == nil { - return nil, ErrKey - } - priv.PublicKey = *pub - return priv, nil - default: - return nil, ErrPrivKey - } -} - -// Read a private key (file) string and create a public key. Return the private key. -func readPrivateKeyRSA(m map[string]string) (*rsa.PrivateKey, error) { - p := new(rsa.PrivateKey) - p.Primes = []*big.Int{nil, nil} - for k, v := range m { - switch k { - case "modulus", "publicexponent", "privateexponent", "prime1", "prime2": - v1, err := fromBase64([]byte(v)) - if err != nil { - return nil, err - } - switch k { - case "modulus": - p.PublicKey.N = big.NewInt(0) - p.PublicKey.N.SetBytes(v1) - case "publicexponent": - i := big.NewInt(0) - i.SetBytes(v1) - p.PublicKey.E = int(i.Int64()) // int64 should be large enough - case "privateexponent": - p.D = big.NewInt(0) - p.D.SetBytes(v1) - case "prime1": - p.Primes[0] = big.NewInt(0) - p.Primes[0].SetBytes(v1) - case "prime2": - p.Primes[1] = big.NewInt(0) - p.Primes[1].SetBytes(v1) - } - case "exponent1", "exponent2", "coefficient": - // not used in Go (yet) - case "created", "publish", "activate": - // not used in Go (yet) - } - } - return p, nil -} - -func readPrivateKeyDSA(m map[string]string) (*dsa.PrivateKey, error) { - p := new(dsa.PrivateKey) - p.X = big.NewInt(0) - for k, v := range m { - switch k { - case "private_value(x)": - v1, err := fromBase64([]byte(v)) - if err != nil { - return nil, err - } - p.X.SetBytes(v1) - case "created", "publish", "activate": - /* not used in Go (yet) */ - } - } - return p, nil -} - -func readPrivateKeyECDSA(m map[string]string) (*ecdsa.PrivateKey, error) { - p := new(ecdsa.PrivateKey) - p.D = big.NewInt(0) - // TODO: validate that the required flags are present - for k, v := range m { - switch k { - case "privatekey": - v1, err := fromBase64([]byte(v)) - if err != nil { - return nil, err - } - p.D.SetBytes(v1) - case "created", "publish", "activate": - /* not used in Go (yet) */ - } - } - return p, nil -} - -// parseKey reads a private key from r. It returns a map[string]string, -// with the key-value pairs, or an error when the file is not correct. -func parseKey(r io.Reader, file string) (map[string]string, error) { - s := scanInit(r) - m := make(map[string]string) - c := make(chan lex) - k := "" - // Start the lexer - go klexer(s, c) - for l := range c { - // It should alternate - switch l.value { - case zKey: - k = l.token - case zValue: - if k == "" { - return nil, &ParseError{file, "no private key seen", l} - } - //println("Setting", strings.ToLower(k), "to", l.token, "b") - m[strings.ToLower(k)] = l.token - k = "" - } - } - return m, nil -} - -// klexer scans the sourcefile and returns tokens on the channel c. -func klexer(s *scan, c chan lex) { - var l lex - str := "" // Hold the current read text - commt := false - key := true - x, err := s.tokenText() - defer close(c) - for err == nil { - l.column = s.position.Column - l.line = s.position.Line - switch x { - case ':': - if commt { - break - } - l.token = str - if key { - l.value = zKey - c <- l - // Next token is a space, eat it - s.tokenText() - key = false - str = "" - } else { - l.value = zValue - } - case ';': - commt = true - case '\n': - if commt { - // Reset a comment - commt = false - } - l.value = zValue - l.token = str - c <- l - str = "" - commt = false - key = true - default: - if commt { - break - } - str += string(x) - } - x, err = s.tokenText() - } - if len(str) > 0 { - // Send remainder - l.token = str - l.value = zValue - c <- l - } -} diff --git a/vendor/github.com/miekg/dns/dnssec_privkey.go b/vendor/github.com/miekg/dns/dnssec_privkey.go deleted file mode 100644 index 56f3ea934f6..00000000000 --- a/vendor/github.com/miekg/dns/dnssec_privkey.go +++ /dev/null @@ -1,85 +0,0 @@ -package dns - -import ( - "crypto" - "crypto/dsa" - "crypto/ecdsa" - "crypto/rsa" - "math/big" - "strconv" -) - -const format = "Private-key-format: v1.3\n" - -// PrivateKeyString converts a PrivateKey to a string. This string has the same -// format as the private-key-file of BIND9 (Private-key-format: v1.3). -// It needs some info from the key (the algorithm), so its a method of the DNSKEY -// It supports rsa.PrivateKey, ecdsa.PrivateKey and dsa.PrivateKey -func (r *DNSKEY) PrivateKeyString(p crypto.PrivateKey) string { - algorithm := strconv.Itoa(int(r.Algorithm)) - algorithm += " (" + AlgorithmToString[r.Algorithm] + ")" - - switch p := p.(type) { - case *rsa.PrivateKey: - modulus := toBase64(p.PublicKey.N.Bytes()) - e := big.NewInt(int64(p.PublicKey.E)) - publicExponent := toBase64(e.Bytes()) - privateExponent := toBase64(p.D.Bytes()) - prime1 := toBase64(p.Primes[0].Bytes()) - prime2 := toBase64(p.Primes[1].Bytes()) - // Calculate Exponent1/2 and Coefficient as per: http://en.wikipedia.org/wiki/RSA#Using_the_Chinese_remainder_algorithm - // and from: http://code.google.com/p/go/issues/detail?id=987 - one := big.NewInt(1) - p1 := big.NewInt(0).Sub(p.Primes[0], one) - q1 := big.NewInt(0).Sub(p.Primes[1], one) - exp1 := big.NewInt(0).Mod(p.D, p1) - exp2 := big.NewInt(0).Mod(p.D, q1) - coeff := big.NewInt(0).ModInverse(p.Primes[1], p.Primes[0]) - - exponent1 := toBase64(exp1.Bytes()) - exponent2 := toBase64(exp2.Bytes()) - coefficient := toBase64(coeff.Bytes()) - - return format + - "Algorithm: " + algorithm + "\n" + - "Modulus: " + modulus + "\n" + - "PublicExponent: " + publicExponent + "\n" + - "PrivateExponent: " + privateExponent + "\n" + - "Prime1: " + prime1 + "\n" + - "Prime2: " + prime2 + "\n" + - "Exponent1: " + exponent1 + "\n" + - "Exponent2: " + exponent2 + "\n" + - "Coefficient: " + coefficient + "\n" - - case *ecdsa.PrivateKey: - var intlen int - switch r.Algorithm { - case ECDSAP256SHA256: - intlen = 32 - case ECDSAP384SHA384: - intlen = 48 - } - private := toBase64(intToBytes(p.D, intlen)) - return format + - "Algorithm: " + algorithm + "\n" + - "PrivateKey: " + private + "\n" - - case *dsa.PrivateKey: - T := divRoundUp(divRoundUp(p.PublicKey.Parameters.G.BitLen(), 8)-64, 8) - prime := toBase64(intToBytes(p.PublicKey.Parameters.P, 64+T*8)) - subprime := toBase64(intToBytes(p.PublicKey.Parameters.Q, 20)) - base := toBase64(intToBytes(p.PublicKey.Parameters.G, 64+T*8)) - priv := toBase64(intToBytes(p.X, 20)) - pub := toBase64(intToBytes(p.PublicKey.Y, 64+T*8)) - return format + - "Algorithm: " + algorithm + "\n" + - "Prime(p): " + prime + "\n" + - "Subprime(q): " + subprime + "\n" + - "Base(g): " + base + "\n" + - "Private_value(x): " + priv + "\n" + - "Public_value(y): " + pub + "\n" - - default: - return "" - } -} diff --git a/vendor/github.com/miekg/dns/dnsutil/util.go b/vendor/github.com/miekg/dns/dnsutil/util.go deleted file mode 100644 index 9ed03f2969c..00000000000 --- a/vendor/github.com/miekg/dns/dnsutil/util.go +++ /dev/null @@ -1,79 +0,0 @@ -// Package dnsutil contains higher-level methods useful with the dns -// package. While package dns implements the DNS protocols itself, -// these functions are related but not directly required for protocol -// processing. They are often useful in preparing input/output of the -// functions in package dns. -package dnsutil - -import ( - "strings" - - "github.com/miekg/dns" -) - -// AddDomain adds origin to s if s is not already a FQDN. -// Note that the result may not be a FQDN. If origin does not end -// with a ".", the result won't either. -// This implements the zonefile convention (specified in RFC 1035, -// Section "5.1. Format") that "@" represents the -// apex (bare) domain. i.e. AddOrigin("@", "foo.com.") returns "foo.com.". -func AddOrigin(s, origin string) string { - // ("foo.", "origin.") -> "foo." (already a FQDN) - // ("foo", "origin.") -> "foo.origin." - // ("foo"), "origin" -> "foo.origin" - // ("@", "origin.") -> "origin." (@ represents the apex (bare) domain) - // ("", "origin.") -> "origin." (not obvious) - // ("foo", "") -> "foo" (not obvious) - - if dns.IsFqdn(s) { - return s // s is already a FQDN, no need to mess with it. - } - if len(origin) == 0 { - return s // Nothing to append. - } - if s == "@" || len(s) == 0 { - return origin // Expand apex. - } - - if origin == "." { - return s + origin // AddOrigin(s, ".") is an expensive way to add a ".". - } - - return s + "." + origin // The simple case. -} - -// TrimDomainName trims origin from s if s is a subdomain. -// This function will never return "", but returns "@" instead (@ represents the apex (bare) domain). -func TrimDomainName(s, origin string) string { - // An apex (bare) domain is always returned as "@". - // If the return value ends in a ".", the domain was not the suffix. - // origin can end in "." or not. Either way the results should be the same. - - if len(s) == 0 { - return "@" // Return the apex (@) rather than "". - } - // Someone is using TrimDomainName(s, ".") to remove a dot if it exists. - if origin == "." { - return strings.TrimSuffix(s, origin) - } - - // Dude, you aren't even if the right subdomain! - if !dns.IsSubDomain(origin, s) { - return s - } - - slabels := dns.Split(s) - olabels := dns.Split(origin) - m := dns.CompareDomainName(s, origin) - if len(olabels) == m { - if len(olabels) == len(slabels) { - return "@" // origin == s - } - if (s[0] == '.') && (len(slabels) == (len(olabels) + 1)) { - return "@" // TrimDomainName(".foo.", "foo.") - } - } - - // Return the first (len-m) labels: - return s[:slabels[len(slabels)-m]-1] -} diff --git a/vendor/github.com/miekg/dns/doc.go b/vendor/github.com/miekg/dns/doc.go deleted file mode 100644 index e38753d7d8a..00000000000 --- a/vendor/github.com/miekg/dns/doc.go +++ /dev/null @@ -1,251 +0,0 @@ -/* -Package dns implements a full featured interface to the Domain Name System. -Server- and client-side programming is supported. -The package allows complete control over what is send out to the DNS. The package -API follows the less-is-more principle, by presenting a small, clean interface. - -The package dns supports (asynchronous) querying/replying, incoming/outgoing zone transfers, -TSIG, EDNS0, dynamic updates, notifies and DNSSEC validation/signing. -Note that domain names MUST be fully qualified, before sending them, unqualified -names in a message will result in a packing failure. - -Resource records are native types. They are not stored in wire format. -Basic usage pattern for creating a new resource record: - - r := new(dns.MX) - r.Hdr = dns.RR_Header{Name: "miek.nl.", Rrtype: dns.TypeMX, - Class: dns.ClassINET, Ttl: 3600} - r.Preference = 10 - r.Mx = "mx.miek.nl." - -Or directly from a string: - - mx, err := dns.NewRR("miek.nl. 3600 IN MX 10 mx.miek.nl.") - -Or when the default TTL (3600) and class (IN) suit you: - - mx, err := dns.NewRR("miek.nl. MX 10 mx.miek.nl.") - -Or even: - - mx, err := dns.NewRR("$ORIGIN nl.\nmiek 1H IN MX 10 mx.miek") - -In the DNS messages are exchanged, these messages contain resource -records (sets). Use pattern for creating a message: - - m := new(dns.Msg) - m.SetQuestion("miek.nl.", dns.TypeMX) - -Or when not certain if the domain name is fully qualified: - - m.SetQuestion(dns.Fqdn("miek.nl"), dns.TypeMX) - -The message m is now a message with the question section set to ask -the MX records for the miek.nl. zone. - -The following is slightly more verbose, but more flexible: - - m1 := new(dns.Msg) - m1.Id = dns.Id() - m1.RecursionDesired = true - m1.Question = make([]dns.Question, 1) - m1.Question[0] = dns.Question{"miek.nl.", dns.TypeMX, dns.ClassINET} - -After creating a message it can be send. -Basic use pattern for synchronous querying the DNS at a -server configured on 127.0.0.1 and port 53: - - c := new(dns.Client) - in, rtt, err := c.Exchange(m1, "127.0.0.1:53") - -Suppressing multiple outstanding queries (with the same question, type and -class) is as easy as setting: - - c.SingleInflight = true - -If these "advanced" features are not needed, a simple UDP query can be send, -with: - - in, err := dns.Exchange(m1, "127.0.0.1:53") - -When this functions returns you will get dns message. A dns message consists -out of four sections. -The question section: in.Question, the answer section: in.Answer, -the authority section: in.Ns and the additional section: in.Extra. - -Each of these sections (except the Question section) contain a []RR. Basic -use pattern for accessing the rdata of a TXT RR as the first RR in -the Answer section: - - if t, ok := in.Answer[0].(*dns.TXT); ok { - // do something with t.Txt - } - -Domain Name and TXT Character String Representations - -Both domain names and TXT character strings are converted to presentation -form both when unpacked and when converted to strings. - -For TXT character strings, tabs, carriage returns and line feeds will be -converted to \t, \r and \n respectively. Back slashes and quotations marks -will be escaped. Bytes below 32 and above 127 will be converted to \DDD -form. - -For domain names, in addition to the above rules brackets, periods, -spaces, semicolons and the at symbol are escaped. - -DNSSEC - -DNSSEC (DNS Security Extension) adds a layer of security to the DNS. It -uses public key cryptography to sign resource records. The -public keys are stored in DNSKEY records and the signatures in RRSIG records. - -Requesting DNSSEC information for a zone is done by adding the DO (DNSSEC OK) bit -to a request. - - m := new(dns.Msg) - m.SetEdns0(4096, true) - -Signature generation, signature verification and key generation are all supported. - -DYNAMIC UPDATES - -Dynamic updates reuses the DNS message format, but renames three of -the sections. Question is Zone, Answer is Prerequisite, Authority is -Update, only the Additional is not renamed. See RFC 2136 for the gory details. - -You can set a rather complex set of rules for the existence of absence of -certain resource records or names in a zone to specify if resource records -should be added or removed. The table from RFC 2136 supplemented with the Go -DNS function shows which functions exist to specify the prerequisites. - - 3.2.4 - Table Of Metavalues Used In Prerequisite Section - - CLASS TYPE RDATA Meaning Function - -------------------------------------------------------------- - ANY ANY empty Name is in use dns.NameUsed - ANY rrset empty RRset exists (value indep) dns.RRsetUsed - NONE ANY empty Name is not in use dns.NameNotUsed - NONE rrset empty RRset does not exist dns.RRsetNotUsed - zone rrset rr RRset exists (value dep) dns.Used - -The prerequisite section can also be left empty. -If you have decided on the prerequisites you can tell what RRs should -be added or deleted. The next table shows the options you have and -what functions to call. - - 3.4.2.6 - Table Of Metavalues Used In Update Section - - CLASS TYPE RDATA Meaning Function - --------------------------------------------------------------- - ANY ANY empty Delete all RRsets from name dns.RemoveName - ANY rrset empty Delete an RRset dns.RemoveRRset - NONE rrset rr Delete an RR from RRset dns.Remove - zone rrset rr Add to an RRset dns.Insert - -TRANSACTION SIGNATURE - -An TSIG or transaction signature adds a HMAC TSIG record to each message sent. -The supported algorithms include: HmacMD5, HmacSHA1, HmacSHA256 and HmacSHA512. - -Basic use pattern when querying with a TSIG name "axfr." (note that these key names -must be fully qualified - as they are domain names) and the base64 secret -"so6ZGir4GPAqINNh9U5c3A==": - - c := new(dns.Client) - c.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} - m := new(dns.Msg) - m.SetQuestion("miek.nl.", dns.TypeMX) - m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix()) - ... - // When sending the TSIG RR is calculated and filled in before sending - -When requesting an zone transfer (almost all TSIG usage is when requesting zone transfers), with -TSIG, this is the basic use pattern. In this example we request an AXFR for -miek.nl. with TSIG key named "axfr." and secret "so6ZGir4GPAqINNh9U5c3A==" -and using the server 176.58.119.54: - - t := new(dns.Transfer) - m := new(dns.Msg) - t.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} - m.SetAxfr("miek.nl.") - m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix()) - c, err := t.In(m, "176.58.119.54:53") - for r := range c { ... } - -You can now read the records from the transfer as they come in. Each envelope is checked with TSIG. -If something is not correct an error is returned. - -Basic use pattern validating and replying to a message that has TSIG set. - - server := &dns.Server{Addr: ":53", Net: "udp"} - server.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} - go server.ListenAndServe() - dns.HandleFunc(".", handleRequest) - - func handleRequest(w dns.ResponseWriter, r *dns.Msg) { - m := new(dns.Msg) - m.SetReply(r) - if r.IsTsig() != nil { - if w.TsigStatus() == nil { - // *Msg r has an TSIG record and it was validated - m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix()) - } else { - // *Msg r has an TSIG records and it was not valided - } - } - w.WriteMsg(m) - } - -PRIVATE RRS - -RFC 6895 sets aside a range of type codes for private use. This range -is 65,280 - 65,534 (0xFF00 - 0xFFFE). When experimenting with new Resource Records these -can be used, before requesting an official type code from IANA. - -see http://miek.nl/2014/September/21/idn-and-private-rr-in-go-dns/ for more -information. - -EDNS0 - -EDNS0 is an extension mechanism for the DNS defined in RFC 2671 and updated -by RFC 6891. It defines an new RR type, the OPT RR, which is then completely -abused. -Basic use pattern for creating an (empty) OPT RR: - - o := new(dns.OPT) - o.Hdr.Name = "." // MUST be the root zone, per definition. - o.Hdr.Rrtype = dns.TypeOPT - -The rdata of an OPT RR consists out of a slice of EDNS0 (RFC 6891) -interfaces. Currently only a few have been standardized: EDNS0_NSID -(RFC 5001) and EDNS0_SUBNET (draft-vandergaast-edns-client-subnet-02). Note -that these options may be combined in an OPT RR. -Basic use pattern for a server to check if (and which) options are set: - - // o is a dns.OPT - for _, s := range o.Option { - switch e := s.(type) { - case *dns.EDNS0_NSID: - // do stuff with e.Nsid - case *dns.EDNS0_SUBNET: - // access e.Family, e.Address, etc. - } - } - -SIG(0) - -From RFC 2931: - - SIG(0) provides protection for DNS transactions and requests .... - ... protection for glue records, DNS requests, protection for message headers - on requests and responses, and protection of the overall integrity of a response. - -It works like TSIG, except that SIG(0) uses public key cryptography, instead of the shared -secret approach in TSIG. -Supported algorithms: DSA, ECDSAP256SHA256, ECDSAP384SHA384, RSASHA1, RSASHA256 and -RSASHA512. - -Signing subsequent messages in multi-message sessions is not implemented. -*/ -package dns diff --git a/vendor/github.com/miekg/dns/edns.go b/vendor/github.com/miekg/dns/edns.go deleted file mode 100644 index dbff3714cf5..00000000000 --- a/vendor/github.com/miekg/dns/edns.go +++ /dev/null @@ -1,597 +0,0 @@ -package dns - -import ( - "encoding/binary" - "encoding/hex" - "errors" - "fmt" - "net" - "strconv" -) - -// EDNS0 Option codes. -const ( - EDNS0LLQ = 0x1 // long lived queries: http://tools.ietf.org/html/draft-sekar-dns-llq-01 - EDNS0UL = 0x2 // update lease draft: http://files.dns-sd.org/draft-sekar-dns-ul.txt - EDNS0NSID = 0x3 // nsid (RFC5001) - EDNS0DAU = 0x5 // DNSSEC Algorithm Understood - EDNS0DHU = 0x6 // DS Hash Understood - EDNS0N3U = 0x7 // NSEC3 Hash Understood - EDNS0SUBNET = 0x8 // client-subnet (RFC6891) - EDNS0EXPIRE = 0x9 // EDNS0 expire - EDNS0COOKIE = 0xa // EDNS0 Cookie - EDNS0TCPKEEPALIVE = 0xb // EDNS0 tcp keep alive (RFC7828) - EDNS0SUBNETDRAFT = 0x50fa // Don't use! Use EDNS0SUBNET - EDNS0LOCALSTART = 0xFDE9 // Beginning of range reserved for local/experimental use (RFC6891) - EDNS0LOCALEND = 0xFFFE // End of range reserved for local/experimental use (RFC6891) - _DO = 1 << 15 // dnssec ok -) - -// OPT is the EDNS0 RR appended to messages to convey extra (meta) information. -// See RFC 6891. -type OPT struct { - Hdr RR_Header - Option []EDNS0 `dns:"opt"` -} - -func (rr *OPT) String() string { - s := "\n;; OPT PSEUDOSECTION:\n; EDNS: version " + strconv.Itoa(int(rr.Version())) + "; " - if rr.Do() { - s += "flags: do; " - } else { - s += "flags: ; " - } - s += "udp: " + strconv.Itoa(int(rr.UDPSize())) - - for _, o := range rr.Option { - switch o.(type) { - case *EDNS0_NSID: - s += "\n; NSID: " + o.String() - h, e := o.pack() - var r string - if e == nil { - for _, c := range h { - r += "(" + string(c) + ")" - } - s += " " + r - } - case *EDNS0_SUBNET: - s += "\n; SUBNET: " + o.String() - if o.(*EDNS0_SUBNET).DraftOption { - s += " (draft)" - } - case *EDNS0_COOKIE: - s += "\n; COOKIE: " + o.String() - case *EDNS0_UL: - s += "\n; UPDATE LEASE: " + o.String() - case *EDNS0_LLQ: - s += "\n; LONG LIVED QUERIES: " + o.String() - case *EDNS0_DAU: - s += "\n; DNSSEC ALGORITHM UNDERSTOOD: " + o.String() - case *EDNS0_DHU: - s += "\n; DS HASH UNDERSTOOD: " + o.String() - case *EDNS0_N3U: - s += "\n; NSEC3 HASH UNDERSTOOD: " + o.String() - case *EDNS0_LOCAL: - s += "\n; LOCAL OPT: " + o.String() - } - } - return s -} - -func (rr *OPT) len() int { - l := rr.Hdr.len() - for i := 0; i < len(rr.Option); i++ { - l += 4 // Account for 2-byte option code and 2-byte option length. - lo, _ := rr.Option[i].pack() - l += len(lo) - } - return l -} - -// return the old value -> delete SetVersion? - -// Version returns the EDNS version used. Only zero is defined. -func (rr *OPT) Version() uint8 { - return uint8((rr.Hdr.Ttl & 0x00FF0000) >> 16) -} - -// SetVersion sets the version of EDNS. This is usually zero. -func (rr *OPT) SetVersion(v uint8) { - rr.Hdr.Ttl = rr.Hdr.Ttl&0xFF00FFFF | (uint32(v) << 16) -} - -// ExtendedRcode returns the EDNS extended RCODE field (the upper 8 bits of the TTL). -func (rr *OPT) ExtendedRcode() int { - return int((rr.Hdr.Ttl&0xFF000000)>>24) + 15 -} - -// SetExtendedRcode sets the EDNS extended RCODE field. -func (rr *OPT) SetExtendedRcode(v uint8) { - if v < RcodeBadVers { // Smaller than 16.. Use the 4 bits you have! - return - } - rr.Hdr.Ttl = rr.Hdr.Ttl&0x00FFFFFF | (uint32(v-15) << 24) -} - -// UDPSize returns the UDP buffer size. -func (rr *OPT) UDPSize() uint16 { - return rr.Hdr.Class -} - -// SetUDPSize sets the UDP buffer size. -func (rr *OPT) SetUDPSize(size uint16) { - rr.Hdr.Class = size -} - -// Do returns the value of the DO (DNSSEC OK) bit. -func (rr *OPT) Do() bool { - return rr.Hdr.Ttl&_DO == _DO -} - -// SetDo sets the DO (DNSSEC OK) bit. -// If we pass an argument, set the DO bit to that value. -// It is possible to pass 2 or more arguments. Any arguments after the 1st is silently ignored. -func (rr *OPT) SetDo(do ...bool) { - if len(do) == 1 { - if do[0] { - rr.Hdr.Ttl |= _DO - } else { - rr.Hdr.Ttl &^= _DO - } - } else { - rr.Hdr.Ttl |= _DO - } -} - -// EDNS0 defines an EDNS0 Option. An OPT RR can have multiple options appended to it. -type EDNS0 interface { - // Option returns the option code for the option. - Option() uint16 - // pack returns the bytes of the option data. - pack() ([]byte, error) - // unpack sets the data as found in the buffer. Is also sets - // the length of the slice as the length of the option data. - unpack([]byte) error - // String returns the string representation of the option. - String() string -} - -// EDNS0_NSID option is used to retrieve a nameserver -// identifier. When sending a request Nsid must be set to the empty string -// The identifier is an opaque string encoded as hex. -// Basic use pattern for creating an nsid option: -// -// o := new(dns.OPT) -// o.Hdr.Name = "." -// o.Hdr.Rrtype = dns.TypeOPT -// e := new(dns.EDNS0_NSID) -// e.Code = dns.EDNS0NSID -// e.Nsid = "AA" -// o.Option = append(o.Option, e) -type EDNS0_NSID struct { - Code uint16 // Always EDNS0NSID - Nsid string // This string needs to be hex encoded -} - -func (e *EDNS0_NSID) pack() ([]byte, error) { - h, err := hex.DecodeString(e.Nsid) - if err != nil { - return nil, err - } - return h, nil -} - -func (e *EDNS0_NSID) Option() uint16 { return EDNS0NSID } -func (e *EDNS0_NSID) unpack(b []byte) error { e.Nsid = hex.EncodeToString(b); return nil } -func (e *EDNS0_NSID) String() string { return string(e.Nsid) } - -// EDNS0_SUBNET is the subnet option that is used to give the remote nameserver -// an idea of where the client lives. It can then give back a different -// answer depending on the location or network topology. -// Basic use pattern for creating an subnet option: -// -// o := new(dns.OPT) -// o.Hdr.Name = "." -// o.Hdr.Rrtype = dns.TypeOPT -// e := new(dns.EDNS0_SUBNET) -// e.Code = dns.EDNS0SUBNET -// e.Family = 1 // 1 for IPv4 source address, 2 for IPv6 -// e.SourceNetmask = 32 // 32 for IPV4, 128 for IPv6 -// e.SourceScope = 0 -// e.Address = net.ParseIP("127.0.0.1").To4() // for IPv4 -// // e.Address = net.ParseIP("2001:7b8:32a::2") // for IPV6 -// o.Option = append(o.Option, e) -// -// Note: the spec (draft-ietf-dnsop-edns-client-subnet-00) has some insane logic -// for which netmask applies to the address. This code will parse all the -// available bits when unpacking (up to optlen). When packing it will apply -// SourceNetmask. If you need more advanced logic, patches welcome and good luck. -type EDNS0_SUBNET struct { - Code uint16 // Always EDNS0SUBNET - Family uint16 // 1 for IP, 2 for IP6 - SourceNetmask uint8 - SourceScope uint8 - Address net.IP - DraftOption bool // Set to true if using the old (0x50fa) option code -} - -func (e *EDNS0_SUBNET) Option() uint16 { - if e.DraftOption { - return EDNS0SUBNETDRAFT - } - return EDNS0SUBNET -} - -func (e *EDNS0_SUBNET) pack() ([]byte, error) { - b := make([]byte, 4) - binary.BigEndian.PutUint16(b[0:], e.Family) - b[2] = e.SourceNetmask - b[3] = e.SourceScope - switch e.Family { - case 1: - if e.SourceNetmask > net.IPv4len*8 { - return nil, errors.New("dns: bad netmask") - } - if len(e.Address.To4()) != net.IPv4len { - return nil, errors.New("dns: bad address") - } - ip := e.Address.To4().Mask(net.CIDRMask(int(e.SourceNetmask), net.IPv4len*8)) - needLength := (e.SourceNetmask + 8 - 1) / 8 // division rounding up - b = append(b, ip[:needLength]...) - case 2: - if e.SourceNetmask > net.IPv6len*8 { - return nil, errors.New("dns: bad netmask") - } - if len(e.Address) != net.IPv6len { - return nil, errors.New("dns: bad address") - } - ip := e.Address.Mask(net.CIDRMask(int(e.SourceNetmask), net.IPv6len*8)) - needLength := (e.SourceNetmask + 8 - 1) / 8 // division rounding up - b = append(b, ip[:needLength]...) - default: - return nil, errors.New("dns: bad address family") - } - return b, nil -} - -func (e *EDNS0_SUBNET) unpack(b []byte) error { - if len(b) < 4 { - return ErrBuf - } - e.Family = binary.BigEndian.Uint16(b) - e.SourceNetmask = b[2] - e.SourceScope = b[3] - switch e.Family { - case 1: - if e.SourceNetmask > net.IPv4len*8 || e.SourceScope > net.IPv4len*8 { - return errors.New("dns: bad netmask") - } - addr := make([]byte, net.IPv4len) - for i := 0; i < net.IPv4len && 4+i < len(b); i++ { - addr[i] = b[4+i] - } - e.Address = net.IPv4(addr[0], addr[1], addr[2], addr[3]) - case 2: - if e.SourceNetmask > net.IPv6len*8 || e.SourceScope > net.IPv6len*8 { - return errors.New("dns: bad netmask") - } - addr := make([]byte, net.IPv6len) - for i := 0; i < net.IPv6len && 4+i < len(b); i++ { - addr[i] = b[4+i] - } - e.Address = net.IP{addr[0], addr[1], addr[2], addr[3], addr[4], - addr[5], addr[6], addr[7], addr[8], addr[9], addr[10], - addr[11], addr[12], addr[13], addr[14], addr[15]} - default: - return errors.New("dns: bad address family") - } - return nil -} - -func (e *EDNS0_SUBNET) String() (s string) { - if e.Address == nil { - s = "" - } else if e.Address.To4() != nil { - s = e.Address.String() - } else { - s = "[" + e.Address.String() + "]" - } - s += "/" + strconv.Itoa(int(e.SourceNetmask)) + "/" + strconv.Itoa(int(e.SourceScope)) - return -} - -// The EDNS0_COOKIE option is used to add a DNS Cookie to a message. -// -// o := new(dns.OPT) -// o.Hdr.Name = "." -// o.Hdr.Rrtype = dns.TypeOPT -// e := new(dns.EDNS0_COOKIE) -// e.Code = dns.EDNS0COOKIE -// e.Cookie = "24a5ac.." -// o.Option = append(o.Option, e) -// -// The Cookie field consists out of a client cookie (RFC 7873 Section 4), that is -// always 8 bytes. It may then optionally be followed by the server cookie. The server -// cookie is of variable length, 8 to a maximum of 32 bytes. In other words: -// -// cCookie := o.Cookie[:16] -// sCookie := o.Cookie[16:] -// -// There is no guarantee that the Cookie string has a specific length. -type EDNS0_COOKIE struct { - Code uint16 // Always EDNS0COOKIE - Cookie string // Hex-encoded cookie data -} - -func (e *EDNS0_COOKIE) pack() ([]byte, error) { - h, err := hex.DecodeString(e.Cookie) - if err != nil { - return nil, err - } - return h, nil -} - -func (e *EDNS0_COOKIE) Option() uint16 { return EDNS0COOKIE } -func (e *EDNS0_COOKIE) unpack(b []byte) error { e.Cookie = hex.EncodeToString(b); return nil } -func (e *EDNS0_COOKIE) String() string { return e.Cookie } - -// The EDNS0_UL (Update Lease) (draft RFC) option is used to tell the server to set -// an expiration on an update RR. This is helpful for clients that cannot clean -// up after themselves. This is a draft RFC and more information can be found at -// http://files.dns-sd.org/draft-sekar-dns-ul.txt -// -// o := new(dns.OPT) -// o.Hdr.Name = "." -// o.Hdr.Rrtype = dns.TypeOPT -// e := new(dns.EDNS0_UL) -// e.Code = dns.EDNS0UL -// e.Lease = 120 // in seconds -// o.Option = append(o.Option, e) -type EDNS0_UL struct { - Code uint16 // Always EDNS0UL - Lease uint32 -} - -func (e *EDNS0_UL) Option() uint16 { return EDNS0UL } -func (e *EDNS0_UL) String() string { return strconv.FormatUint(uint64(e.Lease), 10) } - -// Copied: http://golang.org/src/pkg/net/dnsmsg.go -func (e *EDNS0_UL) pack() ([]byte, error) { - b := make([]byte, 4) - binary.BigEndian.PutUint32(b, e.Lease) - return b, nil -} - -func (e *EDNS0_UL) unpack(b []byte) error { - if len(b) < 4 { - return ErrBuf - } - e.Lease = binary.BigEndian.Uint32(b) - return nil -} - -// EDNS0_LLQ stands for Long Lived Queries: http://tools.ietf.org/html/draft-sekar-dns-llq-01 -// Implemented for completeness, as the EDNS0 type code is assigned. -type EDNS0_LLQ struct { - Code uint16 // Always EDNS0LLQ - Version uint16 - Opcode uint16 - Error uint16 - Id uint64 - LeaseLife uint32 -} - -func (e *EDNS0_LLQ) Option() uint16 { return EDNS0LLQ } - -func (e *EDNS0_LLQ) pack() ([]byte, error) { - b := make([]byte, 18) - binary.BigEndian.PutUint16(b[0:], e.Version) - binary.BigEndian.PutUint16(b[2:], e.Opcode) - binary.BigEndian.PutUint16(b[4:], e.Error) - binary.BigEndian.PutUint64(b[6:], e.Id) - binary.BigEndian.PutUint32(b[14:], e.LeaseLife) - return b, nil -} - -func (e *EDNS0_LLQ) unpack(b []byte) error { - if len(b) < 18 { - return ErrBuf - } - e.Version = binary.BigEndian.Uint16(b[0:]) - e.Opcode = binary.BigEndian.Uint16(b[2:]) - e.Error = binary.BigEndian.Uint16(b[4:]) - e.Id = binary.BigEndian.Uint64(b[6:]) - e.LeaseLife = binary.BigEndian.Uint32(b[14:]) - return nil -} - -func (e *EDNS0_LLQ) String() string { - s := strconv.FormatUint(uint64(e.Version), 10) + " " + strconv.FormatUint(uint64(e.Opcode), 10) + - " " + strconv.FormatUint(uint64(e.Error), 10) + " " + strconv.FormatUint(uint64(e.Id), 10) + - " " + strconv.FormatUint(uint64(e.LeaseLife), 10) - return s -} - -type EDNS0_DAU struct { - Code uint16 // Always EDNS0DAU - AlgCode []uint8 -} - -func (e *EDNS0_DAU) Option() uint16 { return EDNS0DAU } -func (e *EDNS0_DAU) pack() ([]byte, error) { return e.AlgCode, nil } -func (e *EDNS0_DAU) unpack(b []byte) error { e.AlgCode = b; return nil } - -func (e *EDNS0_DAU) String() string { - s := "" - for i := 0; i < len(e.AlgCode); i++ { - if a, ok := AlgorithmToString[e.AlgCode[i]]; ok { - s += " " + a - } else { - s += " " + strconv.Itoa(int(e.AlgCode[i])) - } - } - return s -} - -type EDNS0_DHU struct { - Code uint16 // Always EDNS0DHU - AlgCode []uint8 -} - -func (e *EDNS0_DHU) Option() uint16 { return EDNS0DHU } -func (e *EDNS0_DHU) pack() ([]byte, error) { return e.AlgCode, nil } -func (e *EDNS0_DHU) unpack(b []byte) error { e.AlgCode = b; return nil } - -func (e *EDNS0_DHU) String() string { - s := "" - for i := 0; i < len(e.AlgCode); i++ { - if a, ok := HashToString[e.AlgCode[i]]; ok { - s += " " + a - } else { - s += " " + strconv.Itoa(int(e.AlgCode[i])) - } - } - return s -} - -type EDNS0_N3U struct { - Code uint16 // Always EDNS0N3U - AlgCode []uint8 -} - -func (e *EDNS0_N3U) Option() uint16 { return EDNS0N3U } -func (e *EDNS0_N3U) pack() ([]byte, error) { return e.AlgCode, nil } -func (e *EDNS0_N3U) unpack(b []byte) error { e.AlgCode = b; return nil } - -func (e *EDNS0_N3U) String() string { - // Re-use the hash map - s := "" - for i := 0; i < len(e.AlgCode); i++ { - if a, ok := HashToString[e.AlgCode[i]]; ok { - s += " " + a - } else { - s += " " + strconv.Itoa(int(e.AlgCode[i])) - } - } - return s -} - -type EDNS0_EXPIRE struct { - Code uint16 // Always EDNS0EXPIRE - Expire uint32 -} - -func (e *EDNS0_EXPIRE) Option() uint16 { return EDNS0EXPIRE } -func (e *EDNS0_EXPIRE) String() string { return strconv.FormatUint(uint64(e.Expire), 10) } - -func (e *EDNS0_EXPIRE) pack() ([]byte, error) { - b := make([]byte, 4) - b[0] = byte(e.Expire >> 24) - b[1] = byte(e.Expire >> 16) - b[2] = byte(e.Expire >> 8) - b[3] = byte(e.Expire) - return b, nil -} - -func (e *EDNS0_EXPIRE) unpack(b []byte) error { - if len(b) < 4 { - return ErrBuf - } - e.Expire = binary.BigEndian.Uint32(b) - return nil -} - -// The EDNS0_LOCAL option is used for local/experimental purposes. The option -// code is recommended to be within the range [EDNS0LOCALSTART, EDNS0LOCALEND] -// (RFC6891), although any unassigned code can actually be used. The content of -// the option is made available in Data, unaltered. -// Basic use pattern for creating a local option: -// -// o := new(dns.OPT) -// o.Hdr.Name = "." -// o.Hdr.Rrtype = dns.TypeOPT -// e := new(dns.EDNS0_LOCAL) -// e.Code = dns.EDNS0LOCALSTART -// e.Data = []byte{72, 82, 74} -// o.Option = append(o.Option, e) -type EDNS0_LOCAL struct { - Code uint16 - Data []byte -} - -func (e *EDNS0_LOCAL) Option() uint16 { return e.Code } -func (e *EDNS0_LOCAL) String() string { - return strconv.FormatInt(int64(e.Code), 10) + ":0x" + hex.EncodeToString(e.Data) -} - -func (e *EDNS0_LOCAL) pack() ([]byte, error) { - b := make([]byte, len(e.Data)) - copied := copy(b, e.Data) - if copied != len(e.Data) { - return nil, ErrBuf - } - return b, nil -} - -func (e *EDNS0_LOCAL) unpack(b []byte) error { - e.Data = make([]byte, len(b)) - copied := copy(e.Data, b) - if copied != len(b) { - return ErrBuf - } - return nil -} - -// EDNS0_TCP_KEEPALIVE is an EDNS0 option that instructs the server to keep -// the TCP connection alive. See RFC 7828. -type EDNS0_TCP_KEEPALIVE struct { - Code uint16 // Always EDNSTCPKEEPALIVE - Length uint16 // the value 0 if the TIMEOUT is omitted, the value 2 if it is present; - Timeout uint16 // an idle timeout value for the TCP connection, specified in units of 100 milliseconds, encoded in network byte order. -} - -func (e *EDNS0_TCP_KEEPALIVE) Option() uint16 { return EDNS0TCPKEEPALIVE } - -func (e *EDNS0_TCP_KEEPALIVE) pack() ([]byte, error) { - if e.Timeout != 0 && e.Length != 2 { - return nil, errors.New("dns: timeout specified but length is not 2") - } - if e.Timeout == 0 && e.Length != 0 { - return nil, errors.New("dns: timeout not specified but length is not 0") - } - b := make([]byte, 4+e.Length) - binary.BigEndian.PutUint16(b[0:], e.Code) - binary.BigEndian.PutUint16(b[2:], e.Length) - if e.Length == 2 { - binary.BigEndian.PutUint16(b[4:], e.Timeout) - } - return b, nil -} - -func (e *EDNS0_TCP_KEEPALIVE) unpack(b []byte) error { - if len(b) < 4 { - return ErrBuf - } - e.Length = binary.BigEndian.Uint16(b[2:4]) - if e.Length != 0 && e.Length != 2 { - return errors.New("dns: length mismatch, want 0/2 but got " + strconv.FormatUint(uint64(e.Length), 10)) - } - if e.Length == 2 { - if len(b) < 6 { - return ErrBuf - } - e.Timeout = binary.BigEndian.Uint16(b[4:6]) - } - return nil -} - -func (e *EDNS0_TCP_KEEPALIVE) String() (s string) { - s = "use tcp keep-alive" - if e.Length == 0 { - s += ", timeout omitted" - } else { - s += fmt.Sprintf(", timeout %dms", e.Timeout*100) - } - return -} diff --git a/vendor/github.com/miekg/dns/format.go b/vendor/github.com/miekg/dns/format.go deleted file mode 100644 index 3f5303c2013..00000000000 --- a/vendor/github.com/miekg/dns/format.go +++ /dev/null @@ -1,87 +0,0 @@ -package dns - -import ( - "net" - "reflect" - "strconv" -) - -// NumField returns the number of rdata fields r has. -func NumField(r RR) int { - return reflect.ValueOf(r).Elem().NumField() - 1 // Remove RR_Header -} - -// Field returns the rdata field i as a string. Fields are indexed starting from 1. -// RR types that holds slice data, for instance the NSEC type bitmap will return a single -// string where the types are concatenated using a space. -// Accessing non existing fields will cause a panic. -func Field(r RR, i int) string { - if i == 0 { - return "" - } - d := reflect.ValueOf(r).Elem().Field(i) - switch k := d.Kind(); k { - case reflect.String: - return d.String() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return strconv.FormatInt(d.Int(), 10) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return strconv.FormatUint(d.Uint(), 10) - case reflect.Slice: - switch reflect.ValueOf(r).Elem().Type().Field(i).Tag { - case `dns:"a"`: - // TODO(miek): Hmm store this as 16 bytes - if d.Len() < net.IPv6len { - return net.IPv4(byte(d.Index(0).Uint()), - byte(d.Index(1).Uint()), - byte(d.Index(2).Uint()), - byte(d.Index(3).Uint())).String() - } - return net.IPv4(byte(d.Index(12).Uint()), - byte(d.Index(13).Uint()), - byte(d.Index(14).Uint()), - byte(d.Index(15).Uint())).String() - case `dns:"aaaa"`: - return net.IP{ - byte(d.Index(0).Uint()), - byte(d.Index(1).Uint()), - byte(d.Index(2).Uint()), - byte(d.Index(3).Uint()), - byte(d.Index(4).Uint()), - byte(d.Index(5).Uint()), - byte(d.Index(6).Uint()), - byte(d.Index(7).Uint()), - byte(d.Index(8).Uint()), - byte(d.Index(9).Uint()), - byte(d.Index(10).Uint()), - byte(d.Index(11).Uint()), - byte(d.Index(12).Uint()), - byte(d.Index(13).Uint()), - byte(d.Index(14).Uint()), - byte(d.Index(15).Uint()), - }.String() - case `dns:"nsec"`: - if d.Len() == 0 { - return "" - } - s := Type(d.Index(0).Uint()).String() - for i := 1; i < d.Len(); i++ { - s += " " + Type(d.Index(i).Uint()).String() - } - return s - default: - // if it does not have a tag its a string slice - fallthrough - case `dns:"txt"`: - if d.Len() == 0 { - return "" - } - s := d.Index(0).String() - for i := 1; i < d.Len(); i++ { - s += " " + d.Index(i).String() - } - return s - } - } - return "" -} diff --git a/vendor/github.com/miekg/dns/generate.go b/vendor/github.com/miekg/dns/generate.go deleted file mode 100644 index e4481a4b0d8..00000000000 --- a/vendor/github.com/miekg/dns/generate.go +++ /dev/null @@ -1,159 +0,0 @@ -package dns - -import ( - "bytes" - "errors" - "fmt" - "strconv" - "strings" -) - -// Parse the $GENERATE statement as used in BIND9 zones. -// See http://www.zytrax.com/books/dns/ch8/generate.html for instance. -// We are called after '$GENERATE '. After which we expect: -// * the range (12-24/2) -// * lhs (ownername) -// * [[ttl][class]] -// * type -// * rhs (rdata) -// But we are lazy here, only the range is parsed *all* occurrences -// of $ after that are interpreted. -// Any error are returned as a string value, the empty string signals -// "no error". -func generate(l lex, c chan lex, t chan *Token, o string) string { - step := 1 - if i := strings.IndexAny(l.token, "/"); i != -1 { - if i+1 == len(l.token) { - return "bad step in $GENERATE range" - } - if s, err := strconv.Atoi(l.token[i+1:]); err == nil { - if s < 0 { - return "bad step in $GENERATE range" - } - step = s - } else { - return "bad step in $GENERATE range" - } - l.token = l.token[:i] - } - sx := strings.SplitN(l.token, "-", 2) - if len(sx) != 2 { - return "bad start-stop in $GENERATE range" - } - start, err := strconv.Atoi(sx[0]) - if err != nil { - return "bad start in $GENERATE range" - } - end, err := strconv.Atoi(sx[1]) - if err != nil { - return "bad stop in $GENERATE range" - } - if end < 0 || start < 0 || end < start { - return "bad range in $GENERATE range" - } - - <-c // _BLANK - // Create a complete new string, which we then parse again. - s := "" -BuildRR: - l = <-c - if l.value != zNewline && l.value != zEOF { - s += l.token - goto BuildRR - } - for i := start; i <= end; i += step { - var ( - escape bool - dom bytes.Buffer - mod string - err error - offset int - ) - - for j := 0; j < len(s); j++ { // No 'range' because we need to jump around - switch s[j] { - case '\\': - if escape { - dom.WriteByte('\\') - escape = false - continue - } - escape = true - case '$': - mod = "%d" - offset = 0 - if escape { - dom.WriteByte('$') - escape = false - continue - } - escape = false - if j+1 >= len(s) { // End of the string - dom.WriteString(fmt.Sprintf(mod, i+offset)) - continue - } else { - if s[j+1] == '$' { - dom.WriteByte('$') - j++ - continue - } - } - // Search for { and } - if s[j+1] == '{' { // Modifier block - sep := strings.Index(s[j+2:], "}") - if sep == -1 { - return "bad modifier in $GENERATE" - } - mod, offset, err = modToPrintf(s[j+2 : j+2+sep]) - if err != nil { - return err.Error() - } - j += 2 + sep // Jump to it - } - dom.WriteString(fmt.Sprintf(mod, i+offset)) - default: - if escape { // Pretty useless here - escape = false - continue - } - dom.WriteByte(s[j]) - } - } - // Re-parse the RR and send it on the current channel t - rx, err := NewRR("$ORIGIN " + o + "\n" + dom.String()) - if err != nil { - return err.Error() - } - t <- &Token{RR: rx} - // Its more efficient to first built the rrlist and then parse it in - // one go! But is this a problem? - } - return "" -} - -// Convert a $GENERATE modifier 0,0,d to something Printf can deal with. -func modToPrintf(s string) (string, int, error) { - xs := strings.SplitN(s, ",", 3) - if len(xs) != 3 { - return "", 0, errors.New("bad modifier in $GENERATE") - } - // xs[0] is offset, xs[1] is width, xs[2] is base - if xs[2] != "o" && xs[2] != "d" && xs[2] != "x" && xs[2] != "X" { - return "", 0, errors.New("bad base in $GENERATE") - } - offset, err := strconv.Atoi(xs[0]) - if err != nil || offset > 255 { - return "", 0, errors.New("bad offset in $GENERATE") - } - width, err := strconv.Atoi(xs[1]) - if err != nil || width > 255 { - return "", offset, errors.New("bad width in $GENERATE") - } - switch { - case width < 0: - return "", offset, errors.New("bad width in $GENERATE") - case width == 0: - return "%" + xs[1] + xs[2], offset, nil - } - return "%0" + xs[1] + xs[2], offset, nil -} diff --git a/vendor/github.com/miekg/dns/idn/code_points.go b/vendor/github.com/miekg/dns/idn/code_points.go deleted file mode 100644 index 129c3742f58..00000000000 --- a/vendor/github.com/miekg/dns/idn/code_points.go +++ /dev/null @@ -1,2346 +0,0 @@ -package idn - -const ( - propertyUnknown property = iota // unknown character property - propertyPVALID // allowed to be used in IDNs - propertyCONTEXTJ // invisible or problematic characters (join controls) - propertyCONTEXTO // invisible or problematic characters (others) - propertyDISALLOWED // should not be included in IDNs - propertyUNASSIGNED // code points that are not designated in the Unicode Standard -) - -// property stores the property of a code point, as described in RFC 5892, -// section 1 -type property int - -// codePoints list all code points in Unicode Character Database (UCD) Format -// according to RFC 5892, appendix B.1. Thanks to libidn2 (GNU) - -// http://www.gnu.org/software/libidn/libidn2/ -var codePoints = []struct { - start rune - end rune - state property -}{ - {0x0000, 0x002C, propertyDISALLOWED}, // ..COMMA - {0x002D, 0x0, propertyPVALID}, // HYPHEN-MINUS - {0x002E, 0x002F, propertyDISALLOWED}, // FULL STOP..SOLIDUS - {0x0030, 0x0039, propertyPVALID}, // DIGIT ZERO..DIGIT NINE - {0x003A, 0x0060, propertyDISALLOWED}, // COLON..GRAVE ACCENT - {0x0041, 0x005A, propertyPVALID}, // LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z - {0x0061, 0x007A, propertyPVALID}, // LATIN SMALL LETTER A..LATIN SMALL LETTER Z - {0x007B, 0x00B6, propertyDISALLOWED}, // LEFT CURLY BRACKET..PILCROW SIGN - {0x00B7, 0x0, propertyCONTEXTO}, // MIDDLE DOT - {0x00B8, 0x00DE, propertyDISALLOWED}, // CEDILLA..LATIN CAPITAL LETTER THORN - {0x00DF, 0x00F6, propertyPVALID}, // LATIN SMALL LETTER SHARP S..LATIN SMALL LETT - {0x00F7, 0x0, propertyDISALLOWED}, // DIVISION SIGN - {0x00F8, 0x00FF, propertyPVALID}, // LATIN SMALL LETTER O WITH STROKE..LATIN SMAL - {0x0100, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER A WITH MACRON - {0x0101, 0x0, propertyPVALID}, // LATIN SMALL LETTER A WITH MACRON - {0x0102, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER A WITH BREVE - {0x0103, 0x0, propertyPVALID}, // LATIN SMALL LETTER A WITH BREVE - {0x0104, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER A WITH OGONEK - {0x0105, 0x0, propertyPVALID}, // LATIN SMALL LETTER A WITH OGONEK - {0x0106, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER C WITH ACUTE - {0x0107, 0x0, propertyPVALID}, // LATIN SMALL LETTER C WITH ACUTE - {0x0108, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER C WITH CIRCUMFLEX - {0x0109, 0x0, propertyPVALID}, // LATIN SMALL LETTER C WITH CIRCUMFLEX - {0x010A, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER C WITH DOT ABOVE - {0x010B, 0x0, propertyPVALID}, // LATIN SMALL LETTER C WITH DOT ABOVE - {0x010C, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER C WITH CARON - {0x010D, 0x0, propertyPVALID}, // LATIN SMALL LETTER C WITH CARON - {0x010E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER D WITH CARON - {0x010F, 0x0, propertyPVALID}, // LATIN SMALL LETTER D WITH CARON - {0x0110, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER D WITH STROKE - {0x0111, 0x0, propertyPVALID}, // LATIN SMALL LETTER D WITH STROKE - {0x0112, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER E WITH MACRON - {0x0113, 0x0, propertyPVALID}, // LATIN SMALL LETTER E WITH MACRON - {0x0114, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER E WITH BREVE - {0x0115, 0x0, propertyPVALID}, // LATIN SMALL LETTER E WITH BREVE - {0x0116, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER E WITH DOT ABOVE - {0x0117, 0x0, propertyPVALID}, // LATIN SMALL LETTER E WITH DOT ABOVE - {0x0118, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER E WITH OGONEK - {0x0119, 0x0, propertyPVALID}, // LATIN SMALL LETTER E WITH OGONEK - {0x011A, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER E WITH CARON - {0x011B, 0x0, propertyPVALID}, // LATIN SMALL LETTER E WITH CARON - {0x011C, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER G WITH CIRCUMFLEX - {0x011D, 0x0, propertyPVALID}, // LATIN SMALL LETTER G WITH CIRCUMFLEX - {0x011E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER G WITH BREVE - {0x011F, 0x0, propertyPVALID}, // LATIN SMALL LETTER G WITH BREVE - {0x0120, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER G WITH DOT ABOVE - {0x0121, 0x0, propertyPVALID}, // LATIN SMALL LETTER G WITH DOT ABOVE - {0x0122, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER G WITH CEDILLA - {0x0123, 0x0, propertyPVALID}, // LATIN SMALL LETTER G WITH CEDILLA - {0x0124, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER H WITH CIRCUMFLEX - {0x0125, 0x0, propertyPVALID}, // LATIN SMALL LETTER H WITH CIRCUMFLEX - {0x0126, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER H WITH STROKE - {0x0127, 0x0, propertyPVALID}, // LATIN SMALL LETTER H WITH STROKE - {0x0128, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER I WITH TILDE - {0x0129, 0x0, propertyPVALID}, // LATIN SMALL LETTER I WITH TILDE - {0x012A, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER I WITH MACRON - {0x012B, 0x0, propertyPVALID}, // LATIN SMALL LETTER I WITH MACRON - {0x012C, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER I WITH BREVE - {0x012D, 0x0, propertyPVALID}, // LATIN SMALL LETTER I WITH BREVE - {0x012E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER I WITH OGONEK - {0x012F, 0x0, propertyPVALID}, // LATIN SMALL LETTER I WITH OGONEK - {0x0130, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER I WITH DOT ABOVE - {0x0131, 0x0, propertyPVALID}, // LATIN SMALL LETTER DOTLESS I - {0x0132, 0x0134, propertyDISALLOWED}, // LATIN CAPITAL LIGATURE IJ..LATIN CAPITAL LET - {0x0135, 0x0, propertyPVALID}, // LATIN SMALL LETTER J WITH CIRCUMFLEX - {0x0136, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER K WITH CEDILLA - {0x0137, 0x0138, propertyPVALID}, // LATIN SMALL LETTER K WITH CEDILLA..LATIN SMA - {0x0139, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER L WITH ACUTE - {0x013A, 0x0, propertyPVALID}, // LATIN SMALL LETTER L WITH ACUTE - {0x013B, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER L WITH CEDILLA - {0x013C, 0x0, propertyPVALID}, // LATIN SMALL LETTER L WITH CEDILLA - {0x013D, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER L WITH CARON - {0x013E, 0x0, propertyPVALID}, // LATIN SMALL LETTER L WITH CARON - {0x013F, 0x0141, propertyDISALLOWED}, // LATIN CAPITAL LETTER L WITH MIDDLE DOT..LATI - {0x0142, 0x0, propertyPVALID}, // LATIN SMALL LETTER L WITH STROKE - {0x0143, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER N WITH ACUTE - {0x0144, 0x0, propertyPVALID}, // LATIN SMALL LETTER N WITH ACUTE - {0x0145, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER N WITH CEDILLA - {0x0146, 0x0, propertyPVALID}, // LATIN SMALL LETTER N WITH CEDILLA - {0x0147, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER N WITH CARON - {0x0148, 0x0, propertyPVALID}, // LATIN SMALL LETTER N WITH CARON - {0x0149, 0x014A, propertyDISALLOWED}, // LATIN SMALL LETTER N PRECEDED BY APOSTROPHE. - {0x014B, 0x0, propertyPVALID}, // LATIN SMALL LETTER ENG - {0x014C, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH MACRON - {0x014D, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH MACRON - {0x014E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH BREVE - {0x014F, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH BREVE - {0x0150, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH DOUBLE ACUTE - {0x0151, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH DOUBLE ACUTE - {0x0152, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LIGATURE OE - {0x0153, 0x0, propertyPVALID}, // LATIN SMALL LIGATURE OE - {0x0154, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER R WITH ACUTE - {0x0155, 0x0, propertyPVALID}, // LATIN SMALL LETTER R WITH ACUTE - {0x0156, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER R WITH CEDILLA - {0x0157, 0x0, propertyPVALID}, // LATIN SMALL LETTER R WITH CEDILLA - {0x0158, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER R WITH CARON - {0x0159, 0x0, propertyPVALID}, // LATIN SMALL LETTER R WITH CARON - {0x015A, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER S WITH ACUTE - {0x015B, 0x0, propertyPVALID}, // LATIN SMALL LETTER S WITH ACUTE - {0x015C, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER S WITH CIRCUMFLEX - {0x015D, 0x0, propertyPVALID}, // LATIN SMALL LETTER S WITH CIRCUMFLEX - {0x015E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER S WITH CEDILLA - {0x015F, 0x0, propertyPVALID}, // LATIN SMALL LETTER S WITH CEDILLA - {0x0160, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER S WITH CARON - {0x0161, 0x0, propertyPVALID}, // LATIN SMALL LETTER S WITH CARON - {0x0162, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER T WITH CEDILLA - {0x0163, 0x0, propertyPVALID}, // LATIN SMALL LETTER T WITH CEDILLA - {0x0164, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER T WITH CARON - {0x0165, 0x0, propertyPVALID}, // LATIN SMALL LETTER T WITH CARON - {0x0166, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER T WITH STROKE - {0x0167, 0x0, propertyPVALID}, // LATIN SMALL LETTER T WITH STROKE - {0x0168, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH TILDE - {0x0169, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH TILDE - {0x016A, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH MACRON - {0x016B, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH MACRON - {0x016C, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH BREVE - {0x016D, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH BREVE - {0x016E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH RING ABOVE - {0x016F, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH RING ABOVE - {0x0170, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH DOUBLE ACUTE - {0x0171, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH DOUBLE ACUTE - {0x0172, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH OGONEK - {0x0173, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH OGONEK - {0x0174, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER W WITH CIRCUMFLEX - {0x0175, 0x0, propertyPVALID}, // LATIN SMALL LETTER W WITH CIRCUMFLEX - {0x0176, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER Y WITH CIRCUMFLEX - {0x0177, 0x0, propertyPVALID}, // LATIN SMALL LETTER Y WITH CIRCUMFLEX - {0x0178, 0x0179, propertyDISALLOWED}, // LATIN CAPITAL LETTER Y WITH DIAERESIS..LATIN - {0x017A, 0x0, propertyPVALID}, // LATIN SMALL LETTER Z WITH ACUTE - {0x017B, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER Z WITH DOT ABOVE - {0x017C, 0x0, propertyPVALID}, // LATIN SMALL LETTER Z WITH DOT ABOVE - {0x017D, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER Z WITH CARON - {0x017E, 0x0, propertyPVALID}, // LATIN SMALL LETTER Z WITH CARON - {0x017F, 0x0, propertyDISALLOWED}, // LATIN SMALL LETTER LONG S - {0x0180, 0x0, propertyPVALID}, // LATIN SMALL LETTER B WITH STROKE - {0x0181, 0x0182, propertyDISALLOWED}, // LATIN CAPITAL LETTER B WITH HOOK..LATIN CAPI - {0x0183, 0x0, propertyPVALID}, // LATIN SMALL LETTER B WITH TOPBAR - {0x0184, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER TONE SIX - {0x0185, 0x0, propertyPVALID}, // LATIN SMALL LETTER TONE SIX - {0x0186, 0x0187, propertyDISALLOWED}, // LATIN CAPITAL LETTER OPEN O..LATIN CAPITAL L - {0x0188, 0x0, propertyPVALID}, // LATIN SMALL LETTER C WITH HOOK - {0x0189, 0x018B, propertyDISALLOWED}, // LATIN CAPITAL LETTER AFRICAN D..LATIN CAPITA - {0x018C, 0x018D, propertyPVALID}, // LATIN SMALL LETTER D WITH TOPBAR..LATIN SMAL - {0x018E, 0x0191, propertyDISALLOWED}, // LATIN CAPITAL LETTER REVERSED E..LATIN CAPIT - {0x0192, 0x0, propertyPVALID}, // LATIN SMALL LETTER F WITH HOOK - {0x0193, 0x0194, propertyDISALLOWED}, // LATIN CAPITAL LETTER G WITH HOOK..LATIN CAPI - {0x0195, 0x0, propertyPVALID}, // LATIN SMALL LETTER HV - {0x0196, 0x0198, propertyDISALLOWED}, // LATIN CAPITAL LETTER IOTA..LATIN CAPITAL LET - {0x0199, 0x019B, propertyPVALID}, // LATIN SMALL LETTER K WITH HOOK..LATIN SMALL - {0x019C, 0x019D, propertyDISALLOWED}, // LATIN CAPITAL LETTER TURNED M..LATIN CAPITAL - {0x019E, 0x0, propertyPVALID}, // LATIN SMALL LETTER N WITH LONG RIGHT LEG - {0x019F, 0x01A0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH MIDDLE TILDE..LA - {0x01A1, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH HORN - {0x01A2, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER OI - {0x01A3, 0x0, propertyPVALID}, // LATIN SMALL LETTER OI - {0x01A4, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER P WITH HOOK - {0x01A5, 0x0, propertyPVALID}, // LATIN SMALL LETTER P WITH HOOK - {0x01A6, 0x01A7, propertyDISALLOWED}, // LATIN LETTER YR..LATIN CAPITAL LETTER TONE T - {0x01A8, 0x0, propertyPVALID}, // LATIN SMALL LETTER TONE TWO - {0x01A9, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER ESH - {0x01AA, 0x01AB, propertyPVALID}, // LATIN LETTER REVERSED ESH LOOP..LATIN SMALL - {0x01AC, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER T WITH HOOK - {0x01AD, 0x0, propertyPVALID}, // LATIN SMALL LETTER T WITH HOOK - {0x01AE, 0x01AF, propertyDISALLOWED}, // LATIN CAPITAL LETTER T WITH RETROFLEX HOOK.. - {0x01B0, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH HORN - {0x01B1, 0x01B3, propertyDISALLOWED}, // LATIN CAPITAL LETTER UPSILON..LATIN CAPITAL - {0x01B4, 0x0, propertyPVALID}, // LATIN SMALL LETTER Y WITH HOOK - {0x01B5, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER Z WITH STROKE - {0x01B6, 0x0, propertyPVALID}, // LATIN SMALL LETTER Z WITH STROKE - {0x01B7, 0x01B8, propertyDISALLOWED}, // LATIN CAPITAL LETTER EZH..LATIN CAPITAL LETT - {0x01B9, 0x01BB, propertyPVALID}, // LATIN SMALL LETTER EZH REVERSED..LATIN LETTE - {0x01BC, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER TONE FIVE - {0x01BD, 0x01C3, propertyPVALID}, // LATIN SMALL LETTER TONE FIVE..LATIN LETTER R - {0x01C4, 0x01CD, propertyDISALLOWED}, // LATIN CAPITAL LETTER DZ WITH CARON..LATIN CA - {0x01CE, 0x0, propertyPVALID}, // LATIN SMALL LETTER A WITH CARON - {0x01CF, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER I WITH CARON - {0x01D0, 0x0, propertyPVALID}, // LATIN SMALL LETTER I WITH CARON - {0x01D1, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH CARON - {0x01D2, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH CARON - {0x01D3, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH CARON - {0x01D4, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH CARON - {0x01D5, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH DIAERESIS AND MA - {0x01D6, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH DIAERESIS AND MACR - {0x01D7, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH DIAERESIS AND AC - {0x01D8, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH DIAERESIS AND ACUT - {0x01D9, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH DIAERESIS AND CA - {0x01DA, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH DIAERESIS AND CARO - {0x01DB, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH DIAERESIS AND GR - {0x01DC, 0x01DD, propertyPVALID}, // LATIN SMALL LETTER U WITH DIAERESIS AND GRAV - {0x01DE, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER A WITH DIAERESIS AND MA - {0x01DF, 0x0, propertyPVALID}, // LATIN SMALL LETTER A WITH DIAERESIS AND MACR - {0x01E0, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER A WITH DOT ABOVE AND MA - {0x01E1, 0x0, propertyPVALID}, // LATIN SMALL LETTER A WITH DOT ABOVE AND MACR - {0x01E2, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER AE WITH MACRON - {0x01E3, 0x0, propertyPVALID}, // LATIN SMALL LETTER AE WITH MACRON - {0x01E4, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER G WITH STROKE - {0x01E5, 0x0, propertyPVALID}, // LATIN SMALL LETTER G WITH STROKE - {0x01E6, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER G WITH CARON - {0x01E7, 0x0, propertyPVALID}, // LATIN SMALL LETTER G WITH CARON - {0x01E8, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER K WITH CARON - {0x01E9, 0x0, propertyPVALID}, // LATIN SMALL LETTER K WITH CARON - {0x01EA, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH OGONEK - {0x01EB, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH OGONEK - {0x01EC, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH OGONEK AND MACRO - {0x01ED, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH OGONEK AND MACRON - {0x01EE, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER EZH WITH CARON - {0x01EF, 0x01F0, propertyPVALID}, // LATIN SMALL LETTER EZH WITH CARON..LATIN SMA - {0x01F1, 0x01F4, propertyDISALLOWED}, // LATIN CAPITAL LETTER DZ..LATIN CAPITAL LETTE - {0x01F5, 0x0, propertyPVALID}, // LATIN SMALL LETTER G WITH ACUTE - {0x01F6, 0x01F8, propertyDISALLOWED}, // LATIN CAPITAL LETTER HWAIR..LATIN CAPITAL LE - {0x01F9, 0x0, propertyPVALID}, // LATIN SMALL LETTER N WITH GRAVE - {0x01FA, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER A WITH RING ABOVE AND A - {0x01FB, 0x0, propertyPVALID}, // LATIN SMALL LETTER A WITH RING ABOVE AND ACU - {0x01FC, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER AE WITH ACUTE - {0x01FD, 0x0, propertyPVALID}, // LATIN SMALL LETTER AE WITH ACUTE - {0x01FE, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH STROKE AND ACUTE - {0x01FF, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH STROKE AND ACUTE - {0x0200, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER A WITH DOUBLE GRAVE - {0x0201, 0x0, propertyPVALID}, // LATIN SMALL LETTER A WITH DOUBLE GRAVE - {0x0202, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER A WITH INVERTED BREVE - {0x0203, 0x0, propertyPVALID}, // LATIN SMALL LETTER A WITH INVERTED BREVE - {0x0204, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER E WITH DOUBLE GRAVE - {0x0205, 0x0, propertyPVALID}, // LATIN SMALL LETTER E WITH DOUBLE GRAVE - {0x0206, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER E WITH INVERTED BREVE - {0x0207, 0x0, propertyPVALID}, // LATIN SMALL LETTER E WITH INVERTED BREVE - {0x0208, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER I WITH DOUBLE GRAVE - {0x0209, 0x0, propertyPVALID}, // LATIN SMALL LETTER I WITH DOUBLE GRAVE - {0x020A, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER I WITH INVERTED BREVE - {0x020B, 0x0, propertyPVALID}, // LATIN SMALL LETTER I WITH INVERTED BREVE - {0x020C, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH DOUBLE GRAVE - {0x020D, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH DOUBLE GRAVE - {0x020E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH INVERTED BREVE - {0x020F, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH INVERTED BREVE - {0x0210, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER R WITH DOUBLE GRAVE - {0x0211, 0x0, propertyPVALID}, // LATIN SMALL LETTER R WITH DOUBLE GRAVE - {0x0212, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER R WITH INVERTED BREVE - {0x0213, 0x0, propertyPVALID}, // LATIN SMALL LETTER R WITH INVERTED BREVE - {0x0214, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH DOUBLE GRAVE - {0x0215, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH DOUBLE GRAVE - {0x0216, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH INVERTED BREVE - {0x0217, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH INVERTED BREVE - {0x0218, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER S WITH COMMA BELOW - {0x0219, 0x0, propertyPVALID}, // LATIN SMALL LETTER S WITH COMMA BELOW - {0x021A, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER T WITH COMMA BELOW - {0x021B, 0x0, propertyPVALID}, // LATIN SMALL LETTER T WITH COMMA BELOW - {0x021C, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER YOGH - {0x021D, 0x0, propertyPVALID}, // LATIN SMALL LETTER YOGH - {0x021E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER H WITH CARON - {0x021F, 0x0, propertyPVALID}, // LATIN SMALL LETTER H WITH CARON - {0x0220, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER N WITH LONG RIGHT LEG - {0x0221, 0x0, propertyPVALID}, // LATIN SMALL LETTER D WITH CURL - {0x0222, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER OU - {0x0223, 0x0, propertyPVALID}, // LATIN SMALL LETTER OU - {0x0224, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER Z WITH HOOK - {0x0225, 0x0, propertyPVALID}, // LATIN SMALL LETTER Z WITH HOOK - {0x0226, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER A WITH DOT ABOVE - {0x0227, 0x0, propertyPVALID}, // LATIN SMALL LETTER A WITH DOT ABOVE - {0x0228, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER E WITH CEDILLA - {0x0229, 0x0, propertyPVALID}, // LATIN SMALL LETTER E WITH CEDILLA - {0x022A, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH DIAERESIS AND MA - {0x022B, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH DIAERESIS AND MACR - {0x022C, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH TILDE AND MACRON - {0x022D, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH TILDE AND MACRON - {0x022E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH DOT ABOVE - {0x022F, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH DOT ABOVE - {0x0230, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH DOT ABOVE AND MA - {0x0231, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH DOT ABOVE AND MACR - {0x0232, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER Y WITH MACRON - {0x0233, 0x0239, propertyPVALID}, // LATIN SMALL LETTER Y WITH MACRON..LATIN SMAL - {0x023A, 0x023B, propertyDISALLOWED}, // LATIN CAPITAL LETTER A WITH STROKE..LATIN CA - {0x023C, 0x0, propertyPVALID}, // LATIN SMALL LETTER C WITH STROKE - {0x023D, 0x023E, propertyDISALLOWED}, // LATIN CAPITAL LETTER L WITH BAR..LATIN CAPIT - {0x023F, 0x0240, propertyPVALID}, // LATIN SMALL LETTER S WITH SWASH TAIL..LATIN - {0x0241, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER GLOTTAL STOP - {0x0242, 0x0, propertyPVALID}, // LATIN SMALL LETTER GLOTTAL STOP - {0x0243, 0x0246, propertyDISALLOWED}, // LATIN CAPITAL LETTER B WITH STROKE..LATIN CA - {0x0247, 0x0, propertyPVALID}, // LATIN SMALL LETTER E WITH STROKE - {0x0248, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER J WITH STROKE - {0x0249, 0x0, propertyPVALID}, // LATIN SMALL LETTER J WITH STROKE - {0x024A, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL - {0x024B, 0x0, propertyPVALID}, // LATIN SMALL LETTER Q WITH HOOK TAIL - {0x024C, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER R WITH STROKE - {0x024D, 0x0, propertyPVALID}, // LATIN SMALL LETTER R WITH STROKE - {0x024E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER Y WITH STROKE - {0x024F, 0x02AF, propertyPVALID}, // LATIN SMALL LETTER Y WITH STROKE..LATIN SMAL - {0x02B0, 0x02B8, propertyDISALLOWED}, // MODIFIER LETTER SMALL H..MODIFIER LETTER SMA - {0x02B9, 0x02C1, propertyPVALID}, // MODIFIER LETTER PRIME..MODIFIER LETTER REVER - {0x02C2, 0x02C5, propertyDISALLOWED}, // MODIFIER LETTER LEFT ARROWHEAD..MODIFIER LET - {0x02C6, 0x02D1, propertyPVALID}, // MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER - {0x02D2, 0x02EB, propertyDISALLOWED}, // MODIFIER LETTER CENTRED RIGHT HALF RING..MOD - {0x02EC, 0x0, propertyPVALID}, // MODIFIER LETTER VOICING - {0x02ED, 0x0, propertyDISALLOWED}, // MODIFIER LETTER UNASPIRATED - {0x02EE, 0x0, propertyPVALID}, // MODIFIER LETTER DOUBLE APOSTROPHE - {0x02EF, 0x02FF, propertyDISALLOWED}, // MODIFIER LETTER LOW DOWN ARROWHEAD..MODIFIER - {0x0300, 0x033F, propertyPVALID}, // COMBINING GRAVE ACCENT..COMBINING DOUBLE OVE - {0x0340, 0x0341, propertyDISALLOWED}, // COMBINING GRAVE TONE MARK..COMBINING ACUTE T - {0x0342, 0x0, propertyPVALID}, // COMBINING GREEK PERISPOMENI - {0x0343, 0x0345, propertyDISALLOWED}, // COMBINING GREEK KORONIS..COMBINING GREEK YPO - {0x0346, 0x034E, propertyPVALID}, // COMBINING BRIDGE ABOVE..COMBINING UPWARDS AR - {0x034F, 0x0, propertyDISALLOWED}, // COMBINING GRAPHEME JOINER - {0x0350, 0x036F, propertyPVALID}, // COMBINING RIGHT ARROWHEAD ABOVE..COMBINING L - {0x0370, 0x0, propertyDISALLOWED}, // GREEK CAPITAL LETTER HETA - {0x0371, 0x0, propertyPVALID}, // GREEK SMALL LETTER HETA - {0x0372, 0x0, propertyDISALLOWED}, // GREEK CAPITAL LETTER ARCHAIC SAMPI - {0x0373, 0x0, propertyPVALID}, // GREEK SMALL LETTER ARCHAIC SAMPI - {0x0374, 0x0, propertyDISALLOWED}, // GREEK NUMERAL SIGN - {0x0375, 0x0, propertyCONTEXTO}, // GREEK LOWER NUMERAL SIGN - {0x0376, 0x0, propertyDISALLOWED}, // GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA - {0x0377, 0x0, propertyPVALID}, // GREEK SMALL LETTER PAMPHYLIAN DIGAMMA - {0x0378, 0x0379, propertyUNASSIGNED}, // .. - {0x037A, 0x0, propertyDISALLOWED}, // GREEK YPOGEGRAMMENI - {0x037B, 0x037D, propertyPVALID}, // GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GR - {0x037E, 0x0, propertyDISALLOWED}, // GREEK QUESTION MARK - {0x037F, 0x0383, propertyUNASSIGNED}, // .. - {0x0384, 0x038A, propertyDISALLOWED}, // GREEK TONOS..GREEK CAPITAL LETTER IOTA WITH - {0x038B, 0x0, propertyUNASSIGNED}, // - {0x038C, 0x0, propertyDISALLOWED}, // GREEK CAPITAL LETTER OMICRON WITH TONOS - {0x038D, 0x0, propertyUNASSIGNED}, // - {0x038E, 0x038F, propertyDISALLOWED}, // GREEK CAPITAL LETTER UPSILON WITH TONOS..GRE - {0x0390, 0x0, propertyPVALID}, // GREEK SMALL LETTER IOTA WITH DIALYTIKA AND T - {0x0391, 0x03A1, propertyDISALLOWED}, // GREEK CAPITAL LETTER ALPHA..GREEK CAPITAL LE - {0x03A2, 0x0, propertyUNASSIGNED}, // - {0x03A3, 0x03AB, propertyDISALLOWED}, // GREEK CAPITAL LETTER SIGMA..GREEK CAPITAL LE - {0x03AC, 0x03CE, propertyPVALID}, // GREEK SMALL LETTER ALPHA WITH TONOS..GREEK S - {0x03CF, 0x03D6, propertyDISALLOWED}, // GREEK CAPITAL KAI SYMBOL..GREEK PI SYMBOL - {0x03D7, 0x0, propertyPVALID}, // GREEK KAI SYMBOL - {0x03D8, 0x0, propertyDISALLOWED}, // GREEK LETTER ARCHAIC KOPPA - {0x03D9, 0x0, propertyPVALID}, // GREEK SMALL LETTER ARCHAIC KOPPA - {0x03DA, 0x0, propertyDISALLOWED}, // GREEK LETTER STIGMA - {0x03DB, 0x0, propertyPVALID}, // GREEK SMALL LETTER STIGMA - {0x03DC, 0x0, propertyDISALLOWED}, // GREEK LETTER DIGAMMA - {0x03DD, 0x0, propertyPVALID}, // GREEK SMALL LETTER DIGAMMA - {0x03DE, 0x0, propertyDISALLOWED}, // GREEK LETTER KOPPA - {0x03DF, 0x0, propertyPVALID}, // GREEK SMALL LETTER KOPPA - {0x03E0, 0x0, propertyDISALLOWED}, // GREEK LETTER SAMPI - {0x03E1, 0x0, propertyPVALID}, // GREEK SMALL LETTER SAMPI - {0x03E2, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER SHEI - {0x03E3, 0x0, propertyPVALID}, // COPTIC SMALL LETTER SHEI - {0x03E4, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER FEI - {0x03E5, 0x0, propertyPVALID}, // COPTIC SMALL LETTER FEI - {0x03E6, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER KHEI - {0x03E7, 0x0, propertyPVALID}, // COPTIC SMALL LETTER KHEI - {0x03E8, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER HORI - {0x03E9, 0x0, propertyPVALID}, // COPTIC SMALL LETTER HORI - {0x03EA, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER GANGIA - {0x03EB, 0x0, propertyPVALID}, // COPTIC SMALL LETTER GANGIA - {0x03EC, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER SHIMA - {0x03ED, 0x0, propertyPVALID}, // COPTIC SMALL LETTER SHIMA - {0x03EE, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER DEI - {0x03EF, 0x0, propertyPVALID}, // COPTIC SMALL LETTER DEI - {0x03F0, 0x03F2, propertyDISALLOWED}, // GREEK KAPPA SYMBOL..GREEK LUNATE SIGMA SYMBO - {0x03F3, 0x0, propertyPVALID}, // GREEK LETTER YOT - {0x03F4, 0x03F7, propertyDISALLOWED}, // GREEK CAPITAL THETA SYMBOL..GREEK CAPITAL LE - {0x03F8, 0x0, propertyPVALID}, // GREEK SMALL LETTER SHO - {0x03F9, 0x03FA, propertyDISALLOWED}, // GREEK CAPITAL LUNATE SIGMA SYMBOL..GREEK CAP - {0x03FB, 0x03FC, propertyPVALID}, // GREEK SMALL LETTER SAN..GREEK RHO WITH STROK - {0x03FD, 0x042F, propertyDISALLOWED}, // GREEK CAPITAL REVERSED LUNATE SIGMA SYMBOL.. - {0x0430, 0x045F, propertyPVALID}, // CYRILLIC SMALL LETTER A..CYRILLIC SMALL LETT - {0x0460, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER OMEGA - {0x0461, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER OMEGA - {0x0462, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER YAT - {0x0463, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER YAT - {0x0464, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER IOTIFIED E - {0x0465, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER IOTIFIED E - {0x0466, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER LITTLE YUS - {0x0467, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER LITTLE YUS - {0x0468, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER IOTIFIED LITTLE YUS - {0x0469, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER IOTIFIED LITTLE YUS - {0x046A, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER BIG YUS - {0x046B, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER BIG YUS - {0x046C, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER IOTIFIED BIG YUS - {0x046D, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER IOTIFIED BIG YUS - {0x046E, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER KSI - {0x046F, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER KSI - {0x0470, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER PSI - {0x0471, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER PSI - {0x0472, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER FITA - {0x0473, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER FITA - {0x0474, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER IZHITSA - {0x0475, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER IZHITSA - {0x0476, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER IZHITSA WITH DOUBLE - {0x0477, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER IZHITSA WITH DOUBLE GR - {0x0478, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER UK - {0x0479, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER UK - {0x047A, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER ROUND OMEGA - {0x047B, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER ROUND OMEGA - {0x047C, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER OMEGA WITH TITLO - {0x047D, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER OMEGA WITH TITLO - {0x047E, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER OT - {0x047F, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER OT - {0x0480, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER KOPPA - {0x0481, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER KOPPA - {0x0482, 0x0, propertyDISALLOWED}, // CYRILLIC THOUSANDS SIGN - {0x0483, 0x0487, propertyPVALID}, // COMBINING CYRILLIC TITLO..COMBINING CYRILLIC - {0x0488, 0x048A, propertyDISALLOWED}, // COMBINING CYRILLIC HUNDRED THOUSANDS SIGN..C - {0x048B, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER SHORT I WITH TAIL - {0x048C, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER SEMISOFT SIGN - {0x048D, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER SEMISOFT SIGN - {0x048E, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER ER WITH TICK - {0x048F, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER ER WITH TICK - {0x0490, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER GHE WITH UPTURN - {0x0491, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER GHE WITH UPTURN - {0x0492, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER GHE WITH STROKE - {0x0493, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER GHE WITH STROKE - {0x0494, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER GHE WITH MIDDLE HOOK - {0x0495, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER GHE WITH MIDDLE HOOK - {0x0496, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER - {0x0497, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER ZHE WITH DESCENDER - {0x0498, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER ZE WITH DESCENDER - {0x0499, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER ZE WITH DESCENDER - {0x049A, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER KA WITH DESCENDER - {0x049B, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER KA WITH DESCENDER - {0x049C, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER KA WITH VERTICAL STR - {0x049D, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER KA WITH VERTICAL STROK - {0x049E, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER KA WITH STROKE - {0x049F, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER KA WITH STROKE - {0x04A0, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER BASHKIR KA - {0x04A1, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER BASHKIR KA - {0x04A2, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER EN WITH DESCENDER - {0x04A3, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER EN WITH DESCENDER - {0x04A4, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LIGATURE EN GHE - {0x04A5, 0x0, propertyPVALID}, // CYRILLIC SMALL LIGATURE EN GHE - {0x04A6, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER PE WITH MIDDLE HOOK - {0x04A7, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER PE WITH MIDDLE HOOK - {0x04A8, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER ABKHASIAN HA - {0x04A9, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER ABKHASIAN HA - {0x04AA, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER ES WITH DESCENDER - {0x04AB, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER ES WITH DESCENDER - {0x04AC, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER TE WITH DESCENDER - {0x04AD, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER TE WITH DESCENDER - {0x04AE, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER STRAIGHT U - {0x04AF, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER STRAIGHT U - {0x04B0, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER STRAIGHT U WITH STRO - {0x04B1, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE - {0x04B2, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER HA WITH DESCENDER - {0x04B3, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER HA WITH DESCENDER - {0x04B4, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LIGATURE TE TSE - {0x04B5, 0x0, propertyPVALID}, // CYRILLIC SMALL LIGATURE TE TSE - {0x04B6, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER CHE WITH DESCENDER - {0x04B7, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER CHE WITH DESCENDER - {0x04B8, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER CHE WITH VERTICAL ST - {0x04B9, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER CHE WITH VERTICAL STRO - {0x04BA, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER SHHA - {0x04BB, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER SHHA - {0x04BC, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER ABKHASIAN CHE - {0x04BD, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER ABKHASIAN CHE - {0x04BE, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER ABKHASIAN CHE WITH D - {0x04BF, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER ABKHASIAN CHE WITH DES - {0x04C0, 0x04C1, propertyDISALLOWED}, // CYRILLIC LETTER PALOCHKA..CYRILLIC CAPITAL L - {0x04C2, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER ZHE WITH BREVE - {0x04C3, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER KA WITH HOOK - {0x04C4, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER KA WITH HOOK - {0x04C5, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER EL WITH TAIL - {0x04C6, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER EL WITH TAIL - {0x04C7, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER EN WITH HOOK - {0x04C8, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER EN WITH HOOK - {0x04C9, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER EN WITH TAIL - {0x04CA, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER EN WITH TAIL - {0x04CB, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER KHAKASSIAN CHE - {0x04CC, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER KHAKASSIAN CHE - {0x04CD, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER EM WITH TAIL - {0x04CE, 0x04CF, propertyPVALID}, // CYRILLIC SMALL LETTER EM WITH TAIL..CYRILLIC - {0x04D0, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER A WITH BREVE - {0x04D1, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER A WITH BREVE - {0x04D2, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER A WITH DIAERESIS - {0x04D3, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER A WITH DIAERESIS - {0x04D4, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LIGATURE A IE - {0x04D5, 0x0, propertyPVALID}, // CYRILLIC SMALL LIGATURE A IE - {0x04D6, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER IE WITH BREVE - {0x04D7, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER IE WITH BREVE - {0x04D8, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER SCHWA - {0x04D9, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER SCHWA - {0x04DA, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER SCHWA WITH DIAERESIS - {0x04DB, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER SCHWA WITH DIAERESIS - {0x04DC, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER ZHE WITH DIAERESIS - {0x04DD, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER ZHE WITH DIAERESIS - {0x04DE, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER ZE WITH DIAERESIS - {0x04DF, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER ZE WITH DIAERESIS - {0x04E0, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER ABKHASIAN DZE - {0x04E1, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER ABKHASIAN DZE - {0x04E2, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER I WITH MACRON - {0x04E3, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER I WITH MACRON - {0x04E4, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER I WITH DIAERESIS - {0x04E5, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER I WITH DIAERESIS - {0x04E6, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER O WITH DIAERESIS - {0x04E7, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER O WITH DIAERESIS - {0x04E8, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER BARRED O - {0x04E9, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER BARRED O - {0x04EA, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER BARRED O WITH DIAERE - {0x04EB, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER BARRED O WITH DIAERESI - {0x04EC, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER E WITH DIAERESIS - {0x04ED, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER E WITH DIAERESIS - {0x04EE, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER U WITH MACRON - {0x04EF, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER U WITH MACRON - {0x04F0, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER U WITH DIAERESIS - {0x04F1, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER U WITH DIAERESIS - {0x04F2, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER U WITH DOUBLE ACUTE - {0x04F3, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER U WITH DOUBLE ACUTE - {0x04F4, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER CHE WITH DIAERESIS - {0x04F5, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER CHE WITH DIAERESIS - {0x04F6, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER GHE WITH DESCENDER - {0x04F7, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER GHE WITH DESCENDER - {0x04F8, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER YERU WITH DIAERESIS - {0x04F9, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER YERU WITH DIAERESIS - {0x04FA, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER GHE WITH STROKE AND - {0x04FB, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER GHE WITH STROKE AND HO - {0x04FC, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER HA WITH HOOK - {0x04FD, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER HA WITH HOOK - {0x04FE, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER HA WITH STROKE - {0x04FF, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER HA WITH STROKE - {0x0500, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER KOMI DE - {0x0501, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER KOMI DE - {0x0502, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER KOMI DJE - {0x0503, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER KOMI DJE - {0x0504, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER KOMI ZJE - {0x0505, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER KOMI ZJE - {0x0506, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER KOMI DZJE - {0x0507, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER KOMI DZJE - {0x0508, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER KOMI LJE - {0x0509, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER KOMI LJE - {0x050A, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER KOMI NJE - {0x050B, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER KOMI NJE - {0x050C, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER KOMI SJE - {0x050D, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER KOMI SJE - {0x050E, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER KOMI TJE - {0x050F, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER KOMI TJE - {0x0510, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER REVERSED ZE - {0x0511, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER REVERSED ZE - {0x0512, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER EL WITH HOOK - {0x0513, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER EL WITH HOOK - {0x0514, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER LHA - {0x0515, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER LHA - {0x0516, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER RHA - {0x0517, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER RHA - {0x0518, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER YAE - {0x0519, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER YAE - {0x051A, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER QA - {0x051B, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER QA - {0x051C, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER WE - {0x051D, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER WE - {0x051E, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER ALEUT KA - {0x051F, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER ALEUT KA - {0x0520, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER EL WITH MIDDLE HOOK - {0x0521, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER EL WITH MIDDLE HOOK - {0x0522, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER EN WITH MIDDLE HOOK - {0x0523, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER EN WITH MIDDLE HOOK - {0x0524, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER PE WITH DESCENDER - {0x0525, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER PE WITH DESCENDER - {0x0526, 0x0530, propertyUNASSIGNED}, // .. - {0x0531, 0x0556, propertyDISALLOWED}, // ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITA - {0x0557, 0x0558, propertyUNASSIGNED}, // .. - {0x0559, 0x0, propertyPVALID}, // ARMENIAN MODIFIER LETTER LEFT HALF RING - {0x055A, 0x055F, propertyDISALLOWED}, // ARMENIAN APOSTROPHE..ARMENIAN ABBREVIATION M - {0x0560, 0x0, propertyUNASSIGNED}, // - {0x0561, 0x0586, propertyPVALID}, // ARMENIAN SMALL LETTER AYB..ARMENIAN SMALL LE - {0x0587, 0x0, propertyDISALLOWED}, // ARMENIAN SMALL LIGATURE ECH YIWN - {0x0588, 0x0, propertyUNASSIGNED}, // - {0x0589, 0x058A, propertyDISALLOWED}, // ARMENIAN FULL STOP..ARMENIAN HYPHEN - {0x058B, 0x0590, propertyUNASSIGNED}, // .. - {0x0591, 0x05BD, propertyPVALID}, // HEBREW ACCENT ETNAHTA..HEBREW POINT METEG - {0x05BE, 0x0, propertyDISALLOWED}, // HEBREW PUNCTUATION MAQAF - {0x05BF, 0x0, propertyPVALID}, // HEBREW POINT RAFE - {0x05C0, 0x0, propertyDISALLOWED}, // HEBREW PUNCTUATION PASEQ - {0x05C1, 0x05C2, propertyPVALID}, // HEBREW POINT SHIN DOT..HEBREW POINT SIN DOT - {0x05C3, 0x0, propertyDISALLOWED}, // HEBREW PUNCTUATION SOF PASUQ - {0x05C4, 0x05C5, propertyPVALID}, // HEBREW MARK UPPER DOT..HEBREW MARK LOWER DOT - {0x05C6, 0x0, propertyDISALLOWED}, // HEBREW PUNCTUATION NUN HAFUKHA - {0x05C7, 0x0, propertyPVALID}, // HEBREW POINT QAMATS QATAN - {0x05C8, 0x05CF, propertyUNASSIGNED}, // .. - {0x05D0, 0x05EA, propertyPVALID}, // HEBREW LETTER ALEF..HEBREW LETTER TAV - {0x05EB, 0x05EF, propertyUNASSIGNED}, // .. - {0x05F0, 0x05F2, propertyPVALID}, // HEBREW LIGATURE YIDDISH DOUBLE VAV..HEBREW L - {0x05F3, 0x05F4, propertyCONTEXTO}, // HEBREW PUNCTUATION GERESH..HEBREW PUNCTUATIO - {0x05F5, 0x05FF, propertyUNASSIGNED}, // .. - {0x0600, 0x0603, propertyDISALLOWED}, // ARABIC NUMBER SIGN..ARABIC SIGN SAFHA - {0x0604, 0x0605, propertyUNASSIGNED}, // .. - {0x0606, 0x060F, propertyDISALLOWED}, // ARABIC-INDIC CUBE ROOT..ARABIC SIGN MISRA - {0x0610, 0x061A, propertyPVALID}, // ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM..AR - {0x061B, 0x0, propertyDISALLOWED}, // ARABIC SEMICOLON - {0x061C, 0x061D, propertyUNASSIGNED}, // .. - {0x061E, 0x061F, propertyDISALLOWED}, // ARABIC TRIPLE DOT PUNCTUATION MARK..ARABIC Q - {0x0620, 0x0, propertyUNASSIGNED}, // - {0x0621, 0x063F, propertyPVALID}, // ARABIC LETTER HAMZA..ARABIC LETTER FARSI YEH - {0x0640, 0x0, propertyDISALLOWED}, // ARABIC TATWEEL - {0x0641, 0x065E, propertyPVALID}, // ARABIC LETTER FEH..ARABIC FATHA WITH TWO DOT - {0x065F, 0x0, propertyUNASSIGNED}, // - {0x0660, 0x0669, propertyCONTEXTO}, // ARABIC-INDIC DIGIT ZERO..ARABIC-INDIC DIGIT - {0x066A, 0x066D, propertyDISALLOWED}, // ARABIC PERCENT SIGN..ARABIC FIVE POINTED STA - {0x066E, 0x0674, propertyPVALID}, // ARABIC LETTER DOTLESS BEH..ARABIC LETTER HIG - {0x0675, 0x0678, propertyDISALLOWED}, // ARABIC LETTER HIGH HAMZA ALEF..ARABIC LETTER - {0x0679, 0x06D3, propertyPVALID}, // ARABIC LETTER TTEH..ARABIC LETTER YEH BARREE - {0x06D4, 0x0, propertyDISALLOWED}, // ARABIC FULL STOP - {0x06D5, 0x06DC, propertyPVALID}, // ARABIC LETTER AE..ARABIC SMALL HIGH SEEN - {0x06DD, 0x06DE, propertyDISALLOWED}, // ARABIC END OF AYAH..ARABIC START OF RUB EL H - {0x06DF, 0x06E8, propertyPVALID}, // ARABIC SMALL HIGH ROUNDED ZERO..ARABIC SMALL - {0x06E9, 0x0, propertyDISALLOWED}, // ARABIC PLACE OF SAJDAH - {0x06EA, 0x06EF, propertyPVALID}, // ARABIC EMPTY CENTRE LOW STOP..ARABIC LETTER - {0x06F0, 0x06F9, propertyCONTEXTO}, // EXTENDED ARABIC-INDIC DIGIT ZERO..EXTENDED A - {0x06FA, 0x06FF, propertyPVALID}, // ARABIC LETTER SHEEN WITH DOT BELOW..ARABIC L - {0x0700, 0x070D, propertyDISALLOWED}, // SYRIAC END OF PARAGRAPH..SYRIAC HARKLEAN AST - {0x070E, 0x0, propertyUNASSIGNED}, // - {0x070F, 0x0, propertyDISALLOWED}, // SYRIAC ABBREVIATION MARK - {0x0710, 0x074A, propertyPVALID}, // SYRIAC LETTER ALAPH..SYRIAC BARREKH - {0x074B, 0x074C, propertyUNASSIGNED}, // .. - {0x074D, 0x07B1, propertyPVALID}, // SYRIAC LETTER SOGDIAN ZHAIN..THAANA LETTER N - {0x07B2, 0x07BF, propertyUNASSIGNED}, // .. - {0x07C0, 0x07F5, propertyPVALID}, // NKO DIGIT ZERO..NKO LOW TONE APOSTROPHE - {0x07F6, 0x07FA, propertyDISALLOWED}, // NKO SYMBOL OO DENNEN..NKO LAJANYALAN - {0x07FB, 0x07FF, propertyUNASSIGNED}, // .. - {0x0800, 0x082D, propertyPVALID}, // SAMARITAN LETTER ALAF..SAMARITAN MARK NEQUDA - {0x082E, 0x082F, propertyUNASSIGNED}, // .. - {0x0830, 0x083E, propertyDISALLOWED}, // SAMARITAN PUNCTUATION NEQUDAA..SAMARITAN PUN - {0x083F, 0x08FF, propertyUNASSIGNED}, // .. - {0x0900, 0x0939, propertyPVALID}, // DEVANAGARI SIGN INVERTED CANDRABINDU..DEVANA - {0x093A, 0x093B, propertyUNASSIGNED}, // .. - {0x093C, 0x094E, propertyPVALID}, // DEVANAGARI SIGN NUKTA..DEVANAGARI VOWEL SIGN - {0x094F, 0x0, propertyUNASSIGNED}, // - {0x0950, 0x0955, propertyPVALID}, // DEVANAGARI OM..DEVANAGARI VOWEL SIGN CANDRA - {0x0956, 0x0957, propertyUNASSIGNED}, // .. - {0x0958, 0x095F, propertyDISALLOWED}, // DEVANAGARI LETTER QA..DEVANAGARI LETTER YYA - {0x0960, 0x0963, propertyPVALID}, // DEVANAGARI LETTER VOCALIC RR..DEVANAGARI VOW - {0x0964, 0x0965, propertyDISALLOWED}, // DEVANAGARI DANDA..DEVANAGARI DOUBLE DANDA - {0x0966, 0x096F, propertyPVALID}, // DEVANAGARI DIGIT ZERO..DEVANAGARI DIGIT NINE - {0x0970, 0x0, propertyDISALLOWED}, // DEVANAGARI ABBREVIATION SIGN - {0x0971, 0x0972, propertyPVALID}, // DEVANAGARI SIGN HIGH SPACING DOT..DEVANAGARI - {0x0973, 0x0978, propertyUNASSIGNED}, // .. - {0x0979, 0x097F, propertyPVALID}, // DEVANAGARI LETTER ZHA..DEVANAGARI LETTER BBA - {0x0980, 0x0, propertyUNASSIGNED}, // - {0x0981, 0x0983, propertyPVALID}, // BENGALI SIGN CANDRABINDU..BENGALI SIGN VISAR - {0x0984, 0x0, propertyUNASSIGNED}, // - {0x0985, 0x098C, propertyPVALID}, // BENGALI LETTER A..BENGALI LETTER VOCALIC L - {0x098D, 0x098E, propertyUNASSIGNED}, // .. - {0x098F, 0x0990, propertyPVALID}, // BENGALI LETTER E..BENGALI LETTER AI - {0x0991, 0x0992, propertyUNASSIGNED}, // .. - {0x0993, 0x09A8, propertyPVALID}, // BENGALI LETTER O..BENGALI LETTER NA - {0x09A9, 0x0, propertyUNASSIGNED}, // - {0x09AA, 0x09B0, propertyPVALID}, // BENGALI LETTER PA..BENGALI LETTER RA - {0x09B1, 0x0, propertyUNASSIGNED}, // - {0x09B2, 0x0, propertyPVALID}, // BENGALI LETTER LA - {0x09B3, 0x09B5, propertyUNASSIGNED}, // .. - {0x09B6, 0x09B9, propertyPVALID}, // BENGALI LETTER SHA..BENGALI LETTER HA - {0x09BA, 0x09BB, propertyUNASSIGNED}, // .. - {0x09BC, 0x09C4, propertyPVALID}, // BENGALI SIGN NUKTA..BENGALI VOWEL SIGN VOCAL - {0x09C5, 0x09C6, propertyUNASSIGNED}, // .. - {0x09C7, 0x09C8, propertyPVALID}, // BENGALI VOWEL SIGN E..BENGALI VOWEL SIGN AI - {0x09C9, 0x09CA, propertyUNASSIGNED}, // .. - {0x09CB, 0x09CE, propertyPVALID}, // BENGALI VOWEL SIGN O..BENGALI LETTER KHANDA - {0x09CF, 0x09D6, propertyUNASSIGNED}, // .. - {0x09D7, 0x0, propertyPVALID}, // BENGALI AU LENGTH MARK - {0x09D8, 0x09DB, propertyUNASSIGNED}, // .. - {0x09DC, 0x09DD, propertyDISALLOWED}, // BENGALI LETTER RRA..BENGALI LETTER RHA - {0x09DE, 0x0, propertyUNASSIGNED}, // - {0x09DF, 0x0, propertyDISALLOWED}, // BENGALI LETTER YYA - {0x09E0, 0x09E3, propertyPVALID}, // BENGALI LETTER VOCALIC RR..BENGALI VOWEL SIG - {0x09E4, 0x09E5, propertyUNASSIGNED}, // .. - {0x09E6, 0x09F1, propertyPVALID}, // BENGALI DIGIT ZERO..BENGALI LETTER RA WITH L - {0x09F2, 0x09FB, propertyDISALLOWED}, // BENGALI RUPEE MARK..BENGALI GANDA MARK - {0x09FC, 0x0A00, propertyUNASSIGNED}, // .. - {0x0A01, 0x0A03, propertyPVALID}, // GURMUKHI SIGN ADAK BINDI..GURMUKHI SIGN VISA - {0x0A04, 0x0, propertyUNASSIGNED}, // - {0x0A05, 0x0A0A, propertyPVALID}, // GURMUKHI LETTER A..GURMUKHI LETTER UU - {0x0A0B, 0x0A0E, propertyUNASSIGNED}, // .. - {0x0A0F, 0x0A10, propertyPVALID}, // GURMUKHI LETTER EE..GURMUKHI LETTER AI - {0x0A11, 0x0A12, propertyUNASSIGNED}, // .. - {0x0A13, 0x0A28, propertyPVALID}, // GURMUKHI LETTER OO..GURMUKHI LETTER NA - {0x0A29, 0x0, propertyUNASSIGNED}, // - {0x0A2A, 0x0A30, propertyPVALID}, // GURMUKHI LETTER PA..GURMUKHI LETTER RA - {0x0A31, 0x0, propertyUNASSIGNED}, // - {0x0A32, 0x0, propertyPVALID}, // GURMUKHI LETTER LA - {0x0A33, 0x0, propertyDISALLOWED}, // GURMUKHI LETTER LLA - {0x0A34, 0x0, propertyUNASSIGNED}, // - {0x0A35, 0x0, propertyPVALID}, // GURMUKHI LETTER VA - {0x0A36, 0x0, propertyDISALLOWED}, // GURMUKHI LETTER SHA - {0x0A37, 0x0, propertyUNASSIGNED}, // - {0x0A38, 0x0A39, propertyPVALID}, // GURMUKHI LETTER SA..GURMUKHI LETTER HA - {0x0A3A, 0x0A3B, propertyUNASSIGNED}, // .. - {0x0A3C, 0x0, propertyPVALID}, // GURMUKHI SIGN NUKTA - {0x0A3D, 0x0, propertyUNASSIGNED}, // - {0x0A3E, 0x0A42, propertyPVALID}, // GURMUKHI VOWEL SIGN AA..GURMUKHI VOWEL SIGN - {0x0A43, 0x0A46, propertyUNASSIGNED}, // .. - {0x0A47, 0x0A48, propertyPVALID}, // GURMUKHI VOWEL SIGN EE..GURMUKHI VOWEL SIGN - {0x0A49, 0x0A4A, propertyUNASSIGNED}, // .. - {0x0A4B, 0x0A4D, propertyPVALID}, // GURMUKHI VOWEL SIGN OO..GURMUKHI SIGN VIRAMA - {0x0A4E, 0x0A50, propertyUNASSIGNED}, // .. - {0x0A51, 0x0, propertyPVALID}, // GURMUKHI SIGN UDAAT - {0x0A52, 0x0A58, propertyUNASSIGNED}, // .. - {0x0A59, 0x0A5B, propertyDISALLOWED}, // GURMUKHI LETTER KHHA..GURMUKHI LETTER ZA - {0x0A5C, 0x0, propertyPVALID}, // GURMUKHI LETTER RRA - {0x0A5D, 0x0, propertyUNASSIGNED}, // - {0x0A5E, 0x0, propertyDISALLOWED}, // GURMUKHI LETTER FA - {0x0A5F, 0x0A65, propertyUNASSIGNED}, // .. - {0x0A66, 0x0A75, propertyPVALID}, // GURMUKHI DIGIT ZERO..GURMUKHI SIGN YAKASH - {0x0A76, 0x0A80, propertyUNASSIGNED}, // .. - {0x0A81, 0x0A83, propertyPVALID}, // GUJARATI SIGN CANDRABINDU..GUJARATI SIGN VIS - {0x0A84, 0x0, propertyUNASSIGNED}, // - {0x0A85, 0x0A8D, propertyPVALID}, // GUJARATI LETTER A..GUJARATI VOWEL CANDRA E - {0x0A8E, 0x0, propertyUNASSIGNED}, // - {0x0A8F, 0x0A91, propertyPVALID}, // GUJARATI LETTER E..GUJARATI VOWEL CANDRA O - {0x0A92, 0x0, propertyUNASSIGNED}, // - {0x0A93, 0x0AA8, propertyPVALID}, // GUJARATI LETTER O..GUJARATI LETTER NA - {0x0AA9, 0x0, propertyUNASSIGNED}, // - {0x0AAA, 0x0AB0, propertyPVALID}, // GUJARATI LETTER PA..GUJARATI LETTER RA - {0x0AB1, 0x0, propertyUNASSIGNED}, // - {0x0AB2, 0x0AB3, propertyPVALID}, // GUJARATI LETTER LA..GUJARATI LETTER LLA - {0x0AB4, 0x0, propertyUNASSIGNED}, // - {0x0AB5, 0x0AB9, propertyPVALID}, // GUJARATI LETTER VA..GUJARATI LETTER HA - {0x0ABA, 0x0ABB, propertyUNASSIGNED}, // .. - {0x0ABC, 0x0AC5, propertyPVALID}, // GUJARATI SIGN NUKTA..GUJARATI VOWEL SIGN CAN - {0x0AC6, 0x0, propertyUNASSIGNED}, // - {0x0AC7, 0x0AC9, propertyPVALID}, // GUJARATI VOWEL SIGN E..GUJARATI VOWEL SIGN C - {0x0ACA, 0x0, propertyUNASSIGNED}, // - {0x0ACB, 0x0ACD, propertyPVALID}, // GUJARATI VOWEL SIGN O..GUJARATI SIGN VIRAMA - {0x0ACE, 0x0ACF, propertyUNASSIGNED}, // .. - {0x0AD0, 0x0, propertyPVALID}, // GUJARATI OM - {0x0AD1, 0x0ADF, propertyUNASSIGNED}, // .. - {0x0AE0, 0x0AE3, propertyPVALID}, // GUJARATI LETTER VOCALIC RR..GUJARATI VOWEL S - {0x0AE4, 0x0AE5, propertyUNASSIGNED}, // .. - {0x0AE6, 0x0AEF, propertyPVALID}, // GUJARATI DIGIT ZERO..GUJARATI DIGIT NINE - {0x0AF0, 0x0, propertyUNASSIGNED}, // - {0x0AF1, 0x0, propertyDISALLOWED}, // GUJARATI RUPEE SIGN - {0x0AF2, 0x0B00, propertyUNASSIGNED}, // .. - {0x0B01, 0x0B03, propertyPVALID}, // ORIYA SIGN CANDRABINDU..ORIYA SIGN VISARGA - {0x0B04, 0x0, propertyUNASSIGNED}, // - {0x0B05, 0x0B0C, propertyPVALID}, // ORIYA LETTER A..ORIYA LETTER VOCALIC L - {0x0B0D, 0x0B0E, propertyUNASSIGNED}, // .. - {0x0B0F, 0x0B10, propertyPVALID}, // ORIYA LETTER E..ORIYA LETTER AI - {0x0B11, 0x0B12, propertyUNASSIGNED}, // .. - {0x0B13, 0x0B28, propertyPVALID}, // ORIYA LETTER O..ORIYA LETTER NA - {0x0B29, 0x0, propertyUNASSIGNED}, // - {0x0B2A, 0x0B30, propertyPVALID}, // ORIYA LETTER PA..ORIYA LETTER RA - {0x0B31, 0x0, propertyUNASSIGNED}, // - {0x0B32, 0x0B33, propertyPVALID}, // ORIYA LETTER LA..ORIYA LETTER LLA - {0x0B34, 0x0, propertyUNASSIGNED}, // - {0x0B35, 0x0B39, propertyPVALID}, // ORIYA LETTER VA..ORIYA LETTER HA - {0x0B3A, 0x0B3B, propertyUNASSIGNED}, // .. - {0x0B3C, 0x0B44, propertyPVALID}, // ORIYA SIGN NUKTA..ORIYA VOWEL SIGN VOCALIC R - {0x0B45, 0x0B46, propertyUNASSIGNED}, // .. - {0x0B47, 0x0B48, propertyPVALID}, // ORIYA VOWEL SIGN E..ORIYA VOWEL SIGN AI - {0x0B49, 0x0B4A, propertyUNASSIGNED}, // .. - {0x0B4B, 0x0B4D, propertyPVALID}, // ORIYA VOWEL SIGN O..ORIYA SIGN VIRAMA - {0x0B4E, 0x0B55, propertyUNASSIGNED}, // .. - {0x0B56, 0x0B57, propertyPVALID}, // ORIYA AI LENGTH MARK..ORIYA AU LENGTH MARK - {0x0B58, 0x0B5B, propertyUNASSIGNED}, // .. - {0x0B5C, 0x0B5D, propertyDISALLOWED}, // ORIYA LETTER RRA..ORIYA LETTER RHA - {0x0B5E, 0x0, propertyUNASSIGNED}, // - {0x0B5F, 0x0B63, propertyPVALID}, // ORIYA LETTER YYA..ORIYA VOWEL SIGN VOCALIC L - {0x0B64, 0x0B65, propertyUNASSIGNED}, // .. - {0x0B66, 0x0B6F, propertyPVALID}, // ORIYA DIGIT ZERO..ORIYA DIGIT NINE - {0x0B70, 0x0, propertyDISALLOWED}, // ORIYA ISSHAR - {0x0B71, 0x0, propertyPVALID}, // ORIYA LETTER WA - {0x0B72, 0x0B81, propertyUNASSIGNED}, // .. - {0x0B82, 0x0B83, propertyPVALID}, // TAMIL SIGN ANUSVARA..TAMIL SIGN VISARGA - {0x0B84, 0x0, propertyUNASSIGNED}, // - {0x0B85, 0x0B8A, propertyPVALID}, // TAMIL LETTER A..TAMIL LETTER UU - {0x0B8B, 0x0B8D, propertyUNASSIGNED}, // .. - {0x0B8E, 0x0B90, propertyPVALID}, // TAMIL LETTER E..TAMIL LETTER AI - {0x0B91, 0x0, propertyUNASSIGNED}, // - {0x0B92, 0x0B95, propertyPVALID}, // TAMIL LETTER O..TAMIL LETTER KA - {0x0B96, 0x0B98, propertyUNASSIGNED}, // .. - {0x0B99, 0x0B9A, propertyPVALID}, // TAMIL LETTER NGA..TAMIL LETTER CA - {0x0B9B, 0x0, propertyUNASSIGNED}, // - {0x0B9C, 0x0, propertyPVALID}, // TAMIL LETTER JA - {0x0B9D, 0x0, propertyUNASSIGNED}, // - {0x0B9E, 0x0B9F, propertyPVALID}, // TAMIL LETTER NYA..TAMIL LETTER TTA - {0x0BA0, 0x0BA2, propertyUNASSIGNED}, // .. - {0x0BA3, 0x0BA4, propertyPVALID}, // TAMIL LETTER NNA..TAMIL LETTER TA - {0x0BA5, 0x0BA7, propertyUNASSIGNED}, // .. - {0x0BA8, 0x0BAA, propertyPVALID}, // TAMIL LETTER NA..TAMIL LETTER PA - {0x0BAB, 0x0BAD, propertyUNASSIGNED}, // .. - {0x0BAE, 0x0BB9, propertyPVALID}, // TAMIL LETTER MA..TAMIL LETTER HA - {0x0BBA, 0x0BBD, propertyUNASSIGNED}, // .. - {0x0BBE, 0x0BC2, propertyPVALID}, // TAMIL VOWEL SIGN AA..TAMIL VOWEL SIGN UU - {0x0BC3, 0x0BC5, propertyUNASSIGNED}, // .. - {0x0BC6, 0x0BC8, propertyPVALID}, // TAMIL VOWEL SIGN E..TAMIL VOWEL SIGN AI - {0x0BC9, 0x0, propertyUNASSIGNED}, // - {0x0BCA, 0x0BCD, propertyPVALID}, // TAMIL VOWEL SIGN O..TAMIL SIGN VIRAMA - {0x0BCE, 0x0BCF, propertyUNASSIGNED}, // .. - {0x0BD0, 0x0, propertyPVALID}, // TAMIL OM - {0x0BD1, 0x0BD6, propertyUNASSIGNED}, // .. - {0x0BD7, 0x0, propertyPVALID}, // TAMIL AU LENGTH MARK - {0x0BD8, 0x0BE5, propertyUNASSIGNED}, // .. - {0x0BE6, 0x0BEF, propertyPVALID}, // TAMIL DIGIT ZERO..TAMIL DIGIT NINE - {0x0BF0, 0x0BFA, propertyDISALLOWED}, // TAMIL NUMBER TEN..TAMIL NUMBER SIGN - {0x0BFB, 0x0C00, propertyUNASSIGNED}, // .. - {0x0C01, 0x0C03, propertyPVALID}, // TELUGU SIGN CANDRABINDU..TELUGU SIGN VISARGA - {0x0C04, 0x0, propertyUNASSIGNED}, // - {0x0C05, 0x0C0C, propertyPVALID}, // TELUGU LETTER A..TELUGU LETTER VOCALIC L - {0x0C0D, 0x0, propertyUNASSIGNED}, // - {0x0C0E, 0x0C10, propertyPVALID}, // TELUGU LETTER E..TELUGU LETTER AI - {0x0C11, 0x0, propertyUNASSIGNED}, // - {0x0C12, 0x0C28, propertyPVALID}, // TELUGU LETTER O..TELUGU LETTER NA - {0x0C29, 0x0, propertyUNASSIGNED}, // - {0x0C2A, 0x0C33, propertyPVALID}, // TELUGU LETTER PA..TELUGU LETTER LLA - {0x0C34, 0x0, propertyUNASSIGNED}, // - {0x0C35, 0x0C39, propertyPVALID}, // TELUGU LETTER VA..TELUGU LETTER HA - {0x0C3A, 0x0C3C, propertyUNASSIGNED}, // .. - {0x0C3D, 0x0C44, propertyPVALID}, // TELUGU SIGN AVAGRAHA..TELUGU VOWEL SIGN VOCA - {0x0C45, 0x0, propertyUNASSIGNED}, // - {0x0C46, 0x0C48, propertyPVALID}, // TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI - {0x0C49, 0x0, propertyUNASSIGNED}, // - {0x0C4A, 0x0C4D, propertyPVALID}, // TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA - {0x0C4E, 0x0C54, propertyUNASSIGNED}, // .. - {0x0C55, 0x0C56, propertyPVALID}, // TELUGU LENGTH MARK..TELUGU AI LENGTH MARK - {0x0C57, 0x0, propertyUNASSIGNED}, // - {0x0C58, 0x0C59, propertyPVALID}, // TELUGU LETTER TSA..TELUGU LETTER DZA - {0x0C5A, 0x0C5F, propertyUNASSIGNED}, // .. - {0x0C60, 0x0C63, propertyPVALID}, // TELUGU LETTER VOCALIC RR..TELUGU VOWEL SIGN - {0x0C64, 0x0C65, propertyUNASSIGNED}, // .. - {0x0C66, 0x0C6F, propertyPVALID}, // TELUGU DIGIT ZERO..TELUGU DIGIT NINE - {0x0C70, 0x0C77, propertyUNASSIGNED}, // .. - {0x0C78, 0x0C7F, propertyDISALLOWED}, // TELUGU FRACTION DIGIT ZERO FOR ODD POWERS OF - {0x0C80, 0x0C81, propertyUNASSIGNED}, // .. - {0x0C82, 0x0C83, propertyPVALID}, // KANNADA SIGN ANUSVARA..KANNADA SIGN VISARGA - {0x0C84, 0x0, propertyUNASSIGNED}, // - {0x0C85, 0x0C8C, propertyPVALID}, // KANNADA LETTER A..KANNADA LETTER VOCALIC L - {0x0C8D, 0x0, propertyUNASSIGNED}, // - {0x0C8E, 0x0C90, propertyPVALID}, // KANNADA LETTER E..KANNADA LETTER AI - {0x0C91, 0x0, propertyUNASSIGNED}, // - {0x0C92, 0x0CA8, propertyPVALID}, // KANNADA LETTER O..KANNADA LETTER NA - {0x0CA9, 0x0, propertyUNASSIGNED}, // - {0x0CAA, 0x0CB3, propertyPVALID}, // KANNADA LETTER PA..KANNADA LETTER LLA - {0x0CB4, 0x0, propertyUNASSIGNED}, // - {0x0CB5, 0x0CB9, propertyPVALID}, // KANNADA LETTER VA..KANNADA LETTER HA - {0x0CBA, 0x0CBB, propertyUNASSIGNED}, // .. - {0x0CBC, 0x0CC4, propertyPVALID}, // KANNADA SIGN NUKTA..KANNADA VOWEL SIGN VOCAL - {0x0CC5, 0x0, propertyUNASSIGNED}, // - {0x0CC6, 0x0CC8, propertyPVALID}, // KANNADA VOWEL SIGN E..KANNADA VOWEL SIGN AI - {0x0CC9, 0x0, propertyUNASSIGNED}, // - {0x0CCA, 0x0CCD, propertyPVALID}, // KANNADA VOWEL SIGN O..KANNADA SIGN VIRAMA - {0x0CCE, 0x0CD4, propertyUNASSIGNED}, // .. - {0x0CD5, 0x0CD6, propertyPVALID}, // KANNADA LENGTH MARK..KANNADA AI LENGTH MARK - {0x0CD7, 0x0CDD, propertyUNASSIGNED}, // .. - {0x0CDE, 0x0, propertyPVALID}, // KANNADA LETTER FA - {0x0CDF, 0x0, propertyUNASSIGNED}, // - {0x0CE0, 0x0CE3, propertyPVALID}, // KANNADA LETTER VOCALIC RR..KANNADA VOWEL SIG - {0x0CE4, 0x0CE5, propertyUNASSIGNED}, // .. - {0x0CE6, 0x0CEF, propertyPVALID}, // KANNADA DIGIT ZERO..KANNADA DIGIT NINE - {0x0CF0, 0x0, propertyUNASSIGNED}, // - {0x0CF1, 0x0CF2, propertyDISALLOWED}, // KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADH - {0x0CF3, 0x0D01, propertyUNASSIGNED}, // .. - {0x0D02, 0x0D03, propertyPVALID}, // MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISA - {0x0D04, 0x0, propertyUNASSIGNED}, // - {0x0D05, 0x0D0C, propertyPVALID}, // MALAYALAM LETTER A..MALAYALAM LETTER VOCALIC - {0x0D0D, 0x0, propertyUNASSIGNED}, // - {0x0D0E, 0x0D10, propertyPVALID}, // MALAYALAM LETTER E..MALAYALAM LETTER AI - {0x0D11, 0x0, propertyUNASSIGNED}, // - {0x0D12, 0x0D28, propertyPVALID}, // MALAYALAM LETTER O..MALAYALAM LETTER NA - {0x0D29, 0x0, propertyUNASSIGNED}, // - {0x0D2A, 0x0D39, propertyPVALID}, // MALAYALAM LETTER PA..MALAYALAM LETTER HA - {0x0D3A, 0x0D3C, propertyUNASSIGNED}, // .. - {0x0D3D, 0x0D44, propertyPVALID}, // MALAYALAM SIGN AVAGRAHA..MALAYALAM VOWEL SIG - {0x0D45, 0x0, propertyUNASSIGNED}, // - {0x0D46, 0x0D48, propertyPVALID}, // MALAYALAM VOWEL SIGN E..MALAYALAM VOWEL SIGN - {0x0D49, 0x0, propertyUNASSIGNED}, // - {0x0D4A, 0x0D4D, propertyPVALID}, // MALAYALAM VOWEL SIGN O..MALAYALAM SIGN VIRAM - {0x0D4E, 0x0D56, propertyUNASSIGNED}, // .. - {0x0D57, 0x0, propertyPVALID}, // MALAYALAM AU LENGTH MARK - {0x0D58, 0x0D5F, propertyUNASSIGNED}, // .. - {0x0D60, 0x0D63, propertyPVALID}, // MALAYALAM LETTER VOCALIC RR..MALAYALAM VOWEL - {0x0D64, 0x0D65, propertyUNASSIGNED}, // .. - {0x0D66, 0x0D6F, propertyPVALID}, // MALAYALAM DIGIT ZERO..MALAYALAM DIGIT NINE - {0x0D70, 0x0D75, propertyDISALLOWED}, // MALAYALAM NUMBER TEN..MALAYALAM FRACTION THR - {0x0D76, 0x0D78, propertyUNASSIGNED}, // .. - {0x0D79, 0x0, propertyDISALLOWED}, // MALAYALAM DATE MARK - {0x0D7A, 0x0D7F, propertyPVALID}, // MALAYALAM LETTER CHILLU NN..MALAYALAM LETTER - {0x0D80, 0x0D81, propertyUNASSIGNED}, // .. - {0x0D82, 0x0D83, propertyPVALID}, // SINHALA SIGN ANUSVARAYA..SINHALA SIGN VISARG - {0x0D84, 0x0, propertyUNASSIGNED}, // - {0x0D85, 0x0D96, propertyPVALID}, // SINHALA LETTER AYANNA..SINHALA LETTER AUYANN - {0x0D97, 0x0D99, propertyUNASSIGNED}, // .. - {0x0D9A, 0x0DB1, propertyPVALID}, // SINHALA LETTER ALPAPRAANA KAYANNA..SINHALA L - {0x0DB2, 0x0, propertyUNASSIGNED}, // - {0x0DB3, 0x0DBB, propertyPVALID}, // SINHALA LETTER SANYAKA DAYANNA..SINHALA LETT - {0x0DBC, 0x0, propertyUNASSIGNED}, // - {0x0DBD, 0x0, propertyPVALID}, // SINHALA LETTER DANTAJA LAYANNA - {0x0DBE, 0x0DBF, propertyUNASSIGNED}, // .. - {0x0DC0, 0x0DC6, propertyPVALID}, // SINHALA LETTER VAYANNA..SINHALA LETTER FAYAN - {0x0DC7, 0x0DC9, propertyUNASSIGNED}, // .. - {0x0DCA, 0x0, propertyPVALID}, // SINHALA SIGN AL-LAKUNA - {0x0DCB, 0x0DCE, propertyUNASSIGNED}, // .. - {0x0DCF, 0x0DD4, propertyPVALID}, // SINHALA VOWEL SIGN AELA-PILLA..SINHALA VOWEL - {0x0DD5, 0x0, propertyUNASSIGNED}, // - {0x0DD6, 0x0, propertyPVALID}, // SINHALA VOWEL SIGN DIGA PAA-PILLA - {0x0DD7, 0x0, propertyUNASSIGNED}, // - {0x0DD8, 0x0DDF, propertyPVALID}, // SINHALA VOWEL SIGN GAETTA-PILLA..SINHALA VOW - {0x0DE0, 0x0DF1, propertyUNASSIGNED}, // .. - {0x0DF2, 0x0DF3, propertyPVALID}, // SINHALA VOWEL SIGN DIGA GAETTA-PILLA..SINHAL - {0x0DF4, 0x0, propertyDISALLOWED}, // SINHALA PUNCTUATION KUNDDALIYA - {0x0DF5, 0x0E00, propertyUNASSIGNED}, // .. - {0x0E01, 0x0E32, propertyPVALID}, // THAI CHARACTER KO KAI..THAI CHARACTER SARA A - {0x0E33, 0x0, propertyDISALLOWED}, // THAI CHARACTER SARA AM - {0x0E34, 0x0E3A, propertyPVALID}, // THAI CHARACTER SARA I..THAI CHARACTER PHINTH - {0x0E3B, 0x0E3E, propertyUNASSIGNED}, // .. - {0x0E3F, 0x0, propertyDISALLOWED}, // THAI CURRENCY SYMBOL BAHT - {0x0E40, 0x0E4E, propertyPVALID}, // THAI CHARACTER SARA E..THAI CHARACTER YAMAKK - {0x0E4F, 0x0, propertyDISALLOWED}, // THAI CHARACTER FONGMAN - {0x0E50, 0x0E59, propertyPVALID}, // THAI DIGIT ZERO..THAI DIGIT NINE - {0x0E5A, 0x0E5B, propertyDISALLOWED}, // THAI CHARACTER ANGKHANKHU..THAI CHARACTER KH - {0x0E5C, 0x0E80, propertyUNASSIGNED}, // .. - {0x0E81, 0x0E82, propertyPVALID}, // LAO LETTER KO..LAO LETTER KHO SUNG - {0x0E83, 0x0, propertyUNASSIGNED}, // - {0x0E84, 0x0, propertyPVALID}, // LAO LETTER KHO TAM - {0x0E85, 0x0E86, propertyUNASSIGNED}, // .. - {0x0E87, 0x0E88, propertyPVALID}, // LAO LETTER NGO..LAO LETTER CO - {0x0E89, 0x0, propertyUNASSIGNED}, // - {0x0E8A, 0x0, propertyPVALID}, // LAO LETTER SO TAM - {0x0E8B, 0x0E8C, propertyUNASSIGNED}, // .. - {0x0E8D, 0x0, propertyPVALID}, // LAO LETTER NYO - {0x0E8E, 0x0E93, propertyUNASSIGNED}, // .. - {0x0E94, 0x0E97, propertyPVALID}, // LAO LETTER DO..LAO LETTER THO TAM - {0x0E98, 0x0, propertyUNASSIGNED}, // - {0x0E99, 0x0E9F, propertyPVALID}, // LAO LETTER NO..LAO LETTER FO SUNG - {0x0EA0, 0x0, propertyUNASSIGNED}, // - {0x0EA1, 0x0EA3, propertyPVALID}, // LAO LETTER MO..LAO LETTER LO LING - {0x0EA4, 0x0, propertyUNASSIGNED}, // - {0x0EA5, 0x0, propertyPVALID}, // LAO LETTER LO LOOT - {0x0EA6, 0x0, propertyUNASSIGNED}, // - {0x0EA7, 0x0, propertyPVALID}, // LAO LETTER WO - {0x0EA8, 0x0EA9, propertyUNASSIGNED}, // .. - {0x0EAA, 0x0EAB, propertyPVALID}, // LAO LETTER SO SUNG..LAO LETTER HO SUNG - {0x0EAC, 0x0, propertyUNASSIGNED}, // - {0x0EAD, 0x0EB2, propertyPVALID}, // LAO LETTER O..LAO VOWEL SIGN AA - {0x0EB3, 0x0, propertyDISALLOWED}, // LAO VOWEL SIGN AM - {0x0EB4, 0x0EB9, propertyPVALID}, // LAO VOWEL SIGN I..LAO VOWEL SIGN UU - {0x0EBA, 0x0, propertyUNASSIGNED}, // - {0x0EBB, 0x0EBD, propertyPVALID}, // LAO VOWEL SIGN MAI KON..LAO SEMIVOWEL SIGN N - {0x0EBE, 0x0EBF, propertyUNASSIGNED}, // .. - {0x0EC0, 0x0EC4, propertyPVALID}, // LAO VOWEL SIGN E..LAO VOWEL SIGN AI - {0x0EC5, 0x0, propertyUNASSIGNED}, // - {0x0EC6, 0x0, propertyPVALID}, // LAO KO LA - {0x0EC7, 0x0, propertyUNASSIGNED}, // - {0x0EC8, 0x0ECD, propertyPVALID}, // LAO TONE MAI EK..LAO NIGGAHITA - {0x0ECE, 0x0ECF, propertyUNASSIGNED}, // .. - {0x0ED0, 0x0ED9, propertyPVALID}, // LAO DIGIT ZERO..LAO DIGIT NINE - {0x0EDA, 0x0EDB, propertyUNASSIGNED}, // .. - {0x0EDC, 0x0EDD, propertyDISALLOWED}, // LAO HO NO..LAO HO MO - {0x0EDE, 0x0EFF, propertyUNASSIGNED}, // .. - {0x0F00, 0x0, propertyPVALID}, // TIBETAN SYLLABLE OM - {0x0F01, 0x0F0A, propertyDISALLOWED}, // TIBETAN MARK GTER YIG MGO TRUNCATED A..TIBET - {0x0F0B, 0x0, propertyPVALID}, // TIBETAN MARK INTERSYLLABIC TSHEG - {0x0F0C, 0x0F17, propertyDISALLOWED}, // TIBETAN MARK DELIMITER TSHEG BSTAR..TIBETAN - {0x0F18, 0x0F19, propertyPVALID}, // TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN - {0x0F1A, 0x0F1F, propertyDISALLOWED}, // TIBETAN SIGN RDEL DKAR GCIG..TIBETAN SIGN RD - {0x0F20, 0x0F29, propertyPVALID}, // TIBETAN DIGIT ZERO..TIBETAN DIGIT NINE - {0x0F2A, 0x0F34, propertyDISALLOWED}, // TIBETAN DIGIT HALF ONE..TIBETAN MARK BSDUS R - {0x0F35, 0x0, propertyPVALID}, // TIBETAN MARK NGAS BZUNG NYI ZLA - {0x0F36, 0x0, propertyDISALLOWED}, // TIBETAN MARK CARET -DZUD RTAGS BZHI MIG CAN - {0x0F37, 0x0, propertyPVALID}, // TIBETAN MARK NGAS BZUNG SGOR RTAGS - {0x0F38, 0x0, propertyDISALLOWED}, // TIBETAN MARK CHE MGO - {0x0F39, 0x0, propertyPVALID}, // TIBETAN MARK TSA -PHRU - {0x0F3A, 0x0F3D, propertyDISALLOWED}, // TIBETAN MARK GUG RTAGS GYON..TIBETAN MARK AN - {0x0F3E, 0x0F42, propertyPVALID}, // TIBETAN SIGN YAR TSHES..TIBETAN LETTER GA - {0x0F43, 0x0, propertyDISALLOWED}, // TIBETAN LETTER GHA - {0x0F44, 0x0F47, propertyPVALID}, // TIBETAN LETTER NGA..TIBETAN LETTER JA - {0x0F48, 0x0, propertyUNASSIGNED}, // - {0x0F49, 0x0F4C, propertyPVALID}, // TIBETAN LETTER NYA..TIBETAN LETTER DDA - {0x0F4D, 0x0, propertyDISALLOWED}, // TIBETAN LETTER DDHA - {0x0F4E, 0x0F51, propertyPVALID}, // TIBETAN LETTER NNA..TIBETAN LETTER DA - {0x0F52, 0x0, propertyDISALLOWED}, // TIBETAN LETTER DHA - {0x0F53, 0x0F56, propertyPVALID}, // TIBETAN LETTER NA..TIBETAN LETTER BA - {0x0F57, 0x0, propertyDISALLOWED}, // TIBETAN LETTER BHA - {0x0F58, 0x0F5B, propertyPVALID}, // TIBETAN LETTER MA..TIBETAN LETTER DZA - {0x0F5C, 0x0, propertyDISALLOWED}, // TIBETAN LETTER DZHA - {0x0F5D, 0x0F68, propertyPVALID}, // TIBETAN LETTER WA..TIBETAN LETTER A - {0x0F69, 0x0, propertyDISALLOWED}, // TIBETAN LETTER KSSA - {0x0F6A, 0x0F6C, propertyPVALID}, // TIBETAN LETTER FIXED-FORM RA..TIBETAN LETTER - {0x0F6D, 0x0F70, propertyUNASSIGNED}, // .. - {0x0F71, 0x0F72, propertyPVALID}, // TIBETAN VOWEL SIGN AA..TIBETAN VOWEL SIGN I - {0x0F73, 0x0, propertyDISALLOWED}, // TIBETAN VOWEL SIGN II - {0x0F74, 0x0, propertyPVALID}, // TIBETAN VOWEL SIGN U - {0x0F75, 0x0F79, propertyDISALLOWED}, // TIBETAN VOWEL SIGN UU..TIBETAN VOWEL SIGN VO - {0x0F7A, 0x0F80, propertyPVALID}, // TIBETAN VOWEL SIGN E..TIBETAN VOWEL SIGN REV - {0x0F81, 0x0, propertyDISALLOWED}, // TIBETAN VOWEL SIGN REVERSED II - {0x0F82, 0x0F84, propertyPVALID}, // TIBETAN SIGN NYI ZLA NAA DA..TIBETAN MARK HA - {0x0F85, 0x0, propertyDISALLOWED}, // TIBETAN MARK PALUTA - {0x0F86, 0x0F8B, propertyPVALID}, // TIBETAN SIGN LCI RTAGS..TIBETAN SIGN GRU MED - {0x0F8C, 0x0F8F, propertyUNASSIGNED}, // .. - {0x0F90, 0x0F92, propertyPVALID}, // TIBETAN SUBJOINED LETTER KA..TIBETAN SUBJOIN - {0x0F93, 0x0, propertyDISALLOWED}, // TIBETAN SUBJOINED LETTER GHA - {0x0F94, 0x0F97, propertyPVALID}, // TIBETAN SUBJOINED LETTER NGA..TIBETAN SUBJOI - {0x0F98, 0x0, propertyUNASSIGNED}, // - {0x0F99, 0x0F9C, propertyPVALID}, // TIBETAN SUBJOINED LETTER NYA..TIBETAN SUBJOI - {0x0F9D, 0x0, propertyDISALLOWED}, // TIBETAN SUBJOINED LETTER DDHA - {0x0F9E, 0x0FA1, propertyPVALID}, // TIBETAN SUBJOINED LETTER NNA..TIBETAN SUBJOI - {0x0FA2, 0x0, propertyDISALLOWED}, // TIBETAN SUBJOINED LETTER DHA - {0x0FA3, 0x0FA6, propertyPVALID}, // TIBETAN SUBJOINED LETTER NA..TIBETAN SUBJOIN - {0x0FA7, 0x0, propertyDISALLOWED}, // TIBETAN SUBJOINED LETTER BHA - {0x0FA8, 0x0FAB, propertyPVALID}, // TIBETAN SUBJOINED LETTER MA..TIBETAN SUBJOIN - {0x0FAC, 0x0, propertyDISALLOWED}, // TIBETAN SUBJOINED LETTER DZHA - {0x0FAD, 0x0FB8, propertyPVALID}, // TIBETAN SUBJOINED LETTER WA..TIBETAN SUBJOIN - {0x0FB9, 0x0, propertyDISALLOWED}, // TIBETAN SUBJOINED LETTER KSSA - {0x0FBA, 0x0FBC, propertyPVALID}, // TIBETAN SUBJOINED LETTER FIXED-FORM WA..TIBE - {0x0FBD, 0x0, propertyUNASSIGNED}, // - {0x0FBE, 0x0FC5, propertyDISALLOWED}, // TIBETAN KU RU KHA..TIBETAN SYMBOL RDO RJE - {0x0FC6, 0x0, propertyPVALID}, // TIBETAN SYMBOL PADMA GDAN - {0x0FC7, 0x0FCC, propertyDISALLOWED}, // TIBETAN SYMBOL RDO RJE RGYA GRAM..TIBETAN SY - {0x0FCD, 0x0, propertyUNASSIGNED}, // - {0x0FCE, 0x0FD8, propertyDISALLOWED}, // TIBETAN SIGN RDEL NAG RDEL DKAR..LEFT-FACING - {0x0FD9, 0x0FFF, propertyUNASSIGNED}, // .. - {0x1000, 0x1049, propertyPVALID}, // MYANMAR LETTER KA..MYANMAR DIGIT NINE - {0x104A, 0x104F, propertyDISALLOWED}, // MYANMAR SIGN LITTLE SECTION..MYANMAR SYMBOL - {0x1050, 0x109D, propertyPVALID}, // MYANMAR LETTER SHA..MYANMAR VOWEL SIGN AITON - {0x109E, 0x10C5, propertyDISALLOWED}, // MYANMAR SYMBOL SHAN ONE..GEORGIAN CAPITAL LE - {0x10C6, 0x10CF, propertyUNASSIGNED}, // .. - {0x10D0, 0x10FA, propertyPVALID}, // GEORGIAN LETTER AN..GEORGIAN LETTER AIN - {0x10FB, 0x10FC, propertyDISALLOWED}, // GEORGIAN PARAGRAPH SEPARATOR..MODIFIER LETTE - {0x10FD, 0x10FF, propertyUNASSIGNED}, // .. - {0x1100, 0x11FF, propertyDISALLOWED}, // HANGUL CHOSEONG KIYEOK..HANGUL JONGSEONG SSA - {0x1200, 0x1248, propertyPVALID}, // ETHIOPIC SYLLABLE HA..ETHIOPIC SYLLABLE QWA - {0x1249, 0x0, propertyUNASSIGNED}, // - {0x124A, 0x124D, propertyPVALID}, // ETHIOPIC SYLLABLE QWI..ETHIOPIC SYLLABLE QWE - {0x124E, 0x124F, propertyUNASSIGNED}, // .. - {0x1250, 0x1256, propertyPVALID}, // ETHIOPIC SYLLABLE QHA..ETHIOPIC SYLLABLE QHO - {0x1257, 0x0, propertyUNASSIGNED}, // - {0x1258, 0x0, propertyPVALID}, // ETHIOPIC SYLLABLE QHWA - {0x1259, 0x0, propertyUNASSIGNED}, // - {0x125A, 0x125D, propertyPVALID}, // ETHIOPIC SYLLABLE QHWI..ETHIOPIC SYLLABLE QH - {0x125E, 0x125F, propertyUNASSIGNED}, // .. - {0x1260, 0x1288, propertyPVALID}, // ETHIOPIC SYLLABLE BA..ETHIOPIC SYLLABLE XWA - {0x1289, 0x0, propertyUNASSIGNED}, // - {0x128A, 0x128D, propertyPVALID}, // ETHIOPIC SYLLABLE XWI..ETHIOPIC SYLLABLE XWE - {0x128E, 0x128F, propertyUNASSIGNED}, // .. - {0x1290, 0x12B0, propertyPVALID}, // ETHIOPIC SYLLABLE NA..ETHIOPIC SYLLABLE KWA - {0x12B1, 0x0, propertyUNASSIGNED}, // - {0x12B2, 0x12B5, propertyPVALID}, // ETHIOPIC SYLLABLE KWI..ETHIOPIC SYLLABLE KWE - {0x12B6, 0x12B7, propertyUNASSIGNED}, // .. - {0x12B8, 0x12BE, propertyPVALID}, // ETHIOPIC SYLLABLE KXA..ETHIOPIC SYLLABLE KXO - {0x12BF, 0x0, propertyUNASSIGNED}, // - {0x12C0, 0x0, propertyPVALID}, // ETHIOPIC SYLLABLE KXWA - {0x12C1, 0x0, propertyUNASSIGNED}, // - {0x12C2, 0x12C5, propertyPVALID}, // ETHIOPIC SYLLABLE KXWI..ETHIOPIC SYLLABLE KX - {0x12C6, 0x12C7, propertyUNASSIGNED}, // .. - {0x12C8, 0x12D6, propertyPVALID}, // ETHIOPIC SYLLABLE WA..ETHIOPIC SYLLABLE PHAR - {0x12D7, 0x0, propertyUNASSIGNED}, // - {0x12D8, 0x1310, propertyPVALID}, // ETHIOPIC SYLLABLE ZA..ETHIOPIC SYLLABLE GWA - {0x1311, 0x0, propertyUNASSIGNED}, // - {0x1312, 0x1315, propertyPVALID}, // ETHIOPIC SYLLABLE GWI..ETHIOPIC SYLLABLE GWE - {0x1316, 0x1317, propertyUNASSIGNED}, // .. - {0x1318, 0x135A, propertyPVALID}, // ETHIOPIC SYLLABLE GGA..ETHIOPIC SYLLABLE FYA - {0x135B, 0x135E, propertyUNASSIGNED}, // .. - {0x135F, 0x0, propertyPVALID}, // ETHIOPIC COMBINING GEMINATION MARK - {0x1360, 0x137C, propertyDISALLOWED}, // ETHIOPIC SECTION MARK..ETHIOPIC NUMBER TEN T - {0x137D, 0x137F, propertyUNASSIGNED}, // .. - {0x1380, 0x138F, propertyPVALID}, // ETHIOPIC SYLLABLE SEBATBEIT MWA..ETHIOPIC SY - {0x1390, 0x1399, propertyDISALLOWED}, // ETHIOPIC TONAL MARK YIZET..ETHIOPIC TONAL MA - {0x139A, 0x139F, propertyUNASSIGNED}, // .. - {0x13A0, 0x13F4, propertyPVALID}, // CHEROKEE LETTER A..CHEROKEE LETTER YV - {0x13F5, 0x13FF, propertyUNASSIGNED}, // .. - {0x1400, 0x0, propertyDISALLOWED}, // CANADIAN SYLLABICS HYPHEN - {0x1401, 0x166C, propertyPVALID}, // CANADIAN SYLLABICS E..CANADIAN SYLLABICS CAR - {0x166D, 0x166E, propertyDISALLOWED}, // CANADIAN SYLLABICS CHI SIGN..CANADIAN SYLLAB - {0x166F, 0x167F, propertyPVALID}, // CANADIAN SYLLABICS QAI..CANADIAN SYLLABICS B - {0x1680, 0x0, propertyDISALLOWED}, // OGHAM SPACE MARK - {0x1681, 0x169A, propertyPVALID}, // OGHAM LETTER BEITH..OGHAM LETTER PEITH - {0x169B, 0x169C, propertyDISALLOWED}, // OGHAM FEATHER MARK..OGHAM REVERSED FEATHER M - {0x169D, 0x169F, propertyUNASSIGNED}, // .. - {0x16A0, 0x16EA, propertyPVALID}, // RUNIC LETTER FEHU FEOH FE F..RUNIC LETTER X - {0x16EB, 0x16F0, propertyDISALLOWED}, // RUNIC SINGLE PUNCTUATION..RUNIC BELGTHOR SYM - {0x16F1, 0x16FF, propertyUNASSIGNED}, // .. - {0x1700, 0x170C, propertyPVALID}, // TAGALOG LETTER A..TAGALOG LETTER YA - {0x170D, 0x0, propertyUNASSIGNED}, // - {0x170E, 0x1714, propertyPVALID}, // TAGALOG LETTER LA..TAGALOG SIGN VIRAMA - {0x1715, 0x171F, propertyUNASSIGNED}, // .. - {0x1720, 0x1734, propertyPVALID}, // HANUNOO LETTER A..HANUNOO SIGN PAMUDPOD - {0x1735, 0x1736, propertyDISALLOWED}, // PHILIPPINE SINGLE PUNCTUATION..PHILIPPINE DO - {0x1737, 0x173F, propertyUNASSIGNED}, // .. - {0x1740, 0x1753, propertyPVALID}, // BUHID LETTER A..BUHID VOWEL SIGN U - {0x1754, 0x175F, propertyUNASSIGNED}, // .. - {0x1760, 0x176C, propertyPVALID}, // TAGBANWA LETTER A..TAGBANWA LETTER YA - {0x176D, 0x0, propertyUNASSIGNED}, // - {0x176E, 0x1770, propertyPVALID}, // TAGBANWA LETTER LA..TAGBANWA LETTER SA - {0x1771, 0x0, propertyUNASSIGNED}, // - {0x1772, 0x1773, propertyPVALID}, // TAGBANWA VOWEL SIGN I..TAGBANWA VOWEL SIGN U - {0x1774, 0x177F, propertyUNASSIGNED}, // .. - {0x1780, 0x17B3, propertyPVALID}, // KHMER LETTER KA..KHMER INDEPENDENT VOWEL QAU - {0x17B4, 0x17B5, propertyDISALLOWED}, // KHMER VOWEL INHERENT AQ..KHMER VOWEL INHEREN - {0x17B6, 0x17D3, propertyPVALID}, // KHMER VOWEL SIGN AA..KHMER SIGN BATHAMASAT - {0x17D4, 0x17D6, propertyDISALLOWED}, // KHMER SIGN KHAN..KHMER SIGN CAMNUC PII KUUH - {0x17D7, 0x0, propertyPVALID}, // KHMER SIGN LEK TOO - {0x17D8, 0x17DB, propertyDISALLOWED}, // KHMER SIGN BEYYAL..KHMER CURRENCY SYMBOL RIE - {0x17DC, 0x17DD, propertyPVALID}, // KHMER SIGN AVAKRAHASANYA..KHMER SIGN ATTHACA - {0x17DE, 0x17DF, propertyUNASSIGNED}, // .. - {0x17E0, 0x17E9, propertyPVALID}, // KHMER DIGIT ZERO..KHMER DIGIT NINE - {0x17EA, 0x17EF, propertyUNASSIGNED}, // .. - {0x17F0, 0x17F9, propertyDISALLOWED}, // KHMER SYMBOL LEK ATTAK SON..KHMER SYMBOL LEK - {0x17FA, 0x17FF, propertyUNASSIGNED}, // .. - {0x1800, 0x180E, propertyDISALLOWED}, // MONGOLIAN BIRGA..MONGOLIAN VOWEL SEPARATOR - {0x180F, 0x0, propertyUNASSIGNED}, // - {0x1810, 0x1819, propertyPVALID}, // MONGOLIAN DIGIT ZERO..MONGOLIAN DIGIT NINE - {0x181A, 0x181F, propertyUNASSIGNED}, // .. - {0x1820, 0x1877, propertyPVALID}, // MONGOLIAN LETTER A..MONGOLIAN LETTER MANCHU - {0x1878, 0x187F, propertyUNASSIGNED}, // .. - {0x1880, 0x18AA, propertyPVALID}, // MONGOLIAN LETTER ALI GALI ANUSVARA ONE..MONG - {0x18AB, 0x18AF, propertyUNASSIGNED}, // .. - {0x18B0, 0x18F5, propertyPVALID}, // CANADIAN SYLLABICS OY..CANADIAN SYLLABICS CA - {0x18F6, 0x18FF, propertyUNASSIGNED}, // .. - {0x1900, 0x191C, propertyPVALID}, // LIMBU VOWEL-CARRIER LETTER..LIMBU LETTER HA - {0x191D, 0x191F, propertyUNASSIGNED}, // .. - {0x1920, 0x192B, propertyPVALID}, // LIMBU VOWEL SIGN A..LIMBU SUBJOINED LETTER W - {0x192C, 0x192F, propertyUNASSIGNED}, // .. - {0x1930, 0x193B, propertyPVALID}, // LIMBU SMALL LETTER KA..LIMBU SIGN SA-I - {0x193C, 0x193F, propertyUNASSIGNED}, // .. - {0x1940, 0x0, propertyDISALLOWED}, // LIMBU SIGN LOO - {0x1941, 0x1943, propertyUNASSIGNED}, // .. - {0x1944, 0x1945, propertyDISALLOWED}, // LIMBU EXCLAMATION MARK..LIMBU QUESTION MARK - {0x1946, 0x196D, propertyPVALID}, // LIMBU DIGIT ZERO..TAI LE LETTER AI - {0x196E, 0x196F, propertyUNASSIGNED}, // .. - {0x1970, 0x1974, propertyPVALID}, // TAI LE LETTER TONE-2..TAI LE LETTER TONE-6 - {0x1975, 0x197F, propertyUNASSIGNED}, // .. - {0x1980, 0x19AB, propertyPVALID}, // NEW TAI LUE LETTER HIGH QA..NEW TAI LUE LETT - {0x19AC, 0x19AF, propertyUNASSIGNED}, // .. - {0x19B0, 0x19C9, propertyPVALID}, // NEW TAI LUE VOWEL SIGN VOWEL SHORTENER..NEW - {0x19CA, 0x19CF, propertyUNASSIGNED}, // .. - {0x19D0, 0x19DA, propertyPVALID}, // NEW TAI LUE DIGIT ZERO..NEW TAI LUE THAM DIG - {0x19DB, 0x19DD, propertyUNASSIGNED}, // .. - {0x19DE, 0x19FF, propertyDISALLOWED}, // NEW TAI LUE SIGN LAE..KHMER SYMBOL DAP-PRAM - {0x1A00, 0x1A1B, propertyPVALID}, // BUGINESE LETTER KA..BUGINESE VOWEL SIGN AE - {0x1A1C, 0x1A1D, propertyUNASSIGNED}, // .. - {0x1A1E, 0x1A1F, propertyDISALLOWED}, // BUGINESE PALLAWA..BUGINESE END OF SECTION - {0x1A20, 0x1A5E, propertyPVALID}, // TAI THAM LETTER HIGH KA..TAI THAM CONSONANT - {0x1A5F, 0x0, propertyUNASSIGNED}, // - {0x1A60, 0x1A7C, propertyPVALID}, // TAI THAM SIGN SAKOT..TAI THAM SIGN KHUEN-LUE - {0x1A7D, 0x1A7E, propertyUNASSIGNED}, // .. - {0x1A7F, 0x1A89, propertyPVALID}, // TAI THAM COMBINING CRYPTOGRAMMIC DOT..TAI TH - {0x1A8A, 0x1A8F, propertyUNASSIGNED}, // .. - {0x1A90, 0x1A99, propertyPVALID}, // TAI THAM THAM DIGIT ZERO..TAI THAM THAM DIGI - {0x1A9A, 0x1A9F, propertyUNASSIGNED}, // .. - {0x1AA0, 0x1AA6, propertyDISALLOWED}, // TAI THAM SIGN WIANG..TAI THAM SIGN REVERSED - {0x1AA7, 0x0, propertyPVALID}, // TAI THAM SIGN MAI YAMOK - {0x1AA8, 0x1AAD, propertyDISALLOWED}, // TAI THAM SIGN KAAN..TAI THAM SIGN CAANG - {0x1AAE, 0x1AFF, propertyUNASSIGNED}, // .. - {0x1B00, 0x1B4B, propertyPVALID}, // BALINESE SIGN ULU RICEM..BALINESE LETTER ASY - {0x1B4C, 0x1B4F, propertyUNASSIGNED}, // .. - {0x1B50, 0x1B59, propertyPVALID}, // BALINESE DIGIT ZERO..BALINESE DIGIT NINE - {0x1B5A, 0x1B6A, propertyDISALLOWED}, // BALINESE PANTI..BALINESE MUSICAL SYMBOL DANG - {0x1B6B, 0x1B73, propertyPVALID}, // BALINESE MUSICAL SYMBOL COMBINING TEGEH..BAL - {0x1B74, 0x1B7C, propertyDISALLOWED}, // BALINESE MUSICAL SYMBOL RIGHT-HAND OPEN DUG. - {0x1B7D, 0x1B7F, propertyUNASSIGNED}, // .. - {0x1B80, 0x1BAA, propertyPVALID}, // SUNDANESE SIGN PANYECEK..SUNDANESE SIGN PAMA - {0x1BAB, 0x1BAD, propertyUNASSIGNED}, // .. - {0x1BAE, 0x1BB9, propertyPVALID}, // SUNDANESE LETTER KHA..SUNDANESE DIGIT NINE - {0x1BBA, 0x1BFF, propertyUNASSIGNED}, // .. - {0x1C00, 0x1C37, propertyPVALID}, // LEPCHA LETTER KA..LEPCHA SIGN NUKTA - {0x1C38, 0x1C3A, propertyUNASSIGNED}, // .. - {0x1C3B, 0x1C3F, propertyDISALLOWED}, // LEPCHA PUNCTUATION TA-ROL..LEPCHA PUNCTUATIO - {0x1C40, 0x1C49, propertyPVALID}, // LEPCHA DIGIT ZERO..LEPCHA DIGIT NINE - {0x1C4A, 0x1C4C, propertyUNASSIGNED}, // .. - {0x1C4D, 0x1C7D, propertyPVALID}, // LEPCHA LETTER TTA..OL CHIKI AHAD - {0x1C7E, 0x1C7F, propertyDISALLOWED}, // OL CHIKI PUNCTUATION MUCAAD..OL CHIKI PUNCTU - {0x1C80, 0x1CCF, propertyUNASSIGNED}, // .. - {0x1CD0, 0x1CD2, propertyPVALID}, // VEDIC TONE KARSHANA..VEDIC TONE PRENKHA - {0x1CD3, 0x0, propertyDISALLOWED}, // VEDIC SIGN NIHSHVASA - {0x1CD4, 0x1CF2, propertyPVALID}, // VEDIC SIGN YAJURVEDIC MIDLINE SVARITA..VEDIC - {0x1CF3, 0x1CFF, propertyUNASSIGNED}, // .. - {0x1D00, 0x1D2B, propertyPVALID}, // LATIN LETTER SMALL CAPITAL A..CYRILLIC LETTE - {0x1D2C, 0x1D2E, propertyDISALLOWED}, // MODIFIER LETTER CAPITAL A..MODIFIER LETTER C - {0x1D2F, 0x0, propertyPVALID}, // MODIFIER LETTER CAPITAL BARRED B - {0x1D30, 0x1D3A, propertyDISALLOWED}, // MODIFIER LETTER CAPITAL D..MODIFIER LETTER C - {0x1D3B, 0x0, propertyPVALID}, // MODIFIER LETTER CAPITAL REVERSED N - {0x1D3C, 0x1D4D, propertyDISALLOWED}, // MODIFIER LETTER CAPITAL O..MODIFIER LETTER S - {0x1D4E, 0x0, propertyPVALID}, // MODIFIER LETTER SMALL TURNED I - {0x1D4F, 0x1D6A, propertyDISALLOWED}, // MODIFIER LETTER SMALL K..GREEK SUBSCRIPT SMA - {0x1D6B, 0x1D77, propertyPVALID}, // LATIN SMALL LETTER UE..LATIN SMALL LETTER TU - {0x1D78, 0x0, propertyDISALLOWED}, // MODIFIER LETTER CYRILLIC EN - {0x1D79, 0x1D9A, propertyPVALID}, // LATIN SMALL LETTER INSULAR G..LATIN SMALL LE - {0x1D9B, 0x1DBF, propertyDISALLOWED}, // MODIFIER LETTER SMALL TURNED ALPHA..MODIFIER - {0x1DC0, 0x1DE6, propertyPVALID}, // COMBINING DOTTED GRAVE ACCENT..COMBINING LAT - {0x1DE7, 0x1DFC, propertyUNASSIGNED}, // .. - {0x1DFD, 0x1DFF, propertyPVALID}, // COMBINING ALMOST EQUAL TO BELOW..COMBINING R - {0x1E00, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER A WITH RING BELOW - {0x1E01, 0x0, propertyPVALID}, // LATIN SMALL LETTER A WITH RING BELOW - {0x1E02, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER B WITH DOT ABOVE - {0x1E03, 0x0, propertyPVALID}, // LATIN SMALL LETTER B WITH DOT ABOVE - {0x1E04, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER B WITH DOT BELOW - {0x1E05, 0x0, propertyPVALID}, // LATIN SMALL LETTER B WITH DOT BELOW - {0x1E06, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER B WITH LINE BELOW - {0x1E07, 0x0, propertyPVALID}, // LATIN SMALL LETTER B WITH LINE BELOW - {0x1E08, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER C WITH CEDILLA AND ACUT - {0x1E09, 0x0, propertyPVALID}, // LATIN SMALL LETTER C WITH CEDILLA AND ACUTE - {0x1E0A, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER D WITH DOT ABOVE - {0x1E0B, 0x0, propertyPVALID}, // LATIN SMALL LETTER D WITH DOT ABOVE - {0x1E0C, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER D WITH DOT BELOW - {0x1E0D, 0x0, propertyPVALID}, // LATIN SMALL LETTER D WITH DOT BELOW - {0x1E0E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER D WITH LINE BELOW - {0x1E0F, 0x0, propertyPVALID}, // LATIN SMALL LETTER D WITH LINE BELOW - {0x1E10, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER D WITH CEDILLA - {0x1E11, 0x0, propertyPVALID}, // LATIN SMALL LETTER D WITH CEDILLA - {0x1E12, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW - {0x1E13, 0x0, propertyPVALID}, // LATIN SMALL LETTER D WITH CIRCUMFLEX BELOW - {0x1E14, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER E WITH MACRON AND GRAVE - {0x1E15, 0x0, propertyPVALID}, // LATIN SMALL LETTER E WITH MACRON AND GRAVE - {0x1E16, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER E WITH MACRON AND ACUTE - {0x1E17, 0x0, propertyPVALID}, // LATIN SMALL LETTER E WITH MACRON AND ACUTE - {0x1E18, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW - {0x1E19, 0x0, propertyPVALID}, // LATIN SMALL LETTER E WITH CIRCUMFLEX BELOW - {0x1E1A, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER E WITH TILDE BELOW - {0x1E1B, 0x0, propertyPVALID}, // LATIN SMALL LETTER E WITH TILDE BELOW - {0x1E1C, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER E WITH CEDILLA AND BREV - {0x1E1D, 0x0, propertyPVALID}, // LATIN SMALL LETTER E WITH CEDILLA AND BREVE - {0x1E1E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER F WITH DOT ABOVE - {0x1E1F, 0x0, propertyPVALID}, // LATIN SMALL LETTER F WITH DOT ABOVE - {0x1E20, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER G WITH MACRON - {0x1E21, 0x0, propertyPVALID}, // LATIN SMALL LETTER G WITH MACRON - {0x1E22, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER H WITH DOT ABOVE - {0x1E23, 0x0, propertyPVALID}, // LATIN SMALL LETTER H WITH DOT ABOVE - {0x1E24, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER H WITH DOT BELOW - {0x1E25, 0x0, propertyPVALID}, // LATIN SMALL LETTER H WITH DOT BELOW - {0x1E26, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER H WITH DIAERESIS - {0x1E27, 0x0, propertyPVALID}, // LATIN SMALL LETTER H WITH DIAERESIS - {0x1E28, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER H WITH CEDILLA - {0x1E29, 0x0, propertyPVALID}, // LATIN SMALL LETTER H WITH CEDILLA - {0x1E2A, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER H WITH BREVE BELOW - {0x1E2B, 0x0, propertyPVALID}, // LATIN SMALL LETTER H WITH BREVE BELOW - {0x1E2C, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER I WITH TILDE BELOW - {0x1E2D, 0x0, propertyPVALID}, // LATIN SMALL LETTER I WITH TILDE BELOW - {0x1E2E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER I WITH DIAERESIS AND AC - {0x1E2F, 0x0, propertyPVALID}, // LATIN SMALL LETTER I WITH DIAERESIS AND ACUT - {0x1E30, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER K WITH ACUTE - {0x1E31, 0x0, propertyPVALID}, // LATIN SMALL LETTER K WITH ACUTE - {0x1E32, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER K WITH DOT BELOW - {0x1E33, 0x0, propertyPVALID}, // LATIN SMALL LETTER K WITH DOT BELOW - {0x1E34, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER K WITH LINE BELOW - {0x1E35, 0x0, propertyPVALID}, // LATIN SMALL LETTER K WITH LINE BELOW - {0x1E36, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER L WITH DOT BELOW - {0x1E37, 0x0, propertyPVALID}, // LATIN SMALL LETTER L WITH DOT BELOW - {0x1E38, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER L WITH DOT BELOW AND MA - {0x1E39, 0x0, propertyPVALID}, // LATIN SMALL LETTER L WITH DOT BELOW AND MACR - {0x1E3A, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER L WITH LINE BELOW - {0x1E3B, 0x0, propertyPVALID}, // LATIN SMALL LETTER L WITH LINE BELOW - {0x1E3C, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW - {0x1E3D, 0x0, propertyPVALID}, // LATIN SMALL LETTER L WITH CIRCUMFLEX BELOW - {0x1E3E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER M WITH ACUTE - {0x1E3F, 0x0, propertyPVALID}, // LATIN SMALL LETTER M WITH ACUTE - {0x1E40, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER M WITH DOT ABOVE - {0x1E41, 0x0, propertyPVALID}, // LATIN SMALL LETTER M WITH DOT ABOVE - {0x1E42, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER M WITH DOT BELOW - {0x1E43, 0x0, propertyPVALID}, // LATIN SMALL LETTER M WITH DOT BELOW - {0x1E44, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER N WITH DOT ABOVE - {0x1E45, 0x0, propertyPVALID}, // LATIN SMALL LETTER N WITH DOT ABOVE - {0x1E46, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER N WITH DOT BELOW - {0x1E47, 0x0, propertyPVALID}, // LATIN SMALL LETTER N WITH DOT BELOW - {0x1E48, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER N WITH LINE BELOW - {0x1E49, 0x0, propertyPVALID}, // LATIN SMALL LETTER N WITH LINE BELOW - {0x1E4A, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW - {0x1E4B, 0x0, propertyPVALID}, // LATIN SMALL LETTER N WITH CIRCUMFLEX BELOW - {0x1E4C, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH TILDE AND ACUTE - {0x1E4D, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH TILDE AND ACUTE - {0x1E4E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH TILDE AND DIAERE - {0x1E4F, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH TILDE AND DIAERESI - {0x1E50, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH MACRON AND GRAVE - {0x1E51, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH MACRON AND GRAVE - {0x1E52, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH MACRON AND ACUTE - {0x1E53, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH MACRON AND ACUTE - {0x1E54, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER P WITH ACUTE - {0x1E55, 0x0, propertyPVALID}, // LATIN SMALL LETTER P WITH ACUTE - {0x1E56, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER P WITH DOT ABOVE - {0x1E57, 0x0, propertyPVALID}, // LATIN SMALL LETTER P WITH DOT ABOVE - {0x1E58, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER R WITH DOT ABOVE - {0x1E59, 0x0, propertyPVALID}, // LATIN SMALL LETTER R WITH DOT ABOVE - {0x1E5A, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER R WITH DOT BELOW - {0x1E5B, 0x0, propertyPVALID}, // LATIN SMALL LETTER R WITH DOT BELOW - {0x1E5C, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER R WITH DOT BELOW AND MA - {0x1E5D, 0x0, propertyPVALID}, // LATIN SMALL LETTER R WITH DOT BELOW AND MACR - {0x1E5E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER R WITH LINE BELOW - {0x1E5F, 0x0, propertyPVALID}, // LATIN SMALL LETTER R WITH LINE BELOW - {0x1E60, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER S WITH DOT ABOVE - {0x1E61, 0x0, propertyPVALID}, // LATIN SMALL LETTER S WITH DOT ABOVE - {0x1E62, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER S WITH DOT BELOW - {0x1E63, 0x0, propertyPVALID}, // LATIN SMALL LETTER S WITH DOT BELOW - {0x1E64, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER S WITH ACUTE AND DOT AB - {0x1E65, 0x0, propertyPVALID}, // LATIN SMALL LETTER S WITH ACUTE AND DOT ABOV - {0x1E66, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER S WITH CARON AND DOT AB - {0x1E67, 0x0, propertyPVALID}, // LATIN SMALL LETTER S WITH CARON AND DOT ABOV - {0x1E68, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER S WITH DOT BELOW AND DO - {0x1E69, 0x0, propertyPVALID}, // LATIN SMALL LETTER S WITH DOT BELOW AND DOT - {0x1E6A, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER T WITH DOT ABOVE - {0x1E6B, 0x0, propertyPVALID}, // LATIN SMALL LETTER T WITH DOT ABOVE - {0x1E6C, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER T WITH DOT BELOW - {0x1E6D, 0x0, propertyPVALID}, // LATIN SMALL LETTER T WITH DOT BELOW - {0x1E6E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER T WITH LINE BELOW - {0x1E6F, 0x0, propertyPVALID}, // LATIN SMALL LETTER T WITH LINE BELOW - {0x1E70, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW - {0x1E71, 0x0, propertyPVALID}, // LATIN SMALL LETTER T WITH CIRCUMFLEX BELOW - {0x1E72, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH DIAERESIS BELOW - {0x1E73, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH DIAERESIS BELOW - {0x1E74, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH TILDE BELOW - {0x1E75, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH TILDE BELOW - {0x1E76, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW - {0x1E77, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH CIRCUMFLEX BELOW - {0x1E78, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH TILDE AND ACUTE - {0x1E79, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH TILDE AND ACUTE - {0x1E7A, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH MACRON AND DIAER - {0x1E7B, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH MACRON AND DIAERES - {0x1E7C, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER V WITH TILDE - {0x1E7D, 0x0, propertyPVALID}, // LATIN SMALL LETTER V WITH TILDE - {0x1E7E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER V WITH DOT BELOW - {0x1E7F, 0x0, propertyPVALID}, // LATIN SMALL LETTER V WITH DOT BELOW - {0x1E80, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER W WITH GRAVE - {0x1E81, 0x0, propertyPVALID}, // LATIN SMALL LETTER W WITH GRAVE - {0x1E82, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER W WITH ACUTE - {0x1E83, 0x0, propertyPVALID}, // LATIN SMALL LETTER W WITH ACUTE - {0x1E84, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER W WITH DIAERESIS - {0x1E85, 0x0, propertyPVALID}, // LATIN SMALL LETTER W WITH DIAERESIS - {0x1E86, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER W WITH DOT ABOVE - {0x1E87, 0x0, propertyPVALID}, // LATIN SMALL LETTER W WITH DOT ABOVE - {0x1E88, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER W WITH DOT BELOW - {0x1E89, 0x0, propertyPVALID}, // LATIN SMALL LETTER W WITH DOT BELOW - {0x1E8A, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER X WITH DOT ABOVE - {0x1E8B, 0x0, propertyPVALID}, // LATIN SMALL LETTER X WITH DOT ABOVE - {0x1E8C, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER X WITH DIAERESIS - {0x1E8D, 0x0, propertyPVALID}, // LATIN SMALL LETTER X WITH DIAERESIS - {0x1E8E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER Y WITH DOT ABOVE - {0x1E8F, 0x0, propertyPVALID}, // LATIN SMALL LETTER Y WITH DOT ABOVE - {0x1E90, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER Z WITH CIRCUMFLEX - {0x1E91, 0x0, propertyPVALID}, // LATIN SMALL LETTER Z WITH CIRCUMFLEX - {0x1E92, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER Z WITH DOT BELOW - {0x1E93, 0x0, propertyPVALID}, // LATIN SMALL LETTER Z WITH DOT BELOW - {0x1E94, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER Z WITH LINE BELOW - {0x1E95, 0x1E99, propertyPVALID}, // LATIN SMALL LETTER Z WITH LINE BELOW..LATIN - {0x1E9A, 0x1E9B, propertyDISALLOWED}, // LATIN SMALL LETTER A WITH RIGHT HALF RING..L - {0x1E9C, 0x1E9D, propertyPVALID}, // LATIN SMALL LETTER LONG S WITH DIAGONAL STRO - {0x1E9E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER SHARP S - {0x1E9F, 0x0, propertyPVALID}, // LATIN SMALL LETTER DELTA - {0x1EA0, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER A WITH DOT BELOW - {0x1EA1, 0x0, propertyPVALID}, // LATIN SMALL LETTER A WITH DOT BELOW - {0x1EA2, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER A WITH HOOK ABOVE - {0x1EA3, 0x0, propertyPVALID}, // LATIN SMALL LETTER A WITH HOOK ABOVE - {0x1EA4, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND A - {0x1EA5, 0x0, propertyPVALID}, // LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACU - {0x1EA6, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND G - {0x1EA7, 0x0, propertyPVALID}, // LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRA - {0x1EA8, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND H - {0x1EA9, 0x0, propertyPVALID}, // LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOO - {0x1EAA, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND T - {0x1EAB, 0x0, propertyPVALID}, // LATIN SMALL LETTER A WITH CIRCUMFLEX AND TIL - {0x1EAC, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND D - {0x1EAD, 0x0, propertyPVALID}, // LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT - {0x1EAE, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER A WITH BREVE AND ACUTE - {0x1EAF, 0x0, propertyPVALID}, // LATIN SMALL LETTER A WITH BREVE AND ACUTE - {0x1EB0, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER A WITH BREVE AND GRAVE - {0x1EB1, 0x0, propertyPVALID}, // LATIN SMALL LETTER A WITH BREVE AND GRAVE - {0x1EB2, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER A WITH BREVE AND HOOK A - {0x1EB3, 0x0, propertyPVALID}, // LATIN SMALL LETTER A WITH BREVE AND HOOK ABO - {0x1EB4, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER A WITH BREVE AND TILDE - {0x1EB5, 0x0, propertyPVALID}, // LATIN SMALL LETTER A WITH BREVE AND TILDE - {0x1EB6, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER A WITH BREVE AND DOT BE - {0x1EB7, 0x0, propertyPVALID}, // LATIN SMALL LETTER A WITH BREVE AND DOT BELO - {0x1EB8, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER E WITH DOT BELOW - {0x1EB9, 0x0, propertyPVALID}, // LATIN SMALL LETTER E WITH DOT BELOW - {0x1EBA, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER E WITH HOOK ABOVE - {0x1EBB, 0x0, propertyPVALID}, // LATIN SMALL LETTER E WITH HOOK ABOVE - {0x1EBC, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER E WITH TILDE - {0x1EBD, 0x0, propertyPVALID}, // LATIN SMALL LETTER E WITH TILDE - {0x1EBE, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND A - {0x1EBF, 0x0, propertyPVALID}, // LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACU - {0x1EC0, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND G - {0x1EC1, 0x0, propertyPVALID}, // LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRA - {0x1EC2, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND H - {0x1EC3, 0x0, propertyPVALID}, // LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOO - {0x1EC4, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND T - {0x1EC5, 0x0, propertyPVALID}, // LATIN SMALL LETTER E WITH CIRCUMFLEX AND TIL - {0x1EC6, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND D - {0x1EC7, 0x0, propertyPVALID}, // LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT - {0x1EC8, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER I WITH HOOK ABOVE - {0x1EC9, 0x0, propertyPVALID}, // LATIN SMALL LETTER I WITH HOOK ABOVE - {0x1ECA, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER I WITH DOT BELOW - {0x1ECB, 0x0, propertyPVALID}, // LATIN SMALL LETTER I WITH DOT BELOW - {0x1ECC, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH DOT BELOW - {0x1ECD, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH DOT BELOW - {0x1ECE, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH HOOK ABOVE - {0x1ECF, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH HOOK ABOVE - {0x1ED0, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND A - {0x1ED1, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACU - {0x1ED2, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND G - {0x1ED3, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRA - {0x1ED4, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND H - {0x1ED5, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOO - {0x1ED6, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND T - {0x1ED7, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH CIRCUMFLEX AND TIL - {0x1ED8, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND D - {0x1ED9, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT - {0x1EDA, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH HORN AND ACUTE - {0x1EDB, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH HORN AND ACUTE - {0x1EDC, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH HORN AND GRAVE - {0x1EDD, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH HORN AND GRAVE - {0x1EDE, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH HORN AND HOOK AB - {0x1EDF, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH HORN AND HOOK ABOV - {0x1EE0, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH HORN AND TILDE - {0x1EE1, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH HORN AND TILDE - {0x1EE2, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH HORN AND DOT BEL - {0x1EE3, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH HORN AND DOT BELOW - {0x1EE4, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH DOT BELOW - {0x1EE5, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH DOT BELOW - {0x1EE6, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH HOOK ABOVE - {0x1EE7, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH HOOK ABOVE - {0x1EE8, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH HORN AND ACUTE - {0x1EE9, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH HORN AND ACUTE - {0x1EEA, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH HORN AND GRAVE - {0x1EEB, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH HORN AND GRAVE - {0x1EEC, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH HORN AND HOOK AB - {0x1EED, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH HORN AND HOOK ABOV - {0x1EEE, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH HORN AND TILDE - {0x1EEF, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH HORN AND TILDE - {0x1EF0, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER U WITH HORN AND DOT BEL - {0x1EF1, 0x0, propertyPVALID}, // LATIN SMALL LETTER U WITH HORN AND DOT BELOW - {0x1EF2, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER Y WITH GRAVE - {0x1EF3, 0x0, propertyPVALID}, // LATIN SMALL LETTER Y WITH GRAVE - {0x1EF4, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER Y WITH DOT BELOW - {0x1EF5, 0x0, propertyPVALID}, // LATIN SMALL LETTER Y WITH DOT BELOW - {0x1EF6, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER Y WITH HOOK ABOVE - {0x1EF7, 0x0, propertyPVALID}, // LATIN SMALL LETTER Y WITH HOOK ABOVE - {0x1EF8, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER Y WITH TILDE - {0x1EF9, 0x0, propertyPVALID}, // LATIN SMALL LETTER Y WITH TILDE - {0x1EFA, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER MIDDLE-WELSH LL - {0x1EFB, 0x0, propertyPVALID}, // LATIN SMALL LETTER MIDDLE-WELSH LL - {0x1EFC, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER MIDDLE-WELSH V - {0x1EFD, 0x0, propertyPVALID}, // LATIN SMALL LETTER MIDDLE-WELSH V - {0x1EFE, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER Y WITH LOOP - {0x1EFF, 0x1F07, propertyPVALID}, // LATIN SMALL LETTER Y WITH LOOP..GREEK SMALL - {0x1F08, 0x1F0F, propertyDISALLOWED}, // GREEK CAPITAL LETTER ALPHA WITH PSILI..GREEK - {0x1F10, 0x1F15, propertyPVALID}, // GREEK SMALL LETTER EPSILON WITH PSILI..GREEK - {0x1F16, 0x1F17, propertyUNASSIGNED}, // .. - {0x1F18, 0x1F1D, propertyDISALLOWED}, // GREEK CAPITAL LETTER EPSILON WITH PSILI..GRE - {0x1F1E, 0x1F1F, propertyUNASSIGNED}, // .. - {0x1F20, 0x1F27, propertyPVALID}, // GREEK SMALL LETTER ETA WITH PSILI..GREEK SMA - {0x1F28, 0x1F2F, propertyDISALLOWED}, // GREEK CAPITAL LETTER ETA WITH PSILI..GREEK C - {0x1F30, 0x1F37, propertyPVALID}, // GREEK SMALL LETTER IOTA WITH PSILI..GREEK SM - {0x1F38, 0x1F3F, propertyDISALLOWED}, // GREEK CAPITAL LETTER IOTA WITH PSILI..GREEK - {0x1F40, 0x1F45, propertyPVALID}, // GREEK SMALL LETTER OMICRON WITH PSILI..GREEK - {0x1F46, 0x1F47, propertyUNASSIGNED}, // .. - {0x1F48, 0x1F4D, propertyDISALLOWED}, // GREEK CAPITAL LETTER OMICRON WITH PSILI..GRE - {0x1F4E, 0x1F4F, propertyUNASSIGNED}, // .. - {0x1F50, 0x1F57, propertyPVALID}, // GREEK SMALL LETTER UPSILON WITH PSILI..GREEK - {0x1F58, 0x0, propertyUNASSIGNED}, // - {0x1F59, 0x0, propertyDISALLOWED}, // GREEK CAPITAL LETTER UPSILON WITH DASIA - {0x1F5A, 0x0, propertyUNASSIGNED}, // - {0x1F5B, 0x0, propertyDISALLOWED}, // GREEK CAPITAL LETTER UPSILON WITH DASIA AND - {0x1F5C, 0x0, propertyUNASSIGNED}, // - {0x1F5D, 0x0, propertyDISALLOWED}, // GREEK CAPITAL LETTER UPSILON WITH DASIA AND - {0x1F5E, 0x0, propertyUNASSIGNED}, // - {0x1F5F, 0x0, propertyDISALLOWED}, // GREEK CAPITAL LETTER UPSILON WITH DASIA AND - {0x1F60, 0x1F67, propertyPVALID}, // GREEK SMALL LETTER OMEGA WITH PSILI..GREEK S - {0x1F68, 0x1F6F, propertyDISALLOWED}, // GREEK CAPITAL LETTER OMEGA WITH PSILI..GREEK - {0x1F70, 0x0, propertyPVALID}, // GREEK SMALL LETTER ALPHA WITH VARIA - {0x1F71, 0x0, propertyDISALLOWED}, // GREEK SMALL LETTER ALPHA WITH OXIA - {0x1F72, 0x0, propertyPVALID}, // GREEK SMALL LETTER EPSILON WITH VARIA - {0x1F73, 0x0, propertyDISALLOWED}, // GREEK SMALL LETTER EPSILON WITH OXIA - {0x1F74, 0x0, propertyPVALID}, // GREEK SMALL LETTER ETA WITH VARIA - {0x1F75, 0x0, propertyDISALLOWED}, // GREEK SMALL LETTER ETA WITH OXIA - {0x1F76, 0x0, propertyPVALID}, // GREEK SMALL LETTER IOTA WITH VARIA - {0x1F77, 0x0, propertyDISALLOWED}, // GREEK SMALL LETTER IOTA WITH OXIA - {0x1F78, 0x0, propertyPVALID}, // GREEK SMALL LETTER OMICRON WITH VARIA - {0x1F79, 0x0, propertyDISALLOWED}, // GREEK SMALL LETTER OMICRON WITH OXIA - {0x1F7A, 0x0, propertyPVALID}, // GREEK SMALL LETTER UPSILON WITH VARIA - {0x1F7B, 0x0, propertyDISALLOWED}, // GREEK SMALL LETTER UPSILON WITH OXIA - {0x1F7C, 0x0, propertyPVALID}, // GREEK SMALL LETTER OMEGA WITH VARIA - {0x1F7D, 0x0, propertyDISALLOWED}, // GREEK SMALL LETTER OMEGA WITH OXIA - {0x1F7E, 0x1F7F, propertyUNASSIGNED}, // .. - {0x1F80, 0x1FAF, propertyDISALLOWED}, // GREEK SMALL LETTER ALPHA WITH PSILI AND YPOG - {0x1FB0, 0x1FB1, propertyPVALID}, // GREEK SMALL LETTER ALPHA WITH VRACHY..GREEK - {0x1FB2, 0x1FB4, propertyDISALLOWED}, // GREEK SMALL LETTER ALPHA WITH VARIA AND YPOG - {0x1FB5, 0x0, propertyUNASSIGNED}, // - {0x1FB6, 0x0, propertyPVALID}, // GREEK SMALL LETTER ALPHA WITH PERISPOMENI - {0x1FB7, 0x1FC4, propertyDISALLOWED}, // GREEK SMALL LETTER ALPHA WITH PERISPOMENI AN - {0x1FC5, 0x0, propertyUNASSIGNED}, // - {0x1FC6, 0x0, propertyPVALID}, // GREEK SMALL LETTER ETA WITH PERISPOMENI - {0x1FC7, 0x1FCF, propertyDISALLOWED}, // GREEK SMALL LETTER ETA WITH PERISPOMENI AND - {0x1FD0, 0x1FD2, propertyPVALID}, // GREEK SMALL LETTER IOTA WITH VRACHY..GREEK S - {0x1FD3, 0x0, propertyDISALLOWED}, // GREEK SMALL LETTER IOTA WITH DIALYTIKA AND O - {0x1FD4, 0x1FD5, propertyUNASSIGNED}, // .. - {0x1FD6, 0x1FD7, propertyPVALID}, // GREEK SMALL LETTER IOTA WITH PERISPOMENI..GR - {0x1FD8, 0x1FDB, propertyDISALLOWED}, // GREEK CAPITAL LETTER IOTA WITH VRACHY..GREEK - {0x1FDC, 0x0, propertyUNASSIGNED}, // - {0x1FDD, 0x1FDF, propertyDISALLOWED}, // GREEK DASIA AND VARIA..GREEK DASIA AND PERIS - {0x1FE0, 0x1FE2, propertyPVALID}, // GREEK SMALL LETTER UPSILON WITH VRACHY..GREE - {0x1FE3, 0x0, propertyDISALLOWED}, // GREEK SMALL LETTER UPSILON WITH DIALYTIKA AN - {0x1FE4, 0x1FE7, propertyPVALID}, // GREEK SMALL LETTER RHO WITH PSILI..GREEK SMA - {0x1FE8, 0x1FEF, propertyDISALLOWED}, // GREEK CAPITAL LETTER UPSILON WITH VRACHY..GR - {0x1FF0, 0x1FF1, propertyUNASSIGNED}, // .. - {0x1FF2, 0x1FF4, propertyDISALLOWED}, // GREEK SMALL LETTER OMEGA WITH VARIA AND YPOG - {0x1FF5, 0x0, propertyUNASSIGNED}, // - {0x1FF6, 0x0, propertyPVALID}, // GREEK SMALL LETTER OMEGA WITH PERISPOMENI - {0x1FF7, 0x1FFE, propertyDISALLOWED}, // GREEK SMALL LETTER OMEGA WITH PERISPOMENI AN - {0x1FFF, 0x0, propertyUNASSIGNED}, // - {0x2000, 0x200B, propertyDISALLOWED}, // EN QUAD..ZERO WIDTH SPACE - {0x200C, 0x200D, propertyCONTEXTJ}, // ZERO WIDTH NON-JOINER..ZERO WIDTH JOINER - {0x200E, 0x2064, propertyDISALLOWED}, // LEFT-TO-RIGHT MARK..INVISIBLE PLUS - {0x2065, 0x2069, propertyUNASSIGNED}, // .. - {0x206A, 0x2071, propertyDISALLOWED}, // INHIBIT SYMMETRIC SWAPPING..SUPERSCRIPT LATI - {0x2072, 0x2073, propertyUNASSIGNED}, // .. - {0x2074, 0x208E, propertyDISALLOWED}, // SUPERSCRIPT FOUR..SUBSCRIPT RIGHT PARENTHESI - {0x208F, 0x0, propertyUNASSIGNED}, // - {0x2090, 0x2094, propertyDISALLOWED}, // LATIN SUBSCRIPT SMALL LETTER A..LATIN SUBSCR - {0x2095, 0x209F, propertyUNASSIGNED}, // .. - {0x20A0, 0x20B8, propertyDISALLOWED}, // EURO-CURRENCY SIGN..TENGE SIGN - {0x20B9, 0x20CF, propertyUNASSIGNED}, // .. - {0x20D0, 0x20F0, propertyDISALLOWED}, // COMBINING LEFT HARPOON ABOVE..COMBINING ASTE - {0x20F1, 0x20FF, propertyUNASSIGNED}, // .. - {0x2100, 0x214D, propertyDISALLOWED}, // ACCOUNT OF..AKTIESELSKAB - {0x214E, 0x0, propertyPVALID}, // TURNED SMALL F - {0x214F, 0x2183, propertyDISALLOWED}, // SYMBOL FOR SAMARITAN SOURCE..ROMAN NUMERAL R - {0x2184, 0x0, propertyPVALID}, // LATIN SMALL LETTER REVERSED C - {0x2185, 0x2189, propertyDISALLOWED}, // ROMAN NUMERAL SIX LATE FORM..VULGAR FRACTION - {0x218A, 0x218F, propertyUNASSIGNED}, // .. - {0x2190, 0x23E8, propertyDISALLOWED}, // LEFTWARDS ARROW..DECIMAL EXPONENT SYMBOL - {0x23E9, 0x23FF, propertyUNASSIGNED}, // .. - {0x2400, 0x2426, propertyDISALLOWED}, // SYMBOL FOR NULL..SYMBOL FOR SUBSTITUTE FORM - {0x2427, 0x243F, propertyUNASSIGNED}, // .. - {0x2440, 0x244A, propertyDISALLOWED}, // OCR HOOK..OCR DOUBLE BACKSLASH - {0x244B, 0x245F, propertyUNASSIGNED}, // .. - {0x2460, 0x26CD, propertyDISALLOWED}, // CIRCLED DIGIT ONE..DISABLED CAR - {0x26CE, 0x0, propertyUNASSIGNED}, // - {0x26CF, 0x26E1, propertyDISALLOWED}, // PICK..RESTRICTED LEFT ENTRY-2 - {0x26E2, 0x0, propertyUNASSIGNED}, // - {0x26E3, 0x0, propertyDISALLOWED}, // HEAVY CIRCLE WITH STROKE AND TWO DOTS ABOVE - {0x26E4, 0x26E7, propertyUNASSIGNED}, // .. - {0x26E8, 0x26FF, propertyDISALLOWED}, // BLACK CROSS ON SHIELD..WHITE FLAG WITH HORIZ - {0x2700, 0x0, propertyUNASSIGNED}, // - {0x2701, 0x2704, propertyDISALLOWED}, // UPPER BLADE SCISSORS..WHITE SCISSORS - {0x2705, 0x0, propertyUNASSIGNED}, // - {0x2706, 0x2709, propertyDISALLOWED}, // TELEPHONE LOCATION SIGN..ENVELOPE - {0x270A, 0x270B, propertyUNASSIGNED}, // .. - {0x270C, 0x2727, propertyDISALLOWED}, // VICTORY HAND..WHITE FOUR POINTED STAR - {0x2728, 0x0, propertyUNASSIGNED}, // - {0x2729, 0x274B, propertyDISALLOWED}, // STRESS OUTLINED WHITE STAR..HEAVY EIGHT TEAR - {0x274C, 0x0, propertyUNASSIGNED}, // - {0x274D, 0x0, propertyDISALLOWED}, // SHADOWED WHITE CIRCLE - {0x274E, 0x0, propertyUNASSIGNED}, // - {0x274F, 0x2752, propertyDISALLOWED}, // LOWER RIGHT DROP-SHADOWED WHITE SQUARE..UPPE - {0x2753, 0x2755, propertyUNASSIGNED}, // .. - {0x2756, 0x275E, propertyDISALLOWED}, // BLACK DIAMOND MINUS WHITE X..HEAVY DOUBLE CO - {0x275F, 0x2760, propertyUNASSIGNED}, // .. - {0x2761, 0x2794, propertyDISALLOWED}, // CURVED STEM PARAGRAPH SIGN ORNAMENT..HEAVY W - {0x2795, 0x2797, propertyUNASSIGNED}, // .. - {0x2798, 0x27AF, propertyDISALLOWED}, // HEAVY SOUTH EAST ARROW..NOTCHED LOWER RIGHT- - {0x27B0, 0x0, propertyUNASSIGNED}, // - {0x27B1, 0x27BE, propertyDISALLOWED}, // NOTCHED UPPER RIGHT-SHADOWED WHITE RIGHTWARD - {0x27BF, 0x0, propertyUNASSIGNED}, // - {0x27C0, 0x27CA, propertyDISALLOWED}, // THREE DIMENSIONAL ANGLE..VERTICAL BAR WITH H - {0x27CB, 0x0, propertyUNASSIGNED}, // - {0x27CC, 0x0, propertyDISALLOWED}, // LONG DIVISION - {0x27CD, 0x27CF, propertyUNASSIGNED}, // .. - {0x27D0, 0x2B4C, propertyDISALLOWED}, // WHITE DIAMOND WITH CENTRED DOT..RIGHTWARDS A - {0x2B4D, 0x2B4F, propertyUNASSIGNED}, // .. - {0x2B50, 0x2B59, propertyDISALLOWED}, // WHITE MEDIUM STAR..HEAVY CIRCLED SALTIRE - {0x2B5A, 0x2BFF, propertyUNASSIGNED}, // .. - {0x2C00, 0x2C2E, propertyDISALLOWED}, // GLAGOLITIC CAPITAL LETTER AZU..GLAGOLITIC CA - {0x2C2F, 0x0, propertyUNASSIGNED}, // - {0x2C30, 0x2C5E, propertyPVALID}, // GLAGOLITIC SMALL LETTER AZU..GLAGOLITIC SMAL - {0x2C5F, 0x0, propertyUNASSIGNED}, // - {0x2C60, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER L WITH DOUBLE BAR - {0x2C61, 0x0, propertyPVALID}, // LATIN SMALL LETTER L WITH DOUBLE BAR - {0x2C62, 0x2C64, propertyDISALLOWED}, // LATIN CAPITAL LETTER L WITH MIDDLE TILDE..LA - {0x2C65, 0x2C66, propertyPVALID}, // LATIN SMALL LETTER A WITH STROKE..LATIN SMAL - {0x2C67, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER H WITH DESCENDER - {0x2C68, 0x0, propertyPVALID}, // LATIN SMALL LETTER H WITH DESCENDER - {0x2C69, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER K WITH DESCENDER - {0x2C6A, 0x0, propertyPVALID}, // LATIN SMALL LETTER K WITH DESCENDER - {0x2C6B, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER Z WITH DESCENDER - {0x2C6C, 0x0, propertyPVALID}, // LATIN SMALL LETTER Z WITH DESCENDER - {0x2C6D, 0x2C70, propertyDISALLOWED}, // LATIN CAPITAL LETTER ALPHA..LATIN CAPITAL LE - {0x2C71, 0x0, propertyPVALID}, // LATIN SMALL LETTER V WITH RIGHT HOOK - {0x2C72, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER W WITH HOOK - {0x2C73, 0x2C74, propertyPVALID}, // LATIN SMALL LETTER W WITH HOOK..LATIN SMALL - {0x2C75, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER HALF H - {0x2C76, 0x2C7B, propertyPVALID}, // LATIN SMALL LETTER HALF H..LATIN LETTER SMAL - {0x2C7C, 0x2C80, propertyDISALLOWED}, // LATIN SUBSCRIPT SMALL LETTER J..COPTIC CAPIT - {0x2C81, 0x0, propertyPVALID}, // COPTIC SMALL LETTER ALFA - {0x2C82, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER VIDA - {0x2C83, 0x0, propertyPVALID}, // COPTIC SMALL LETTER VIDA - {0x2C84, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER GAMMA - {0x2C85, 0x0, propertyPVALID}, // COPTIC SMALL LETTER GAMMA - {0x2C86, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER DALDA - {0x2C87, 0x0, propertyPVALID}, // COPTIC SMALL LETTER DALDA - {0x2C88, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER EIE - {0x2C89, 0x0, propertyPVALID}, // COPTIC SMALL LETTER EIE - {0x2C8A, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER SOU - {0x2C8B, 0x0, propertyPVALID}, // COPTIC SMALL LETTER SOU - {0x2C8C, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER ZATA - {0x2C8D, 0x0, propertyPVALID}, // COPTIC SMALL LETTER ZATA - {0x2C8E, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER HATE - {0x2C8F, 0x0, propertyPVALID}, // COPTIC SMALL LETTER HATE - {0x2C90, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER THETHE - {0x2C91, 0x0, propertyPVALID}, // COPTIC SMALL LETTER THETHE - {0x2C92, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER IAUDA - {0x2C93, 0x0, propertyPVALID}, // COPTIC SMALL LETTER IAUDA - {0x2C94, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER KAPA - {0x2C95, 0x0, propertyPVALID}, // COPTIC SMALL LETTER KAPA - {0x2C96, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER LAULA - {0x2C97, 0x0, propertyPVALID}, // COPTIC SMALL LETTER LAULA - {0x2C98, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER MI - {0x2C99, 0x0, propertyPVALID}, // COPTIC SMALL LETTER MI - {0x2C9A, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER NI - {0x2C9B, 0x0, propertyPVALID}, // COPTIC SMALL LETTER NI - {0x2C9C, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER KSI - {0x2C9D, 0x0, propertyPVALID}, // COPTIC SMALL LETTER KSI - {0x2C9E, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER O - {0x2C9F, 0x0, propertyPVALID}, // COPTIC SMALL LETTER O - {0x2CA0, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER PI - {0x2CA1, 0x0, propertyPVALID}, // COPTIC SMALL LETTER PI - {0x2CA2, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER RO - {0x2CA3, 0x0, propertyPVALID}, // COPTIC SMALL LETTER RO - {0x2CA4, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER SIMA - {0x2CA5, 0x0, propertyPVALID}, // COPTIC SMALL LETTER SIMA - {0x2CA6, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER TAU - {0x2CA7, 0x0, propertyPVALID}, // COPTIC SMALL LETTER TAU - {0x2CA8, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER UA - {0x2CA9, 0x0, propertyPVALID}, // COPTIC SMALL LETTER UA - {0x2CAA, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER FI - {0x2CAB, 0x0, propertyPVALID}, // COPTIC SMALL LETTER FI - {0x2CAC, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER KHI - {0x2CAD, 0x0, propertyPVALID}, // COPTIC SMALL LETTER KHI - {0x2CAE, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER PSI - {0x2CAF, 0x0, propertyPVALID}, // COPTIC SMALL LETTER PSI - {0x2CB0, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER OOU - {0x2CB1, 0x0, propertyPVALID}, // COPTIC SMALL LETTER OOU - {0x2CB2, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER DIALECT-P ALEF - {0x2CB3, 0x0, propertyPVALID}, // COPTIC SMALL LETTER DIALECT-P ALEF - {0x2CB4, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER OLD COPTIC AIN - {0x2CB5, 0x0, propertyPVALID}, // COPTIC SMALL LETTER OLD COPTIC AIN - {0x2CB6, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER CRYPTOGRAMMIC EIE - {0x2CB7, 0x0, propertyPVALID}, // COPTIC SMALL LETTER CRYPTOGRAMMIC EIE - {0x2CB8, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER DIALECT-P KAPA - {0x2CB9, 0x0, propertyPVALID}, // COPTIC SMALL LETTER DIALECT-P KAPA - {0x2CBA, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER DIALECT-P NI - {0x2CBB, 0x0, propertyPVALID}, // COPTIC SMALL LETTER DIALECT-P NI - {0x2CBC, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER CRYPTOGRAMMIC NI - {0x2CBD, 0x0, propertyPVALID}, // COPTIC SMALL LETTER CRYPTOGRAMMIC NI - {0x2CBE, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER OLD COPTIC OOU - {0x2CBF, 0x0, propertyPVALID}, // COPTIC SMALL LETTER OLD COPTIC OOU - {0x2CC0, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER SAMPI - {0x2CC1, 0x0, propertyPVALID}, // COPTIC SMALL LETTER SAMPI - {0x2CC2, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER CROSSED SHEI - {0x2CC3, 0x0, propertyPVALID}, // COPTIC SMALL LETTER CROSSED SHEI - {0x2CC4, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER OLD COPTIC SHEI - {0x2CC5, 0x0, propertyPVALID}, // COPTIC SMALL LETTER OLD COPTIC SHEI - {0x2CC6, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER OLD COPTIC ESH - {0x2CC7, 0x0, propertyPVALID}, // COPTIC SMALL LETTER OLD COPTIC ESH - {0x2CC8, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER AKHMIMIC KHEI - {0x2CC9, 0x0, propertyPVALID}, // COPTIC SMALL LETTER AKHMIMIC KHEI - {0x2CCA, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER DIALECT-P HORI - {0x2CCB, 0x0, propertyPVALID}, // COPTIC SMALL LETTER DIALECT-P HORI - {0x2CCC, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER OLD COPTIC HORI - {0x2CCD, 0x0, propertyPVALID}, // COPTIC SMALL LETTER OLD COPTIC HORI - {0x2CCE, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER OLD COPTIC HA - {0x2CCF, 0x0, propertyPVALID}, // COPTIC SMALL LETTER OLD COPTIC HA - {0x2CD0, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER L-SHAPED HA - {0x2CD1, 0x0, propertyPVALID}, // COPTIC SMALL LETTER L-SHAPED HA - {0x2CD2, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER OLD COPTIC HEI - {0x2CD3, 0x0, propertyPVALID}, // COPTIC SMALL LETTER OLD COPTIC HEI - {0x2CD4, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER OLD COPTIC HAT - {0x2CD5, 0x0, propertyPVALID}, // COPTIC SMALL LETTER OLD COPTIC HAT - {0x2CD6, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER OLD COPTIC GANGIA - {0x2CD7, 0x0, propertyPVALID}, // COPTIC SMALL LETTER OLD COPTIC GANGIA - {0x2CD8, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER OLD COPTIC DJA - {0x2CD9, 0x0, propertyPVALID}, // COPTIC SMALL LETTER OLD COPTIC DJA - {0x2CDA, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER OLD COPTIC SHIMA - {0x2CDB, 0x0, propertyPVALID}, // COPTIC SMALL LETTER OLD COPTIC SHIMA - {0x2CDC, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER OLD NUBIAN SHIMA - {0x2CDD, 0x0, propertyPVALID}, // COPTIC SMALL LETTER OLD NUBIAN SHIMA - {0x2CDE, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER OLD NUBIAN NGI - {0x2CDF, 0x0, propertyPVALID}, // COPTIC SMALL LETTER OLD NUBIAN NGI - {0x2CE0, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER OLD NUBIAN NYI - {0x2CE1, 0x0, propertyPVALID}, // COPTIC SMALL LETTER OLD NUBIAN NYI - {0x2CE2, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER OLD NUBIAN WAU - {0x2CE3, 0x2CE4, propertyPVALID}, // COPTIC SMALL LETTER OLD NUBIAN WAU..COPTIC S - {0x2CE5, 0x2CEB, propertyDISALLOWED}, // COPTIC SYMBOL MI RO..COPTIC CAPITAL LETTER C - {0x2CEC, 0x0, propertyPVALID}, // COPTIC SMALL LETTER CRYPTOGRAMMIC SHEI - {0x2CED, 0x0, propertyDISALLOWED}, // COPTIC CAPITAL LETTER CRYPTOGRAMMIC GANGIA - {0x2CEE, 0x2CF1, propertyPVALID}, // COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA..CO - {0x2CF2, 0x2CF8, propertyUNASSIGNED}, // .. - {0x2CF9, 0x2CFF, propertyDISALLOWED}, // COPTIC OLD NUBIAN FULL STOP..COPTIC MORPHOLO - {0x2D00, 0x2D25, propertyPVALID}, // GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LET - {0x2D26, 0x2D2F, propertyUNASSIGNED}, // .. - {0x2D30, 0x2D65, propertyPVALID}, // TIFINAGH LETTER YA..TIFINAGH LETTER YAZZ - {0x2D66, 0x2D6E, propertyUNASSIGNED}, // .. - {0x2D6F, 0x0, propertyDISALLOWED}, // TIFINAGH MODIFIER LETTER LABIALIZATION MARK - {0x2D70, 0x2D7F, propertyUNASSIGNED}, // .. - {0x2D80, 0x2D96, propertyPVALID}, // ETHIOPIC SYLLABLE LOA..ETHIOPIC SYLLABLE GGW - {0x2D97, 0x2D9F, propertyUNASSIGNED}, // .. - {0x2DA0, 0x2DA6, propertyPVALID}, // ETHIOPIC SYLLABLE SSA..ETHIOPIC SYLLABLE SSO - {0x2DA7, 0x0, propertyUNASSIGNED}, // - {0x2DA8, 0x2DAE, propertyPVALID}, // ETHIOPIC SYLLABLE CCA..ETHIOPIC SYLLABLE CCO - {0x2DAF, 0x0, propertyUNASSIGNED}, // - {0x2DB0, 0x2DB6, propertyPVALID}, // ETHIOPIC SYLLABLE ZZA..ETHIOPIC SYLLABLE ZZO - {0x2DB7, 0x0, propertyUNASSIGNED}, // - {0x2DB8, 0x2DBE, propertyPVALID}, // ETHIOPIC SYLLABLE CCHA..ETHIOPIC SYLLABLE CC - {0x2DBF, 0x0, propertyUNASSIGNED}, // - {0x2DC0, 0x2DC6, propertyPVALID}, // ETHIOPIC SYLLABLE QYA..ETHIOPIC SYLLABLE QYO - {0x2DC7, 0x0, propertyUNASSIGNED}, // - {0x2DC8, 0x2DCE, propertyPVALID}, // ETHIOPIC SYLLABLE KYA..ETHIOPIC SYLLABLE KYO - {0x2DCF, 0x0, propertyUNASSIGNED}, // - {0x2DD0, 0x2DD6, propertyPVALID}, // ETHIOPIC SYLLABLE XYA..ETHIOPIC SYLLABLE XYO - {0x2DD7, 0x0, propertyUNASSIGNED}, // - {0x2DD8, 0x2DDE, propertyPVALID}, // ETHIOPIC SYLLABLE GYA..ETHIOPIC SYLLABLE GYO - {0x2DDF, 0x0, propertyUNASSIGNED}, // - {0x2DE0, 0x2DFF, propertyPVALID}, // COMBINING CYRILLIC LETTER BE..COMBINING CYRI - {0x2E00, 0x2E2E, propertyDISALLOWED}, // RIGHT ANGLE SUBSTITUTION MARKER..REVERSED QU - {0x2E2F, 0x0, propertyPVALID}, // VERTICAL TILDE - {0x2E30, 0x2E31, propertyDISALLOWED}, // RING POINT..WORD SEPARATOR MIDDLE DOT - {0x2E32, 0x2E7F, propertyUNASSIGNED}, // .. - {0x2E80, 0x2E99, propertyDISALLOWED}, // CJK RADICAL REPEAT..CJK RADICAL RAP - {0x2E9A, 0x0, propertyUNASSIGNED}, // - {0x2E9B, 0x2EF3, propertyDISALLOWED}, // CJK RADICAL CHOKE..CJK RADICAL C-SIMPLIFIED - {0x2EF4, 0x2EFF, propertyUNASSIGNED}, // .. - {0x2F00, 0x2FD5, propertyDISALLOWED}, // KANGXI RADICAL ONE..KANGXI RADICAL FLUTE - {0x2FD6, 0x2FEF, propertyUNASSIGNED}, // .. - {0x2FF0, 0x2FFB, propertyDISALLOWED}, // IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO RI - {0x2FFC, 0x2FFF, propertyUNASSIGNED}, // .. - {0x3000, 0x3004, propertyDISALLOWED}, // IDEOGRAPHIC SPACE..JAPANESE INDUSTRIAL STAND - {0x3005, 0x3007, propertyPVALID}, // IDEOGRAPHIC ITERATION MARK..IDEOGRAPHIC NUMB - {0x3008, 0x3029, propertyDISALLOWED}, // LEFT ANGLE BRACKET..HANGZHOU NUMERAL NINE - {0x302A, 0x302D, propertyPVALID}, // IDEOGRAPHIC LEVEL TONE MARK..IDEOGRAPHIC ENT - {0x302E, 0x303B, propertyDISALLOWED}, // HANGUL SINGLE DOT TONE MARK..VERTICAL IDEOGR - {0x303C, 0x0, propertyPVALID}, // MASU MARK - {0x303D, 0x303F, propertyDISALLOWED}, // PART ALTERNATION MARK..IDEOGRAPHIC HALF FILL - {0x3040, 0x0, propertyUNASSIGNED}, // - {0x3041, 0x3096, propertyPVALID}, // HIRAGANA LETTER SMALL A..HIRAGANA LETTER SMA - {0x3097, 0x3098, propertyUNASSIGNED}, // .. - {0x3099, 0x309A, propertyPVALID}, // COMBINING KATAKANA-HIRAGANA VOICED SOUND MAR - {0x309B, 0x309C, propertyDISALLOWED}, // KATAKANA-HIRAGANA VOICED SOUND MARK..KATAKAN - {0x309D, 0x309E, propertyPVALID}, // HIRAGANA ITERATION MARK..HIRAGANA VOICED ITE - {0x309F, 0x30A0, propertyDISALLOWED}, // HIRAGANA DIGRAPH YORI..KATAKANA-HIRAGANA DOU - {0x30A1, 0x30FA, propertyPVALID}, // KATAKANA LETTER SMALL A..KATAKANA LETTER VO - {0x30FB, 0x0, propertyCONTEXTO}, // KATAKANA MIDDLE DOT - {0x30FC, 0x30FE, propertyPVALID}, // KATAKANA-HIRAGANA PROLONGED SOUND MARK..KATA - {0x30FF, 0x0, propertyDISALLOWED}, // KATAKANA DIGRAPH KOTO - {0x3100, 0x3104, propertyUNASSIGNED}, // .. - {0x3105, 0x312D, propertyPVALID}, // BOPOMOFO LETTER B..BOPOMOFO LETTER IH - {0x312E, 0x3130, propertyUNASSIGNED}, // .. - {0x3131, 0x318E, propertyDISALLOWED}, // HANGUL LETTER KIYEOK..HANGUL LETTER ARAEAE - {0x318F, 0x0, propertyUNASSIGNED}, // - {0x3190, 0x319F, propertyDISALLOWED}, // IDEOGRAPHIC ANNOTATION LINKING MARK..IDEOGRA - {0x31A0, 0x31B7, propertyPVALID}, // BOPOMOFO LETTER BU..BOPOMOFO FINAL LETTER H - {0x31B8, 0x31BF, propertyUNASSIGNED}, // .. - {0x31C0, 0x31E3, propertyDISALLOWED}, // CJK STROKE T..CJK STROKE Q - {0x31E4, 0x31EF, propertyUNASSIGNED}, // .. - {0x31F0, 0x31FF, propertyPVALID}, // KATAKANA LETTER SMALL KU..KATAKANA LETTER SM - {0x3200, 0x321E, propertyDISALLOWED}, // PARENTHESIZED HANGUL KIYEOK..PARENTHESIZED K - {0x321F, 0x0, propertyUNASSIGNED}, // - {0x3220, 0x32FE, propertyDISALLOWED}, // PARENTHESIZED IDEOGRAPH ONE..CIRCLED KATAKAN - {0x32FF, 0x0, propertyUNASSIGNED}, // - {0x3300, 0x33FF, propertyDISALLOWED}, // SQUARE APAATO..SQUARE GAL - {0x3400, 0x4DB5, propertyPVALID}, // .... - {0x4DC0, 0x4DFF, propertyDISALLOWED}, // HEXAGRAM FOR THE CREATIVE HEAVEN..HEXAGRAM F - {0x4E00, 0x9FCB, propertyPVALID}, // .. - {0x9FCC, 0x9FFF, propertyUNASSIGNED}, // .. - {0xA000, 0xA48C, propertyPVALID}, // YI SYLLABLE IT..YI SYLLABLE YYR - {0xA48D, 0xA48F, propertyUNASSIGNED}, // .. - {0xA490, 0xA4C6, propertyDISALLOWED}, // YI RADICAL QOT..YI RADICAL KE - {0xA4C7, 0xA4CF, propertyUNASSIGNED}, // .. - {0xA4D0, 0xA4FD, propertyPVALID}, // LISU LETTER BA..LISU LETTER TONE MYA JEU - {0xA4FE, 0xA4FF, propertyDISALLOWED}, // LISU PUNCTUATION COMMA..LISU PUNCTUATION FUL - {0xA500, 0xA60C, propertyPVALID}, // VAI SYLLABLE EE..VAI SYLLABLE LENGTHENER - {0xA60D, 0xA60F, propertyDISALLOWED}, // VAI COMMA..VAI QUESTION MARK - {0xA610, 0xA62B, propertyPVALID}, // VAI SYLLABLE NDOLE FA..VAI SYLLABLE NDOLE DO - {0xA62C, 0xA63F, propertyUNASSIGNED}, // .. - {0xA640, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER ZEMLYA - {0xA641, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER ZEMLYA - {0xA642, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER DZELO - {0xA643, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER DZELO - {0xA644, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER REVERSED DZE - {0xA645, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER REVERSED DZE - {0xA646, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER IOTA - {0xA647, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER IOTA - {0xA648, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER DJERV - {0xA649, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER DJERV - {0xA64A, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER MONOGRAPH UK - {0xA64B, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER MONOGRAPH UK - {0xA64C, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER BROAD OMEGA - {0xA64D, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER BROAD OMEGA - {0xA64E, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER NEUTRAL YER - {0xA64F, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER NEUTRAL YER - {0xA650, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER YERU WITH BACK YER - {0xA651, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER YERU WITH BACK YER - {0xA652, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER IOTIFIED YAT - {0xA653, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER IOTIFIED YAT - {0xA654, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER REVERSED YU - {0xA655, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER REVERSED YU - {0xA656, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER IOTIFIED A - {0xA657, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER IOTIFIED A - {0xA658, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER CLOSED LITTLE YUS - {0xA659, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER CLOSED LITTLE YUS - {0xA65A, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER BLENDED YUS - {0xA65B, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER BLENDED YUS - {0xA65C, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER IOTIFIED CLOSED LITT - {0xA65D, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER IOTIFIED CLOSED LITTLE - {0xA65E, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER YN - {0xA65F, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER YN - {0xA660, 0xA661, propertyUNASSIGNED}, // .. - {0xA662, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER SOFT DE - {0xA663, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER SOFT DE - {0xA664, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER SOFT EL - {0xA665, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER SOFT EL - {0xA666, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER SOFT EM - {0xA667, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER SOFT EM - {0xA668, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER MONOCULAR O - {0xA669, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER MONOCULAR O - {0xA66A, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER BINOCULAR O - {0xA66B, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER BINOCULAR O - {0xA66C, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER DOUBLE MONOCULAR O - {0xA66D, 0xA66F, propertyPVALID}, // CYRILLIC SMALL LETTER DOUBLE MONOCULAR O..CO - {0xA670, 0xA673, propertyDISALLOWED}, // COMBINING CYRILLIC TEN MILLIONS SIGN..SLAVON - {0xA674, 0xA67B, propertyUNASSIGNED}, // .. - {0xA67C, 0xA67D, propertyPVALID}, // COMBINING CYRILLIC KAVYKA..COMBINING CYRILLI - {0xA67E, 0x0, propertyDISALLOWED}, // CYRILLIC KAVYKA - {0xA67F, 0x0, propertyPVALID}, // CYRILLIC PAYEROK - {0xA680, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER DWE - {0xA681, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER DWE - {0xA682, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER DZWE - {0xA683, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER DZWE - {0xA684, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER ZHWE - {0xA685, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER ZHWE - {0xA686, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER CCHE - {0xA687, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER CCHE - {0xA688, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER DZZE - {0xA689, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER DZZE - {0xA68A, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER TE WITH MIDDLE HOOK - {0xA68B, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER TE WITH MIDDLE HOOK - {0xA68C, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER TWE - {0xA68D, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER TWE - {0xA68E, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER TSWE - {0xA68F, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER TSWE - {0xA690, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER TSSE - {0xA691, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER TSSE - {0xA692, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER TCHE - {0xA693, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER TCHE - {0xA694, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER HWE - {0xA695, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER HWE - {0xA696, 0x0, propertyDISALLOWED}, // CYRILLIC CAPITAL LETTER SHWE - {0xA697, 0x0, propertyPVALID}, // CYRILLIC SMALL LETTER SHWE - {0xA698, 0xA69F, propertyUNASSIGNED}, // .. - {0xA6A0, 0xA6E5, propertyPVALID}, // BAMUM LETTER A..BAMUM LETTER KI - {0xA6E6, 0xA6EF, propertyDISALLOWED}, // BAMUM LETTER MO..BAMUM LETTER KOGHOM - {0xA6F0, 0xA6F1, propertyPVALID}, // BAMUM COMBINING MARK KOQNDON..BAMUM COMBININ - {0xA6F2, 0xA6F7, propertyDISALLOWED}, // BAMUM NJAEMLI..BAMUM QUESTION MARK - {0xA6F8, 0xA6FF, propertyUNASSIGNED}, // .. - {0xA700, 0xA716, propertyDISALLOWED}, // MODIFIER LETTER CHINESE TONE YIN PING..MODIF - {0xA717, 0xA71F, propertyPVALID}, // MODIFIER LETTER DOT VERTICAL BAR..MODIFIER L - {0xA720, 0xA722, propertyDISALLOWED}, // MODIFIER LETTER STRESS AND HIGH TONE..LATIN - {0xA723, 0x0, propertyPVALID}, // LATIN SMALL LETTER EGYPTOLOGICAL ALEF - {0xA724, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER EGYPTOLOGICAL AIN - {0xA725, 0x0, propertyPVALID}, // LATIN SMALL LETTER EGYPTOLOGICAL AIN - {0xA726, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER HENG - {0xA727, 0x0, propertyPVALID}, // LATIN SMALL LETTER HENG - {0xA728, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER TZ - {0xA729, 0x0, propertyPVALID}, // LATIN SMALL LETTER TZ - {0xA72A, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER TRESILLO - {0xA72B, 0x0, propertyPVALID}, // LATIN SMALL LETTER TRESILLO - {0xA72C, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER CUATRILLO - {0xA72D, 0x0, propertyPVALID}, // LATIN SMALL LETTER CUATRILLO - {0xA72E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER CUATRILLO WITH COMMA - {0xA72F, 0xA731, propertyPVALID}, // LATIN SMALL LETTER CUATRILLO WITH COMMA..LAT - {0xA732, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER AA - {0xA733, 0x0, propertyPVALID}, // LATIN SMALL LETTER AA - {0xA734, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER AO - {0xA735, 0x0, propertyPVALID}, // LATIN SMALL LETTER AO - {0xA736, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER AU - {0xA737, 0x0, propertyPVALID}, // LATIN SMALL LETTER AU - {0xA738, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER AV - {0xA739, 0x0, propertyPVALID}, // LATIN SMALL LETTER AV - {0xA73A, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER AV WITH HORIZONTAL BAR - {0xA73B, 0x0, propertyPVALID}, // LATIN SMALL LETTER AV WITH HORIZONTAL BAR - {0xA73C, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER AY - {0xA73D, 0x0, propertyPVALID}, // LATIN SMALL LETTER AY - {0xA73E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER REVERSED C WITH DOT - {0xA73F, 0x0, propertyPVALID}, // LATIN SMALL LETTER REVERSED C WITH DOT - {0xA740, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER K WITH STROKE - {0xA741, 0x0, propertyPVALID}, // LATIN SMALL LETTER K WITH STROKE - {0xA742, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER K WITH DIAGONAL STROKE - {0xA743, 0x0, propertyPVALID}, // LATIN SMALL LETTER K WITH DIAGONAL STROKE - {0xA744, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER K WITH STROKE AND DIAGO - {0xA745, 0x0, propertyPVALID}, // LATIN SMALL LETTER K WITH STROKE AND DIAGONA - {0xA746, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER BROKEN L - {0xA747, 0x0, propertyPVALID}, // LATIN SMALL LETTER BROKEN L - {0xA748, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER L WITH HIGH STROKE - {0xA749, 0x0, propertyPVALID}, // LATIN SMALL LETTER L WITH HIGH STROKE - {0xA74A, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH LONG STROKE OVER - {0xA74B, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH LONG STROKE OVERLA - {0xA74C, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER O WITH LOOP - {0xA74D, 0x0, propertyPVALID}, // LATIN SMALL LETTER O WITH LOOP - {0xA74E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER OO - {0xA74F, 0x0, propertyPVALID}, // LATIN SMALL LETTER OO - {0xA750, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER P WITH STROKE THROUGH D - {0xA751, 0x0, propertyPVALID}, // LATIN SMALL LETTER P WITH STROKE THROUGH DES - {0xA752, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER P WITH FLOURISH - {0xA753, 0x0, propertyPVALID}, // LATIN SMALL LETTER P WITH FLOURISH - {0xA754, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER P WITH SQUIRREL TAIL - {0xA755, 0x0, propertyPVALID}, // LATIN SMALL LETTER P WITH SQUIRREL TAIL - {0xA756, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER Q WITH STROKE THROUGH D - {0xA757, 0x0, propertyPVALID}, // LATIN SMALL LETTER Q WITH STROKE THROUGH DES - {0xA758, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER Q WITH DIAGONAL STROKE - {0xA759, 0x0, propertyPVALID}, // LATIN SMALL LETTER Q WITH DIAGONAL STROKE - {0xA75A, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER R ROTUNDA - {0xA75B, 0x0, propertyPVALID}, // LATIN SMALL LETTER R ROTUNDA - {0xA75C, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER RUM ROTUNDA - {0xA75D, 0x0, propertyPVALID}, // LATIN SMALL LETTER RUM ROTUNDA - {0xA75E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER V WITH DIAGONAL STROKE - {0xA75F, 0x0, propertyPVALID}, // LATIN SMALL LETTER V WITH DIAGONAL STROKE - {0xA760, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER VY - {0xA761, 0x0, propertyPVALID}, // LATIN SMALL LETTER VY - {0xA762, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER VISIGOTHIC Z - {0xA763, 0x0, propertyPVALID}, // LATIN SMALL LETTER VISIGOTHIC Z - {0xA764, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER THORN WITH STROKE - {0xA765, 0x0, propertyPVALID}, // LATIN SMALL LETTER THORN WITH STROKE - {0xA766, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER THORN WITH STROKE THROU - {0xA767, 0x0, propertyPVALID}, // LATIN SMALL LETTER THORN WITH STROKE THROUGH - {0xA768, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER VEND - {0xA769, 0x0, propertyPVALID}, // LATIN SMALL LETTER VEND - {0xA76A, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER ET - {0xA76B, 0x0, propertyPVALID}, // LATIN SMALL LETTER ET - {0xA76C, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER IS - {0xA76D, 0x0, propertyPVALID}, // LATIN SMALL LETTER IS - {0xA76E, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER CON - {0xA76F, 0x0, propertyPVALID}, // LATIN SMALL LETTER CON - {0xA770, 0x0, propertyDISALLOWED}, // MODIFIER LETTER US - {0xA771, 0xA778, propertyPVALID}, // LATIN SMALL LETTER DUM..LATIN SMALL LETTER U - {0xA779, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER INSULAR D - {0xA77A, 0x0, propertyPVALID}, // LATIN SMALL LETTER INSULAR D - {0xA77B, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER INSULAR F - {0xA77C, 0x0, propertyPVALID}, // LATIN SMALL LETTER INSULAR F - {0xA77D, 0xA77E, propertyDISALLOWED}, // LATIN CAPITAL LETTER INSULAR G..LATIN CAPITA - {0xA77F, 0x0, propertyPVALID}, // LATIN SMALL LETTER TURNED INSULAR G - {0xA780, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER TURNED L - {0xA781, 0x0, propertyPVALID}, // LATIN SMALL LETTER TURNED L - {0xA782, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER INSULAR R - {0xA783, 0x0, propertyPVALID}, // LATIN SMALL LETTER INSULAR R - {0xA784, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER INSULAR S - {0xA785, 0x0, propertyPVALID}, // LATIN SMALL LETTER INSULAR S - {0xA786, 0x0, propertyDISALLOWED}, // LATIN CAPITAL LETTER INSULAR T - {0xA787, 0xA788, propertyPVALID}, // LATIN SMALL LETTER INSULAR T..MODIFIER LETTE - {0xA789, 0xA78B, propertyDISALLOWED}, // MODIFIER LETTER COLON..LATIN CAPITAL LETTER - {0xA78C, 0x0, propertyPVALID}, // LATIN SMALL LETTER SALTILLO - {0xA78D, 0xA7FA, propertyUNASSIGNED}, // .. - {0xA7FB, 0xA827, propertyPVALID}, // LATIN EPIGRAPHIC LETTER REVERSED F..SYLOTI N - {0xA828, 0xA82B, propertyDISALLOWED}, // SYLOTI NAGRI POETRY MARK-1..SYLOTI NAGRI POE - {0xA82C, 0xA82F, propertyUNASSIGNED}, // .. - {0xA830, 0xA839, propertyDISALLOWED}, // NORTH INDIC FRACTION ONE QUARTER..NORTH INDI - {0xA83A, 0xA83F, propertyUNASSIGNED}, // .. - {0xA840, 0xA873, propertyPVALID}, // PHAGS-PA LETTER KA..PHAGS-PA LETTER CANDRABI - {0xA874, 0xA877, propertyDISALLOWED}, // PHAGS-PA SINGLE HEAD MARK..PHAGS-PA MARK DOU - {0xA878, 0xA87F, propertyUNASSIGNED}, // .. - {0xA880, 0xA8C4, propertyPVALID}, // SAURASHTRA SIGN ANUSVARA..SAURASHTRA SIGN VI - {0xA8C5, 0xA8CD, propertyUNASSIGNED}, // .. - {0xA8CE, 0xA8CF, propertyDISALLOWED}, // SAURASHTRA DANDA..SAURASHTRA DOUBLE DANDA - {0xA8D0, 0xA8D9, propertyPVALID}, // SAURASHTRA DIGIT ZERO..SAURASHTRA DIGIT NINE - {0xA8DA, 0xA8DF, propertyUNASSIGNED}, // .. - {0xA8E0, 0xA8F7, propertyPVALID}, // COMBINING DEVANAGARI DIGIT ZERO..DEVANAGARI - {0xA8F8, 0xA8FA, propertyDISALLOWED}, // DEVANAGARI SIGN PUSHPIKA..DEVANAGARI CARET - {0xA8FB, 0x0, propertyPVALID}, // DEVANAGARI HEADSTROKE - {0xA8FC, 0xA8FF, propertyUNASSIGNED}, // .. - {0xA900, 0xA92D, propertyPVALID}, // KAYAH LI DIGIT ZERO..KAYAH LI TONE CALYA PLO - {0xA92E, 0xA92F, propertyDISALLOWED}, // KAYAH LI SIGN CWI..KAYAH LI SIGN SHYA - {0xA930, 0xA953, propertyPVALID}, // REJANG LETTER KA..REJANG VIRAMA - {0xA954, 0xA95E, propertyUNASSIGNED}, // .. - {0xA95F, 0xA97C, propertyDISALLOWED}, // REJANG SECTION MARK..HANGUL CHOSEONG SSANGYE - {0xA97D, 0xA97F, propertyUNASSIGNED}, // .. - {0xA980, 0xA9C0, propertyPVALID}, // JAVANESE SIGN PANYANGGA..JAVANESE PANGKON - {0xA9C1, 0xA9CD, propertyDISALLOWED}, // JAVANESE LEFT RERENGGAN..JAVANESE TURNED PAD - {0xA9CE, 0x0, propertyUNASSIGNED}, // - {0xA9CF, 0xA9D9, propertyPVALID}, // JAVANESE PANGRANGKEP..JAVANESE DIGIT NINE - {0xA9DA, 0xA9DD, propertyUNASSIGNED}, // .. - {0xA9DE, 0xA9DF, propertyDISALLOWED}, // JAVANESE PADA TIRTA TUMETES..JAVANESE PADA I - {0xA9E0, 0xA9FF, propertyUNASSIGNED}, // .. - {0xAA00, 0xAA36, propertyPVALID}, // CHAM LETTER A..CHAM CONSONANT SIGN WA - {0xAA37, 0xAA3F, propertyUNASSIGNED}, // .. - {0xAA40, 0xAA4D, propertyPVALID}, // CHAM LETTER FINAL K..CHAM CONSONANT SIGN FIN - {0xAA4E, 0xAA4F, propertyUNASSIGNED}, // .. - {0xAA50, 0xAA59, propertyPVALID}, // CHAM DIGIT ZERO..CHAM DIGIT NINE - {0xAA5A, 0xAA5B, propertyUNASSIGNED}, // .. - {0xAA5C, 0xAA5F, propertyDISALLOWED}, // CHAM PUNCTUATION SPIRAL..CHAM PUNCTUATION TR - {0xAA60, 0xAA76, propertyPVALID}, // MYANMAR LETTER KHAMTI GA..MYANMAR LOGOGRAM K - {0xAA77, 0xAA79, propertyDISALLOWED}, // MYANMAR SYMBOL AITON EXCLAMATION..MYANMAR SY - {0xAA7A, 0xAA7B, propertyPVALID}, // MYANMAR LETTER AITON RA..MYANMAR SIGN PAO KA - {0xAA7C, 0xAA7F, propertyUNASSIGNED}, // .. - {0xAA80, 0xAAC2, propertyPVALID}, // TAI VIET LETTER LOW KO..TAI VIET TONE MAI SO - {0xAAC3, 0xAADA, propertyUNASSIGNED}, // .. - {0xAADB, 0xAADD, propertyPVALID}, // TAI VIET SYMBOL KON..TAI VIET SYMBOL SAM - {0xAADE, 0xAADF, propertyDISALLOWED}, // TAI VIET SYMBOL HO HOI..TAI VIET SYMBOL KOI - {0xAAE0, 0xABBF, propertyUNASSIGNED}, // .. - {0xABC0, 0xABEA, propertyPVALID}, // MEETEI MAYEK LETTER KOK..MEETEI MAYEK VOWEL - {0xABEB, 0x0, propertyDISALLOWED}, // MEETEI MAYEK CHEIKHEI - {0xABEC, 0xABED, propertyPVALID}, // MEETEI MAYEK LUM IYEK..MEETEI MAYEK APUN IYE - {0xABEE, 0xABEF, propertyUNASSIGNED}, // .. - {0xABF0, 0xABF9, propertyPVALID}, // MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DIGIT - {0xABFA, 0xABFF, propertyUNASSIGNED}, // .. - {0xAC00, 0xD7A3, propertyPVALID}, // .. - {0xD7A4, 0xD7AF, propertyUNASSIGNED}, // .. - {0xD7B0, 0xD7C6, propertyDISALLOWED}, // HANGUL JUNGSEONG O-YEO..HANGUL JUNGSEONG ARA - {0xD7C7, 0xD7CA, propertyUNASSIGNED}, // .. - {0xD7CB, 0xD7FB, propertyDISALLOWED}, // HANGUL JONGSEONG NIEUN-RIEUL..HANGUL JONGSEO - {0xD7FC, 0xD7FF, propertyUNASSIGNED}, // .. - {0xD800, 0xFA0D, propertyDISALLOWED}, // ..CJK COMPAT - {0xFA0E, 0xFA0F, propertyPVALID}, // CJK COMPATIBILITY IDEOGRAPH-FA0E..CJK COMPAT - {0xFA10, 0x0, propertyDISALLOWED}, // CJK COMPATIBILITY IDEOGRAPH-FA10 - {0xFA11, 0x0, propertyPVALID}, // CJK COMPATIBILITY IDEOGRAPH-FA11 - {0xFA12, 0x0, propertyDISALLOWED}, // CJK COMPATIBILITY IDEOGRAPH-FA12 - {0xFA13, 0xFA14, propertyPVALID}, // CJK COMPATIBILITY IDEOGRAPH-FA13..CJK COMPAT - {0xFA15, 0xFA1E, propertyDISALLOWED}, // CJK COMPATIBILITY IDEOGRAPH-FA15..CJK COMPAT - {0xFA1F, 0x0, propertyPVALID}, // CJK COMPATIBILITY IDEOGRAPH-FA1F - {0xFA20, 0x0, propertyDISALLOWED}, // CJK COMPATIBILITY IDEOGRAPH-FA20 - {0xFA21, 0x0, propertyPVALID}, // CJK COMPATIBILITY IDEOGRAPH-FA21 - {0xFA22, 0x0, propertyDISALLOWED}, // CJK COMPATIBILITY IDEOGRAPH-FA22 - {0xFA23, 0xFA24, propertyPVALID}, // CJK COMPATIBILITY IDEOGRAPH-FA23..CJK COMPAT - {0xFA25, 0xFA26, propertyDISALLOWED}, // CJK COMPATIBILITY IDEOGRAPH-FA25..CJK COMPAT - {0xFA27, 0xFA29, propertyPVALID}, // CJK COMPATIBILITY IDEOGRAPH-FA27..CJK COMPAT - {0xFA2A, 0xFA2D, propertyDISALLOWED}, // CJK COMPATIBILITY IDEOGRAPH-FA2A..CJK COMPAT - {0xFA2E, 0xFA2F, propertyUNASSIGNED}, // .. - {0xFA30, 0xFA6D, propertyDISALLOWED}, // CJK COMPATIBILITY IDEOGRAPH-FA30..CJK COMPAT - {0xFA6E, 0xFA6F, propertyUNASSIGNED}, // .. - {0xFA70, 0xFAD9, propertyDISALLOWED}, // CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPAT - {0xFADA, 0xFAFF, propertyUNASSIGNED}, // .. - {0xFB00, 0xFB06, propertyDISALLOWED}, // LATIN SMALL LIGATURE FF..LATIN SMALL LIGATUR - {0xFB07, 0xFB12, propertyUNASSIGNED}, // .. - {0xFB13, 0xFB17, propertyDISALLOWED}, // ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SM - {0xFB18, 0xFB1C, propertyUNASSIGNED}, // .. - {0xFB1D, 0x0, propertyDISALLOWED}, // HEBREW LETTER YOD WITH HIRIQ - {0xFB1E, 0x0, propertyPVALID}, // HEBREW POINT JUDEO-SPANISH VARIKA - {0xFB1F, 0xFB36, propertyDISALLOWED}, // HEBREW LIGATURE YIDDISH YOD YOD PATAH..HEBRE - {0xFB37, 0x0, propertyUNASSIGNED}, // - {0xFB38, 0xFB3C, propertyDISALLOWED}, // HEBREW LETTER TET WITH DAGESH..HEBREW LETTER - {0xFB3D, 0x0, propertyUNASSIGNED}, // - {0xFB3E, 0x0, propertyDISALLOWED}, // HEBREW LETTER MEM WITH DAGESH - {0xFB3F, 0x0, propertyUNASSIGNED}, // - {0xFB40, 0xFB41, propertyDISALLOWED}, // HEBREW LETTER NUN WITH DAGESH..HEBREW LETTER - {0xFB42, 0x0, propertyUNASSIGNED}, // - {0xFB43, 0xFB44, propertyDISALLOWED}, // HEBREW LETTER FINAL PE WITH DAGESH..HEBREW L - {0xFB45, 0x0, propertyUNASSIGNED}, // - {0xFB46, 0xFBB1, propertyDISALLOWED}, // HEBREW LETTER TSADI WITH DAGESH..ARABIC LETT - {0xFBB2, 0xFBD2, propertyUNASSIGNED}, // .. - {0xFBD3, 0xFD3F, propertyDISALLOWED}, // ARABIC LETTER NG ISOLATED FORM..ORNATE RIGHT - {0xFD40, 0xFD4F, propertyUNASSIGNED}, // .. - {0xFD50, 0xFD8F, propertyDISALLOWED}, // ARABIC LIGATURE TEH WITH JEEM WITH MEEM INIT - {0xFD90, 0xFD91, propertyUNASSIGNED}, // .. - {0xFD92, 0xFDC7, propertyDISALLOWED}, // ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INI - {0xFDC8, 0xFDCF, propertyUNASSIGNED}, // .. - {0xFDD0, 0xFDFD, propertyDISALLOWED}, // ..ARABIC LIGATURE BISMILLAH AR - {0xFDFE, 0xFDFF, propertyUNASSIGNED}, // .. - {0xFE00, 0xFE19, propertyDISALLOWED}, // VARIATION SELECTOR-1..PRESENTATION FORM FOR - {0xFE1A, 0xFE1F, propertyUNASSIGNED}, // .. - {0xFE20, 0xFE26, propertyPVALID}, // COMBINING LIGATURE LEFT HALF..COMBINING CONJ - {0xFE27, 0xFE2F, propertyUNASSIGNED}, // .. - {0xFE30, 0xFE52, propertyDISALLOWED}, // PRESENTATION FORM FOR VERTICAL TWO DOT LEADE - {0xFE53, 0x0, propertyUNASSIGNED}, // - {0xFE54, 0xFE66, propertyDISALLOWED}, // SMALL SEMICOLON..SMALL EQUALS SIGN - {0xFE67, 0x0, propertyUNASSIGNED}, // - {0xFE68, 0xFE6B, propertyDISALLOWED}, // SMALL REVERSE SOLIDUS..SMALL COMMERCIAL AT - {0xFE6C, 0xFE6F, propertyUNASSIGNED}, // .. - {0xFE70, 0xFE72, propertyDISALLOWED}, // ARABIC FATHATAN ISOLATED FORM..ARABIC DAMMAT - {0xFE73, 0x0, propertyPVALID}, // ARABIC TAIL FRAGMENT - {0xFE74, 0x0, propertyDISALLOWED}, // ARABIC KASRATAN ISOLATED FORM - {0xFE75, 0x0, propertyUNASSIGNED}, // - {0xFE76, 0xFEFC, propertyDISALLOWED}, // ARABIC FATHA ISOLATED FORM..ARABIC LIGATURE - {0xFEFD, 0xFEFE, propertyUNASSIGNED}, // .. - {0xFEFF, 0x0, propertyDISALLOWED}, // ZERO WIDTH NO-BREAK SPACE - {0xFF00, 0x0, propertyUNASSIGNED}, // - {0xFF01, 0xFFBE, propertyDISALLOWED}, // FULLWIDTH EXCLAMATION MARK..HALFWIDTH HANGUL - {0xFFBF, 0xFFC1, propertyUNASSIGNED}, // .. - {0xFFC2, 0xFFC7, propertyDISALLOWED}, // HALFWIDTH HANGUL LETTER A..HALFWIDTH HANGUL - {0xFFC8, 0xFFC9, propertyUNASSIGNED}, // .. - {0xFFCA, 0xFFCF, propertyDISALLOWED}, // HALFWIDTH HANGUL LETTER YEO..HALFWIDTH HANGU - {0xFFD0, 0xFFD1, propertyUNASSIGNED}, // .. - {0xFFD2, 0xFFD7, propertyDISALLOWED}, // HALFWIDTH HANGUL LETTER YO..HALFWIDTH HANGUL - {0xFFD8, 0xFFD9, propertyUNASSIGNED}, // .. - {0xFFDA, 0xFFDC, propertyDISALLOWED}, // HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL - {0xFFDD, 0xFFDF, propertyUNASSIGNED}, // .. - {0xFFE0, 0xFFE6, propertyDISALLOWED}, // FULLWIDTH CENT SIGN..FULLWIDTH WON SIGN - {0xFFE7, 0x0, propertyUNASSIGNED}, // - {0xFFE8, 0xFFEE, propertyDISALLOWED}, // HALFWIDTH FORMS LIGHT VERTICAL..HALFWIDTH WH - {0xFFEF, 0xFFF8, propertyUNASSIGNED}, // .. - {0xFFF9, 0xFFFF, propertyDISALLOWED}, // INTERLINEAR ANNOTATION ANCHOR.. - {0x1000D, 0x10026, propertyPVALID}, // LINEAR B SYLLABLE B036 JO..LINEAR B SYLLABLE - {0x10027, 0x0, propertyUNASSIGNED}, // - {0x10028, 0x1003A, propertyPVALID}, // LINEAR B SYLLABLE B060 RA..LINEAR B SYLLABLE - {0x1003B, 0x0, propertyUNASSIGNED}, // - {0x1003C, 0x1003D, propertyPVALID}, // LINEAR B SYLLABLE B017 ZA..LINEAR B SYLLABLE - {0x1003E, 0x0, propertyUNASSIGNED}, // - {0x1003F, 0x1004D, propertyPVALID}, // LINEAR B SYLLABLE B020 ZO..LINEAR B SYLLABLE - {0x1004E, 0x1004F, propertyUNASSIGNED}, // .. - {0x10050, 0x1005D, propertyPVALID}, // LINEAR B SYMBOL B018..LINEAR B SYMBOL B089 - {0x1005E, 0x1007F, propertyUNASSIGNED}, // .. - {0x10080, 0x100FA, propertyPVALID}, // LINEAR B IDEOGRAM B100 MAN..LINEAR B IDEOGRA - {0x100FB, 0x100FF, propertyUNASSIGNED}, // .. - {0x10100, 0x10102, propertyDISALLOWED}, // AEGEAN WORD SEPARATOR LINE..AEGEAN CHECK MAR - {0x10103, 0x10106, propertyUNASSIGNED}, // .. - {0x10107, 0x10133, propertyDISALLOWED}, // AEGEAN NUMBER ONE..AEGEAN NUMBER NINETY THOU - {0x10134, 0x10136, propertyUNASSIGNED}, // .. - {0x10137, 0x1018A, propertyDISALLOWED}, // AEGEAN WEIGHT BASE UNIT..GREEK ZERO SIGN - {0x1018B, 0x1018F, propertyUNASSIGNED}, // .. - {0x10190, 0x1019B, propertyDISALLOWED}, // ROMAN SEXTANS SIGN..ROMAN CENTURIAL SIGN - {0x1019C, 0x101CF, propertyUNASSIGNED}, // .. - {0x101D0, 0x101FC, propertyDISALLOWED}, // PHAISTOS DISC SIGN PEDESTRIAN..PHAISTOS DISC - {0x101FD, 0x0, propertyPVALID}, // PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE - {0x101FE, 0x1027F, propertyUNASSIGNED}, // .. - {0x10280, 0x1029C, propertyPVALID}, // LYCIAN LETTER A..LYCIAN LETTER X - {0x1029D, 0x1029F, propertyUNASSIGNED}, // .. - {0x102A0, 0x102D0, propertyPVALID}, // CARIAN LETTER A..CARIAN LETTER UUU3 - {0x102D1, 0x102FF, propertyUNASSIGNED}, // .. - {0x10300, 0x1031E, propertyPVALID}, // OLD ITALIC LETTER A..OLD ITALIC LETTER UU - {0x1031F, 0x0, propertyUNASSIGNED}, // - {0x10320, 0x10323, propertyDISALLOWED}, // OLD ITALIC NUMERAL ONE..OLD ITALIC NUMERAL F - {0x10324, 0x1032F, propertyUNASSIGNED}, // .. - {0x10330, 0x10340, propertyPVALID}, // GOTHIC LETTER AHSA..GOTHIC LETTER PAIRTHRA - {0x10341, 0x0, propertyDISALLOWED}, // GOTHIC LETTER NINETY - {0x10342, 0x10349, propertyPVALID}, // GOTHIC LETTER RAIDA..GOTHIC LETTER OTHAL - {0x1034A, 0x0, propertyDISALLOWED}, // GOTHIC LETTER NINE HUNDRED - {0x1034B, 0x1037F, propertyUNASSIGNED}, // .. - {0x10380, 0x1039D, propertyPVALID}, // UGARITIC LETTER ALPA..UGARITIC LETTER SSU - {0x1039E, 0x0, propertyUNASSIGNED}, // - {0x1039F, 0x0, propertyDISALLOWED}, // UGARITIC WORD DIVIDER - {0x103A0, 0x103C3, propertyPVALID}, // OLD PERSIAN SIGN A..OLD PERSIAN SIGN HA - {0x103C4, 0x103C7, propertyUNASSIGNED}, // .. - {0x103C8, 0x103CF, propertyPVALID}, // OLD PERSIAN SIGN AURAMAZDAA..OLD PERSIAN SIG - {0x103D0, 0x103D5, propertyDISALLOWED}, // OLD PERSIAN WORD DIVIDER..OLD PERSIAN NUMBER - {0x103D6, 0x103FF, propertyUNASSIGNED}, // .. - {0x10400, 0x10427, propertyDISALLOWED}, // DESERET CAPITAL LETTER LONG I..DESERET CAPIT - {0x10428, 0x1049D, propertyPVALID}, // DESERET SMALL LETTER LONG I..OSMANYA LETTER - {0x1049E, 0x1049F, propertyUNASSIGNED}, // .. - {0x104A0, 0x104A9, propertyPVALID}, // OSMANYA DIGIT ZERO..OSMANYA DIGIT NINE - {0x104AA, 0x107FF, propertyUNASSIGNED}, // .. - {0x10800, 0x10805, propertyPVALID}, // CYPRIOT SYLLABLE A..CYPRIOT SYLLABLE JA - {0x10806, 0x10807, propertyUNASSIGNED}, // .. - {0x10808, 0x0, propertyPVALID}, // CYPRIOT SYLLABLE JO - {0x10809, 0x0, propertyUNASSIGNED}, // - {0x1080A, 0x10835, propertyPVALID}, // CYPRIOT SYLLABLE KA..CYPRIOT SYLLABLE WO - {0x10836, 0x0, propertyUNASSIGNED}, // - {0x10837, 0x10838, propertyPVALID}, // CYPRIOT SYLLABLE XA..CYPRIOT SYLLABLE XE - {0x10839, 0x1083B, propertyUNASSIGNED}, // .. - {0x1083C, 0x0, propertyPVALID}, // CYPRIOT SYLLABLE ZA - {0x1083D, 0x1083E, propertyUNASSIGNED}, // .. - {0x1083F, 0x10855, propertyPVALID}, // CYPRIOT SYLLABLE ZO..IMPERIAL ARAMAIC LETTER - {0x10856, 0x0, propertyUNASSIGNED}, // - {0x10857, 0x1085F, propertyDISALLOWED}, // IMPERIAL ARAMAIC SECTION SIGN..IMPERIAL ARAM - {0x10860, 0x108FF, propertyUNASSIGNED}, // .. - {0x10900, 0x10915, propertyPVALID}, // PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU - {0x10916, 0x1091B, propertyDISALLOWED}, // PHOENICIAN NUMBER ONE..PHOENICIAN NUMBER THR - {0x1091C, 0x1091E, propertyUNASSIGNED}, // .. - {0x1091F, 0x0, propertyDISALLOWED}, // PHOENICIAN WORD SEPARATOR - {0x10920, 0x10939, propertyPVALID}, // LYDIAN LETTER A..LYDIAN LETTER C - {0x1093A, 0x1093E, propertyUNASSIGNED}, // .. - {0x1093F, 0x0, propertyDISALLOWED}, // LYDIAN TRIANGULAR MARK - {0x10940, 0x109FF, propertyUNASSIGNED}, // .. - {0x10A00, 0x10A03, propertyPVALID}, // KHAROSHTHI LETTER A..KHAROSHTHI VOWEL SIGN V - {0x10A04, 0x0, propertyUNASSIGNED}, // - {0x10A05, 0x10A06, propertyPVALID}, // KHAROSHTHI VOWEL SIGN E..KHAROSHTHI VOWEL SI - {0x10A07, 0x10A0B, propertyUNASSIGNED}, // .. - {0x10A0C, 0x10A13, propertyPVALID}, // KHAROSHTHI VOWEL LENGTH MARK..KHAROSHTHI LET - {0x10A14, 0x0, propertyUNASSIGNED}, // - {0x10A15, 0x10A17, propertyPVALID}, // KHAROSHTHI LETTER CA..KHAROSHTHI LETTER JA - {0x10A18, 0x0, propertyUNASSIGNED}, // - {0x10A19, 0x10A33, propertyPVALID}, // KHAROSHTHI LETTER NYA..KHAROSHTHI LETTER TTT - {0x10A34, 0x10A37, propertyUNASSIGNED}, // .. - {0x10A38, 0x10A3A, propertyPVALID}, // KHAROSHTHI SIGN BAR ABOVE..KHAROSHTHI SIGN D - {0x10A3B, 0x10A3E, propertyUNASSIGNED}, // .. - {0x10A3F, 0x0, propertyPVALID}, // KHAROSHTHI VIRAMA - {0x10A40, 0x10A47, propertyDISALLOWED}, // KHAROSHTHI DIGIT ONE..KHAROSHTHI NUMBER ONE - {0x10A48, 0x10A4F, propertyUNASSIGNED}, // .. - {0x10A50, 0x10A58, propertyDISALLOWED}, // KHAROSHTHI PUNCTUATION DOT..KHAROSHTHI PUNCT - {0x10A59, 0x10A5F, propertyUNASSIGNED}, // .. - {0x10A60, 0x10A7C, propertyPVALID}, // OLD SOUTH ARABIAN LETTER HE..OLD SOUTH ARABI - {0x10A7D, 0x10A7F, propertyDISALLOWED}, // OLD SOUTH ARABIAN NUMBER ONE..OLD SOUTH ARAB - {0x10A80, 0x10AFF, propertyUNASSIGNED}, // .. - {0x10B00, 0x10B35, propertyPVALID}, // AVESTAN LETTER A..AVESTAN LETTER HE - {0x10B36, 0x10B38, propertyUNASSIGNED}, // .. - {0x10B39, 0x10B3F, propertyDISALLOWED}, // AVESTAN ABBREVIATION MARK..LARGE ONE RING OV - {0x10B40, 0x10B55, propertyPVALID}, // INSCRIPTIONAL PARTHIAN LETTER ALEPH..INSCRIP - {0x10B56, 0x10B57, propertyUNASSIGNED}, // .. - {0x10B58, 0x10B5F, propertyDISALLOWED}, // INSCRIPTIONAL PARTHIAN NUMBER ONE..INSCRIPTI - {0x10B60, 0x10B72, propertyPVALID}, // INSCRIPTIONAL PAHLAVI LETTER ALEPH..INSCRIPT - {0x10B73, 0x10B77, propertyUNASSIGNED}, // .. - {0x10B78, 0x10B7F, propertyDISALLOWED}, // INSCRIPTIONAL PAHLAVI NUMBER ONE..INSCRIPTIO - {0x10B80, 0x10BFF, propertyUNASSIGNED}, // .. - {0x10C00, 0x10C48, propertyPVALID}, // OLD TURKIC LETTER ORKHON A..OLD TURKIC LETTE - {0x10C49, 0x10E5F, propertyUNASSIGNED}, // .. - {0x10E60, 0x10E7E, propertyDISALLOWED}, // RUMI DIGIT ONE..RUMI FRACTION TWO THIRDS - {0x10E7F, 0x1107F, propertyUNASSIGNED}, // .. - {0x11080, 0x110BA, propertyPVALID}, // KAITHI SIGN CANDRABINDU..KAITHI SIGN NUKTA - {0x110BB, 0x110C1, propertyDISALLOWED}, // KAITHI ABBREVIATION SIGN..KAITHI DOUBLE DAND - {0x110C2, 0x11FFF, propertyUNASSIGNED}, // .. - {0x12000, 0x1236E, propertyPVALID}, // CUNEIFORM SIGN A..CUNEIFORM SIGN ZUM - {0x1236F, 0x123FF, propertyUNASSIGNED}, // .. - {0x12400, 0x12462, propertyDISALLOWED}, // CUNEIFORM NUMERIC SIGN TWO ASH..CUNEIFORM NU - {0x12463, 0x1246F, propertyUNASSIGNED}, // .. - {0x12470, 0x12473, propertyDISALLOWED}, // CUNEIFORM PUNCTUATION SIGN OLD ASSYRIAN WORD - {0x12474, 0x12FFF, propertyUNASSIGNED}, // .. - {0x13000, 0x1342E, propertyPVALID}, // EGYPTIAN HIEROGLYPH A001..EGYPTIAN HIEROGLYP - {0x1342F, 0x1CFFF, propertyUNASSIGNED}, // .. - {0x1D000, 0x1D0F5, propertyDISALLOWED}, // BYZANTINE MUSICAL SYMBOL PSILI..BYZANTINE MU - {0x1D0F6, 0x1D0FF, propertyUNASSIGNED}, // .. - {0x1D100, 0x1D126, propertyDISALLOWED}, // MUSICAL SYMBOL SINGLE BARLINE..MUSICAL SYMBO - {0x1D127, 0x1D128, propertyUNASSIGNED}, // .. - {0x1D129, 0x1D1DD, propertyDISALLOWED}, // MUSICAL SYMBOL MULTIPLE MEASURE REST..MUSICA - {0x1D1DE, 0x1D1FF, propertyUNASSIGNED}, // .. - {0x1D200, 0x1D245, propertyDISALLOWED}, // GREEK VOCAL NOTATION SYMBOL-1..GREEK MUSICAL - {0x1D246, 0x1D2FF, propertyUNASSIGNED}, // .. - {0x1D300, 0x1D356, propertyDISALLOWED}, // MONOGRAM FOR EARTH..TETRAGRAM FOR FOSTERING - {0x1D357, 0x1D35F, propertyUNASSIGNED}, // .. - {0x1D360, 0x1D371, propertyDISALLOWED}, // COUNTING ROD UNIT DIGIT ONE..COUNTING ROD TE - {0x1D372, 0x1D3FF, propertyUNASSIGNED}, // .. - {0x1D400, 0x1D454, propertyDISALLOWED}, // MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL IT - {0x1D455, 0x0, propertyUNASSIGNED}, // - {0x1D456, 0x1D49C, propertyDISALLOWED}, // MATHEMATICAL ITALIC SMALL I..MATHEMATICAL SC - {0x1D49D, 0x0, propertyUNASSIGNED}, // - {0x1D49E, 0x1D49F, propertyDISALLOWED}, // MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL - {0x1D4A0, 0x1D4A1, propertyUNASSIGNED}, // .. - {0x1D4A2, 0x0, propertyDISALLOWED}, // MATHEMATICAL SCRIPT CAPITAL G - {0x1D4A3, 0x1D4A4, propertyUNASSIGNED}, // .. - {0x1D4A5, 0x1D4A6, propertyDISALLOWED}, // MATHEMATICAL SCRIPT CAPITAL J..MATHEMATICAL - {0x1D4A7, 0x1D4A8, propertyUNASSIGNED}, // .. - {0x1D4A9, 0x1D4AC, propertyDISALLOWED}, // MATHEMATICAL SCRIPT CAPITAL N..MATHEMATICAL - {0x1D4AD, 0x0, propertyUNASSIGNED}, // - {0x1D4AE, 0x1D4B9, propertyDISALLOWED}, // MATHEMATICAL SCRIPT CAPITAL S..MATHEMATICAL - {0x1D4BA, 0x0, propertyUNASSIGNED}, // - {0x1D4BB, 0x0, propertyDISALLOWED}, // MATHEMATICAL SCRIPT SMALL F - {0x1D4BC, 0x0, propertyUNASSIGNED}, // - {0x1D4BD, 0x1D4C3, propertyDISALLOWED}, // MATHEMATICAL SCRIPT SMALL H..MATHEMATICAL SC - {0x1D4C4, 0x0, propertyUNASSIGNED}, // - {0x1D4C5, 0x1D505, propertyDISALLOWED}, // MATHEMATICAL SCRIPT SMALL P..MATHEMATICAL FR - {0x1D506, 0x0, propertyUNASSIGNED}, // - {0x1D507, 0x1D50A, propertyDISALLOWED}, // MATHEMATICAL FRAKTUR CAPITAL D..MATHEMATICAL - {0x1D50B, 0x1D50C, propertyUNASSIGNED}, // .. - {0x1D50D, 0x1D514, propertyDISALLOWED}, // MATHEMATICAL FRAKTUR CAPITAL J..MATHEMATICAL - {0x1D515, 0x0, propertyUNASSIGNED}, // - {0x1D516, 0x1D51C, propertyDISALLOWED}, // MATHEMATICAL FRAKTUR CAPITAL S..MATHEMATICAL - {0x1D51D, 0x0, propertyUNASSIGNED}, // - {0x1D51E, 0x1D539, propertyDISALLOWED}, // MATHEMATICAL FRAKTUR SMALL A..MATHEMATICAL D - {0x1D53A, 0x0, propertyUNASSIGNED}, // - {0x1D53B, 0x1D53E, propertyDISALLOWED}, // MATHEMATICAL DOUBLE-STRUCK CAPITAL D..MATHEM - {0x1D53F, 0x0, propertyUNASSIGNED}, // - {0x1D540, 0x1D544, propertyDISALLOWED}, // MATHEMATICAL DOUBLE-STRUCK CAPITAL I..MATHEM - {0x1D545, 0x0, propertyUNASSIGNED}, // - {0x1D546, 0x0, propertyDISALLOWED}, // MATHEMATICAL DOUBLE-STRUCK CAPITAL O - {0x1D547, 0x1D549, propertyUNASSIGNED}, // .. - {0x1D54A, 0x1D550, propertyDISALLOWED}, // MATHEMATICAL DOUBLE-STRUCK CAPITAL S..MATHEM - {0x1D551, 0x0, propertyUNASSIGNED}, // - {0x1D552, 0x1D6A5, propertyDISALLOWED}, // MATHEMATICAL DOUBLE-STRUCK SMALL A..MATHEMAT - {0x1D6A6, 0x1D6A7, propertyUNASSIGNED}, // .. - {0x1D6A8, 0x1D7CB, propertyDISALLOWED}, // MATHEMATICAL BOLD CAPITAL ALPHA..MATHEMATICA - {0x1D7CC, 0x1D7CD, propertyUNASSIGNED}, // .. - {0x1D7CE, 0x1D7FF, propertyDISALLOWED}, // MATHEMATICAL BOLD DIGIT ZERO..MATHEMATICAL M - {0x1D800, 0x1EFFF, propertyUNASSIGNED}, // .. - {0x1F000, 0x1F02B, propertyDISALLOWED}, // MAHJONG TILE EAST WIND..MAHJONG TILE BACK - {0x1F02C, 0x1F02F, propertyUNASSIGNED}, // .. - {0x1F030, 0x1F093, propertyDISALLOWED}, // DOMINO TILE HORIZONTAL BACK..DOMINO TILE VER - {0x1F094, 0x1F0FF, propertyUNASSIGNED}, // .. - {0x1F100, 0x1F10A, propertyDISALLOWED}, // DIGIT ZERO FULL STOP..DIGIT NINE COMMA - {0x1F10B, 0x1F10F, propertyUNASSIGNED}, // .. - {0x1F110, 0x1F12E, propertyDISALLOWED}, // PARENTHESIZED LATIN CAPITAL LETTER A..CIRCLE - {0x1F12F, 0x1F130, propertyUNASSIGNED}, // .. - {0x1F131, 0x0, propertyDISALLOWED}, // SQUARED LATIN CAPITAL LETTER B - {0x1F132, 0x1F13C, propertyUNASSIGNED}, // .. - {0x1F13D, 0x0, propertyDISALLOWED}, // SQUARED LATIN CAPITAL LETTER N - {0x1F13E, 0x0, propertyUNASSIGNED}, // - {0x1F13F, 0x0, propertyDISALLOWED}, // SQUARED LATIN CAPITAL LETTER P - {0x1F140, 0x1F141, propertyUNASSIGNED}, // .. - {0x1F142, 0x0, propertyDISALLOWED}, // SQUARED LATIN CAPITAL LETTER S - {0x1F143, 0x1F145, propertyUNASSIGNED}, // .. - {0x1F146, 0x0, propertyDISALLOWED}, // SQUARED LATIN CAPITAL LETTER W - {0x1F147, 0x1F149, propertyUNASSIGNED}, // .. - {0x1F14A, 0x1F14E, propertyDISALLOWED}, // SQUARED HV..SQUARED PPV - {0x1F14F, 0x1F156, propertyUNASSIGNED}, // .. - {0x1F157, 0x0, propertyDISALLOWED}, // NEGATIVE CIRCLED LATIN CAPITAL LETTER H - {0x1F158, 0x1F15E, propertyUNASSIGNED}, // .. - {0x1F15F, 0x0, propertyDISALLOWED}, // NEGATIVE CIRCLED LATIN CAPITAL LETTER P - {0x1F160, 0x1F178, propertyUNASSIGNED}, // .. - {0x1F179, 0x0, propertyDISALLOWED}, // NEGATIVE SQUARED LATIN CAPITAL LETTER J - {0x1F17A, 0x0, propertyUNASSIGNED}, // - {0x1F17B, 0x1F17C, propertyDISALLOWED}, // NEGATIVE SQUARED LATIN CAPITAL LETTER L..NEG - {0x1F17D, 0x1F17E, propertyUNASSIGNED}, // .. - {0x1F17F, 0x0, propertyDISALLOWED}, // NEGATIVE SQUARED LATIN CAPITAL LETTER P - {0x1F180, 0x1F189, propertyUNASSIGNED}, // .. - {0x1F18A, 0x1F18D, propertyDISALLOWED}, // CROSSED NEGATIVE SQUARED LATIN CAPITAL LETTE - {0x1F18E, 0x1F18F, propertyUNASSIGNED}, // .. - {0x1F190, 0x0, propertyDISALLOWED}, // SQUARE DJ - {0x1F191, 0x1F1FF, propertyUNASSIGNED}, // .. - {0x1F200, 0x0, propertyDISALLOWED}, // SQUARE HIRAGANA HOKA - {0x1F201, 0x1F20F, propertyUNASSIGNED}, // .. - {0x1F210, 0x1F231, propertyDISALLOWED}, // SQUARED CJK UNIFIED IDEOGRAPH-624B..SQUARED - {0x1F232, 0x1F23F, propertyUNASSIGNED}, // .. - {0x1F240, 0x1F248, propertyDISALLOWED}, // TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRA - {0x1F249, 0x1FFFD, propertyUNASSIGNED}, // .. - {0x1FFFE, 0x1FFFF, propertyDISALLOWED}, // .. - {0x20000, 0x2A6D6, propertyPVALID}, // .... - {0x2A700, 0x2B734, propertyPVALID}, // .... - {0x2F800, 0x2FA1D, propertyDISALLOWED}, // CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPA - {0x2FA1E, 0x2FFFD, propertyUNASSIGNED}, // .. - {0x2FFFE, 0x2FFFF, propertyDISALLOWED}, // .. - {0x30000, 0x3FFFD, propertyUNASSIGNED}, // .. - {0x3FFFE, 0x3FFFF, propertyDISALLOWED}, // .. - {0x40000, 0x4FFFD, propertyUNASSIGNED}, // .. - {0x4FFFE, 0x4FFFF, propertyDISALLOWED}, // .. - {0x50000, 0x5FFFD, propertyUNASSIGNED}, // .. - {0x5FFFE, 0x5FFFF, propertyDISALLOWED}, // .. - {0x60000, 0x6FFFD, propertyUNASSIGNED}, // .. - {0x6FFFE, 0x6FFFF, propertyDISALLOWED}, // .. - {0x70000, 0x7FFFD, propertyUNASSIGNED}, // .. - {0x7FFFE, 0x7FFFF, propertyDISALLOWED}, // .. - {0x80000, 0x8FFFD, propertyUNASSIGNED}, // .. - {0x8FFFE, 0x8FFFF, propertyDISALLOWED}, // .. - {0x90000, 0x9FFFD, propertyUNASSIGNED}, // .. - {0x9FFFE, 0x9FFFF, propertyDISALLOWED}, // .. - {0xA0000, 0xAFFFD, propertyUNASSIGNED}, // .. - {0xAFFFE, 0xAFFFF, propertyDISALLOWED}, // .. - {0xB0000, 0xBFFFD, propertyUNASSIGNED}, // .. - {0xBFFFE, 0xBFFFF, propertyDISALLOWED}, // .. - {0xC0000, 0xCFFFD, propertyUNASSIGNED}, // .. - {0xCFFFE, 0xCFFFF, propertyDISALLOWED}, // .. - {0xD0000, 0xDFFFD, propertyUNASSIGNED}, // .. - {0xDFFFE, 0xDFFFF, propertyDISALLOWED}, // .. - {0xE0000, 0x0, propertyUNASSIGNED}, // - {0xE0001, 0x0, propertyDISALLOWED}, // LANGUAGE TAG - {0xE0002, 0xE001F, propertyUNASSIGNED}, // .. - {0xE0020, 0xE007F, propertyDISALLOWED}, // TAG SPACE..CANCEL TAG - {0xE0080, 0xE00FF, propertyUNASSIGNED}, // .. - {0xE0100, 0xE01EF, propertyDISALLOWED}, // VARIATION SELECTOR-17..VARIATION SELECTOR-25 - {0xE01F0, 0xEFFFD, propertyUNASSIGNED}, // .. - {0xEFFFE, 0x10FFFF, propertyDISALLOWED}, // .. -} diff --git a/vendor/github.com/miekg/dns/idn/punycode.go b/vendor/github.com/miekg/dns/idn/punycode.go deleted file mode 100644 index 1d03bf6ae65..00000000000 --- a/vendor/github.com/miekg/dns/idn/punycode.go +++ /dev/null @@ -1,370 +0,0 @@ -// Package idn implements encoding from and to punycode as speficied by RFC 3492. -package idn - -import ( - "bytes" - "strings" - "unicode" - "unicode/utf8" - - "github.com/miekg/dns" -) - -// Implementation idea from RFC itself and from from IDNA::Punycode created by -// Tatsuhiko Miyagawa and released under Perl Artistic -// License in 2002. - -const ( - _MIN rune = 1 - _MAX rune = 26 - _SKEW rune = 38 - _BASE rune = 36 - _BIAS rune = 72 - _N rune = 128 - _DAMP rune = 700 - - _DELIMITER = '-' - _PREFIX = "xn--" -) - -// ToPunycode converts unicode domain names to DNS-appropriate punycode names. -// This function will return an empty string result for domain names with -// invalid unicode strings. This function expects domain names in lowercase. -func ToPunycode(s string) string { - // Early check to see if encoding is needed. - // This will prevent making heap allocations when not needed. - if !needToPunycode(s) { - return s - } - - tokens := dns.SplitDomainName(s) - switch { - case s == "": - return "" - case tokens == nil: // s == . - return "." - case s[len(s)-1] == '.': - tokens = append(tokens, "") - } - - for i := range tokens { - t := encode([]byte(tokens[i])) - if t == nil { - return "" - } - tokens[i] = string(t) - } - return strings.Join(tokens, ".") -} - -// FromPunycode returns unicode domain name from provided punycode string. -// This function expects punycode strings in lowercase. -func FromPunycode(s string) string { - // Early check to see if decoding is needed. - // This will prevent making heap allocations when not needed. - if !needFromPunycode(s) { - return s - } - - tokens := dns.SplitDomainName(s) - switch { - case s == "": - return "" - case tokens == nil: // s == . - return "." - case s[len(s)-1] == '.': - tokens = append(tokens, "") - } - for i := range tokens { - tokens[i] = string(decode([]byte(tokens[i]))) - } - return strings.Join(tokens, ".") -} - -// digitval converts single byte into meaningful value that's used to calculate decoded unicode character. -const errdigit = 0xffff - -func digitval(code rune) rune { - switch { - case code >= 'A' && code <= 'Z': - return code - 'A' - case code >= 'a' && code <= 'z': - return code - 'a' - case code >= '0' && code <= '9': - return code - '0' + 26 - } - return errdigit -} - -// lettercode finds BASE36 byte (a-z0-9) based on calculated number. -func lettercode(digit rune) rune { - switch { - case digit >= 0 && digit <= 25: - return digit + 'a' - case digit >= 26 && digit <= 36: - return digit - 26 + '0' - } - panic("dns: not reached") -} - -// adapt calculates next bias to be used for next iteration delta. -func adapt(delta rune, numpoints int, firsttime bool) rune { - if firsttime { - delta /= _DAMP - } else { - delta /= 2 - } - - var k rune - for delta = delta + delta/rune(numpoints); delta > (_BASE-_MIN)*_MAX/2; k += _BASE { - delta /= _BASE - _MIN - } - - return k + ((_BASE-_MIN+1)*delta)/(delta+_SKEW) -} - -// next finds minimal rune (one with lowest codepoint value) that should be equal or above boundary. -func next(b []rune, boundary rune) rune { - if len(b) == 0 { - panic("dns: invalid set of runes to determine next one") - } - m := b[0] - for _, x := range b[1:] { - if x >= boundary && (m < boundary || x < m) { - m = x - } - } - return m -} - -// preprune converts unicode rune to lower case. At this time it's not -// supporting all things described in RFCs. -func preprune(r rune) rune { - if unicode.IsUpper(r) { - r = unicode.ToLower(r) - } - return r -} - -// tfunc is a function that helps calculate each character weight. -func tfunc(k, bias rune) rune { - switch { - case k <= bias: - return _MIN - case k >= bias+_MAX: - return _MAX - } - return k - bias -} - -// needToPunycode returns true for strings that require punycode encoding -// (contain unicode characters). -func needToPunycode(s string) bool { - // This function is very similar to bytes.Runes. We don't use bytes.Runes - // because it makes a heap allocation that's not needed here. - for i := 0; len(s) > 0; i++ { - r, l := utf8.DecodeRuneInString(s) - if r > 0x7f { - return true - } - s = s[l:] - } - return false -} - -// needFromPunycode returns true for strings that require punycode decoding. -func needFromPunycode(s string) bool { - if s == "." { - return false - } - - off := 0 - end := false - pl := len(_PREFIX) - sl := len(s) - - // If s starts with _PREFIX. - if sl > pl && s[off:off+pl] == _PREFIX { - return true - } - - for { - // Find the part after the next ".". - off, end = dns.NextLabel(s, off) - if end { - return false - } - // If this parts starts with _PREFIX. - if sl-off > pl && s[off:off+pl] == _PREFIX { - return true - } - } -} - -// encode transforms Unicode input bytes (that represent DNS label) into -// punycode bytestream. This function would return nil if there's an invalid -// character in the label. -func encode(input []byte) []byte { - n, bias := _N, _BIAS - - b := bytes.Runes(input) - for i := range b { - if !isValidRune(b[i]) { - return nil - } - - b[i] = preprune(b[i]) - } - - basic := make([]byte, 0, len(b)) - for _, ltr := range b { - if ltr <= 0x7f { - basic = append(basic, byte(ltr)) - } - } - basiclen := len(basic) - fulllen := len(b) - if basiclen == fulllen { - return basic - } - - var out bytes.Buffer - - out.WriteString(_PREFIX) - if basiclen > 0 { - out.Write(basic) - out.WriteByte(_DELIMITER) - } - - var ( - ltr, nextltr rune - delta, q rune // delta calculation (see rfc) - t, k, cp rune // weight and codepoint calculation - ) - - for h := basiclen; h < fulllen; n, delta = n+1, delta+1 { - nextltr = next(b, n) - delta, n = delta+(nextltr-n)*rune(h+1), nextltr - - for _, ltr = range b { - if ltr < n { - delta++ - } - if ltr == n { - q = delta - for k = _BASE; ; k += _BASE { - t = tfunc(k, bias) - if q < t { - break - } - cp = t + ((q - t) % (_BASE - t)) - out.WriteRune(lettercode(cp)) - q = (q - t) / (_BASE - t) - } - - out.WriteRune(lettercode(q)) - - bias = adapt(delta, h+1, h == basiclen) - h, delta = h+1, 0 - } - } - } - return out.Bytes() -} - -// decode transforms punycode input bytes (that represent DNS label) into Unicode bytestream. -func decode(b []byte) []byte { - src := b // b would move and we need to keep it - - n, bias := _N, _BIAS - if !bytes.HasPrefix(b, []byte(_PREFIX)) { - return b - } - out := make([]rune, 0, len(b)) - b = b[len(_PREFIX):] - for pos := len(b) - 1; pos >= 0; pos-- { - // only last delimiter is our interest - if b[pos] == _DELIMITER { - out = append(out, bytes.Runes(b[:pos])...) - b = b[pos+1:] // trim source string - break - } - } - if len(b) == 0 { - return src - } - var ( - i, oldi, w rune - ch byte - t, digit rune - ln int - ) - - for i = 0; len(b) > 0; i++ { - oldi, w = i, 1 - for k := _BASE; len(b) > 0; k += _BASE { - ch, b = b[0], b[1:] - digit = digitval(rune(ch)) - if digit == errdigit { - return src - } - i += digit * w - if i < 0 { - // safety check for rune overflow - return src - } - - t = tfunc(k, bias) - if digit < t { - break - } - - w *= _BASE - t - } - ln = len(out) + 1 - bias = adapt(i-oldi, ln, oldi == 0) - n += i / rune(ln) - i = i % rune(ln) - // insert - out = append(out, 0) - copy(out[i+1:], out[i:]) - out[i] = n - } - - var ret bytes.Buffer - for _, r := range out { - ret.WriteRune(r) - } - return ret.Bytes() -} - -// isValidRune checks if the character is valid. We will look for the -// character property in the code points list. For now we aren't checking special -// rules in case of contextual property -func isValidRune(r rune) bool { - return findProperty(r) == propertyPVALID -} - -// findProperty will try to check the code point property of the given -// character. It will use a binary search algorithm as we have a slice of -// ordered ranges (average case performance O(log n)) -func findProperty(r rune) property { - imin, imax := 0, len(codePoints) - - for imax >= imin { - imid := (imin + imax) / 2 - - codePoint := codePoints[imid] - if (codePoint.start == r && codePoint.end == 0) || (codePoint.start <= r && codePoint.end >= r) { - return codePoint.state - } - - if (codePoint.end > 0 && codePoint.end < r) || (codePoint.end == 0 && codePoint.start < r) { - imin = imid + 1 - } else { - imax = imid - 1 - } - } - - return propertyUnknown -} diff --git a/vendor/github.com/miekg/dns/labels.go b/vendor/github.com/miekg/dns/labels.go deleted file mode 100644 index 9538d9c3a63..00000000000 --- a/vendor/github.com/miekg/dns/labels.go +++ /dev/null @@ -1,171 +0,0 @@ -package dns - -import "strings" - -// Holds a bunch of helper functions for dealing with labels. - -// SplitDomainName splits a name string into it's labels. -// www.miek.nl. returns []string{"www", "miek", "nl"} -// .www.miek.nl. returns []string{"", "www", "miek", "nl"}, -// The root label (.) returns nil. Note that using -// strings.Split(s) will work in most cases, but does not handle -// escaped dots (\.) for instance. -// s must be a syntactically valid domain name, see IsDomainName. -func SplitDomainName(s string) (labels []string) { - if len(s) == 0 { - return nil - } - fqdnEnd := 0 // offset of the final '.' or the length of the name - idx := Split(s) - begin := 0 - if s[len(s)-1] == '.' { - fqdnEnd = len(s) - 1 - } else { - fqdnEnd = len(s) - } - - switch len(idx) { - case 0: - return nil - case 1: - // no-op - default: - end := 0 - for i := 1; i < len(idx); i++ { - end = idx[i] - labels = append(labels, s[begin:end-1]) - begin = end - } - } - - labels = append(labels, s[begin:fqdnEnd]) - return labels -} - -// CompareDomainName compares the names s1 and s2 and -// returns how many labels they have in common starting from the *right*. -// The comparison stops at the first inequality. The names are not downcased -// before the comparison. -// -// www.miek.nl. and miek.nl. have two labels in common: miek and nl -// www.miek.nl. and www.bla.nl. have one label in common: nl -// -// s1 and s2 must be syntactically valid domain names. -func CompareDomainName(s1, s2 string) (n int) { - s1, s2 = strings.ToLower(s1), strings.ToLower(s2) - s1 = Fqdn(s1) - s2 = Fqdn(s2) - l1 := Split(s1) - l2 := Split(s2) - - // the first check: root label - if l1 == nil || l2 == nil { - return - } - - j1 := len(l1) - 1 // end - i1 := len(l1) - 2 // start - j2 := len(l2) - 1 - i2 := len(l2) - 2 - // the second check can be done here: last/only label - // before we fall through into the for-loop below - if s1[l1[j1]:] == s2[l2[j2]:] { - n++ - } else { - return - } - for { - if i1 < 0 || i2 < 0 { - break - } - if s1[l1[i1]:l1[j1]] == s2[l2[i2]:l2[j2]] { - n++ - } else { - break - } - j1-- - i1-- - j2-- - i2-- - } - return -} - -// CountLabel counts the the number of labels in the string s. -// s must be a syntactically valid domain name. -func CountLabel(s string) (labels int) { - if s == "." { - return - } - off := 0 - end := false - for { - off, end = NextLabel(s, off) - labels++ - if end { - return - } - } -} - -// Split splits a name s into its label indexes. -// www.miek.nl. returns []int{0, 4, 9}, www.miek.nl also returns []int{0, 4, 9}. -// The root name (.) returns nil. Also see SplitDomainName. -// s must be a syntactically valid domain name. -func Split(s string) []int { - if s == "." { - return nil - } - idx := make([]int, 1, 3) - off := 0 - end := false - - for { - off, end = NextLabel(s, off) - if end { - return idx - } - idx = append(idx, off) - } -} - -// NextLabel returns the index of the start of the next label in the -// string s starting at offset. -// The bool end is true when the end of the string has been reached. -// Also see PrevLabel. -func NextLabel(s string, offset int) (i int, end bool) { - quote := false - for i = offset; i < len(s)-1; i++ { - switch s[i] { - case '\\': - quote = !quote - default: - quote = false - case '.': - if quote { - quote = !quote - continue - } - return i + 1, false - } - } - return i + 1, true -} - -// PrevLabel returns the index of the label when starting from the right and -// jumping n labels to the left. -// The bool start is true when the start of the string has been overshot. -// Also see NextLabel. -func PrevLabel(s string, n int) (i int, start bool) { - if n == 0 { - return len(s), false - } - lab := Split(s) - if lab == nil { - return 0, true - } - if n > len(lab) { - return 0, true - } - return lab[len(lab)-n], false -} diff --git a/vendor/github.com/miekg/dns/msg.go b/vendor/github.com/miekg/dns/msg.go deleted file mode 100644 index 605fe6c5c35..00000000000 --- a/vendor/github.com/miekg/dns/msg.go +++ /dev/null @@ -1,1159 +0,0 @@ -// DNS packet assembly, see RFC 1035. Converting from - Unpack() - -// and to - Pack() - wire format. -// All the packers and unpackers take a (msg []byte, off int) -// and return (off1 int, ok bool). If they return ok==false, they -// also return off1==len(msg), so that the next unpacker will -// also fail. This lets us avoid checks of ok until the end of a -// packing sequence. - -package dns - -//go:generate go run msg_generate.go -//go:generate go run compress_generate.go - -import ( - crand "crypto/rand" - "encoding/binary" - "fmt" - "math/big" - "math/rand" - "strconv" - "sync" -) - -const ( - maxCompressionOffset = 2 << 13 // We have 14 bits for the compression pointer - maxDomainNameWireOctets = 255 // See RFC 1035 section 2.3.4 -) - -var ( - ErrAlg error = &Error{err: "bad algorithm"} // ErrAlg indicates an error with the (DNSSEC) algorithm. - ErrAuth error = &Error{err: "bad authentication"} // ErrAuth indicates an error in the TSIG authentication. - ErrBuf error = &Error{err: "buffer size too small"} // ErrBuf indicates that the buffer used is too small for the message. - ErrConnEmpty error = &Error{err: "conn has no connection"} // ErrConnEmpty indicates a connection is being used before it is initialized. - ErrExtendedRcode error = &Error{err: "bad extended rcode"} // ErrExtendedRcode ... - ErrFqdn error = &Error{err: "domain must be fully qualified"} // ErrFqdn indicates that a domain name does not have a closing dot. - ErrId error = &Error{err: "id mismatch"} // ErrId indicates there is a mismatch with the message's ID. - ErrKeyAlg error = &Error{err: "bad key algorithm"} // ErrKeyAlg indicates that the algorithm in the key is not valid. - ErrKey error = &Error{err: "bad key"} - ErrKeySize error = &Error{err: "bad key size"} - ErrLongDomain error = &Error{err: fmt.Sprintf("domain name exceeded %d wire-format octets", maxDomainNameWireOctets)} - ErrNoSig error = &Error{err: "no signature found"} - ErrPrivKey error = &Error{err: "bad private key"} - ErrRcode error = &Error{err: "bad rcode"} - ErrRdata error = &Error{err: "bad rdata"} - ErrRRset error = &Error{err: "bad rrset"} - ErrSecret error = &Error{err: "no secrets defined"} - ErrShortRead error = &Error{err: "short read"} - ErrSig error = &Error{err: "bad signature"} // ErrSig indicates that a signature can not be cryptographically validated. - ErrSoa error = &Error{err: "no SOA"} // ErrSOA indicates that no SOA RR was seen when doing zone transfers. - ErrTime error = &Error{err: "bad time"} // ErrTime indicates a timing error in TSIG authentication. - ErrTruncated error = &Error{err: "failed to unpack truncated message"} // ErrTruncated indicates that we failed to unpack a truncated message. We unpacked as much as we had so Msg can still be used, if desired. -) - -// Id by default, returns a 16 bits random number to be used as a -// message id. The random provided should be good enough. This being a -// variable the function can be reassigned to a custom function. -// For instance, to make it return a static value: -// -// dns.Id = func() uint16 { return 3 } -var Id func() uint16 = id - -var ( - idLock sync.Mutex - idRand *rand.Rand -) - -// id returns a 16 bits random number to be used as a -// message id. The random provided should be good enough. -func id() uint16 { - idLock.Lock() - - if idRand == nil { - // This (partially) works around - // https://github.com/golang/go/issues/11833 by only - // seeding idRand upon the first call to id. - - var seed int64 - var buf [8]byte - - if _, err := crand.Read(buf[:]); err == nil { - seed = int64(binary.LittleEndian.Uint64(buf[:])) - } else { - seed = rand.Int63() - } - - idRand = rand.New(rand.NewSource(seed)) - } - - // The call to idRand.Uint32 must be within the - // mutex lock because *rand.Rand is not safe for - // concurrent use. - // - // There is no added performance overhead to calling - // idRand.Uint32 inside a mutex lock over just - // calling rand.Uint32 as the global math/rand rng - // is internally protected by a sync.Mutex. - id := uint16(idRand.Uint32()) - - idLock.Unlock() - return id -} - -// MsgHdr is a a manually-unpacked version of (id, bits). -type MsgHdr struct { - Id uint16 - Response bool - Opcode int - Authoritative bool - Truncated bool - RecursionDesired bool - RecursionAvailable bool - Zero bool - AuthenticatedData bool - CheckingDisabled bool - Rcode int -} - -// Msg contains the layout of a DNS message. -type Msg struct { - MsgHdr - Compress bool `json:"-"` // If true, the message will be compressed when converted to wire format. - Question []Question // Holds the RR(s) of the question section. - Answer []RR // Holds the RR(s) of the answer section. - Ns []RR // Holds the RR(s) of the authority section. - Extra []RR // Holds the RR(s) of the additional section. -} - -// ClassToString is a maps Classes to strings for each CLASS wire type. -var ClassToString = map[uint16]string{ - ClassINET: "IN", - ClassCSNET: "CS", - ClassCHAOS: "CH", - ClassHESIOD: "HS", - ClassNONE: "NONE", - ClassANY: "ANY", -} - -// OpcodeToString maps Opcodes to strings. -var OpcodeToString = map[int]string{ - OpcodeQuery: "QUERY", - OpcodeIQuery: "IQUERY", - OpcodeStatus: "STATUS", - OpcodeNotify: "NOTIFY", - OpcodeUpdate: "UPDATE", -} - -// RcodeToString maps Rcodes to strings. -var RcodeToString = map[int]string{ - RcodeSuccess: "NOERROR", - RcodeFormatError: "FORMERR", - RcodeServerFailure: "SERVFAIL", - RcodeNameError: "NXDOMAIN", - RcodeNotImplemented: "NOTIMPL", - RcodeRefused: "REFUSED", - RcodeYXDomain: "YXDOMAIN", // See RFC 2136 - RcodeYXRrset: "YXRRSET", - RcodeNXRrset: "NXRRSET", - RcodeNotAuth: "NOTAUTH", - RcodeNotZone: "NOTZONE", - RcodeBadSig: "BADSIG", // Also known as RcodeBadVers, see RFC 6891 - // RcodeBadVers: "BADVERS", - RcodeBadKey: "BADKEY", - RcodeBadTime: "BADTIME", - RcodeBadMode: "BADMODE", - RcodeBadName: "BADNAME", - RcodeBadAlg: "BADALG", - RcodeBadTrunc: "BADTRUNC", - RcodeBadCookie: "BADCOOKIE", -} - -// Domain names are a sequence of counted strings -// split at the dots. They end with a zero-length string. - -// PackDomainName packs a domain name s into msg[off:]. -// If compression is wanted compress must be true and the compression -// map needs to hold a mapping between domain names and offsets -// pointing into msg. -func PackDomainName(s string, msg []byte, off int, compression map[string]int, compress bool) (off1 int, err error) { - off1, _, err = packDomainName(s, msg, off, compression, compress) - return -} - -func packDomainName(s string, msg []byte, off int, compression map[string]int, compress bool) (off1 int, labels int, err error) { - // special case if msg == nil - lenmsg := 256 - if msg != nil { - lenmsg = len(msg) - } - ls := len(s) - if ls == 0 { // Ok, for instance when dealing with update RR without any rdata. - return off, 0, nil - } - // If not fully qualified, error out, but only if msg == nil #ugly - switch { - case msg == nil: - if s[ls-1] != '.' { - s += "." - ls++ - } - case msg != nil: - if s[ls-1] != '.' { - return lenmsg, 0, ErrFqdn - } - } - // Each dot ends a segment of the name. - // We trade each dot byte for a length byte. - // Except for escaped dots (\.), which are normal dots. - // There is also a trailing zero. - - // Compression - nameoffset := -1 - pointer := -1 - // Emit sequence of counted strings, chopping at dots. - begin := 0 - bs := []byte(s) - roBs, bsFresh, escapedDot := s, true, false - for i := 0; i < ls; i++ { - if bs[i] == '\\' { - for j := i; j < ls-1; j++ { - bs[j] = bs[j+1] - } - ls-- - if off+1 > lenmsg { - return lenmsg, labels, ErrBuf - } - // check for \DDD - if i+2 < ls && isDigit(bs[i]) && isDigit(bs[i+1]) && isDigit(bs[i+2]) { - bs[i] = dddToByte(bs[i:]) - for j := i + 1; j < ls-2; j++ { - bs[j] = bs[j+2] - } - ls -= 2 - } - escapedDot = bs[i] == '.' - bsFresh = false - continue - } - - if bs[i] == '.' { - if i > 0 && bs[i-1] == '.' && !escapedDot { - // two dots back to back is not legal - return lenmsg, labels, ErrRdata - } - if i-begin >= 1<<6 { // top two bits of length must be clear - return lenmsg, labels, ErrRdata - } - // off can already (we're in a loop) be bigger than len(msg) - // this happens when a name isn't fully qualified - if off+1 > lenmsg { - return lenmsg, labels, ErrBuf - } - if msg != nil { - msg[off] = byte(i - begin) - } - offset := off - off++ - for j := begin; j < i; j++ { - if off+1 > lenmsg { - return lenmsg, labels, ErrBuf - } - if msg != nil { - msg[off] = bs[j] - } - off++ - } - if compress && !bsFresh { - roBs = string(bs) - bsFresh = true - } - // Don't try to compress '.' - // We should only compress when compress it true, but we should also still pick - // up names that can be used for *future* compression(s). - if compression != nil && roBs[begin:] != "." { - if p, ok := compression[roBs[begin:]]; !ok { - // Only offsets smaller than this can be used. - if offset < maxCompressionOffset { - compression[roBs[begin:]] = offset - } - } else { - // The first hit is the longest matching dname - // keep the pointer offset we get back and store - // the offset of the current name, because that's - // where we need to insert the pointer later - - // If compress is true, we're allowed to compress this dname - if pointer == -1 && compress { - pointer = p // Where to point to - nameoffset = offset // Where to point from - break - } - } - } - labels++ - begin = i + 1 - } - escapedDot = false - } - // Root label is special - if len(bs) == 1 && bs[0] == '.' { - return off, labels, nil - } - // If we did compression and we find something add the pointer here - if pointer != -1 { - // We have two bytes (14 bits) to put the pointer in - // if msg == nil, we will never do compression - binary.BigEndian.PutUint16(msg[nameoffset:], uint16(pointer^0xC000)) - off = nameoffset + 1 - goto End - } - if msg != nil && off < len(msg) { - msg[off] = 0 - } -End: - off++ - return off, labels, nil -} - -// Unpack a domain name. -// In addition to the simple sequences of counted strings above, -// domain names are allowed to refer to strings elsewhere in the -// packet, to avoid repeating common suffixes when returning -// many entries in a single domain. The pointers are marked -// by a length byte with the top two bits set. Ignoring those -// two bits, that byte and the next give a 14 bit offset from msg[0] -// where we should pick up the trail. -// Note that if we jump elsewhere in the packet, -// we return off1 == the offset after the first pointer we found, -// which is where the next record will start. -// In theory, the pointers are only allowed to jump backward. -// We let them jump anywhere and stop jumping after a while. - -// UnpackDomainName unpacks a domain name into a string. -func UnpackDomainName(msg []byte, off int) (string, int, error) { - s := make([]byte, 0, 64) - off1 := 0 - lenmsg := len(msg) - maxLen := maxDomainNameWireOctets - ptr := 0 // number of pointers followed -Loop: - for { - if off >= lenmsg { - return "", lenmsg, ErrBuf - } - c := int(msg[off]) - off++ - switch c & 0xC0 { - case 0x00: - if c == 0x00 { - // end of name - break Loop - } - // literal string - if off+c > lenmsg { - return "", lenmsg, ErrBuf - } - for j := off; j < off+c; j++ { - switch b := msg[j]; b { - case '.', '(', ')', ';', ' ', '@': - fallthrough - case '"', '\\': - s = append(s, '\\', b) - // presentation-format \X escapes add an extra byte - maxLen += 1 - default: - if b < 32 || b >= 127 { // unprintable, use \DDD - var buf [3]byte - bufs := strconv.AppendInt(buf[:0], int64(b), 10) - s = append(s, '\\') - for i := 0; i < 3-len(bufs); i++ { - s = append(s, '0') - } - for _, r := range bufs { - s = append(s, r) - } - // presentation-format \DDD escapes add 3 extra bytes - maxLen += 3 - } else { - s = append(s, b) - } - } - } - s = append(s, '.') - off += c - case 0xC0: - // pointer to somewhere else in msg. - // remember location after first ptr, - // since that's how many bytes we consumed. - // also, don't follow too many pointers -- - // maybe there's a loop. - if off >= lenmsg { - return "", lenmsg, ErrBuf - } - c1 := msg[off] - off++ - if ptr == 0 { - off1 = off - } - if ptr++; ptr > 10 { - return "", lenmsg, &Error{err: "too many compression pointers"} - } - // pointer should guarantee that it advances and points forwards at least - // but the condition on previous three lines guarantees that it's - // at least loop-free - off = (c^0xC0)<<8 | int(c1) - default: - // 0x80 and 0x40 are reserved - return "", lenmsg, ErrRdata - } - } - if ptr == 0 { - off1 = off - } - if len(s) == 0 { - s = []byte(".") - } else if len(s) >= maxLen { - // error if the name is too long, but don't throw it away - return string(s), lenmsg, ErrLongDomain - } - return string(s), off1, nil -} - -func packTxt(txt []string, msg []byte, offset int, tmp []byte) (int, error) { - if len(txt) == 0 { - if offset >= len(msg) { - return offset, ErrBuf - } - msg[offset] = 0 - return offset, nil - } - var err error - for i := range txt { - if len(txt[i]) > len(tmp) { - return offset, ErrBuf - } - offset, err = packTxtString(txt[i], msg, offset, tmp) - if err != nil { - return offset, err - } - } - return offset, nil -} - -func packTxtString(s string, msg []byte, offset int, tmp []byte) (int, error) { - lenByteOffset := offset - if offset >= len(msg) || len(s) > len(tmp) { - return offset, ErrBuf - } - offset++ - bs := tmp[:len(s)] - copy(bs, s) - for i := 0; i < len(bs); i++ { - if len(msg) <= offset { - return offset, ErrBuf - } - if bs[i] == '\\' { - i++ - if i == len(bs) { - break - } - // check for \DDD - if i+2 < len(bs) && isDigit(bs[i]) && isDigit(bs[i+1]) && isDigit(bs[i+2]) { - msg[offset] = dddToByte(bs[i:]) - i += 2 - } else { - msg[offset] = bs[i] - } - } else { - msg[offset] = bs[i] - } - offset++ - } - l := offset - lenByteOffset - 1 - if l > 255 { - return offset, &Error{err: "string exceeded 255 bytes in txt"} - } - msg[lenByteOffset] = byte(l) - return offset, nil -} - -func packOctetString(s string, msg []byte, offset int, tmp []byte) (int, error) { - if offset >= len(msg) || len(s) > len(tmp) { - return offset, ErrBuf - } - bs := tmp[:len(s)] - copy(bs, s) - for i := 0; i < len(bs); i++ { - if len(msg) <= offset { - return offset, ErrBuf - } - if bs[i] == '\\' { - i++ - if i == len(bs) { - break - } - // check for \DDD - if i+2 < len(bs) && isDigit(bs[i]) && isDigit(bs[i+1]) && isDigit(bs[i+2]) { - msg[offset] = dddToByte(bs[i:]) - i += 2 - } else { - msg[offset] = bs[i] - } - } else { - msg[offset] = bs[i] - } - offset++ - } - return offset, nil -} - -func unpackTxt(msg []byte, off0 int) (ss []string, off int, err error) { - off = off0 - var s string - for off < len(msg) && err == nil { - s, off, err = unpackTxtString(msg, off) - if err == nil { - ss = append(ss, s) - } - } - return -} - -func unpackTxtString(msg []byte, offset int) (string, int, error) { - if offset+1 > len(msg) { - return "", offset, &Error{err: "overflow unpacking txt"} - } - l := int(msg[offset]) - if offset+l+1 > len(msg) { - return "", offset, &Error{err: "overflow unpacking txt"} - } - s := make([]byte, 0, l) - for _, b := range msg[offset+1 : offset+1+l] { - switch b { - case '"', '\\': - s = append(s, '\\', b) - default: - if b < 32 || b > 127 { // unprintable - var buf [3]byte - bufs := strconv.AppendInt(buf[:0], int64(b), 10) - s = append(s, '\\') - for i := 0; i < 3-len(bufs); i++ { - s = append(s, '0') - } - for _, r := range bufs { - s = append(s, r) - } - } else { - s = append(s, b) - } - } - } - offset += 1 + l - return string(s), offset, nil -} - -// Helpers for dealing with escaped bytes -func isDigit(b byte) bool { return b >= '0' && b <= '9' } - -func dddToByte(s []byte) byte { - return byte((s[0]-'0')*100 + (s[1]-'0')*10 + (s[2] - '0')) -} - -// Helper function for packing and unpacking -func intToBytes(i *big.Int, length int) []byte { - buf := i.Bytes() - if len(buf) < length { - b := make([]byte, length) - copy(b[length-len(buf):], buf) - return b - } - return buf -} - -// PackRR packs a resource record rr into msg[off:]. -// See PackDomainName for documentation about the compression. -func PackRR(rr RR, msg []byte, off int, compression map[string]int, compress bool) (off1 int, err error) { - if rr == nil { - return len(msg), &Error{err: "nil rr"} - } - - off1, err = rr.pack(msg, off, compression, compress) - if err != nil { - return len(msg), err - } - // TODO(miek): Not sure if this is needed? If removed we can remove rawmsg.go as well. - if rawSetRdlength(msg, off, off1) { - return off1, nil - } - return off, ErrRdata -} - -// UnpackRR unpacks msg[off:] into an RR. -func UnpackRR(msg []byte, off int) (rr RR, off1 int, err error) { - h, off, msg, err := unpackHeader(msg, off) - if err != nil { - return nil, len(msg), err - } - end := off + int(h.Rdlength) - - if fn, known := typeToUnpack[h.Rrtype]; !known { - rr, off, err = unpackRFC3597(h, msg, off) - } else { - rr, off, err = fn(h, msg, off) - } - if off != end { - return &h, end, &Error{err: "bad rdlength"} - } - return rr, off, err -} - -// unpackRRslice unpacks msg[off:] into an []RR. -// If we cannot unpack the whole array, then it will return nil -func unpackRRslice(l int, msg []byte, off int) (dst1 []RR, off1 int, err error) { - var r RR - // Optimistically make dst be the length that was sent - dst := make([]RR, 0, l) - for i := 0; i < l; i++ { - off1 := off - r, off, err = UnpackRR(msg, off) - if err != nil { - off = len(msg) - break - } - // If offset does not increase anymore, l is a lie - if off1 == off { - l = i - break - } - dst = append(dst, r) - } - if err != nil && off == len(msg) { - dst = nil - } - return dst, off, err -} - -// Convert a MsgHdr to a string, with dig-like headers: -// -//;; opcode: QUERY, status: NOERROR, id: 48404 -// -//;; flags: qr aa rd ra; -func (h *MsgHdr) String() string { - if h == nil { - return " MsgHdr" - } - - s := ";; opcode: " + OpcodeToString[h.Opcode] - s += ", status: " + RcodeToString[h.Rcode] - s += ", id: " + strconv.Itoa(int(h.Id)) + "\n" - - s += ";; flags:" - if h.Response { - s += " qr" - } - if h.Authoritative { - s += " aa" - } - if h.Truncated { - s += " tc" - } - if h.RecursionDesired { - s += " rd" - } - if h.RecursionAvailable { - s += " ra" - } - if h.Zero { // Hmm - s += " z" - } - if h.AuthenticatedData { - s += " ad" - } - if h.CheckingDisabled { - s += " cd" - } - - s += ";" - return s -} - -// Pack packs a Msg: it is converted to to wire format. -// If the dns.Compress is true the message will be in compressed wire format. -func (dns *Msg) Pack() (msg []byte, err error) { - return dns.PackBuffer(nil) -} - -// PackBuffer packs a Msg, using the given buffer buf. If buf is too small -// a new buffer is allocated. -func (dns *Msg) PackBuffer(buf []byte) (msg []byte, err error) { - // We use a similar function in tsig.go's stripTsig. - var ( - dh Header - compression map[string]int - ) - - if dns.Compress { - compression = make(map[string]int) // Compression pointer mappings - } - - if dns.Rcode < 0 || dns.Rcode > 0xFFF { - return nil, ErrRcode - } - if dns.Rcode > 0xF { - // Regular RCODE field is 4 bits - opt := dns.IsEdns0() - if opt == nil { - return nil, ErrExtendedRcode - } - opt.SetExtendedRcode(uint8(dns.Rcode >> 4)) - dns.Rcode &= 0xF - } - - // Convert convenient Msg into wire-like Header. - dh.Id = dns.Id - dh.Bits = uint16(dns.Opcode)<<11 | uint16(dns.Rcode) - if dns.Response { - dh.Bits |= _QR - } - if dns.Authoritative { - dh.Bits |= _AA - } - if dns.Truncated { - dh.Bits |= _TC - } - if dns.RecursionDesired { - dh.Bits |= _RD - } - if dns.RecursionAvailable { - dh.Bits |= _RA - } - if dns.Zero { - dh.Bits |= _Z - } - if dns.AuthenticatedData { - dh.Bits |= _AD - } - if dns.CheckingDisabled { - dh.Bits |= _CD - } - - // Prepare variable sized arrays. - question := dns.Question - answer := dns.Answer - ns := dns.Ns - extra := dns.Extra - - dh.Qdcount = uint16(len(question)) - dh.Ancount = uint16(len(answer)) - dh.Nscount = uint16(len(ns)) - dh.Arcount = uint16(len(extra)) - - // We need the uncompressed length here, because we first pack it and then compress it. - msg = buf - uncompressedLen := compressedLen(dns, false) - if packLen := uncompressedLen + 1; len(msg) < packLen { - msg = make([]byte, packLen) - } - - // Pack it in: header and then the pieces. - off := 0 - off, err = dh.pack(msg, off, compression, dns.Compress) - if err != nil { - return nil, err - } - for i := 0; i < len(question); i++ { - off, err = question[i].pack(msg, off, compression, dns.Compress) - if err != nil { - return nil, err - } - } - for i := 0; i < len(answer); i++ { - off, err = PackRR(answer[i], msg, off, compression, dns.Compress) - if err != nil { - return nil, err - } - } - for i := 0; i < len(ns); i++ { - off, err = PackRR(ns[i], msg, off, compression, dns.Compress) - if err != nil { - return nil, err - } - } - for i := 0; i < len(extra); i++ { - off, err = PackRR(extra[i], msg, off, compression, dns.Compress) - if err != nil { - return nil, err - } - } - return msg[:off], nil -} - -// Unpack unpacks a binary message to a Msg structure. -func (dns *Msg) Unpack(msg []byte) (err error) { - var ( - dh Header - off int - ) - if dh, off, err = unpackMsgHdr(msg, off); err != nil { - return err - } - - dns.Id = dh.Id - dns.Response = (dh.Bits & _QR) != 0 - dns.Opcode = int(dh.Bits>>11) & 0xF - dns.Authoritative = (dh.Bits & _AA) != 0 - dns.Truncated = (dh.Bits & _TC) != 0 - dns.RecursionDesired = (dh.Bits & _RD) != 0 - dns.RecursionAvailable = (dh.Bits & _RA) != 0 - dns.Zero = (dh.Bits & _Z) != 0 - dns.AuthenticatedData = (dh.Bits & _AD) != 0 - dns.CheckingDisabled = (dh.Bits & _CD) != 0 - dns.Rcode = int(dh.Bits & 0xF) - - if off == len(msg) { - return ErrTruncated - } - - // Optimistically use the count given to us in the header - dns.Question = make([]Question, 0, int(dh.Qdcount)) - - for i := 0; i < int(dh.Qdcount); i++ { - off1 := off - var q Question - q, off, err = unpackQuestion(msg, off) - if err != nil { - // Even if Truncated is set, we only will set ErrTruncated if we - // actually got the questions - return err - } - if off1 == off { // Offset does not increase anymore, dh.Qdcount is a lie! - dh.Qdcount = uint16(i) - break - } - dns.Question = append(dns.Question, q) - } - - dns.Answer, off, err = unpackRRslice(int(dh.Ancount), msg, off) - // The header counts might have been wrong so we need to update it - dh.Ancount = uint16(len(dns.Answer)) - if err == nil { - dns.Ns, off, err = unpackRRslice(int(dh.Nscount), msg, off) - } - // The header counts might have been wrong so we need to update it - dh.Nscount = uint16(len(dns.Ns)) - if err == nil { - dns.Extra, off, err = unpackRRslice(int(dh.Arcount), msg, off) - } - // The header counts might have been wrong so we need to update it - dh.Arcount = uint16(len(dns.Extra)) - - if off != len(msg) { - // TODO(miek) make this an error? - // use PackOpt to let people tell how detailed the error reporting should be? - // println("dns: extra bytes in dns packet", off, "<", len(msg)) - } else if dns.Truncated { - // Whether we ran into a an error or not, we want to return that it - // was truncated - err = ErrTruncated - } - return err -} - -// Convert a complete message to a string with dig-like output. -func (dns *Msg) String() string { - if dns == nil { - return " MsgHdr" - } - s := dns.MsgHdr.String() + " " - s += "QUERY: " + strconv.Itoa(len(dns.Question)) + ", " - s += "ANSWER: " + strconv.Itoa(len(dns.Answer)) + ", " - s += "AUTHORITY: " + strconv.Itoa(len(dns.Ns)) + ", " - s += "ADDITIONAL: " + strconv.Itoa(len(dns.Extra)) + "\n" - if len(dns.Question) > 0 { - s += "\n;; QUESTION SECTION:\n" - for i := 0; i < len(dns.Question); i++ { - s += dns.Question[i].String() + "\n" - } - } - if len(dns.Answer) > 0 { - s += "\n;; ANSWER SECTION:\n" - for i := 0; i < len(dns.Answer); i++ { - if dns.Answer[i] != nil { - s += dns.Answer[i].String() + "\n" - } - } - } - if len(dns.Ns) > 0 { - s += "\n;; AUTHORITY SECTION:\n" - for i := 0; i < len(dns.Ns); i++ { - if dns.Ns[i] != nil { - s += dns.Ns[i].String() + "\n" - } - } - } - if len(dns.Extra) > 0 { - s += "\n;; ADDITIONAL SECTION:\n" - for i := 0; i < len(dns.Extra); i++ { - if dns.Extra[i] != nil { - s += dns.Extra[i].String() + "\n" - } - } - } - return s -} - -// Len returns the message length when in (un)compressed wire format. -// If dns.Compress is true compression it is taken into account. Len() -// is provided to be a faster way to get the size of the resulting packet, -// than packing it, measuring the size and discarding the buffer. -func (dns *Msg) Len() int { return compressedLen(dns, dns.Compress) } - -// compressedLen returns the message length when in compressed wire format -// when compress is true, otherwise the uncompressed length is returned. -func compressedLen(dns *Msg, compress bool) int { - // We always return one more than needed. - l := 12 // Message header is always 12 bytes - compression := map[string]int{} - - for i := 0; i < len(dns.Question); i++ { - l += dns.Question[i].len() - if compress { - compressionLenHelper(compression, dns.Question[i].Name) - } - } - for i := 0; i < len(dns.Answer); i++ { - if dns.Answer[i] == nil { - continue - } - l += dns.Answer[i].len() - if compress { - k, ok := compressionLenSearch(compression, dns.Answer[i].Header().Name) - if ok { - l += 1 - k - } - compressionLenHelper(compression, dns.Answer[i].Header().Name) - k, ok = compressionLenSearchType(compression, dns.Answer[i]) - if ok { - l += 1 - k - } - compressionLenHelperType(compression, dns.Answer[i]) - } - } - for i := 0; i < len(dns.Ns); i++ { - if dns.Ns[i] == nil { - continue - } - l += dns.Ns[i].len() - if compress { - k, ok := compressionLenSearch(compression, dns.Ns[i].Header().Name) - if ok { - l += 1 - k - } - compressionLenHelper(compression, dns.Ns[i].Header().Name) - k, ok = compressionLenSearchType(compression, dns.Ns[i]) - if ok { - l += 1 - k - } - compressionLenHelperType(compression, dns.Ns[i]) - } - } - for i := 0; i < len(dns.Extra); i++ { - if dns.Extra[i] == nil { - continue - } - l += dns.Extra[i].len() - if compress { - k, ok := compressionLenSearch(compression, dns.Extra[i].Header().Name) - if ok { - l += 1 - k - } - compressionLenHelper(compression, dns.Extra[i].Header().Name) - k, ok = compressionLenSearchType(compression, dns.Extra[i]) - if ok { - l += 1 - k - } - compressionLenHelperType(compression, dns.Extra[i]) - } - } - return l -} - -// Put the parts of the name in the compression map. -func compressionLenHelper(c map[string]int, s string) { - pref := "" - lbs := Split(s) - for j := len(lbs) - 1; j >= 0; j-- { - pref = s[lbs[j]:] - if _, ok := c[pref]; !ok { - c[pref] = len(pref) - } - } -} - -// Look for each part in the compression map and returns its length, -// keep on searching so we get the longest match. -func compressionLenSearch(c map[string]int, s string) (int, bool) { - off := 0 - end := false - if s == "" { // don't bork on bogus data - return 0, false - } - for { - if _, ok := c[s[off:]]; ok { - return len(s[off:]), true - } - if end { - break - } - off, end = NextLabel(s, off) - } - return 0, false -} - -// Copy returns a new RR which is a deep-copy of r. -func Copy(r RR) RR { r1 := r.copy(); return r1 } - -// Len returns the length (in octets) of the uncompressed RR in wire format. -func Len(r RR) int { return r.len() } - -// Copy returns a new *Msg which is a deep-copy of dns. -func (dns *Msg) Copy() *Msg { return dns.CopyTo(new(Msg)) } - -// CopyTo copies the contents to the provided message using a deep-copy and returns the copy. -func (dns *Msg) CopyTo(r1 *Msg) *Msg { - r1.MsgHdr = dns.MsgHdr - r1.Compress = dns.Compress - - if len(dns.Question) > 0 { - r1.Question = make([]Question, len(dns.Question)) - copy(r1.Question, dns.Question) // TODO(miek): Question is an immutable value, ok to do a shallow-copy - } - - rrArr := make([]RR, len(dns.Answer)+len(dns.Ns)+len(dns.Extra)) - var rri int - - if len(dns.Answer) > 0 { - rrbegin := rri - for i := 0; i < len(dns.Answer); i++ { - rrArr[rri] = dns.Answer[i].copy() - rri++ - } - r1.Answer = rrArr[rrbegin:rri:rri] - } - - if len(dns.Ns) > 0 { - rrbegin := rri - for i := 0; i < len(dns.Ns); i++ { - rrArr[rri] = dns.Ns[i].copy() - rri++ - } - r1.Ns = rrArr[rrbegin:rri:rri] - } - - if len(dns.Extra) > 0 { - rrbegin := rri - for i := 0; i < len(dns.Extra); i++ { - rrArr[rri] = dns.Extra[i].copy() - rri++ - } - r1.Extra = rrArr[rrbegin:rri:rri] - } - - return r1 -} - -func (q *Question) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := PackDomainName(q.Name, msg, off, compression, compress) - if err != nil { - return off, err - } - off, err = packUint16(q.Qtype, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(q.Qclass, msg, off) - if err != nil { - return off, err - } - return off, nil -} - -func unpackQuestion(msg []byte, off int) (Question, int, error) { - var ( - q Question - err error - ) - q.Name, off, err = UnpackDomainName(msg, off) - if err != nil { - return q, off, err - } - if off == len(msg) { - return q, off, nil - } - q.Qtype, off, err = unpackUint16(msg, off) - if err != nil { - return q, off, err - } - if off == len(msg) { - return q, off, nil - } - q.Qclass, off, err = unpackUint16(msg, off) - if off == len(msg) { - return q, off, nil - } - return q, off, err -} - -func (dh *Header) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := packUint16(dh.Id, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(dh.Bits, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(dh.Qdcount, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(dh.Ancount, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(dh.Nscount, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(dh.Arcount, msg, off) - return off, err -} - -func unpackMsgHdr(msg []byte, off int) (Header, int, error) { - var ( - dh Header - err error - ) - dh.Id, off, err = unpackUint16(msg, off) - if err != nil { - return dh, off, err - } - dh.Bits, off, err = unpackUint16(msg, off) - if err != nil { - return dh, off, err - } - dh.Qdcount, off, err = unpackUint16(msg, off) - if err != nil { - return dh, off, err - } - dh.Ancount, off, err = unpackUint16(msg, off) - if err != nil { - return dh, off, err - } - dh.Nscount, off, err = unpackUint16(msg, off) - if err != nil { - return dh, off, err - } - dh.Arcount, off, err = unpackUint16(msg, off) - return dh, off, err -} diff --git a/vendor/github.com/miekg/dns/msg_generate.go b/vendor/github.com/miekg/dns/msg_generate.go deleted file mode 100644 index 4d9f81d4329..00000000000 --- a/vendor/github.com/miekg/dns/msg_generate.go +++ /dev/null @@ -1,349 +0,0 @@ -//+build ignore - -// msg_generate.go is meant to run with go generate. It will use -// go/{importer,types} to track down all the RR struct types. Then for each type -// it will generate pack/unpack methods based on the struct tags. The generated source is -// written to zmsg.go, and is meant to be checked into git. -package main - -import ( - "bytes" - "fmt" - "go/format" - "go/importer" - "go/types" - "log" - "os" - "strings" -) - -var packageHdr = ` -// *** DO NOT MODIFY *** -// AUTOGENERATED BY go generate from msg_generate.go - -package dns - -` - -// getTypeStruct will take a type and the package scope, and return the -// (innermost) struct if the type is considered a RR type (currently defined as -// those structs beginning with a RR_Header, could be redefined as implementing -// the RR interface). The bool return value indicates if embedded structs were -// resolved. -func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) { - st, ok := t.Underlying().(*types.Struct) - if !ok { - return nil, false - } - if st.Field(0).Type() == scope.Lookup("RR_Header").Type() { - return st, false - } - if st.Field(0).Anonymous() { - st, _ := getTypeStruct(st.Field(0).Type(), scope) - return st, true - } - return nil, false -} - -func main() { - // Import and type-check the package - pkg, err := importer.Default().Import("github.com/miekg/dns") - fatalIfErr(err) - scope := pkg.Scope() - - // Collect actual types (*X) - var namedTypes []string - for _, name := range scope.Names() { - o := scope.Lookup(name) - if o == nil || !o.Exported() { - continue - } - if st, _ := getTypeStruct(o.Type(), scope); st == nil { - continue - } - if name == "PrivateRR" { - continue - } - - // Check if corresponding TypeX exists - if scope.Lookup("Type"+o.Name()) == nil && o.Name() != "RFC3597" { - log.Fatalf("Constant Type%s does not exist.", o.Name()) - } - - namedTypes = append(namedTypes, o.Name()) - } - - b := &bytes.Buffer{} - b.WriteString(packageHdr) - - fmt.Fprint(b, "// pack*() functions\n\n") - for _, name := range namedTypes { - o := scope.Lookup(name) - st, _ := getTypeStruct(o.Type(), scope) - - fmt.Fprintf(b, "func (rr *%s) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) {\n", name) - fmt.Fprint(b, `off, err := rr.Hdr.pack(msg, off, compression, compress) -if err != nil { - return off, err -} -headerEnd := off -`) - for i := 1; i < st.NumFields(); i++ { - o := func(s string) { - fmt.Fprintf(b, s, st.Field(i).Name()) - fmt.Fprint(b, `if err != nil { -return off, err -} -`) - } - - if _, ok := st.Field(i).Type().(*types.Slice); ok { - switch st.Tag(i) { - case `dns:"-"`: // ignored - case `dns:"txt"`: - o("off, err = packStringTxt(rr.%s, msg, off)\n") - case `dns:"opt"`: - o("off, err = packDataOpt(rr.%s, msg, off)\n") - case `dns:"nsec"`: - o("off, err = packDataNsec(rr.%s, msg, off)\n") - case `dns:"domain-name"`: - o("off, err = packDataDomainNames(rr.%s, msg, off, compression, compress)\n") - default: - log.Fatalln(name, st.Field(i).Name(), st.Tag(i)) - } - continue - } - - switch { - case st.Tag(i) == `dns:"-"`: // ignored - case st.Tag(i) == `dns:"cdomain-name"`: - o("off, err = PackDomainName(rr.%s, msg, off, compression, compress)\n") - case st.Tag(i) == `dns:"domain-name"`: - o("off, err = PackDomainName(rr.%s, msg, off, compression, false)\n") - case st.Tag(i) == `dns:"a"`: - o("off, err = packDataA(rr.%s, msg, off)\n") - case st.Tag(i) == `dns:"aaaa"`: - o("off, err = packDataAAAA(rr.%s, msg, off)\n") - case st.Tag(i) == `dns:"uint48"`: - o("off, err = packUint48(rr.%s, msg, off)\n") - case st.Tag(i) == `dns:"txt"`: - o("off, err = packString(rr.%s, msg, off)\n") - - case strings.HasPrefix(st.Tag(i), `dns:"size-base32`): // size-base32 can be packed just like base32 - fallthrough - case st.Tag(i) == `dns:"base32"`: - o("off, err = packStringBase32(rr.%s, msg, off)\n") - - case strings.HasPrefix(st.Tag(i), `dns:"size-base64`): // size-base64 can be packed just like base64 - fallthrough - case st.Tag(i) == `dns:"base64"`: - o("off, err = packStringBase64(rr.%s, msg, off)\n") - - case strings.HasPrefix(st.Tag(i), `dns:"size-hex:SaltLength`): - // directly write instead of using o() so we get the error check in the correct place - field := st.Field(i).Name() - fmt.Fprintf(b, `// Only pack salt if value is not "-", i.e. empty -if rr.%s != "-" { - off, err = packStringHex(rr.%s, msg, off) - if err != nil { - return off, err - } -} -`, field, field) - continue - case strings.HasPrefix(st.Tag(i), `dns:"size-hex`): // size-hex can be packed just like hex - fallthrough - case st.Tag(i) == `dns:"hex"`: - o("off, err = packStringHex(rr.%s, msg, off)\n") - - case st.Tag(i) == `dns:"octet"`: - o("off, err = packStringOctet(rr.%s, msg, off)\n") - case st.Tag(i) == "": - switch st.Field(i).Type().(*types.Basic).Kind() { - case types.Uint8: - o("off, err = packUint8(rr.%s, msg, off)\n") - case types.Uint16: - o("off, err = packUint16(rr.%s, msg, off)\n") - case types.Uint32: - o("off, err = packUint32(rr.%s, msg, off)\n") - case types.Uint64: - o("off, err = packUint64(rr.%s, msg, off)\n") - case types.String: - o("off, err = packString(rr.%s, msg, off)\n") - default: - log.Fatalln(name, st.Field(i).Name()) - } - default: - log.Fatalln(name, st.Field(i).Name(), st.Tag(i)) - } - } - // We have packed everything, only now we know the rdlength of this RR - fmt.Fprintln(b, "rr.Header().Rdlength = uint16(off-headerEnd)") - fmt.Fprintln(b, "return off, nil }\n") - } - - fmt.Fprint(b, "// unpack*() functions\n\n") - for _, name := range namedTypes { - o := scope.Lookup(name) - st, _ := getTypeStruct(o.Type(), scope) - - fmt.Fprintf(b, "func unpack%s(h RR_Header, msg []byte, off int) (RR, int, error) {\n", name) - fmt.Fprintf(b, "rr := new(%s)\n", name) - fmt.Fprint(b, "rr.Hdr = h\n") - fmt.Fprint(b, `if noRdata(h) { -return rr, off, nil - } -var err error -rdStart := off -_ = rdStart - -`) - for i := 1; i < st.NumFields(); i++ { - o := func(s string) { - fmt.Fprintf(b, s, st.Field(i).Name()) - fmt.Fprint(b, `if err != nil { -return rr, off, err -} -`) - } - - // size-* are special, because they reference a struct member we should use for the length. - if strings.HasPrefix(st.Tag(i), `dns:"size-`) { - structMember := structMember(st.Tag(i)) - structTag := structTag(st.Tag(i)) - switch structTag { - case "hex": - fmt.Fprintf(b, "rr.%s, off, err = unpackStringHex(msg, off, off + int(rr.%s))\n", st.Field(i).Name(), structMember) - case "base32": - fmt.Fprintf(b, "rr.%s, off, err = unpackStringBase32(msg, off, off + int(rr.%s))\n", st.Field(i).Name(), structMember) - case "base64": - fmt.Fprintf(b, "rr.%s, off, err = unpackStringBase64(msg, off, off + int(rr.%s))\n", st.Field(i).Name(), structMember) - default: - log.Fatalln(name, st.Field(i).Name(), st.Tag(i)) - } - fmt.Fprint(b, `if err != nil { -return rr, off, err -} -`) - continue - } - - if _, ok := st.Field(i).Type().(*types.Slice); ok { - switch st.Tag(i) { - case `dns:"-"`: // ignored - case `dns:"txt"`: - o("rr.%s, off, err = unpackStringTxt(msg, off)\n") - case `dns:"opt"`: - o("rr.%s, off, err = unpackDataOpt(msg, off)\n") - case `dns:"nsec"`: - o("rr.%s, off, err = unpackDataNsec(msg, off)\n") - case `dns:"domain-name"`: - o("rr.%s, off, err = unpackDataDomainNames(msg, off, rdStart + int(rr.Hdr.Rdlength))\n") - default: - log.Fatalln(name, st.Field(i).Name(), st.Tag(i)) - } - continue - } - - switch st.Tag(i) { - case `dns:"-"`: // ignored - case `dns:"cdomain-name"`: - fallthrough - case `dns:"domain-name"`: - o("rr.%s, off, err = UnpackDomainName(msg, off)\n") - case `dns:"a"`: - o("rr.%s, off, err = unpackDataA(msg, off)\n") - case `dns:"aaaa"`: - o("rr.%s, off, err = unpackDataAAAA(msg, off)\n") - case `dns:"uint48"`: - o("rr.%s, off, err = unpackUint48(msg, off)\n") - case `dns:"txt"`: - o("rr.%s, off, err = unpackString(msg, off)\n") - case `dns:"base32"`: - o("rr.%s, off, err = unpackStringBase32(msg, off, rdStart + int(rr.Hdr.Rdlength))\n") - case `dns:"base64"`: - o("rr.%s, off, err = unpackStringBase64(msg, off, rdStart + int(rr.Hdr.Rdlength))\n") - case `dns:"hex"`: - o("rr.%s, off, err = unpackStringHex(msg, off, rdStart + int(rr.Hdr.Rdlength))\n") - case `dns:"octet"`: - o("rr.%s, off, err = unpackStringOctet(msg, off)\n") - case "": - switch st.Field(i).Type().(*types.Basic).Kind() { - case types.Uint8: - o("rr.%s, off, err = unpackUint8(msg, off)\n") - case types.Uint16: - o("rr.%s, off, err = unpackUint16(msg, off)\n") - case types.Uint32: - o("rr.%s, off, err = unpackUint32(msg, off)\n") - case types.Uint64: - o("rr.%s, off, err = unpackUint64(msg, off)\n") - case types.String: - o("rr.%s, off, err = unpackString(msg, off)\n") - default: - log.Fatalln(name, st.Field(i).Name()) - } - default: - log.Fatalln(name, st.Field(i).Name(), st.Tag(i)) - } - // If we've hit len(msg) we return without error. - if i < st.NumFields()-1 { - fmt.Fprintf(b, `if off == len(msg) { -return rr, off, nil - } -`) - } - } - fmt.Fprintf(b, "return rr, off, err }\n\n") - } - // Generate typeToUnpack map - fmt.Fprintln(b, "var typeToUnpack = map[uint16]func(RR_Header, []byte, int) (RR, int, error){") - for _, name := range namedTypes { - if name == "RFC3597" { - continue - } - fmt.Fprintf(b, "Type%s: unpack%s,\n", name, name) - } - fmt.Fprintln(b, "}\n") - - // gofmt - res, err := format.Source(b.Bytes()) - if err != nil { - b.WriteTo(os.Stderr) - log.Fatal(err) - } - - // write result - f, err := os.Create("zmsg.go") - fatalIfErr(err) - defer f.Close() - f.Write(res) -} - -// structMember will take a tag like dns:"size-base32:SaltLength" and return the last part of this string. -func structMember(s string) string { - fields := strings.Split(s, ":") - if len(fields) == 0 { - return "" - } - f := fields[len(fields)-1] - // f should have a closing " - if len(f) > 1 { - return f[:len(f)-1] - } - return f -} - -// structTag will take a tag like dns:"size-base32:SaltLength" and return base32. -func structTag(s string) string { - fields := strings.Split(s, ":") - if len(fields) < 2 { - return "" - } - return fields[1][len("\"size-"):] -} - -func fatalIfErr(err error) { - if err != nil { - log.Fatal(err) - } -} diff --git a/vendor/github.com/miekg/dns/msg_helpers.go b/vendor/github.com/miekg/dns/msg_helpers.go deleted file mode 100644 index 8d415c92a37..00000000000 --- a/vendor/github.com/miekg/dns/msg_helpers.go +++ /dev/null @@ -1,633 +0,0 @@ -package dns - -import ( - "encoding/base32" - "encoding/base64" - "encoding/binary" - "encoding/hex" - "net" - "strconv" -) - -// helper functions called from the generated zmsg.go - -// These function are named after the tag to help pack/unpack, if there is no tag it is the name -// of the type they pack/unpack (string, int, etc). We prefix all with unpackData or packData, so packDataA or -// packDataDomainName. - -func unpackDataA(msg []byte, off int) (net.IP, int, error) { - if off+net.IPv4len > len(msg) { - return nil, len(msg), &Error{err: "overflow unpacking a"} - } - a := append(make(net.IP, 0, net.IPv4len), msg[off:off+net.IPv4len]...) - off += net.IPv4len - return a, off, nil -} - -func packDataA(a net.IP, msg []byte, off int) (int, error) { - // It must be a slice of 4, even if it is 16, we encode only the first 4 - if off+net.IPv4len > len(msg) { - return len(msg), &Error{err: "overflow packing a"} - } - switch len(a) { - case net.IPv4len, net.IPv6len: - copy(msg[off:], a.To4()) - off += net.IPv4len - case 0: - // Allowed, for dynamic updates. - default: - return len(msg), &Error{err: "overflow packing a"} - } - return off, nil -} - -func unpackDataAAAA(msg []byte, off int) (net.IP, int, error) { - if off+net.IPv6len > len(msg) { - return nil, len(msg), &Error{err: "overflow unpacking aaaa"} - } - aaaa := append(make(net.IP, 0, net.IPv6len), msg[off:off+net.IPv6len]...) - off += net.IPv6len - return aaaa, off, nil -} - -func packDataAAAA(aaaa net.IP, msg []byte, off int) (int, error) { - if off+net.IPv6len > len(msg) { - return len(msg), &Error{err: "overflow packing aaaa"} - } - - switch len(aaaa) { - case net.IPv6len: - copy(msg[off:], aaaa) - off += net.IPv6len - case 0: - // Allowed, dynamic updates. - default: - return len(msg), &Error{err: "overflow packing aaaa"} - } - return off, nil -} - -// unpackHeader unpacks an RR header, returning the offset to the end of the header and a -// re-sliced msg according to the expected length of the RR. -func unpackHeader(msg []byte, off int) (rr RR_Header, off1 int, truncmsg []byte, err error) { - hdr := RR_Header{} - if off == len(msg) { - return hdr, off, msg, nil - } - - hdr.Name, off, err = UnpackDomainName(msg, off) - if err != nil { - return hdr, len(msg), msg, err - } - hdr.Rrtype, off, err = unpackUint16(msg, off) - if err != nil { - return hdr, len(msg), msg, err - } - hdr.Class, off, err = unpackUint16(msg, off) - if err != nil { - return hdr, len(msg), msg, err - } - hdr.Ttl, off, err = unpackUint32(msg, off) - if err != nil { - return hdr, len(msg), msg, err - } - hdr.Rdlength, off, err = unpackUint16(msg, off) - if err != nil { - return hdr, len(msg), msg, err - } - msg, err = truncateMsgFromRdlength(msg, off, hdr.Rdlength) - return hdr, off, msg, err -} - -// pack packs an RR header, returning the offset to the end of the header. -// See PackDomainName for documentation about the compression. -func (hdr RR_Header) pack(msg []byte, off int, compression map[string]int, compress bool) (off1 int, err error) { - if off == len(msg) { - return off, nil - } - - off, err = PackDomainName(hdr.Name, msg, off, compression, compress) - if err != nil { - return len(msg), err - } - off, err = packUint16(hdr.Rrtype, msg, off) - if err != nil { - return len(msg), err - } - off, err = packUint16(hdr.Class, msg, off) - if err != nil { - return len(msg), err - } - off, err = packUint32(hdr.Ttl, msg, off) - if err != nil { - return len(msg), err - } - off, err = packUint16(hdr.Rdlength, msg, off) - if err != nil { - return len(msg), err - } - return off, nil -} - -// helper helper functions. - -// truncateMsgFromRdLength truncates msg to match the expected length of the RR. -// Returns an error if msg is smaller than the expected size. -func truncateMsgFromRdlength(msg []byte, off int, rdlength uint16) (truncmsg []byte, err error) { - lenrd := off + int(rdlength) - if lenrd > len(msg) { - return msg, &Error{err: "overflowing header size"} - } - return msg[:lenrd], nil -} - -func fromBase32(s []byte) (buf []byte, err error) { - for i, b := range s { - if b >= 'a' && b <= 'z' { - s[i] = b - 32 - } - } - buflen := base32.HexEncoding.DecodedLen(len(s)) - buf = make([]byte, buflen) - n, err := base32.HexEncoding.Decode(buf, s) - buf = buf[:n] - return -} - -func toBase32(b []byte) string { return base32.HexEncoding.EncodeToString(b) } - -func fromBase64(s []byte) (buf []byte, err error) { - buflen := base64.StdEncoding.DecodedLen(len(s)) - buf = make([]byte, buflen) - n, err := base64.StdEncoding.Decode(buf, s) - buf = buf[:n] - return -} - -func toBase64(b []byte) string { return base64.StdEncoding.EncodeToString(b) } - -// dynamicUpdate returns true if the Rdlength is zero. -func noRdata(h RR_Header) bool { return h.Rdlength == 0 } - -func unpackUint8(msg []byte, off int) (i uint8, off1 int, err error) { - if off+1 > len(msg) { - return 0, len(msg), &Error{err: "overflow unpacking uint8"} - } - return uint8(msg[off]), off + 1, nil -} - -func packUint8(i uint8, msg []byte, off int) (off1 int, err error) { - if off+1 > len(msg) { - return len(msg), &Error{err: "overflow packing uint8"} - } - msg[off] = byte(i) - return off + 1, nil -} - -func unpackUint16(msg []byte, off int) (i uint16, off1 int, err error) { - if off+2 > len(msg) { - return 0, len(msg), &Error{err: "overflow unpacking uint16"} - } - return binary.BigEndian.Uint16(msg[off:]), off + 2, nil -} - -func packUint16(i uint16, msg []byte, off int) (off1 int, err error) { - if off+2 > len(msg) { - return len(msg), &Error{err: "overflow packing uint16"} - } - binary.BigEndian.PutUint16(msg[off:], i) - return off + 2, nil -} - -func unpackUint32(msg []byte, off int) (i uint32, off1 int, err error) { - if off+4 > len(msg) { - return 0, len(msg), &Error{err: "overflow unpacking uint32"} - } - return binary.BigEndian.Uint32(msg[off:]), off + 4, nil -} - -func packUint32(i uint32, msg []byte, off int) (off1 int, err error) { - if off+4 > len(msg) { - return len(msg), &Error{err: "overflow packing uint32"} - } - binary.BigEndian.PutUint32(msg[off:], i) - return off + 4, nil -} - -func unpackUint48(msg []byte, off int) (i uint64, off1 int, err error) { - if off+6 > len(msg) { - return 0, len(msg), &Error{err: "overflow unpacking uint64 as uint48"} - } - // Used in TSIG where the last 48 bits are occupied, so for now, assume a uint48 (6 bytes) - i = (uint64(uint64(msg[off])<<40 | uint64(msg[off+1])<<32 | uint64(msg[off+2])<<24 | uint64(msg[off+3])<<16 | - uint64(msg[off+4])<<8 | uint64(msg[off+5]))) - off += 6 - return i, off, nil -} - -func packUint48(i uint64, msg []byte, off int) (off1 int, err error) { - if off+6 > len(msg) { - return len(msg), &Error{err: "overflow packing uint64 as uint48"} - } - msg[off] = byte(i >> 40) - msg[off+1] = byte(i >> 32) - msg[off+2] = byte(i >> 24) - msg[off+3] = byte(i >> 16) - msg[off+4] = byte(i >> 8) - msg[off+5] = byte(i) - off += 6 - return off, nil -} - -func unpackUint64(msg []byte, off int) (i uint64, off1 int, err error) { - if off+8 > len(msg) { - return 0, len(msg), &Error{err: "overflow unpacking uint64"} - } - return binary.BigEndian.Uint64(msg[off:]), off + 8, nil -} - -func packUint64(i uint64, msg []byte, off int) (off1 int, err error) { - if off+8 > len(msg) { - return len(msg), &Error{err: "overflow packing uint64"} - } - binary.BigEndian.PutUint64(msg[off:], i) - off += 8 - return off, nil -} - -func unpackString(msg []byte, off int) (string, int, error) { - if off+1 > len(msg) { - return "", off, &Error{err: "overflow unpacking txt"} - } - l := int(msg[off]) - if off+l+1 > len(msg) { - return "", off, &Error{err: "overflow unpacking txt"} - } - s := make([]byte, 0, l) - for _, b := range msg[off+1 : off+1+l] { - switch b { - case '"', '\\': - s = append(s, '\\', b) - default: - if b < 32 || b > 127 { // unprintable - var buf [3]byte - bufs := strconv.AppendInt(buf[:0], int64(b), 10) - s = append(s, '\\') - for i := 0; i < 3-len(bufs); i++ { - s = append(s, '0') - } - for _, r := range bufs { - s = append(s, r) - } - } else { - s = append(s, b) - } - } - } - off += 1 + l - return string(s), off, nil -} - -func packString(s string, msg []byte, off int) (int, error) { - txtTmp := make([]byte, 256*4+1) - off, err := packTxtString(s, msg, off, txtTmp) - if err != nil { - return len(msg), err - } - return off, nil -} - -func unpackStringBase32(msg []byte, off, end int) (string, int, error) { - if end > len(msg) { - return "", len(msg), &Error{err: "overflow unpacking base32"} - } - s := toBase32(msg[off:end]) - return s, end, nil -} - -func packStringBase32(s string, msg []byte, off int) (int, error) { - b32, err := fromBase32([]byte(s)) - if err != nil { - return len(msg), err - } - if off+len(b32) > len(msg) { - return len(msg), &Error{err: "overflow packing base32"} - } - copy(msg[off:off+len(b32)], b32) - off += len(b32) - return off, nil -} - -func unpackStringBase64(msg []byte, off, end int) (string, int, error) { - // Rest of the RR is base64 encoded value, so we don't need an explicit length - // to be set. Thus far all RR's that have base64 encoded fields have those as their - // last one. What we do need is the end of the RR! - if end > len(msg) { - return "", len(msg), &Error{err: "overflow unpacking base64"} - } - s := toBase64(msg[off:end]) - return s, end, nil -} - -func packStringBase64(s string, msg []byte, off int) (int, error) { - b64, err := fromBase64([]byte(s)) - if err != nil { - return len(msg), err - } - if off+len(b64) > len(msg) { - return len(msg), &Error{err: "overflow packing base64"} - } - copy(msg[off:off+len(b64)], b64) - off += len(b64) - return off, nil -} - -func unpackStringHex(msg []byte, off, end int) (string, int, error) { - // Rest of the RR is hex encoded value, so we don't need an explicit length - // to be set. NSEC and TSIG have hex fields with a length field. - // What we do need is the end of the RR! - if end > len(msg) { - return "", len(msg), &Error{err: "overflow unpacking hex"} - } - - s := hex.EncodeToString(msg[off:end]) - return s, end, nil -} - -func packStringHex(s string, msg []byte, off int) (int, error) { - h, err := hex.DecodeString(s) - if err != nil { - return len(msg), err - } - if off+(len(h)) > len(msg) { - return len(msg), &Error{err: "overflow packing hex"} - } - copy(msg[off:off+len(h)], h) - off += len(h) - return off, nil -} - -func unpackStringTxt(msg []byte, off int) ([]string, int, error) { - txt, off, err := unpackTxt(msg, off) - if err != nil { - return nil, len(msg), err - } - return txt, off, nil -} - -func packStringTxt(s []string, msg []byte, off int) (int, error) { - txtTmp := make([]byte, 256*4+1) // If the whole string consists out of \DDD we need this many. - off, err := packTxt(s, msg, off, txtTmp) - if err != nil { - return len(msg), err - } - return off, nil -} - -func unpackDataOpt(msg []byte, off int) ([]EDNS0, int, error) { - var edns []EDNS0 -Option: - code := uint16(0) - if off+4 > len(msg) { - return nil, len(msg), &Error{err: "overflow unpacking opt"} - } - code = binary.BigEndian.Uint16(msg[off:]) - off += 2 - optlen := binary.BigEndian.Uint16(msg[off:]) - off += 2 - if off+int(optlen) > len(msg) { - return nil, len(msg), &Error{err: "overflow unpacking opt"} - } - switch code { - case EDNS0NSID: - e := new(EDNS0_NSID) - if err := e.unpack(msg[off : off+int(optlen)]); err != nil { - return nil, len(msg), err - } - edns = append(edns, e) - off += int(optlen) - case EDNS0SUBNET, EDNS0SUBNETDRAFT: - e := new(EDNS0_SUBNET) - if err := e.unpack(msg[off : off+int(optlen)]); err != nil { - return nil, len(msg), err - } - edns = append(edns, e) - off += int(optlen) - if code == EDNS0SUBNETDRAFT { - e.DraftOption = true - } - case EDNS0COOKIE: - e := new(EDNS0_COOKIE) - if err := e.unpack(msg[off : off+int(optlen)]); err != nil { - return nil, len(msg), err - } - edns = append(edns, e) - off += int(optlen) - case EDNS0UL: - e := new(EDNS0_UL) - if err := e.unpack(msg[off : off+int(optlen)]); err != nil { - return nil, len(msg), err - } - edns = append(edns, e) - off += int(optlen) - case EDNS0LLQ: - e := new(EDNS0_LLQ) - if err := e.unpack(msg[off : off+int(optlen)]); err != nil { - return nil, len(msg), err - } - edns = append(edns, e) - off += int(optlen) - case EDNS0DAU: - e := new(EDNS0_DAU) - if err := e.unpack(msg[off : off+int(optlen)]); err != nil { - return nil, len(msg), err - } - edns = append(edns, e) - off += int(optlen) - case EDNS0DHU: - e := new(EDNS0_DHU) - if err := e.unpack(msg[off : off+int(optlen)]); err != nil { - return nil, len(msg), err - } - edns = append(edns, e) - off += int(optlen) - case EDNS0N3U: - e := new(EDNS0_N3U) - if err := e.unpack(msg[off : off+int(optlen)]); err != nil { - return nil, len(msg), err - } - edns = append(edns, e) - off += int(optlen) - default: - e := new(EDNS0_LOCAL) - e.Code = code - if err := e.unpack(msg[off : off+int(optlen)]); err != nil { - return nil, len(msg), err - } - edns = append(edns, e) - off += int(optlen) - } - - if off < len(msg) { - goto Option - } - - return edns, off, nil -} - -func packDataOpt(options []EDNS0, msg []byte, off int) (int, error) { - for _, el := range options { - b, err := el.pack() - if err != nil || off+3 > len(msg) { - return len(msg), &Error{err: "overflow packing opt"} - } - binary.BigEndian.PutUint16(msg[off:], el.Option()) // Option code - binary.BigEndian.PutUint16(msg[off+2:], uint16(len(b))) // Length - off += 4 - if off+len(b) > len(msg) { - copy(msg[off:], b) - off = len(msg) - continue - } - // Actual data - copy(msg[off:off+len(b)], b) - off += len(b) - } - return off, nil -} - -func unpackStringOctet(msg []byte, off int) (string, int, error) { - s := string(msg[off:]) - return s, len(msg), nil -} - -func packStringOctet(s string, msg []byte, off int) (int, error) { - txtTmp := make([]byte, 256*4+1) - off, err := packOctetString(s, msg, off, txtTmp) - if err != nil { - return len(msg), err - } - return off, nil -} - -func unpackDataNsec(msg []byte, off int) ([]uint16, int, error) { - var nsec []uint16 - length, window, lastwindow := 0, 0, -1 - for off < len(msg) { - if off+2 > len(msg) { - return nsec, len(msg), &Error{err: "overflow unpacking nsecx"} - } - window = int(msg[off]) - length = int(msg[off+1]) - off += 2 - if window <= lastwindow { - // RFC 4034: Blocks are present in the NSEC RR RDATA in - // increasing numerical order. - return nsec, len(msg), &Error{err: "out of order NSEC block"} - } - if length == 0 { - // RFC 4034: Blocks with no types present MUST NOT be included. - return nsec, len(msg), &Error{err: "empty NSEC block"} - } - if length > 32 { - return nsec, len(msg), &Error{err: "NSEC block too long"} - } - if off+length > len(msg) { - return nsec, len(msg), &Error{err: "overflowing NSEC block"} - } - - // Walk the bytes in the window and extract the type bits - for j := 0; j < length; j++ { - b := msg[off+j] - // Check the bits one by one, and set the type - if b&0x80 == 0x80 { - nsec = append(nsec, uint16(window*256+j*8+0)) - } - if b&0x40 == 0x40 { - nsec = append(nsec, uint16(window*256+j*8+1)) - } - if b&0x20 == 0x20 { - nsec = append(nsec, uint16(window*256+j*8+2)) - } - if b&0x10 == 0x10 { - nsec = append(nsec, uint16(window*256+j*8+3)) - } - if b&0x8 == 0x8 { - nsec = append(nsec, uint16(window*256+j*8+4)) - } - if b&0x4 == 0x4 { - nsec = append(nsec, uint16(window*256+j*8+5)) - } - if b&0x2 == 0x2 { - nsec = append(nsec, uint16(window*256+j*8+6)) - } - if b&0x1 == 0x1 { - nsec = append(nsec, uint16(window*256+j*8+7)) - } - } - off += length - lastwindow = window - } - return nsec, off, nil -} - -func packDataNsec(bitmap []uint16, msg []byte, off int) (int, error) { - if len(bitmap) == 0 { - return off, nil - } - var lastwindow, lastlength uint16 - for j := 0; j < len(bitmap); j++ { - t := bitmap[j] - window := t / 256 - length := (t-window*256)/8 + 1 - if window > lastwindow && lastlength != 0 { // New window, jump to the new offset - off += int(lastlength) + 2 - lastlength = 0 - } - if window < lastwindow || length < lastlength { - return len(msg), &Error{err: "nsec bits out of order"} - } - if off+2+int(length) > len(msg) { - return len(msg), &Error{err: "overflow packing nsec"} - } - // Setting the window # - msg[off] = byte(window) - // Setting the octets length - msg[off+1] = byte(length) - // Setting the bit value for the type in the right octet - msg[off+1+int(length)] |= byte(1 << (7 - (t % 8))) - lastwindow, lastlength = window, length - } - off += int(lastlength) + 2 - return off, nil -} - -func unpackDataDomainNames(msg []byte, off, end int) ([]string, int, error) { - var ( - servers []string - s string - err error - ) - if end > len(msg) { - return nil, len(msg), &Error{err: "overflow unpacking domain names"} - } - for off < end { - s, off, err = UnpackDomainName(msg, off) - if err != nil { - return servers, len(msg), err - } - servers = append(servers, s) - } - return servers, off, nil -} - -func packDataDomainNames(names []string, msg []byte, off int, compression map[string]int, compress bool) (int, error) { - var err error - for j := 0; j < len(names); j++ { - off, err = PackDomainName(names[j], msg, off, compression, false && compress) - if err != nil { - return len(msg), err - } - } - return off, nil -} diff --git a/vendor/github.com/miekg/dns/nsecx.go b/vendor/github.com/miekg/dns/nsecx.go deleted file mode 100644 index 9b908c44786..00000000000 --- a/vendor/github.com/miekg/dns/nsecx.go +++ /dev/null @@ -1,106 +0,0 @@ -package dns - -import ( - "crypto/sha1" - "hash" - "strings" -) - -type saltWireFmt struct { - Salt string `dns:"size-hex"` -} - -// HashName hashes a string (label) according to RFC 5155. It returns the hashed string in uppercase. -func HashName(label string, ha uint8, iter uint16, salt string) string { - saltwire := new(saltWireFmt) - saltwire.Salt = salt - wire := make([]byte, DefaultMsgSize) - n, err := packSaltWire(saltwire, wire) - if err != nil { - return "" - } - wire = wire[:n] - name := make([]byte, 255) - off, err := PackDomainName(strings.ToLower(label), name, 0, nil, false) - if err != nil { - return "" - } - name = name[:off] - var s hash.Hash - switch ha { - case SHA1: - s = sha1.New() - default: - return "" - } - - // k = 0 - s.Write(name) - s.Write(wire) - nsec3 := s.Sum(nil) - // k > 0 - for k := uint16(0); k < iter; k++ { - s.Reset() - s.Write(nsec3) - s.Write(wire) - nsec3 = s.Sum(nsec3[:0]) - } - return toBase32(nsec3) -} - -// Cover returns true if a name is covered by the NSEC3 record -func (rr *NSEC3) Cover(name string) bool { - nameHash := HashName(name, rr.Hash, rr.Iterations, rr.Salt) - owner := strings.ToUpper(rr.Hdr.Name) - labelIndices := Split(owner) - if len(labelIndices) < 2 { - return false - } - ownerHash := owner[:labelIndices[1]-1] - ownerZone := owner[labelIndices[1]:] - if !IsSubDomain(ownerZone, strings.ToUpper(name)) { // name is outside owner zone - return false - } - - nextHash := rr.NextDomain - if ownerHash == nextHash { // empty interval - return false - } - if ownerHash > nextHash { // end of zone - if nameHash > ownerHash { // covered since there is nothing after ownerHash - return true - } - return nameHash < nextHash // if nameHash is before beginning of zone it is covered - } - if nameHash < ownerHash { // nameHash is before ownerHash, not covered - return false - } - return nameHash < nextHash // if nameHash is before nextHash is it covered (between ownerHash and nextHash) -} - -// Match returns true if a name matches the NSEC3 record -func (rr *NSEC3) Match(name string) bool { - nameHash := HashName(name, rr.Hash, rr.Iterations, rr.Salt) - owner := strings.ToUpper(rr.Hdr.Name) - labelIndices := Split(owner) - if len(labelIndices) < 2 { - return false - } - ownerHash := owner[:labelIndices[1]-1] - ownerZone := owner[labelIndices[1]:] - if !IsSubDomain(ownerZone, strings.ToUpper(name)) { // name is outside owner zone - return false - } - if ownerHash == nameHash { - return true - } - return false -} - -func packSaltWire(sw *saltWireFmt, msg []byte) (int, error) { - off, err := packStringHex(sw.Salt, msg, 0) - if err != nil { - return off, err - } - return off, nil -} diff --git a/vendor/github.com/miekg/dns/privaterr.go b/vendor/github.com/miekg/dns/privaterr.go deleted file mode 100644 index 6b08e6e9592..00000000000 --- a/vendor/github.com/miekg/dns/privaterr.go +++ /dev/null @@ -1,149 +0,0 @@ -package dns - -import ( - "fmt" - "strings" -) - -// PrivateRdata is an interface used for implementing "Private Use" RR types, see -// RFC 6895. This allows one to experiment with new RR types, without requesting an -// official type code. Also see dns.PrivateHandle and dns.PrivateHandleRemove. -type PrivateRdata interface { - // String returns the text presentaton of the Rdata of the Private RR. - String() string - // Parse parses the Rdata of the private RR. - Parse([]string) error - // Pack is used when packing a private RR into a buffer. - Pack([]byte) (int, error) - // Unpack is used when unpacking a private RR from a buffer. - // TODO(miek): diff. signature than Pack, see edns0.go for instance. - Unpack([]byte) (int, error) - // Copy copies the Rdata. - Copy(PrivateRdata) error - // Len returns the length in octets of the Rdata. - Len() int -} - -// PrivateRR represents an RR that uses a PrivateRdata user-defined type. -// It mocks normal RRs and implements dns.RR interface. -type PrivateRR struct { - Hdr RR_Header - Data PrivateRdata -} - -func mkPrivateRR(rrtype uint16) *PrivateRR { - // Panics if RR is not an instance of PrivateRR. - rrfunc, ok := TypeToRR[rrtype] - if !ok { - panic(fmt.Sprintf("dns: invalid operation with Private RR type %d", rrtype)) - } - - anyrr := rrfunc() - switch rr := anyrr.(type) { - case *PrivateRR: - return rr - } - panic(fmt.Sprintf("dns: RR is not a PrivateRR, TypeToRR[%d] generator returned %T", rrtype, anyrr)) -} - -// Header return the RR header of r. -func (r *PrivateRR) Header() *RR_Header { return &r.Hdr } - -func (r *PrivateRR) String() string { return r.Hdr.String() + r.Data.String() } - -// Private len and copy parts to satisfy RR interface. -func (r *PrivateRR) len() int { return r.Hdr.len() + r.Data.Len() } -func (r *PrivateRR) copy() RR { - // make new RR like this: - rr := mkPrivateRR(r.Hdr.Rrtype) - newh := r.Hdr.copyHeader() - rr.Hdr = *newh - - err := r.Data.Copy(rr.Data) - if err != nil { - panic("dns: got value that could not be used to copy Private rdata") - } - return rr -} -func (r *PrivateRR) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := r.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - n, err := r.Data.Pack(msg[off:]) - if err != nil { - return len(msg), err - } - off += n - r.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -// PrivateHandle registers a private resource record type. It requires -// string and numeric representation of private RR type and generator function as argument. -func PrivateHandle(rtypestr string, rtype uint16, generator func() PrivateRdata) { - rtypestr = strings.ToUpper(rtypestr) - - TypeToRR[rtype] = func() RR { return &PrivateRR{RR_Header{}, generator()} } - TypeToString[rtype] = rtypestr - StringToType[rtypestr] = rtype - - typeToUnpack[rtype] = func(h RR_Header, msg []byte, off int) (RR, int, error) { - if noRdata(h) { - return &h, off, nil - } - var err error - - rr := mkPrivateRR(h.Rrtype) - rr.Hdr = h - - off1, err := rr.Data.Unpack(msg[off:]) - off += off1 - if err != nil { - return rr, off, err - } - return rr, off, err - } - - setPrivateRR := func(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := mkPrivateRR(h.Rrtype) - rr.Hdr = h - - var l lex - text := make([]string, 0, 2) // could be 0..N elements, median is probably 1 - Fetch: - for { - // TODO(miek): we could also be returning _QUOTE, this might or might not - // be an issue (basically parsing TXT becomes hard) - switch l = <-c; l.value { - case zNewline, zEOF: - break Fetch - case zString: - text = append(text, l.token) - } - } - - err := rr.Data.Parse(text) - if err != nil { - return nil, &ParseError{f, err.Error(), l}, "" - } - - return rr, nil, "" - } - - typeToparserFunc[rtype] = parserFunc{setPrivateRR, true} -} - -// PrivateHandleRemove removes defenitions required to support private RR type. -func PrivateHandleRemove(rtype uint16) { - rtypestr, ok := TypeToString[rtype] - if ok { - delete(TypeToRR, rtype) - delete(TypeToString, rtype) - delete(typeToparserFunc, rtype) - delete(StringToType, rtypestr) - delete(typeToUnpack, rtype) - } - return -} diff --git a/vendor/github.com/miekg/dns/rawmsg.go b/vendor/github.com/miekg/dns/rawmsg.go deleted file mode 100644 index 6e21fba7e1f..00000000000 --- a/vendor/github.com/miekg/dns/rawmsg.go +++ /dev/null @@ -1,49 +0,0 @@ -package dns - -import "encoding/binary" - -// rawSetRdlength sets the rdlength in the header of -// the RR. The offset 'off' must be positioned at the -// start of the header of the RR, 'end' must be the -// end of the RR. -func rawSetRdlength(msg []byte, off, end int) bool { - l := len(msg) -Loop: - for { - if off+1 > l { - return false - } - c := int(msg[off]) - off++ - switch c & 0xC0 { - case 0x00: - if c == 0x00 { - // End of the domainname - break Loop - } - if off+c > l { - return false - } - off += c - - case 0xC0: - // pointer, next byte included, ends domainname - off++ - break Loop - } - } - // The domainname has been seen, we at the start of the fixed part in the header. - // Type is 2 bytes, class is 2 bytes, ttl 4 and then 2 bytes for the length. - off += 2 + 2 + 4 - if off+2 > l { - return false - } - //off+1 is the end of the header, 'end' is the end of the rr - //so 'end' - 'off+2' is the length of the rdata - rdatalen := end - (off + 2) - if rdatalen > 0xFFFF { - return false - } - binary.BigEndian.PutUint16(msg[off:], uint16(rdatalen)) - return true -} diff --git a/vendor/github.com/miekg/dns/reverse.go b/vendor/github.com/miekg/dns/reverse.go deleted file mode 100644 index f6e7a47a6e8..00000000000 --- a/vendor/github.com/miekg/dns/reverse.go +++ /dev/null @@ -1,38 +0,0 @@ -package dns - -// StringToType is the reverse of TypeToString, needed for string parsing. -var StringToType = reverseInt16(TypeToString) - -// StringToClass is the reverse of ClassToString, needed for string parsing. -var StringToClass = reverseInt16(ClassToString) - -// StringToOpcode is a map of opcodes to strings. -var StringToOpcode = reverseInt(OpcodeToString) - -// StringToRcode is a map of rcodes to strings. -var StringToRcode = reverseInt(RcodeToString) - -// Reverse a map -func reverseInt8(m map[uint8]string) map[string]uint8 { - n := make(map[string]uint8, len(m)) - for u, s := range m { - n[s] = u - } - return n -} - -func reverseInt16(m map[uint16]string) map[string]uint16 { - n := make(map[string]uint16, len(m)) - for u, s := range m { - n[s] = u - } - return n -} - -func reverseInt(m map[int]string) map[string]int { - n := make(map[string]int, len(m)) - for u, s := range m { - n[s] = u - } - return n -} diff --git a/vendor/github.com/miekg/dns/sanitize.go b/vendor/github.com/miekg/dns/sanitize.go deleted file mode 100644 index b489f3f050b..00000000000 --- a/vendor/github.com/miekg/dns/sanitize.go +++ /dev/null @@ -1,84 +0,0 @@ -package dns - -// Dedup removes identical RRs from rrs. It preserves the original ordering. -// The lowest TTL of any duplicates is used in the remaining one. Dedup modifies -// rrs. -// m is used to store the RRs temporay. If it is nil a new map will be allocated. -func Dedup(rrs []RR, m map[string]RR) []RR { - if m == nil { - m = make(map[string]RR) - } - // Save the keys, so we don't have to call normalizedString twice. - keys := make([]*string, 0, len(rrs)) - - for _, r := range rrs { - key := normalizedString(r) - keys = append(keys, &key) - if _, ok := m[key]; ok { - // Shortest TTL wins. - if m[key].Header().Ttl > r.Header().Ttl { - m[key].Header().Ttl = r.Header().Ttl - } - continue - } - - m[key] = r - } - // If the length of the result map equals the amount of RRs we got, - // it means they were all different. We can then just return the original rrset. - if len(m) == len(rrs) { - return rrs - } - - j := 0 - for i, r := range rrs { - // If keys[i] lives in the map, we should copy and remove it. - if _, ok := m[*keys[i]]; ok { - delete(m, *keys[i]) - rrs[j] = r - j++ - } - - if len(m) == 0 { - break - } - } - - return rrs[:j] -} - -// normalizedString returns a normalized string from r. The TTL -// is removed and the domain name is lowercased. We go from this: -// DomainNameTTLCLASSTYPERDATA to: -// lowercasenameCLASSTYPE... -func normalizedString(r RR) string { - // A string Go DNS makes has: domainnameTTL... - b := []byte(r.String()) - - // find the first non-escaped tab, then another, so we capture where the TTL lives. - esc := false - ttlStart, ttlEnd := 0, 0 - for i := 0; i < len(b) && ttlEnd == 0; i++ { - switch { - case b[i] == '\\': - esc = !esc - case b[i] == '\t' && !esc: - if ttlStart == 0 { - ttlStart = i - continue - } - if ttlEnd == 0 { - ttlEnd = i - } - case b[i] >= 'A' && b[i] <= 'Z' && !esc: - b[i] += 32 - default: - esc = false - } - } - - // remove TTL. - copy(b[ttlStart:], b[ttlEnd:]) - cut := ttlEnd - ttlStart - return string(b[:len(b)-cut]) -} diff --git a/vendor/github.com/miekg/dns/scan.go b/vendor/github.com/miekg/dns/scan.go deleted file mode 100644 index 8d4773c3e5d..00000000000 --- a/vendor/github.com/miekg/dns/scan.go +++ /dev/null @@ -1,987 +0,0 @@ -package dns - -import ( - "io" - "log" - "os" - "strconv" - "strings" -) - -type debugging bool - -const debug debugging = false - -func (d debugging) Printf(format string, args ...interface{}) { - if d { - log.Printf(format, args...) - } -} - -const maxTok = 2048 // Largest token we can return. -const maxUint16 = 1<<16 - 1 - -// Tokinize a RFC 1035 zone file. The tokenizer will normalize it: -// * Add ownernames if they are left blank; -// * Suppress sequences of spaces; -// * Make each RR fit on one line (_NEWLINE is send as last) -// * Handle comments: ; -// * Handle braces - anywhere. -const ( - // Zonefile - zEOF = iota - zString - zBlank - zQuote - zNewline - zRrtpe - zOwner - zClass - zDirOrigin // $ORIGIN - zDirTtl // $TTL - zDirInclude // $INCLUDE - zDirGenerate // $GENERATE - - // Privatekey file - zValue - zKey - - zExpectOwnerDir // Ownername - zExpectOwnerBl // Whitespace after the ownername - zExpectAny // Expect rrtype, ttl or class - zExpectAnyNoClass // Expect rrtype or ttl - zExpectAnyNoClassBl // The whitespace after _EXPECT_ANY_NOCLASS - zExpectAnyNoTtl // Expect rrtype or class - zExpectAnyNoTtlBl // Whitespace after _EXPECT_ANY_NOTTL - zExpectRrtype // Expect rrtype - zExpectRrtypeBl // Whitespace BEFORE rrtype - zExpectRdata // The first element of the rdata - zExpectDirTtlBl // Space after directive $TTL - zExpectDirTtl // Directive $TTL - zExpectDirOriginBl // Space after directive $ORIGIN - zExpectDirOrigin // Directive $ORIGIN - zExpectDirIncludeBl // Space after directive $INCLUDE - zExpectDirInclude // Directive $INCLUDE - zExpectDirGenerate // Directive $GENERATE - zExpectDirGenerateBl // Space after directive $GENERATE -) - -// ParseError is a parsing error. It contains the parse error and the location in the io.Reader -// where the error occurred. -type ParseError struct { - file string - err string - lex lex -} - -func (e *ParseError) Error() (s string) { - if e.file != "" { - s = e.file + ": " - } - s += "dns: " + e.err + ": " + strconv.QuoteToASCII(e.lex.token) + " at line: " + - strconv.Itoa(e.lex.line) + ":" + strconv.Itoa(e.lex.column) - return -} - -type lex struct { - token string // text of the token - tokenUpper string // uppercase text of the token - length int // length of the token - err bool // when true, token text has lexer error - value uint8 // value: zString, _BLANK, etc. - line int // line in the file - column int // column in the file - torc uint16 // type or class as parsed in the lexer, we only need to look this up in the grammar - comment string // any comment text seen -} - -// Token holds the token that are returned when a zone file is parsed. -type Token struct { - // The scanned resource record when error is not nil. - RR - // When an error occurred, this has the error specifics. - Error *ParseError - // A potential comment positioned after the RR and on the same line. - Comment string -} - -// NewRR reads the RR contained in the string s. Only the first RR is -// returned. If s contains no RR, return nil with no error. The class -// defaults to IN and TTL defaults to 3600. The full zone file syntax -// like $TTL, $ORIGIN, etc. is supported. All fields of the returned -// RR are set, except RR.Header().Rdlength which is set to 0. -func NewRR(s string) (RR, error) { - if len(s) > 0 && s[len(s)-1] != '\n' { // We need a closing newline - return ReadRR(strings.NewReader(s+"\n"), "") - } - return ReadRR(strings.NewReader(s), "") -} - -// ReadRR reads the RR contained in q. -// See NewRR for more documentation. -func ReadRR(q io.Reader, filename string) (RR, error) { - r := <-parseZoneHelper(q, ".", filename, 1) - if r == nil { - return nil, nil - } - - if r.Error != nil { - return nil, r.Error - } - return r.RR, nil -} - -// ParseZone reads a RFC 1035 style zonefile from r. It returns *Tokens on the -// returned channel, which consist out the parsed RR, a potential comment or an error. -// If there is an error the RR is nil. The string file is only used -// in error reporting. The string origin is used as the initial origin, as -// if the file would start with: $ORIGIN origin . -// The directives $INCLUDE, $ORIGIN, $TTL and $GENERATE are supported. -// The channel t is closed by ParseZone when the end of r is reached. -// -// Basic usage pattern when reading from a string (z) containing the -// zone data: -// -// for x := range dns.ParseZone(strings.NewReader(z), "", "") { -// if x.Error != nil { -// // log.Println(x.Error) -// } else { -// // Do something with x.RR -// } -// } -// -// Comments specified after an RR (and on the same line!) are returned too: -// -// foo. IN A 10.0.0.1 ; this is a comment -// -// The text "; this is comment" is returned in Token.Comment. Comments inside the -// RR are discarded. Comments on a line by themselves are discarded too. -func ParseZone(r io.Reader, origin, file string) chan *Token { - return parseZoneHelper(r, origin, file, 10000) -} - -func parseZoneHelper(r io.Reader, origin, file string, chansize int) chan *Token { - t := make(chan *Token, chansize) - go parseZone(r, origin, file, t, 0) - return t -} - -func parseZone(r io.Reader, origin, f string, t chan *Token, include int) { - defer func() { - if include == 0 { - close(t) - } - }() - s := scanInit(r) - c := make(chan lex) - // Start the lexer - go zlexer(s, c) - // 6 possible beginnings of a line, _ is a space - // 0. zRRTYPE -> all omitted until the rrtype - // 1. zOwner _ zRrtype -> class/ttl omitted - // 2. zOwner _ zString _ zRrtype -> class omitted - // 3. zOwner _ zString _ zClass _ zRrtype -> ttl/class - // 4. zOwner _ zClass _ zRrtype -> ttl omitted - // 5. zOwner _ zClass _ zString _ zRrtype -> class/ttl (reversed) - // After detecting these, we know the zRrtype so we can jump to functions - // handling the rdata for each of these types. - - if origin == "" { - origin = "." - } - origin = Fqdn(origin) - if _, ok := IsDomainName(origin); !ok { - t <- &Token{Error: &ParseError{f, "bad initial origin name", lex{}}} - return - } - - st := zExpectOwnerDir // initial state - var h RR_Header - var defttl uint32 = defaultTtl - var prevName string - for l := range c { - // Lexer spotted an error already - if l.err == true { - t <- &Token{Error: &ParseError{f, l.token, l}} - return - - } - switch st { - case zExpectOwnerDir: - // We can also expect a directive, like $TTL or $ORIGIN - h.Ttl = defttl - h.Class = ClassINET - switch l.value { - case zNewline: - st = zExpectOwnerDir - case zOwner: - h.Name = l.token - if l.token[0] == '@' { - h.Name = origin - prevName = h.Name - st = zExpectOwnerBl - break - } - if h.Name[l.length-1] != '.' { - h.Name = appendOrigin(h.Name, origin) - } - _, ok := IsDomainName(l.token) - if !ok { - t <- &Token{Error: &ParseError{f, "bad owner name", l}} - return - } - prevName = h.Name - st = zExpectOwnerBl - case zDirTtl: - st = zExpectDirTtlBl - case zDirOrigin: - st = zExpectDirOriginBl - case zDirInclude: - st = zExpectDirIncludeBl - case zDirGenerate: - st = zExpectDirGenerateBl - case zRrtpe: - h.Name = prevName - h.Rrtype = l.torc - st = zExpectRdata - case zClass: - h.Name = prevName - h.Class = l.torc - st = zExpectAnyNoClassBl - case zBlank: - // Discard, can happen when there is nothing on the - // line except the RR type - case zString: - ttl, ok := stringToTtl(l.token) - if !ok { - t <- &Token{Error: &ParseError{f, "not a TTL", l}} - return - } - h.Ttl = ttl - // Don't about the defttl, we should take the $TTL value - // defttl = ttl - st = zExpectAnyNoTtlBl - - default: - t <- &Token{Error: &ParseError{f, "syntax error at beginning", l}} - return - } - case zExpectDirIncludeBl: - if l.value != zBlank { - t <- &Token{Error: &ParseError{f, "no blank after $INCLUDE-directive", l}} - return - } - st = zExpectDirInclude - case zExpectDirInclude: - if l.value != zString { - t <- &Token{Error: &ParseError{f, "expecting $INCLUDE value, not this...", l}} - return - } - neworigin := origin // There may be optionally a new origin set after the filename, if not use current one - l := <-c - switch l.value { - case zBlank: - l := <-c - if l.value == zString { - if _, ok := IsDomainName(l.token); !ok || l.length == 0 || l.err { - t <- &Token{Error: &ParseError{f, "bad origin name", l}} - return - } - // a new origin is specified. - if l.token[l.length-1] != '.' { - if origin != "." { // Prevent .. endings - neworigin = l.token + "." + origin - } else { - neworigin = l.token + origin - } - } else { - neworigin = l.token - } - } - case zNewline, zEOF: - // Ok - default: - t <- &Token{Error: &ParseError{f, "garbage after $INCLUDE", l}} - return - } - // Start with the new file - r1, e1 := os.Open(l.token) - if e1 != nil { - t <- &Token{Error: &ParseError{f, "failed to open `" + l.token + "'", l}} - return - } - if include+1 > 7 { - t <- &Token{Error: &ParseError{f, "too deeply nested $INCLUDE", l}} - return - } - parseZone(r1, l.token, neworigin, t, include+1) - st = zExpectOwnerDir - case zExpectDirTtlBl: - if l.value != zBlank { - t <- &Token{Error: &ParseError{f, "no blank after $TTL-directive", l}} - return - } - st = zExpectDirTtl - case zExpectDirTtl: - if l.value != zString { - t <- &Token{Error: &ParseError{f, "expecting $TTL value, not this...", l}} - return - } - if e, _ := slurpRemainder(c, f); e != nil { - t <- &Token{Error: e} - return - } - ttl, ok := stringToTtl(l.token) - if !ok { - t <- &Token{Error: &ParseError{f, "expecting $TTL value, not this...", l}} - return - } - defttl = ttl - st = zExpectOwnerDir - case zExpectDirOriginBl: - if l.value != zBlank { - t <- &Token{Error: &ParseError{f, "no blank after $ORIGIN-directive", l}} - return - } - st = zExpectDirOrigin - case zExpectDirOrigin: - if l.value != zString { - t <- &Token{Error: &ParseError{f, "expecting $ORIGIN value, not this...", l}} - return - } - if e, _ := slurpRemainder(c, f); e != nil { - t <- &Token{Error: e} - } - if _, ok := IsDomainName(l.token); !ok { - t <- &Token{Error: &ParseError{f, "bad origin name", l}} - return - } - if l.token[l.length-1] != '.' { - if origin != "." { // Prevent .. endings - origin = l.token + "." + origin - } else { - origin = l.token + origin - } - } else { - origin = l.token - } - st = zExpectOwnerDir - case zExpectDirGenerateBl: - if l.value != zBlank { - t <- &Token{Error: &ParseError{f, "no blank after $GENERATE-directive", l}} - return - } - st = zExpectDirGenerate - case zExpectDirGenerate: - if l.value != zString { - t <- &Token{Error: &ParseError{f, "expecting $GENERATE value, not this...", l}} - return - } - if errMsg := generate(l, c, t, origin); errMsg != "" { - t <- &Token{Error: &ParseError{f, errMsg, l}} - return - } - st = zExpectOwnerDir - case zExpectOwnerBl: - if l.value != zBlank { - t <- &Token{Error: &ParseError{f, "no blank after owner", l}} - return - } - st = zExpectAny - case zExpectAny: - switch l.value { - case zRrtpe: - h.Rrtype = l.torc - st = zExpectRdata - case zClass: - h.Class = l.torc - st = zExpectAnyNoClassBl - case zString: - ttl, ok := stringToTtl(l.token) - if !ok { - t <- &Token{Error: &ParseError{f, "not a TTL", l}} - return - } - h.Ttl = ttl - // defttl = ttl // don't set the defttl here - st = zExpectAnyNoTtlBl - default: - t <- &Token{Error: &ParseError{f, "expecting RR type, TTL or class, not this...", l}} - return - } - case zExpectAnyNoClassBl: - if l.value != zBlank { - t <- &Token{Error: &ParseError{f, "no blank before class", l}} - return - } - st = zExpectAnyNoClass - case zExpectAnyNoTtlBl: - if l.value != zBlank { - t <- &Token{Error: &ParseError{f, "no blank before TTL", l}} - return - } - st = zExpectAnyNoTtl - case zExpectAnyNoTtl: - switch l.value { - case zClass: - h.Class = l.torc - st = zExpectRrtypeBl - case zRrtpe: - h.Rrtype = l.torc - st = zExpectRdata - default: - t <- &Token{Error: &ParseError{f, "expecting RR type or class, not this...", l}} - return - } - case zExpectAnyNoClass: - switch l.value { - case zString: - ttl, ok := stringToTtl(l.token) - if !ok { - t <- &Token{Error: &ParseError{f, "not a TTL", l}} - return - } - h.Ttl = ttl - // defttl = ttl // don't set the def ttl anymore - st = zExpectRrtypeBl - case zRrtpe: - h.Rrtype = l.torc - st = zExpectRdata - default: - t <- &Token{Error: &ParseError{f, "expecting RR type or TTL, not this...", l}} - return - } - case zExpectRrtypeBl: - if l.value != zBlank { - t <- &Token{Error: &ParseError{f, "no blank before RR type", l}} - return - } - st = zExpectRrtype - case zExpectRrtype: - if l.value != zRrtpe { - t <- &Token{Error: &ParseError{f, "unknown RR type", l}} - return - } - h.Rrtype = l.torc - st = zExpectRdata - case zExpectRdata: - r, e, c1 := setRR(h, c, origin, f) - if e != nil { - // If e.lex is nil than we have encounter a unknown RR type - // in that case we substitute our current lex token - if e.lex.token == "" && e.lex.value == 0 { - e.lex = l // Uh, dirty - } - t <- &Token{Error: e} - return - } - t <- &Token{RR: r, Comment: c1} - st = zExpectOwnerDir - } - } - // If we get here, we and the h.Rrtype is still zero, we haven't parsed anything, this - // is not an error, because an empty zone file is still a zone file. -} - -// zlexer scans the sourcefile and returns tokens on the channel c. -func zlexer(s *scan, c chan lex) { - var l lex - str := make([]byte, maxTok) // Should be enough for any token - stri := 0 // Offset in str (0 means empty) - com := make([]byte, maxTok) // Hold comment text - comi := 0 - quote := false - escape := false - space := false - commt := false - rrtype := false - owner := true - brace := 0 - x, err := s.tokenText() - defer close(c) - for err == nil { - l.column = s.position.Column - l.line = s.position.Line - if stri >= maxTok { - l.token = "token length insufficient for parsing" - l.err = true - debug.Printf("[%+v]", l.token) - c <- l - return - } - if comi >= maxTok { - l.token = "comment length insufficient for parsing" - l.err = true - debug.Printf("[%+v]", l.token) - c <- l - return - } - - switch x { - case ' ', '\t': - if escape { - escape = false - str[stri] = x - stri++ - break - } - if quote { - // Inside quotes this is legal - str[stri] = x - stri++ - break - } - if commt { - com[comi] = x - comi++ - break - } - if stri == 0 { - // Space directly in the beginning, handled in the grammar - } else if owner { - // If we have a string and its the first, make it an owner - l.value = zOwner - l.token = string(str[:stri]) - l.tokenUpper = strings.ToUpper(l.token) - l.length = stri - // escape $... start with a \ not a $, so this will work - switch l.tokenUpper { - case "$TTL": - l.value = zDirTtl - case "$ORIGIN": - l.value = zDirOrigin - case "$INCLUDE": - l.value = zDirInclude - case "$GENERATE": - l.value = zDirGenerate - } - debug.Printf("[7 %+v]", l.token) - c <- l - } else { - l.value = zString - l.token = string(str[:stri]) - l.tokenUpper = strings.ToUpper(l.token) - l.length = stri - if !rrtype { - if t, ok := StringToType[l.tokenUpper]; ok { - l.value = zRrtpe - l.torc = t - rrtype = true - } else { - if strings.HasPrefix(l.tokenUpper, "TYPE") { - t, ok := typeToInt(l.token) - if !ok { - l.token = "unknown RR type" - l.err = true - c <- l - return - } - l.value = zRrtpe - l.torc = t - } - } - if t, ok := StringToClass[l.tokenUpper]; ok { - l.value = zClass - l.torc = t - } else { - if strings.HasPrefix(l.tokenUpper, "CLASS") { - t, ok := classToInt(l.token) - if !ok { - l.token = "unknown class" - l.err = true - c <- l - return - } - l.value = zClass - l.torc = t - } - } - } - debug.Printf("[6 %+v]", l.token) - c <- l - } - stri = 0 - // I reverse space stuff here - if !space && !commt { - l.value = zBlank - l.token = " " - l.length = 1 - debug.Printf("[5 %+v]", l.token) - c <- l - } - owner = false - space = true - case ';': - if escape { - escape = false - str[stri] = x - stri++ - break - } - if quote { - // Inside quotes this is legal - str[stri] = x - stri++ - break - } - if stri > 0 { - l.value = zString - l.token = string(str[:stri]) - l.tokenUpper = strings.ToUpper(l.token) - l.length = stri - debug.Printf("[4 %+v]", l.token) - c <- l - stri = 0 - } - commt = true - com[comi] = ';' - comi++ - case '\r': - escape = false - if quote { - str[stri] = x - stri++ - break - } - // discard if outside of quotes - case '\n': - escape = false - // Escaped newline - if quote { - str[stri] = x - stri++ - break - } - // inside quotes this is legal - if commt { - // Reset a comment - commt = false - rrtype = false - stri = 0 - // If not in a brace this ends the comment AND the RR - if brace == 0 { - owner = true - owner = true - l.value = zNewline - l.token = "\n" - l.tokenUpper = l.token - l.length = 1 - l.comment = string(com[:comi]) - debug.Printf("[3 %+v %+v]", l.token, l.comment) - c <- l - l.comment = "" - comi = 0 - break - } - com[comi] = ' ' // convert newline to space - comi++ - break - } - - if brace == 0 { - // If there is previous text, we should output it here - if stri != 0 { - l.value = zString - l.token = string(str[:stri]) - l.tokenUpper = strings.ToUpper(l.token) - - l.length = stri - if !rrtype { - if t, ok := StringToType[l.tokenUpper]; ok { - l.value = zRrtpe - l.torc = t - rrtype = true - } - } - debug.Printf("[2 %+v]", l.token) - c <- l - } - l.value = zNewline - l.token = "\n" - l.tokenUpper = l.token - l.length = 1 - debug.Printf("[1 %+v]", l.token) - c <- l - stri = 0 - commt = false - rrtype = false - owner = true - comi = 0 - } - case '\\': - // comments do not get escaped chars, everything is copied - if commt { - com[comi] = x - comi++ - break - } - // something already escaped must be in string - if escape { - str[stri] = x - stri++ - escape = false - break - } - // something escaped outside of string gets added to string - str[stri] = x - stri++ - escape = true - case '"': - if commt { - com[comi] = x - comi++ - break - } - if escape { - str[stri] = x - stri++ - escape = false - break - } - space = false - // send previous gathered text and the quote - if stri != 0 { - l.value = zString - l.token = string(str[:stri]) - l.tokenUpper = strings.ToUpper(l.token) - l.length = stri - - debug.Printf("[%+v]", l.token) - c <- l - stri = 0 - } - - // send quote itself as separate token - l.value = zQuote - l.token = "\"" - l.tokenUpper = l.token - l.length = 1 - c <- l - quote = !quote - case '(', ')': - if commt { - com[comi] = x - comi++ - break - } - if escape { - str[stri] = x - stri++ - escape = false - break - } - if quote { - str[stri] = x - stri++ - break - } - switch x { - case ')': - brace-- - if brace < 0 { - l.token = "extra closing brace" - l.tokenUpper = l.token - l.err = true - debug.Printf("[%+v]", l.token) - c <- l - return - } - case '(': - brace++ - } - default: - escape = false - if commt { - com[comi] = x - comi++ - break - } - str[stri] = x - stri++ - space = false - } - x, err = s.tokenText() - } - if stri > 0 { - // Send remainder - l.token = string(str[:stri]) - l.tokenUpper = strings.ToUpper(l.token) - l.length = stri - l.value = zString - debug.Printf("[%+v]", l.token) - c <- l - } - if brace != 0 { - l.token = "unbalanced brace" - l.tokenUpper = l.token - l.err = true - c <- l - } -} - -// Extract the class number from CLASSxx -func classToInt(token string) (uint16, bool) { - offset := 5 - if len(token) < offset+1 { - return 0, false - } - class, err := strconv.ParseUint(token[offset:], 10, 16) - if err != nil { - return 0, false - } - return uint16(class), true -} - -// Extract the rr number from TYPExxx -func typeToInt(token string) (uint16, bool) { - offset := 4 - if len(token) < offset+1 { - return 0, false - } - typ, err := strconv.ParseUint(token[offset:], 10, 16) - if err != nil { - return 0, false - } - return uint16(typ), true -} - -// Parse things like 2w, 2m, etc, Return the time in seconds. -func stringToTtl(token string) (uint32, bool) { - s := uint32(0) - i := uint32(0) - for _, c := range token { - switch c { - case 's', 'S': - s += i - i = 0 - case 'm', 'M': - s += i * 60 - i = 0 - case 'h', 'H': - s += i * 60 * 60 - i = 0 - case 'd', 'D': - s += i * 60 * 60 * 24 - i = 0 - case 'w', 'W': - s += i * 60 * 60 * 24 * 7 - i = 0 - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - i *= 10 - i += uint32(c) - '0' - default: - return 0, false - } - } - return s + i, true -} - -// Parse LOC records' [.][mM] into a -// mantissa exponent format. Token should contain the entire -// string (i.e. no spaces allowed) -func stringToCm(token string) (e, m uint8, ok bool) { - if token[len(token)-1] == 'M' || token[len(token)-1] == 'm' { - token = token[0 : len(token)-1] - } - s := strings.SplitN(token, ".", 2) - var meters, cmeters, val int - var err error - switch len(s) { - case 2: - if cmeters, err = strconv.Atoi(s[1]); err != nil { - return - } - fallthrough - case 1: - if meters, err = strconv.Atoi(s[0]); err != nil { - return - } - case 0: - // huh? - return 0, 0, false - } - ok = true - if meters > 0 { - e = 2 - val = meters - } else { - e = 0 - val = cmeters - } - for val > 10 { - e++ - val /= 10 - } - if e > 9 { - ok = false - } - m = uint8(val) - return -} - -func appendOrigin(name, origin string) string { - if origin == "." { - return name + origin - } - return name + "." + origin -} - -// LOC record helper function -func locCheckNorth(token string, latitude uint32) (uint32, bool) { - switch token { - case "n", "N": - return LOC_EQUATOR + latitude, true - case "s", "S": - return LOC_EQUATOR - latitude, true - } - return latitude, false -} - -// LOC record helper function -func locCheckEast(token string, longitude uint32) (uint32, bool) { - switch token { - case "e", "E": - return LOC_EQUATOR + longitude, true - case "w", "W": - return LOC_EQUATOR - longitude, true - } - return longitude, false -} - -// "Eat" the rest of the "line". Return potential comments -func slurpRemainder(c chan lex, f string) (*ParseError, string) { - l := <-c - com := "" - switch l.value { - case zBlank: - l = <-c - com = l.comment - if l.value != zNewline && l.value != zEOF { - return &ParseError{f, "garbage after rdata", l}, "" - } - case zNewline: - com = l.comment - case zEOF: - default: - return &ParseError{f, "garbage after rdata", l}, "" - } - return nil, com -} - -// Parse a 64 bit-like ipv6 address: "0014:4fff:ff20:ee64" -// Used for NID and L64 record. -func stringToNodeID(l lex) (uint64, *ParseError) { - if len(l.token) < 19 { - return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l} - } - // There must be three colons at fixes postitions, if not its a parse error - if l.token[4] != ':' && l.token[9] != ':' && l.token[14] != ':' { - return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l} - } - s := l.token[0:4] + l.token[5:9] + l.token[10:14] + l.token[15:19] - u, err := strconv.ParseUint(s, 16, 64) - if err != nil { - return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l} - } - return u, nil -} diff --git a/vendor/github.com/miekg/dns/scan_rr.go b/vendor/github.com/miekg/dns/scan_rr.go deleted file mode 100644 index b8b18fd776d..00000000000 --- a/vendor/github.com/miekg/dns/scan_rr.go +++ /dev/null @@ -1,2184 +0,0 @@ -package dns - -import ( - "encoding/base64" - "net" - "strconv" - "strings" -) - -type parserFunc struct { - // Func defines the function that parses the tokens and returns the RR - // or an error. The last string contains any comments in the line as - // they returned by the lexer as well. - Func func(h RR_Header, c chan lex, origin string, file string) (RR, *ParseError, string) - // Signals if the RR ending is of variable length, like TXT or records - // that have Hexadecimal or Base64 as their last element in the Rdata. Records - // that have a fixed ending or for instance A, AAAA, SOA and etc. - Variable bool -} - -// Parse the rdata of each rrtype. -// All data from the channel c is either zString or zBlank. -// After the rdata there may come a zBlank and then a zNewline -// or immediately a zNewline. If this is not the case we flag -// an *ParseError: garbage after rdata. -func setRR(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - parserfunc, ok := typeToparserFunc[h.Rrtype] - if ok { - r, e, cm := parserfunc.Func(h, c, o, f) - if parserfunc.Variable { - return r, e, cm - } - if e != nil { - return nil, e, "" - } - e, cm = slurpRemainder(c, f) - if e != nil { - return nil, e, "" - } - return r, nil, cm - } - // RFC3957 RR (Unknown RR handling) - return setRFC3597(h, c, o, f) -} - -// A remainder of the rdata with embedded spaces, return the parsed string (sans the spaces) -// or an error -func endingToString(c chan lex, errstr, f string) (string, *ParseError, string) { - s := "" - l := <-c // zString - for l.value != zNewline && l.value != zEOF { - if l.err { - return s, &ParseError{f, errstr, l}, "" - } - switch l.value { - case zString: - s += l.token - case zBlank: // Ok - default: - return "", &ParseError{f, errstr, l}, "" - } - l = <-c - } - return s, nil, l.comment -} - -// A remainder of the rdata with embedded spaces, split on unquoted whitespace -// and return the parsed string slice or an error -func endingToTxtSlice(c chan lex, errstr, f string) ([]string, *ParseError, string) { - // Get the remaining data until we see a zNewline - l := <-c - if l.err { - return nil, &ParseError{f, errstr, l}, "" - } - - // Build the slice - s := make([]string, 0) - quote := false - empty := false - for l.value != zNewline && l.value != zEOF { - if l.err { - return nil, &ParseError{f, errstr, l}, "" - } - switch l.value { - case zString: - empty = false - if len(l.token) > 255 { - // split up tokens that are larger than 255 into 255-chunks - sx := []string{} - p, i := 0, 255 - for { - if i <= len(l.token) { - sx = append(sx, l.token[p:i]) - } else { - sx = append(sx, l.token[p:]) - break - - } - p, i = p+255, i+255 - } - s = append(s, sx...) - break - } - - s = append(s, l.token) - case zBlank: - if quote { - // zBlank can only be seen in between txt parts. - return nil, &ParseError{f, errstr, l}, "" - } - case zQuote: - if empty && quote { - s = append(s, "") - } - quote = !quote - empty = true - default: - return nil, &ParseError{f, errstr, l}, "" - } - l = <-c - } - if quote { - return nil, &ParseError{f, errstr, l}, "" - } - return s, nil, l.comment -} - -func setA(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(A) - rr.Hdr = h - - l := <-c - if l.length == 0 { // Dynamic updates. - return rr, nil, "" - } - rr.A = net.ParseIP(l.token) - if rr.A == nil || l.err { - return nil, &ParseError{f, "bad A A", l}, "" - } - return rr, nil, "" -} - -func setAAAA(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(AAAA) - rr.Hdr = h - - l := <-c - if l.length == 0 { - return rr, nil, "" - } - rr.AAAA = net.ParseIP(l.token) - if rr.AAAA == nil || l.err { - return nil, &ParseError{f, "bad AAAA AAAA", l}, "" - } - return rr, nil, "" -} - -func setNS(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(NS) - rr.Hdr = h - - l := <-c - rr.Ns = l.token - if l.length == 0 { - return rr, nil, "" - } - if l.token == "@" { - rr.Ns = o - return rr, nil, "" - } - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad NS Ns", l}, "" - } - if rr.Ns[l.length-1] != '.' { - rr.Ns = appendOrigin(rr.Ns, o) - } - return rr, nil, "" -} - -func setPTR(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(PTR) - rr.Hdr = h - - l := <-c - rr.Ptr = l.token - if l.length == 0 { // dynamic update rr. - return rr, nil, "" - } - if l.token == "@" { - rr.Ptr = o - return rr, nil, "" - } - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad PTR Ptr", l}, "" - } - if rr.Ptr[l.length-1] != '.' { - rr.Ptr = appendOrigin(rr.Ptr, o) - } - return rr, nil, "" -} - -func setNSAPPTR(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(NSAPPTR) - rr.Hdr = h - - l := <-c - rr.Ptr = l.token - if l.length == 0 { - return rr, nil, "" - } - if l.token == "@" { - rr.Ptr = o - return rr, nil, "" - } - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad NSAP-PTR Ptr", l}, "" - } - if rr.Ptr[l.length-1] != '.' { - rr.Ptr = appendOrigin(rr.Ptr, o) - } - return rr, nil, "" -} - -func setRP(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(RP) - rr.Hdr = h - - l := <-c - rr.Mbox = l.token - if l.length == 0 { - return rr, nil, "" - } - if l.token == "@" { - rr.Mbox = o - } else { - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad RP Mbox", l}, "" - } - if rr.Mbox[l.length-1] != '.' { - rr.Mbox = appendOrigin(rr.Mbox, o) - } - } - <-c // zBlank - l = <-c - rr.Txt = l.token - if l.token == "@" { - rr.Txt = o - return rr, nil, "" - } - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad RP Txt", l}, "" - } - if rr.Txt[l.length-1] != '.' { - rr.Txt = appendOrigin(rr.Txt, o) - } - return rr, nil, "" -} - -func setMR(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(MR) - rr.Hdr = h - - l := <-c - rr.Mr = l.token - if l.length == 0 { - return rr, nil, "" - } - if l.token == "@" { - rr.Mr = o - return rr, nil, "" - } - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad MR Mr", l}, "" - } - if rr.Mr[l.length-1] != '.' { - rr.Mr = appendOrigin(rr.Mr, o) - } - return rr, nil, "" -} - -func setMB(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(MB) - rr.Hdr = h - - l := <-c - rr.Mb = l.token - if l.length == 0 { - return rr, nil, "" - } - if l.token == "@" { - rr.Mb = o - return rr, nil, "" - } - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad MB Mb", l}, "" - } - if rr.Mb[l.length-1] != '.' { - rr.Mb = appendOrigin(rr.Mb, o) - } - return rr, nil, "" -} - -func setMG(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(MG) - rr.Hdr = h - - l := <-c - rr.Mg = l.token - if l.length == 0 { - return rr, nil, "" - } - if l.token == "@" { - rr.Mg = o - return rr, nil, "" - } - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad MG Mg", l}, "" - } - if rr.Mg[l.length-1] != '.' { - rr.Mg = appendOrigin(rr.Mg, o) - } - return rr, nil, "" -} - -func setHINFO(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(HINFO) - rr.Hdr = h - - chunks, e, c1 := endingToTxtSlice(c, "bad HINFO Fields", f) - if e != nil { - return nil, e, c1 - } - - if ln := len(chunks); ln == 0 { - return rr, nil, "" - } else if ln == 1 { - // Can we split it? - if out := strings.Fields(chunks[0]); len(out) > 1 { - chunks = out - } else { - chunks = append(chunks, "") - } - } - - rr.Cpu = chunks[0] - rr.Os = strings.Join(chunks[1:], " ") - - return rr, nil, "" -} - -func setMINFO(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(MINFO) - rr.Hdr = h - - l := <-c - rr.Rmail = l.token - if l.length == 0 { - return rr, nil, "" - } - if l.token == "@" { - rr.Rmail = o - } else { - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad MINFO Rmail", l}, "" - } - if rr.Rmail[l.length-1] != '.' { - rr.Rmail = appendOrigin(rr.Rmail, o) - } - } - <-c // zBlank - l = <-c - rr.Email = l.token - if l.token == "@" { - rr.Email = o - return rr, nil, "" - } - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad MINFO Email", l}, "" - } - if rr.Email[l.length-1] != '.' { - rr.Email = appendOrigin(rr.Email, o) - } - return rr, nil, "" -} - -func setMF(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(MF) - rr.Hdr = h - - l := <-c - rr.Mf = l.token - if l.length == 0 { - return rr, nil, "" - } - if l.token == "@" { - rr.Mf = o - return rr, nil, "" - } - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad MF Mf", l}, "" - } - if rr.Mf[l.length-1] != '.' { - rr.Mf = appendOrigin(rr.Mf, o) - } - return rr, nil, "" -} - -func setMD(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(MD) - rr.Hdr = h - - l := <-c - rr.Md = l.token - if l.length == 0 { - return rr, nil, "" - } - if l.token == "@" { - rr.Md = o - return rr, nil, "" - } - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad MD Md", l}, "" - } - if rr.Md[l.length-1] != '.' { - rr.Md = appendOrigin(rr.Md, o) - } - return rr, nil, "" -} - -func setMX(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(MX) - rr.Hdr = h - - l := <-c - if l.length == 0 { - return rr, nil, "" - } - i, e := strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { - return nil, &ParseError{f, "bad MX Pref", l}, "" - } - rr.Preference = uint16(i) - <-c // zBlank - l = <-c // zString - rr.Mx = l.token - if l.token == "@" { - rr.Mx = o - return rr, nil, "" - } - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad MX Mx", l}, "" - } - if rr.Mx[l.length-1] != '.' { - rr.Mx = appendOrigin(rr.Mx, o) - } - return rr, nil, "" -} - -func setRT(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(RT) - rr.Hdr = h - l := <-c - if l.length == 0 { - return rr, nil, "" - } - i, e := strconv.ParseUint(l.token, 10, 16) - if e != nil { - return nil, &ParseError{f, "bad RT Preference", l}, "" - } - rr.Preference = uint16(i) - <-c // zBlank - l = <-c // zString - rr.Host = l.token - if l.token == "@" { - rr.Host = o - return rr, nil, "" - } - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad RT Host", l}, "" - } - if rr.Host[l.length-1] != '.' { - rr.Host = appendOrigin(rr.Host, o) - } - return rr, nil, "" -} - -func setAFSDB(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(AFSDB) - rr.Hdr = h - - l := <-c - if l.length == 0 { - return rr, nil, "" - } - i, e := strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { - return nil, &ParseError{f, "bad AFSDB Subtype", l}, "" - } - rr.Subtype = uint16(i) - <-c // zBlank - l = <-c // zString - rr.Hostname = l.token - if l.token == "@" { - rr.Hostname = o - return rr, nil, "" - } - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad AFSDB Hostname", l}, "" - } - if rr.Hostname[l.length-1] != '.' { - rr.Hostname = appendOrigin(rr.Hostname, o) - } - return rr, nil, "" -} - -func setX25(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(X25) - rr.Hdr = h - - l := <-c - if l.length == 0 { - return rr, nil, "" - } - if l.err { - return nil, &ParseError{f, "bad X25 PSDNAddress", l}, "" - } - rr.PSDNAddress = l.token - return rr, nil, "" -} - -func setKX(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(KX) - rr.Hdr = h - - l := <-c - if l.length == 0 { - return rr, nil, "" - } - i, e := strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { - return nil, &ParseError{f, "bad KX Pref", l}, "" - } - rr.Preference = uint16(i) - <-c // zBlank - l = <-c // zString - rr.Exchanger = l.token - if l.token == "@" { - rr.Exchanger = o - return rr, nil, "" - } - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad KX Exchanger", l}, "" - } - if rr.Exchanger[l.length-1] != '.' { - rr.Exchanger = appendOrigin(rr.Exchanger, o) - } - return rr, nil, "" -} - -func setCNAME(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(CNAME) - rr.Hdr = h - - l := <-c - rr.Target = l.token - if l.length == 0 { - return rr, nil, "" - } - if l.token == "@" { - rr.Target = o - return rr, nil, "" - } - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad CNAME Target", l}, "" - } - if rr.Target[l.length-1] != '.' { - rr.Target = appendOrigin(rr.Target, o) - } - return rr, nil, "" -} - -func setDNAME(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(DNAME) - rr.Hdr = h - - l := <-c - rr.Target = l.token - if l.length == 0 { - return rr, nil, "" - } - if l.token == "@" { - rr.Target = o - return rr, nil, "" - } - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad CNAME Target", l}, "" - } - if rr.Target[l.length-1] != '.' { - rr.Target = appendOrigin(rr.Target, o) - } - return rr, nil, "" -} - -func setSOA(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(SOA) - rr.Hdr = h - - l := <-c - rr.Ns = l.token - if l.length == 0 { - return rr, nil, "" - } - <-c // zBlank - if l.token == "@" { - rr.Ns = o - } else { - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad SOA Ns", l}, "" - } - if rr.Ns[l.length-1] != '.' { - rr.Ns = appendOrigin(rr.Ns, o) - } - } - - l = <-c - rr.Mbox = l.token - if l.token == "@" { - rr.Mbox = o - } else { - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad SOA Mbox", l}, "" - } - if rr.Mbox[l.length-1] != '.' { - rr.Mbox = appendOrigin(rr.Mbox, o) - } - } - <-c // zBlank - - var ( - v uint32 - ok bool - ) - for i := 0; i < 5; i++ { - l = <-c - if l.err { - return nil, &ParseError{f, "bad SOA zone parameter", l}, "" - } - if j, e := strconv.ParseUint(l.token, 10, 32); e != nil { - if i == 0 { - // Serial should be a number - return nil, &ParseError{f, "bad SOA zone parameter", l}, "" - } - if v, ok = stringToTtl(l.token); !ok { - return nil, &ParseError{f, "bad SOA zone parameter", l}, "" - - } - } else { - v = uint32(j) - } - switch i { - case 0: - rr.Serial = v - <-c // zBlank - case 1: - rr.Refresh = v - <-c // zBlank - case 2: - rr.Retry = v - <-c // zBlank - case 3: - rr.Expire = v - <-c // zBlank - case 4: - rr.Minttl = v - } - } - return rr, nil, "" -} - -func setSRV(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(SRV) - rr.Hdr = h - - l := <-c - if l.length == 0 { - return rr, nil, "" - } - i, e := strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { - return nil, &ParseError{f, "bad SRV Priority", l}, "" - } - rr.Priority = uint16(i) - <-c // zBlank - l = <-c // zString - i, e = strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { - return nil, &ParseError{f, "bad SRV Weight", l}, "" - } - rr.Weight = uint16(i) - <-c // zBlank - l = <-c // zString - i, e = strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { - return nil, &ParseError{f, "bad SRV Port", l}, "" - } - rr.Port = uint16(i) - <-c // zBlank - l = <-c // zString - rr.Target = l.token - if l.token == "@" { - rr.Target = o - return rr, nil, "" - } - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad SRV Target", l}, "" - } - if rr.Target[l.length-1] != '.' { - rr.Target = appendOrigin(rr.Target, o) - } - return rr, nil, "" -} - -func setNAPTR(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(NAPTR) - rr.Hdr = h - - l := <-c - if l.length == 0 { - return rr, nil, "" - } - i, e := strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { - return nil, &ParseError{f, "bad NAPTR Order", l}, "" - } - rr.Order = uint16(i) - <-c // zBlank - l = <-c // zString - i, e = strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { - return nil, &ParseError{f, "bad NAPTR Preference", l}, "" - } - rr.Preference = uint16(i) - // Flags - <-c // zBlank - l = <-c // _QUOTE - if l.value != zQuote { - return nil, &ParseError{f, "bad NAPTR Flags", l}, "" - } - l = <-c // Either String or Quote - if l.value == zString { - rr.Flags = l.token - l = <-c // _QUOTE - if l.value != zQuote { - return nil, &ParseError{f, "bad NAPTR Flags", l}, "" - } - } else if l.value == zQuote { - rr.Flags = "" - } else { - return nil, &ParseError{f, "bad NAPTR Flags", l}, "" - } - - // Service - <-c // zBlank - l = <-c // _QUOTE - if l.value != zQuote { - return nil, &ParseError{f, "bad NAPTR Service", l}, "" - } - l = <-c // Either String or Quote - if l.value == zString { - rr.Service = l.token - l = <-c // _QUOTE - if l.value != zQuote { - return nil, &ParseError{f, "bad NAPTR Service", l}, "" - } - } else if l.value == zQuote { - rr.Service = "" - } else { - return nil, &ParseError{f, "bad NAPTR Service", l}, "" - } - - // Regexp - <-c // zBlank - l = <-c // _QUOTE - if l.value != zQuote { - return nil, &ParseError{f, "bad NAPTR Regexp", l}, "" - } - l = <-c // Either String or Quote - if l.value == zString { - rr.Regexp = l.token - l = <-c // _QUOTE - if l.value != zQuote { - return nil, &ParseError{f, "bad NAPTR Regexp", l}, "" - } - } else if l.value == zQuote { - rr.Regexp = "" - } else { - return nil, &ParseError{f, "bad NAPTR Regexp", l}, "" - } - // After quote no space?? - <-c // zBlank - l = <-c // zString - rr.Replacement = l.token - if l.token == "@" { - rr.Replacement = o - return rr, nil, "" - } - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad NAPTR Replacement", l}, "" - } - if rr.Replacement[l.length-1] != '.' { - rr.Replacement = appendOrigin(rr.Replacement, o) - } - return rr, nil, "" -} - -func setTALINK(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(TALINK) - rr.Hdr = h - - l := <-c - rr.PreviousName = l.token - if l.length == 0 { - return rr, nil, "" - } - if l.token == "@" { - rr.PreviousName = o - } else { - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad TALINK PreviousName", l}, "" - } - if rr.PreviousName[l.length-1] != '.' { - rr.PreviousName = appendOrigin(rr.PreviousName, o) - } - } - <-c // zBlank - l = <-c - rr.NextName = l.token - if l.token == "@" { - rr.NextName = o - return rr, nil, "" - } - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad TALINK NextName", l}, "" - } - if rr.NextName[l.length-1] != '.' { - rr.NextName = appendOrigin(rr.NextName, o) - } - return rr, nil, "" -} - -func setLOC(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(LOC) - rr.Hdr = h - // Non zero defaults for LOC record, see RFC 1876, Section 3. - rr.HorizPre = 165 // 10000 - rr.VertPre = 162 // 10 - rr.Size = 18 // 1 - ok := false - // North - l := <-c - if l.length == 0 { - return rr, nil, "" - } - i, e := strconv.ParseUint(l.token, 10, 32) - if e != nil || l.err { - return nil, &ParseError{f, "bad LOC Latitude", l}, "" - } - rr.Latitude = 1000 * 60 * 60 * uint32(i) - - <-c // zBlank - // Either number, 'N' or 'S' - l = <-c - if rr.Latitude, ok = locCheckNorth(l.token, rr.Latitude); ok { - goto East - } - i, e = strconv.ParseUint(l.token, 10, 32) - if e != nil || l.err { - return nil, &ParseError{f, "bad LOC Latitude minutes", l}, "" - } - rr.Latitude += 1000 * 60 * uint32(i) - - <-c // zBlank - l = <-c - if i, e := strconv.ParseFloat(l.token, 32); e != nil || l.err { - return nil, &ParseError{f, "bad LOC Latitude seconds", l}, "" - } else { - rr.Latitude += uint32(1000 * i) - } - <-c // zBlank - // Either number, 'N' or 'S' - l = <-c - if rr.Latitude, ok = locCheckNorth(l.token, rr.Latitude); ok { - goto East - } - // If still alive, flag an error - return nil, &ParseError{f, "bad LOC Latitude North/South", l}, "" - -East: - // East - <-c // zBlank - l = <-c - if i, e := strconv.ParseUint(l.token, 10, 32); e != nil || l.err { - return nil, &ParseError{f, "bad LOC Longitude", l}, "" - } else { - rr.Longitude = 1000 * 60 * 60 * uint32(i) - } - <-c // zBlank - // Either number, 'E' or 'W' - l = <-c - if rr.Longitude, ok = locCheckEast(l.token, rr.Longitude); ok { - goto Altitude - } - if i, e := strconv.ParseUint(l.token, 10, 32); e != nil || l.err { - return nil, &ParseError{f, "bad LOC Longitude minutes", l}, "" - } else { - rr.Longitude += 1000 * 60 * uint32(i) - } - <-c // zBlank - l = <-c - if i, e := strconv.ParseFloat(l.token, 32); e != nil || l.err { - return nil, &ParseError{f, "bad LOC Longitude seconds", l}, "" - } else { - rr.Longitude += uint32(1000 * i) - } - <-c // zBlank - // Either number, 'E' or 'W' - l = <-c - if rr.Longitude, ok = locCheckEast(l.token, rr.Longitude); ok { - goto Altitude - } - // If still alive, flag an error - return nil, &ParseError{f, "bad LOC Longitude East/West", l}, "" - -Altitude: - <-c // zBlank - l = <-c - if l.length == 0 || l.err { - return nil, &ParseError{f, "bad LOC Altitude", l}, "" - } - if l.token[len(l.token)-1] == 'M' || l.token[len(l.token)-1] == 'm' { - l.token = l.token[0 : len(l.token)-1] - } - if i, e := strconv.ParseFloat(l.token, 32); e != nil { - return nil, &ParseError{f, "bad LOC Altitude", l}, "" - } else { - rr.Altitude = uint32(i*100.0 + 10000000.0 + 0.5) - } - - // And now optionally the other values - l = <-c - count := 0 - for l.value != zNewline && l.value != zEOF { - switch l.value { - case zString: - switch count { - case 0: // Size - e, m, ok := stringToCm(l.token) - if !ok { - return nil, &ParseError{f, "bad LOC Size", l}, "" - } - rr.Size = (e & 0x0f) | (m << 4 & 0xf0) - case 1: // HorizPre - e, m, ok := stringToCm(l.token) - if !ok { - return nil, &ParseError{f, "bad LOC HorizPre", l}, "" - } - rr.HorizPre = (e & 0x0f) | (m << 4 & 0xf0) - case 2: // VertPre - e, m, ok := stringToCm(l.token) - if !ok { - return nil, &ParseError{f, "bad LOC VertPre", l}, "" - } - rr.VertPre = (e & 0x0f) | (m << 4 & 0xf0) - } - count++ - case zBlank: - // Ok - default: - return nil, &ParseError{f, "bad LOC Size, HorizPre or VertPre", l}, "" - } - l = <-c - } - return rr, nil, "" -} - -func setHIP(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(HIP) - rr.Hdr = h - - // HitLength is not represented - l := <-c - if l.length == 0 { - return rr, nil, l.comment - } - i, e := strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { - return nil, &ParseError{f, "bad HIP PublicKeyAlgorithm", l}, "" - } - rr.PublicKeyAlgorithm = uint8(i) - <-c // zBlank - l = <-c // zString - if l.length == 0 || l.err { - return nil, &ParseError{f, "bad HIP Hit", l}, "" - } - rr.Hit = l.token // This can not contain spaces, see RFC 5205 Section 6. - rr.HitLength = uint8(len(rr.Hit)) / 2 - - <-c // zBlank - l = <-c // zString - if l.length == 0 || l.err { - return nil, &ParseError{f, "bad HIP PublicKey", l}, "" - } - rr.PublicKey = l.token // This cannot contain spaces - rr.PublicKeyLength = uint16(base64.StdEncoding.DecodedLen(len(rr.PublicKey))) - - // RendezvousServers (if any) - l = <-c - var xs []string - for l.value != zNewline && l.value != zEOF { - switch l.value { - case zString: - if l.token == "@" { - xs = append(xs, o) - l = <-c - continue - } - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad HIP RendezvousServers", l}, "" - } - if l.token[l.length-1] != '.' { - l.token = appendOrigin(l.token, o) - } - xs = append(xs, l.token) - case zBlank: - // Ok - default: - return nil, &ParseError{f, "bad HIP RendezvousServers", l}, "" - } - l = <-c - } - rr.RendezvousServers = xs - return rr, nil, l.comment -} - -func setCERT(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(CERT) - rr.Hdr = h - - l := <-c - if l.length == 0 { - return rr, nil, l.comment - } - if v, ok := StringToCertType[l.token]; ok { - rr.Type = v - } else if i, e := strconv.ParseUint(l.token, 10, 16); e != nil { - return nil, &ParseError{f, "bad CERT Type", l}, "" - } else { - rr.Type = uint16(i) - } - <-c // zBlank - l = <-c // zString - i, e := strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { - return nil, &ParseError{f, "bad CERT KeyTag", l}, "" - } - rr.KeyTag = uint16(i) - <-c // zBlank - l = <-c // zString - if v, ok := StringToAlgorithm[l.token]; ok { - rr.Algorithm = v - } else if i, e := strconv.ParseUint(l.token, 10, 8); e != nil { - return nil, &ParseError{f, "bad CERT Algorithm", l}, "" - } else { - rr.Algorithm = uint8(i) - } - s, e1, c1 := endingToString(c, "bad CERT Certificate", f) - if e1 != nil { - return nil, e1, c1 - } - rr.Certificate = s - return rr, nil, c1 -} - -func setOPENPGPKEY(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(OPENPGPKEY) - rr.Hdr = h - - s, e, c1 := endingToString(c, "bad OPENPGPKEY PublicKey", f) - if e != nil { - return nil, e, c1 - } - rr.PublicKey = s - return rr, nil, c1 -} - -func setSIG(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - r, e, s := setRRSIG(h, c, o, f) - if r != nil { - return &SIG{*r.(*RRSIG)}, e, s - } - return nil, e, s -} - -func setRRSIG(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(RRSIG) - rr.Hdr = h - l := <-c - if l.length == 0 { - return rr, nil, l.comment - } - if t, ok := StringToType[l.tokenUpper]; !ok { - if strings.HasPrefix(l.tokenUpper, "TYPE") { - t, ok = typeToInt(l.tokenUpper) - if !ok { - return nil, &ParseError{f, "bad RRSIG Typecovered", l}, "" - } - rr.TypeCovered = t - } else { - return nil, &ParseError{f, "bad RRSIG Typecovered", l}, "" - } - } else { - rr.TypeCovered = t - } - <-c // zBlank - l = <-c - i, err := strconv.ParseUint(l.token, 10, 8) - if err != nil || l.err { - return nil, &ParseError{f, "bad RRSIG Algorithm", l}, "" - } - rr.Algorithm = uint8(i) - <-c // zBlank - l = <-c - i, err = strconv.ParseUint(l.token, 10, 8) - if err != nil || l.err { - return nil, &ParseError{f, "bad RRSIG Labels", l}, "" - } - rr.Labels = uint8(i) - <-c // zBlank - l = <-c - i, err = strconv.ParseUint(l.token, 10, 32) - if err != nil || l.err { - return nil, &ParseError{f, "bad RRSIG OrigTtl", l}, "" - } - rr.OrigTtl = uint32(i) - <-c // zBlank - l = <-c - if i, err := StringToTime(l.token); err != nil { - // Try to see if all numeric and use it as epoch - if i, err := strconv.ParseInt(l.token, 10, 64); err == nil { - // TODO(miek): error out on > MAX_UINT32, same below - rr.Expiration = uint32(i) - } else { - return nil, &ParseError{f, "bad RRSIG Expiration", l}, "" - } - } else { - rr.Expiration = i - } - <-c // zBlank - l = <-c - if i, err := StringToTime(l.token); err != nil { - if i, err := strconv.ParseInt(l.token, 10, 64); err == nil { - rr.Inception = uint32(i) - } else { - return nil, &ParseError{f, "bad RRSIG Inception", l}, "" - } - } else { - rr.Inception = i - } - <-c // zBlank - l = <-c - i, err = strconv.ParseUint(l.token, 10, 16) - if err != nil || l.err { - return nil, &ParseError{f, "bad RRSIG KeyTag", l}, "" - } - rr.KeyTag = uint16(i) - <-c // zBlank - l = <-c - rr.SignerName = l.token - if l.token == "@" { - rr.SignerName = o - } else { - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad RRSIG SignerName", l}, "" - } - if rr.SignerName[l.length-1] != '.' { - rr.SignerName = appendOrigin(rr.SignerName, o) - } - } - s, e, c1 := endingToString(c, "bad RRSIG Signature", f) - if e != nil { - return nil, e, c1 - } - rr.Signature = s - return rr, nil, c1 -} - -func setNSEC(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(NSEC) - rr.Hdr = h - - l := <-c - rr.NextDomain = l.token - if l.length == 0 { - return rr, nil, l.comment - } - if l.token == "@" { - rr.NextDomain = o - } else { - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad NSEC NextDomain", l}, "" - } - if rr.NextDomain[l.length-1] != '.' { - rr.NextDomain = appendOrigin(rr.NextDomain, o) - } - } - - rr.TypeBitMap = make([]uint16, 0) - var ( - k uint16 - ok bool - ) - l = <-c - for l.value != zNewline && l.value != zEOF { - switch l.value { - case zBlank: - // Ok - case zString: - if k, ok = StringToType[l.tokenUpper]; !ok { - if k, ok = typeToInt(l.tokenUpper); !ok { - return nil, &ParseError{f, "bad NSEC TypeBitMap", l}, "" - } - } - rr.TypeBitMap = append(rr.TypeBitMap, k) - default: - return nil, &ParseError{f, "bad NSEC TypeBitMap", l}, "" - } - l = <-c - } - return rr, nil, l.comment -} - -func setNSEC3(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(NSEC3) - rr.Hdr = h - - l := <-c - if l.length == 0 { - return rr, nil, l.comment - } - i, e := strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { - return nil, &ParseError{f, "bad NSEC3 Hash", l}, "" - } - rr.Hash = uint8(i) - <-c // zBlank - l = <-c - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { - return nil, &ParseError{f, "bad NSEC3 Flags", l}, "" - } - rr.Flags = uint8(i) - <-c // zBlank - l = <-c - i, e = strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { - return nil, &ParseError{f, "bad NSEC3 Iterations", l}, "" - } - rr.Iterations = uint16(i) - <-c - l = <-c - if len(l.token) == 0 || l.err { - return nil, &ParseError{f, "bad NSEC3 Salt", l}, "" - } - rr.SaltLength = uint8(len(l.token)) / 2 - rr.Salt = l.token - - <-c - l = <-c - if len(l.token) == 0 || l.err { - return nil, &ParseError{f, "bad NSEC3 NextDomain", l}, "" - } - rr.HashLength = 20 // Fix for NSEC3 (sha1 160 bits) - rr.NextDomain = l.token - - rr.TypeBitMap = make([]uint16, 0) - var ( - k uint16 - ok bool - ) - l = <-c - for l.value != zNewline && l.value != zEOF { - switch l.value { - case zBlank: - // Ok - case zString: - if k, ok = StringToType[l.tokenUpper]; !ok { - if k, ok = typeToInt(l.tokenUpper); !ok { - return nil, &ParseError{f, "bad NSEC3 TypeBitMap", l}, "" - } - } - rr.TypeBitMap = append(rr.TypeBitMap, k) - default: - return nil, &ParseError{f, "bad NSEC3 TypeBitMap", l}, "" - } - l = <-c - } - return rr, nil, l.comment -} - -func setNSEC3PARAM(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(NSEC3PARAM) - rr.Hdr = h - - l := <-c - if l.length == 0 { - return rr, nil, "" - } - i, e := strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { - return nil, &ParseError{f, "bad NSEC3PARAM Hash", l}, "" - } - rr.Hash = uint8(i) - <-c // zBlank - l = <-c - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { - return nil, &ParseError{f, "bad NSEC3PARAM Flags", l}, "" - } - rr.Flags = uint8(i) - <-c // zBlank - l = <-c - i, e = strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { - return nil, &ParseError{f, "bad NSEC3PARAM Iterations", l}, "" - } - rr.Iterations = uint16(i) - <-c - l = <-c - rr.SaltLength = uint8(len(l.token)) - rr.Salt = l.token - return rr, nil, "" -} - -func setEUI48(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(EUI48) - rr.Hdr = h - - l := <-c - if l.length == 0 { - return rr, nil, "" - } - if l.length != 17 || l.err { - return nil, &ParseError{f, "bad EUI48 Address", l}, "" - } - addr := make([]byte, 12) - dash := 0 - for i := 0; i < 10; i += 2 { - addr[i] = l.token[i+dash] - addr[i+1] = l.token[i+1+dash] - dash++ - if l.token[i+1+dash] != '-' { - return nil, &ParseError{f, "bad EUI48 Address", l}, "" - } - } - addr[10] = l.token[15] - addr[11] = l.token[16] - - i, e := strconv.ParseUint(string(addr), 16, 48) - if e != nil { - return nil, &ParseError{f, "bad EUI48 Address", l}, "" - } - rr.Address = i - return rr, nil, "" -} - -func setEUI64(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(EUI64) - rr.Hdr = h - - l := <-c - if l.length == 0 { - return rr, nil, "" - } - if l.length != 23 || l.err { - return nil, &ParseError{f, "bad EUI64 Address", l}, "" - } - addr := make([]byte, 16) - dash := 0 - for i := 0; i < 14; i += 2 { - addr[i] = l.token[i+dash] - addr[i+1] = l.token[i+1+dash] - dash++ - if l.token[i+1+dash] != '-' { - return nil, &ParseError{f, "bad EUI64 Address", l}, "" - } - } - addr[14] = l.token[21] - addr[15] = l.token[22] - - i, e := strconv.ParseUint(string(addr), 16, 64) - if e != nil { - return nil, &ParseError{f, "bad EUI68 Address", l}, "" - } - rr.Address = uint64(i) - return rr, nil, "" -} - -func setSSHFP(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(SSHFP) - rr.Hdr = h - - l := <-c - if l.length == 0 { - return rr, nil, "" - } - i, e := strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { - return nil, &ParseError{f, "bad SSHFP Algorithm", l}, "" - } - rr.Algorithm = uint8(i) - <-c // zBlank - l = <-c - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { - return nil, &ParseError{f, "bad SSHFP Type", l}, "" - } - rr.Type = uint8(i) - <-c // zBlank - s, e1, c1 := endingToString(c, "bad SSHFP Fingerprint", f) - if e1 != nil { - return nil, e1, c1 - } - rr.FingerPrint = s - return rr, nil, "" -} - -func setDNSKEYs(h RR_Header, c chan lex, o, f, typ string) (RR, *ParseError, string) { - rr := new(DNSKEY) - rr.Hdr = h - - l := <-c - if l.length == 0 { - return rr, nil, l.comment - } - i, e := strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { - return nil, &ParseError{f, "bad " + typ + " Flags", l}, "" - } - rr.Flags = uint16(i) - <-c // zBlank - l = <-c // zString - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { - return nil, &ParseError{f, "bad " + typ + " Protocol", l}, "" - } - rr.Protocol = uint8(i) - <-c // zBlank - l = <-c // zString - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { - return nil, &ParseError{f, "bad " + typ + " Algorithm", l}, "" - } - rr.Algorithm = uint8(i) - s, e1, c1 := endingToString(c, "bad "+typ+" PublicKey", f) - if e1 != nil { - return nil, e1, c1 - } - rr.PublicKey = s - return rr, nil, c1 -} - -func setKEY(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - r, e, s := setDNSKEYs(h, c, o, f, "KEY") - if r != nil { - return &KEY{*r.(*DNSKEY)}, e, s - } - return nil, e, s -} - -func setDNSKEY(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - r, e, s := setDNSKEYs(h, c, o, f, "DNSKEY") - return r, e, s -} - -func setCDNSKEY(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - r, e, s := setDNSKEYs(h, c, o, f, "CDNSKEY") - if r != nil { - return &CDNSKEY{*r.(*DNSKEY)}, e, s - } - return nil, e, s -} - -func setRKEY(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(RKEY) - rr.Hdr = h - - l := <-c - if l.length == 0 { - return rr, nil, l.comment - } - i, e := strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { - return nil, &ParseError{f, "bad RKEY Flags", l}, "" - } - rr.Flags = uint16(i) - <-c // zBlank - l = <-c // zString - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { - return nil, &ParseError{f, "bad RKEY Protocol", l}, "" - } - rr.Protocol = uint8(i) - <-c // zBlank - l = <-c // zString - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { - return nil, &ParseError{f, "bad RKEY Algorithm", l}, "" - } - rr.Algorithm = uint8(i) - s, e1, c1 := endingToString(c, "bad RKEY PublicKey", f) - if e1 != nil { - return nil, e1, c1 - } - rr.PublicKey = s - return rr, nil, c1 -} - -func setEID(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(EID) - rr.Hdr = h - s, e, c1 := endingToString(c, "bad EID Endpoint", f) - if e != nil { - return nil, e, c1 - } - rr.Endpoint = s - return rr, nil, c1 -} - -func setNIMLOC(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(NIMLOC) - rr.Hdr = h - s, e, c1 := endingToString(c, "bad NIMLOC Locator", f) - if e != nil { - return nil, e, c1 - } - rr.Locator = s - return rr, nil, c1 -} - -func setGPOS(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(GPOS) - rr.Hdr = h - l := <-c - if l.length == 0 { - return rr, nil, "" - } - _, e := strconv.ParseFloat(l.token, 64) - if e != nil || l.err { - return nil, &ParseError{f, "bad GPOS Longitude", l}, "" - } - rr.Longitude = l.token - <-c // zBlank - l = <-c - _, e = strconv.ParseFloat(l.token, 64) - if e != nil || l.err { - return nil, &ParseError{f, "bad GPOS Latitude", l}, "" - } - rr.Latitude = l.token - <-c // zBlank - l = <-c - _, e = strconv.ParseFloat(l.token, 64) - if e != nil || l.err { - return nil, &ParseError{f, "bad GPOS Altitude", l}, "" - } - rr.Altitude = l.token - return rr, nil, "" -} - -func setDSs(h RR_Header, c chan lex, o, f, typ string) (RR, *ParseError, string) { - rr := new(DS) - rr.Hdr = h - l := <-c - if l.length == 0 { - return rr, nil, l.comment - } - i, e := strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { - return nil, &ParseError{f, "bad " + typ + " KeyTag", l}, "" - } - rr.KeyTag = uint16(i) - <-c // zBlank - l = <-c - if i, e = strconv.ParseUint(l.token, 10, 8); e != nil { - i, ok := StringToAlgorithm[l.tokenUpper] - if !ok || l.err { - return nil, &ParseError{f, "bad " + typ + " Algorithm", l}, "" - } - rr.Algorithm = i - } else { - rr.Algorithm = uint8(i) - } - <-c // zBlank - l = <-c - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { - return nil, &ParseError{f, "bad " + typ + " DigestType", l}, "" - } - rr.DigestType = uint8(i) - s, e1, c1 := endingToString(c, "bad "+typ+" Digest", f) - if e1 != nil { - return nil, e1, c1 - } - rr.Digest = s - return rr, nil, c1 -} - -func setDS(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - r, e, s := setDSs(h, c, o, f, "DS") - return r, e, s -} - -func setDLV(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - r, e, s := setDSs(h, c, o, f, "DLV") - if r != nil { - return &DLV{*r.(*DS)}, e, s - } - return nil, e, s -} - -func setCDS(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - r, e, s := setDSs(h, c, o, f, "CDS") - if r != nil { - return &CDS{*r.(*DS)}, e, s - } - return nil, e, s -} - -func setTA(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(TA) - rr.Hdr = h - l := <-c - if l.length == 0 { - return rr, nil, l.comment - } - i, e := strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { - return nil, &ParseError{f, "bad TA KeyTag", l}, "" - } - rr.KeyTag = uint16(i) - <-c // zBlank - l = <-c - if i, e := strconv.ParseUint(l.token, 10, 8); e != nil { - i, ok := StringToAlgorithm[l.tokenUpper] - if !ok || l.err { - return nil, &ParseError{f, "bad TA Algorithm", l}, "" - } - rr.Algorithm = i - } else { - rr.Algorithm = uint8(i) - } - <-c // zBlank - l = <-c - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { - return nil, &ParseError{f, "bad TA DigestType", l}, "" - } - rr.DigestType = uint8(i) - s, e, c1 := endingToString(c, "bad TA Digest", f) - if e != nil { - return nil, e.(*ParseError), c1 - } - rr.Digest = s - return rr, nil, c1 -} - -func setTLSA(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(TLSA) - rr.Hdr = h - l := <-c - if l.length == 0 { - return rr, nil, l.comment - } - i, e := strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { - return nil, &ParseError{f, "bad TLSA Usage", l}, "" - } - rr.Usage = uint8(i) - <-c // zBlank - l = <-c - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { - return nil, &ParseError{f, "bad TLSA Selector", l}, "" - } - rr.Selector = uint8(i) - <-c // zBlank - l = <-c - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { - return nil, &ParseError{f, "bad TLSA MatchingType", l}, "" - } - rr.MatchingType = uint8(i) - // So this needs be e2 (i.e. different than e), because...??t - s, e2, c1 := endingToString(c, "bad TLSA Certificate", f) - if e2 != nil { - return nil, e2, c1 - } - rr.Certificate = s - return rr, nil, c1 -} - -func setSMIMEA(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(SMIMEA) - rr.Hdr = h - l := <-c - if l.length == 0 { - return rr, nil, l.comment - } - i, e := strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { - return nil, &ParseError{f, "bad SMIMEA Usage", l}, "" - } - rr.Usage = uint8(i) - <-c // zBlank - l = <-c - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { - return nil, &ParseError{f, "bad SMIMEA Selector", l}, "" - } - rr.Selector = uint8(i) - <-c // zBlank - l = <-c - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { - return nil, &ParseError{f, "bad SMIMEA MatchingType", l}, "" - } - rr.MatchingType = uint8(i) - // So this needs be e2 (i.e. different than e), because...??t - s, e2, c1 := endingToString(c, "bad SMIMEA Certificate", f) - if e2 != nil { - return nil, e2, c1 - } - rr.Certificate = s - return rr, nil, c1 -} - -func setRFC3597(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(RFC3597) - rr.Hdr = h - l := <-c - if l.token != "\\#" { - return nil, &ParseError{f, "bad RFC3597 Rdata", l}, "" - } - <-c // zBlank - l = <-c - rdlength, e := strconv.Atoi(l.token) - if e != nil || l.err { - return nil, &ParseError{f, "bad RFC3597 Rdata ", l}, "" - } - - s, e1, c1 := endingToString(c, "bad RFC3597 Rdata", f) - if e1 != nil { - return nil, e1, c1 - } - if rdlength*2 != len(s) { - return nil, &ParseError{f, "bad RFC3597 Rdata", l}, "" - } - rr.Rdata = s - return rr, nil, c1 -} - -func setSPF(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(SPF) - rr.Hdr = h - - s, e, c1 := endingToTxtSlice(c, "bad SPF Txt", f) - if e != nil { - return nil, e, "" - } - rr.Txt = s - return rr, nil, c1 -} - -func setAVC(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(AVC) - rr.Hdr = h - - s, e, c1 := endingToTxtSlice(c, "bad AVC Txt", f) - if e != nil { - return nil, e, "" - } - rr.Txt = s - return rr, nil, c1 -} - -func setTXT(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(TXT) - rr.Hdr = h - - // no zBlank reading here, because all this rdata is TXT - s, e, c1 := endingToTxtSlice(c, "bad TXT Txt", f) - if e != nil { - return nil, e, "" - } - rr.Txt = s - return rr, nil, c1 -} - -// identical to setTXT -func setNINFO(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(NINFO) - rr.Hdr = h - - s, e, c1 := endingToTxtSlice(c, "bad NINFO ZSData", f) - if e != nil { - return nil, e, "" - } - rr.ZSData = s - return rr, nil, c1 -} - -func setURI(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(URI) - rr.Hdr = h - - l := <-c - if l.length == 0 { // Dynamic updates. - return rr, nil, "" - } - - i, e := strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { - return nil, &ParseError{f, "bad URI Priority", l}, "" - } - rr.Priority = uint16(i) - <-c // zBlank - l = <-c - i, e = strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { - return nil, &ParseError{f, "bad URI Weight", l}, "" - } - rr.Weight = uint16(i) - - <-c // zBlank - s, err, c1 := endingToTxtSlice(c, "bad URI Target", f) - if err != nil { - return nil, err, "" - } - if len(s) > 1 { - return nil, &ParseError{f, "bad URI Target", l}, "" - } - rr.Target = s[0] - return rr, nil, c1 -} - -func setDHCID(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - // awesome record to parse! - rr := new(DHCID) - rr.Hdr = h - - s, e, c1 := endingToString(c, "bad DHCID Digest", f) - if e != nil { - return nil, e, c1 - } - rr.Digest = s - return rr, nil, c1 -} - -func setNID(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(NID) - rr.Hdr = h - - l := <-c - if l.length == 0 { - return rr, nil, "" - } - i, e := strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { - return nil, &ParseError{f, "bad NID Preference", l}, "" - } - rr.Preference = uint16(i) - <-c // zBlank - l = <-c // zString - u, err := stringToNodeID(l) - if err != nil || l.err { - return nil, err, "" - } - rr.NodeID = u - return rr, nil, "" -} - -func setL32(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(L32) - rr.Hdr = h - - l := <-c - if l.length == 0 { - return rr, nil, "" - } - i, e := strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { - return nil, &ParseError{f, "bad L32 Preference", l}, "" - } - rr.Preference = uint16(i) - <-c // zBlank - l = <-c // zString - rr.Locator32 = net.ParseIP(l.token) - if rr.Locator32 == nil || l.err { - return nil, &ParseError{f, "bad L32 Locator", l}, "" - } - return rr, nil, "" -} - -func setLP(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(LP) - rr.Hdr = h - - l := <-c - if l.length == 0 { - return rr, nil, "" - } - i, e := strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { - return nil, &ParseError{f, "bad LP Preference", l}, "" - } - rr.Preference = uint16(i) - <-c // zBlank - l = <-c // zString - rr.Fqdn = l.token - if l.length == 0 { - return rr, nil, "" - } - if l.token == "@" { - rr.Fqdn = o - return rr, nil, "" - } - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad LP Fqdn", l}, "" - } - if rr.Fqdn[l.length-1] != '.' { - rr.Fqdn = appendOrigin(rr.Fqdn, o) - } - return rr, nil, "" -} - -func setL64(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(L64) - rr.Hdr = h - - l := <-c - if l.length == 0 { - return rr, nil, "" - } - i, e := strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { - return nil, &ParseError{f, "bad L64 Preference", l}, "" - } - rr.Preference = uint16(i) - <-c // zBlank - l = <-c // zString - u, err := stringToNodeID(l) - if err != nil || l.err { - return nil, err, "" - } - rr.Locator64 = u - return rr, nil, "" -} - -func setUID(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(UID) - rr.Hdr = h - l := <-c - if l.length == 0 { - return rr, nil, "" - } - i, e := strconv.ParseUint(l.token, 10, 32) - if e != nil || l.err { - return nil, &ParseError{f, "bad UID Uid", l}, "" - } - rr.Uid = uint32(i) - return rr, nil, "" -} - -func setGID(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(GID) - rr.Hdr = h - l := <-c - if l.length == 0 { - return rr, nil, "" - } - i, e := strconv.ParseUint(l.token, 10, 32) - if e != nil || l.err { - return nil, &ParseError{f, "bad GID Gid", l}, "" - } - rr.Gid = uint32(i) - return rr, nil, "" -} - -func setUINFO(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(UINFO) - rr.Hdr = h - s, e, c1 := endingToTxtSlice(c, "bad UINFO Uinfo", f) - if e != nil { - return nil, e, c1 - } - if ln := len(s); ln == 0 { - return rr, nil, c1 - } - rr.Uinfo = s[0] // silently discard anything after the first character-string - return rr, nil, c1 -} - -func setPX(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(PX) - rr.Hdr = h - - l := <-c - if l.length == 0 { - return rr, nil, "" - } - i, e := strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { - return nil, &ParseError{f, "bad PX Preference", l}, "" - } - rr.Preference = uint16(i) - <-c // zBlank - l = <-c // zString - rr.Map822 = l.token - if l.length == 0 { - return rr, nil, "" - } - if l.token == "@" { - rr.Map822 = o - return rr, nil, "" - } - _, ok := IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad PX Map822", l}, "" - } - if rr.Map822[l.length-1] != '.' { - rr.Map822 = appendOrigin(rr.Map822, o) - } - <-c // zBlank - l = <-c // zString - rr.Mapx400 = l.token - if l.token == "@" { - rr.Mapx400 = o - return rr, nil, "" - } - _, ok = IsDomainName(l.token) - if !ok || l.length == 0 || l.err { - return nil, &ParseError{f, "bad PX Mapx400", l}, "" - } - if rr.Mapx400[l.length-1] != '.' { - rr.Mapx400 = appendOrigin(rr.Mapx400, o) - } - return rr, nil, "" -} - -func setCAA(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { - rr := new(CAA) - rr.Hdr = h - l := <-c - if l.length == 0 { - return rr, nil, l.comment - } - i, err := strconv.ParseUint(l.token, 10, 8) - if err != nil || l.err { - return nil, &ParseError{f, "bad CAA Flag", l}, "" - } - rr.Flag = uint8(i) - - <-c // zBlank - l = <-c // zString - if l.value != zString { - return nil, &ParseError{f, "bad CAA Tag", l}, "" - } - rr.Tag = l.token - - <-c // zBlank - s, e, c1 := endingToTxtSlice(c, "bad CAA Value", f) - if e != nil { - return nil, e, "" - } - if len(s) > 1 { - return nil, &ParseError{f, "bad CAA Value", l}, "" - } - rr.Value = s[0] - return rr, nil, c1 -} - -var typeToparserFunc = map[uint16]parserFunc{ - TypeAAAA: {setAAAA, false}, - TypeAFSDB: {setAFSDB, false}, - TypeA: {setA, false}, - TypeCAA: {setCAA, true}, - TypeCDS: {setCDS, true}, - TypeCDNSKEY: {setCDNSKEY, true}, - TypeCERT: {setCERT, true}, - TypeCNAME: {setCNAME, false}, - TypeDHCID: {setDHCID, true}, - TypeDLV: {setDLV, true}, - TypeDNAME: {setDNAME, false}, - TypeKEY: {setKEY, true}, - TypeDNSKEY: {setDNSKEY, true}, - TypeDS: {setDS, true}, - TypeEID: {setEID, true}, - TypeEUI48: {setEUI48, false}, - TypeEUI64: {setEUI64, false}, - TypeGID: {setGID, false}, - TypeGPOS: {setGPOS, false}, - TypeHINFO: {setHINFO, true}, - TypeHIP: {setHIP, true}, - TypeKX: {setKX, false}, - TypeL32: {setL32, false}, - TypeL64: {setL64, false}, - TypeLOC: {setLOC, true}, - TypeLP: {setLP, false}, - TypeMB: {setMB, false}, - TypeMD: {setMD, false}, - TypeMF: {setMF, false}, - TypeMG: {setMG, false}, - TypeMINFO: {setMINFO, false}, - TypeMR: {setMR, false}, - TypeMX: {setMX, false}, - TypeNAPTR: {setNAPTR, false}, - TypeNID: {setNID, false}, - TypeNIMLOC: {setNIMLOC, true}, - TypeNINFO: {setNINFO, true}, - TypeNSAPPTR: {setNSAPPTR, false}, - TypeNSEC3PARAM: {setNSEC3PARAM, false}, - TypeNSEC3: {setNSEC3, true}, - TypeNSEC: {setNSEC, true}, - TypeNS: {setNS, false}, - TypeOPENPGPKEY: {setOPENPGPKEY, true}, - TypePTR: {setPTR, false}, - TypePX: {setPX, false}, - TypeSIG: {setSIG, true}, - TypeRKEY: {setRKEY, true}, - TypeRP: {setRP, false}, - TypeRRSIG: {setRRSIG, true}, - TypeRT: {setRT, false}, - TypeSMIMEA: {setSMIMEA, true}, - TypeSOA: {setSOA, false}, - TypeSPF: {setSPF, true}, - TypeAVC: {setAVC, true}, - TypeSRV: {setSRV, false}, - TypeSSHFP: {setSSHFP, true}, - TypeTALINK: {setTALINK, false}, - TypeTA: {setTA, true}, - TypeTLSA: {setTLSA, true}, - TypeTXT: {setTXT, true}, - TypeUID: {setUID, false}, - TypeUINFO: {setUINFO, true}, - TypeURI: {setURI, true}, - TypeX25: {setX25, false}, -} diff --git a/vendor/github.com/miekg/dns/scanner.go b/vendor/github.com/miekg/dns/scanner.go deleted file mode 100644 index c29bc2f388e..00000000000 --- a/vendor/github.com/miekg/dns/scanner.go +++ /dev/null @@ -1,43 +0,0 @@ -package dns - -// Implement a simple scanner, return a byte stream from an io reader. - -import ( - "bufio" - "io" - "text/scanner" -) - -type scan struct { - src *bufio.Reader - position scanner.Position - eof bool // Have we just seen a eof -} - -func scanInit(r io.Reader) *scan { - s := new(scan) - s.src = bufio.NewReader(r) - s.position.Line = 1 - return s -} - -// tokenText returns the next byte from the input -func (s *scan) tokenText() (byte, error) { - c, err := s.src.ReadByte() - if err != nil { - return c, err - } - // delay the newline handling until the next token is delivered, - // fixes off-by-one errors when reporting a parse error. - if s.eof == true { - s.position.Line++ - s.position.Column = 0 - s.eof = false - } - if c == '\n' { - s.eof = true - return c, nil - } - s.position.Column++ - return c, nil -} diff --git a/vendor/github.com/miekg/dns/server.go b/vendor/github.com/miekg/dns/server.go deleted file mode 100644 index 0ca6e008c08..00000000000 --- a/vendor/github.com/miekg/dns/server.go +++ /dev/null @@ -1,734 +0,0 @@ -// DNS server implementation. - -package dns - -import ( - "bytes" - "crypto/tls" - "encoding/binary" - "io" - "net" - "sync" - "time" -) - -// Maximum number of TCP queries before we close the socket. -const maxTCPQueries = 128 - -// Handler is implemented by any value that implements ServeDNS. -type Handler interface { - ServeDNS(w ResponseWriter, r *Msg) -} - -// A ResponseWriter interface is used by an DNS handler to -// construct an DNS response. -type ResponseWriter interface { - // LocalAddr returns the net.Addr of the server - LocalAddr() net.Addr - // RemoteAddr returns the net.Addr of the client that sent the current request. - RemoteAddr() net.Addr - // WriteMsg writes a reply back to the client. - WriteMsg(*Msg) error - // Write writes a raw buffer back to the client. - Write([]byte) (int, error) - // Close closes the connection. - Close() error - // TsigStatus returns the status of the Tsig. - TsigStatus() error - // TsigTimersOnly sets the tsig timers only boolean. - TsigTimersOnly(bool) - // Hijack lets the caller take over the connection. - // After a call to Hijack(), the DNS package will not do anything with the connection. - Hijack() -} - -type response struct { - hijacked bool // connection has been hijacked by handler - tsigStatus error - tsigTimersOnly bool - tsigRequestMAC string - tsigSecret map[string]string // the tsig secrets - udp *net.UDPConn // i/o connection if UDP was used - tcp net.Conn // i/o connection if TCP was used - udpSession *SessionUDP // oob data to get egress interface right - remoteAddr net.Addr // address of the client - writer Writer // writer to output the raw DNS bits -} - -// ServeMux is an DNS request multiplexer. It matches the -// zone name of each incoming request against a list of -// registered patterns add calls the handler for the pattern -// that most closely matches the zone name. ServeMux is DNSSEC aware, meaning -// that queries for the DS record are redirected to the parent zone (if that -// is also registered), otherwise the child gets the query. -// ServeMux is also safe for concurrent access from multiple goroutines. -type ServeMux struct { - z map[string]Handler - m *sync.RWMutex -} - -// NewServeMux allocates and returns a new ServeMux. -func NewServeMux() *ServeMux { return &ServeMux{z: make(map[string]Handler), m: new(sync.RWMutex)} } - -// DefaultServeMux is the default ServeMux used by Serve. -var DefaultServeMux = NewServeMux() - -// The HandlerFunc type is an adapter to allow the use of -// ordinary functions as DNS handlers. If f is a function -// with the appropriate signature, HandlerFunc(f) is a -// Handler object that calls f. -type HandlerFunc func(ResponseWriter, *Msg) - -// ServeDNS calls f(w, r). -func (f HandlerFunc) ServeDNS(w ResponseWriter, r *Msg) { - f(w, r) -} - -// HandleFailed returns a HandlerFunc that returns SERVFAIL for every request it gets. -func HandleFailed(w ResponseWriter, r *Msg) { - m := new(Msg) - m.SetRcode(r, RcodeServerFailure) - // does not matter if this write fails - w.WriteMsg(m) -} - -func failedHandler() Handler { return HandlerFunc(HandleFailed) } - -// ListenAndServe Starts a server on address and network specified Invoke handler -// for incoming queries. -func ListenAndServe(addr string, network string, handler Handler) error { - server := &Server{Addr: addr, Net: network, Handler: handler} - return server.ListenAndServe() -} - -// ListenAndServeTLS acts like http.ListenAndServeTLS, more information in -// http://golang.org/pkg/net/http/#ListenAndServeTLS -func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error { - cert, err := tls.LoadX509KeyPair(certFile, keyFile) - if err != nil { - return err - } - - config := tls.Config{ - Certificates: []tls.Certificate{cert}, - } - - server := &Server{ - Addr: addr, - Net: "tcp-tls", - TLSConfig: &config, - Handler: handler, - } - - return server.ListenAndServe() -} - -// ActivateAndServe activates a server with a listener from systemd, -// l and p should not both be non-nil. -// If both l and p are not nil only p will be used. -// Invoke handler for incoming queries. -func ActivateAndServe(l net.Listener, p net.PacketConn, handler Handler) error { - server := &Server{Listener: l, PacketConn: p, Handler: handler} - return server.ActivateAndServe() -} - -func (mux *ServeMux) match(q string, t uint16) Handler { - mux.m.RLock() - defer mux.m.RUnlock() - var handler Handler - b := make([]byte, len(q)) // worst case, one label of length q - off := 0 - end := false - for { - l := len(q[off:]) - for i := 0; i < l; i++ { - b[i] = q[off+i] - if b[i] >= 'A' && b[i] <= 'Z' { - b[i] |= ('a' - 'A') - } - } - if h, ok := mux.z[string(b[:l])]; ok { // causes garbage, might want to change the map key - if t != TypeDS { - return h - } - // Continue for DS to see if we have a parent too, if so delegeate to the parent - handler = h - } - off, end = NextLabel(q, off) - if end { - break - } - } - // Wildcard match, if we have found nothing try the root zone as a last resort. - if h, ok := mux.z["."]; ok { - return h - } - return handler -} - -// Handle adds a handler to the ServeMux for pattern. -func (mux *ServeMux) Handle(pattern string, handler Handler) { - if pattern == "" { - panic("dns: invalid pattern " + pattern) - } - mux.m.Lock() - mux.z[Fqdn(pattern)] = handler - mux.m.Unlock() -} - -// HandleFunc adds a handler function to the ServeMux for pattern. -func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Msg)) { - mux.Handle(pattern, HandlerFunc(handler)) -} - -// HandleRemove deregistrars the handler specific for pattern from the ServeMux. -func (mux *ServeMux) HandleRemove(pattern string) { - if pattern == "" { - panic("dns: invalid pattern " + pattern) - } - mux.m.Lock() - delete(mux.z, Fqdn(pattern)) - mux.m.Unlock() -} - -// ServeDNS dispatches the request to the handler whose -// pattern most closely matches the request message. If DefaultServeMux -// is used the correct thing for DS queries is done: a possible parent -// is sought. -// If no handler is found a standard SERVFAIL message is returned -// If the request message does not have exactly one question in the -// question section a SERVFAIL is returned, unlesss Unsafe is true. -func (mux *ServeMux) ServeDNS(w ResponseWriter, request *Msg) { - var h Handler - if len(request.Question) < 1 { // allow more than one question - h = failedHandler() - } else { - if h = mux.match(request.Question[0].Name, request.Question[0].Qtype); h == nil { - h = failedHandler() - } - } - h.ServeDNS(w, request) -} - -// Handle registers the handler with the given pattern -// in the DefaultServeMux. The documentation for -// ServeMux explains how patterns are matched. -func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) } - -// HandleRemove deregisters the handle with the given pattern -// in the DefaultServeMux. -func HandleRemove(pattern string) { DefaultServeMux.HandleRemove(pattern) } - -// HandleFunc registers the handler function with the given pattern -// in the DefaultServeMux. -func HandleFunc(pattern string, handler func(ResponseWriter, *Msg)) { - DefaultServeMux.HandleFunc(pattern, handler) -} - -// Writer writes raw DNS messages; each call to Write should send an entire message. -type Writer interface { - io.Writer -} - -// Reader reads raw DNS messages; each call to ReadTCP or ReadUDP should return an entire message. -type Reader interface { - // ReadTCP reads a raw message from a TCP connection. Implementations may alter - // connection properties, for example the read-deadline. - ReadTCP(conn net.Conn, timeout time.Duration) ([]byte, error) - // ReadUDP reads a raw message from a UDP connection. Implementations may alter - // connection properties, for example the read-deadline. - ReadUDP(conn *net.UDPConn, timeout time.Duration) ([]byte, *SessionUDP, error) -} - -// defaultReader is an adapter for the Server struct that implements the Reader interface -// using the readTCP and readUDP func of the embedded Server. -type defaultReader struct { - *Server -} - -func (dr *defaultReader) ReadTCP(conn net.Conn, timeout time.Duration) ([]byte, error) { - return dr.readTCP(conn, timeout) -} - -func (dr *defaultReader) ReadUDP(conn *net.UDPConn, timeout time.Duration) ([]byte, *SessionUDP, error) { - return dr.readUDP(conn, timeout) -} - -// DecorateReader is a decorator hook for extending or supplanting the functionality of a Reader. -// Implementations should never return a nil Reader. -type DecorateReader func(Reader) Reader - -// DecorateWriter is a decorator hook for extending or supplanting the functionality of a Writer. -// Implementations should never return a nil Writer. -type DecorateWriter func(Writer) Writer - -// A Server defines parameters for running an DNS server. -type Server struct { - // Address to listen on, ":dns" if empty. - Addr string - // if "tcp" or "tcp-tls" (DNS over TLS) it will invoke a TCP listener, otherwise an UDP one - Net string - // TCP Listener to use, this is to aid in systemd's socket activation. - Listener net.Listener - // TLS connection configuration - TLSConfig *tls.Config - // UDP "Listener" to use, this is to aid in systemd's socket activation. - PacketConn net.PacketConn - // Handler to invoke, dns.DefaultServeMux if nil. - Handler Handler - // Default buffer size to use to read incoming UDP messages. If not set - // it defaults to MinMsgSize (512 B). - UDPSize int - // The net.Conn.SetReadTimeout value for new connections, defaults to 2 * time.Second. - ReadTimeout time.Duration - // The net.Conn.SetWriteTimeout value for new connections, defaults to 2 * time.Second. - WriteTimeout time.Duration - // TCP idle timeout for multiple queries, if nil, defaults to 8 * time.Second (RFC 5966). - IdleTimeout func() time.Duration - // Secret(s) for Tsig map[]. - TsigSecret map[string]string - // Unsafe instructs the server to disregard any sanity checks and directly hand the message to - // the handler. It will specifically not check if the query has the QR bit not set. - Unsafe bool - // If NotifyStartedFunc is set it is called once the server has started listening. - NotifyStartedFunc func() - // DecorateReader is optional, allows customization of the process that reads raw DNS messages. - DecorateReader DecorateReader - // DecorateWriter is optional, allows customization of the process that writes raw DNS messages. - DecorateWriter DecorateWriter - - // Graceful shutdown handling - - inFlight sync.WaitGroup - - lock sync.RWMutex - started bool -} - -// ListenAndServe starts a nameserver on the configured address in *Server. -func (srv *Server) ListenAndServe() error { - srv.lock.Lock() - defer srv.lock.Unlock() - if srv.started { - return &Error{err: "server already started"} - } - addr := srv.Addr - if addr == "" { - addr = ":domain" - } - if srv.UDPSize == 0 { - srv.UDPSize = MinMsgSize - } - switch srv.Net { - case "tcp", "tcp4", "tcp6": - a, err := net.ResolveTCPAddr(srv.Net, addr) - if err != nil { - return err - } - l, err := net.ListenTCP(srv.Net, a) - if err != nil { - return err - } - srv.Listener = l - srv.started = true - srv.lock.Unlock() - err = srv.serveTCP(l) - srv.lock.Lock() // to satisfy the defer at the top - return err - case "tcp-tls", "tcp4-tls", "tcp6-tls": - network := "tcp" - if srv.Net == "tcp4-tls" { - network = "tcp4" - } else if srv.Net == "tcp6-tls" { - network = "tcp6" - } - - l, err := tls.Listen(network, addr, srv.TLSConfig) - if err != nil { - return err - } - srv.Listener = l - srv.started = true - srv.lock.Unlock() - err = srv.serveTCP(l) - srv.lock.Lock() // to satisfy the defer at the top - return err - case "udp", "udp4", "udp6": - a, err := net.ResolveUDPAddr(srv.Net, addr) - if err != nil { - return err - } - l, err := net.ListenUDP(srv.Net, a) - if err != nil { - return err - } - if e := setUDPSocketOptions(l); e != nil { - return e - } - srv.PacketConn = l - srv.started = true - srv.lock.Unlock() - err = srv.serveUDP(l) - srv.lock.Lock() // to satisfy the defer at the top - return err - } - return &Error{err: "bad network"} -} - -// ActivateAndServe starts a nameserver with the PacketConn or Listener -// configured in *Server. Its main use is to start a server from systemd. -func (srv *Server) ActivateAndServe() error { - srv.lock.Lock() - defer srv.lock.Unlock() - if srv.started { - return &Error{err: "server already started"} - } - pConn := srv.PacketConn - l := srv.Listener - if pConn != nil { - if srv.UDPSize == 0 { - srv.UDPSize = MinMsgSize - } - // Check PacketConn interface's type is valid and value - // is not nil - if t, ok := pConn.(*net.UDPConn); ok && t != nil { - if e := setUDPSocketOptions(t); e != nil { - return e - } - srv.started = true - srv.lock.Unlock() - e := srv.serveUDP(t) - srv.lock.Lock() // to satisfy the defer at the top - return e - } - } - if l != nil { - srv.started = true - srv.lock.Unlock() - e := srv.serveTCP(l) - srv.lock.Lock() // to satisfy the defer at the top - return e - } - return &Error{err: "bad listeners"} -} - -// Shutdown gracefully shuts down a server. After a call to Shutdown, ListenAndServe and -// ActivateAndServe will return. All in progress queries are completed before the server -// is taken down. If the Shutdown is taking longer than the reading timeout an error -// is returned. -func (srv *Server) Shutdown() error { - srv.lock.Lock() - if !srv.started { - srv.lock.Unlock() - return &Error{err: "server not started"} - } - srv.started = false - srv.lock.Unlock() - - if srv.PacketConn != nil { - srv.PacketConn.Close() - } - if srv.Listener != nil { - srv.Listener.Close() - } - - fin := make(chan bool) - go func() { - srv.inFlight.Wait() - fin <- true - }() - - select { - case <-time.After(srv.getReadTimeout()): - return &Error{err: "server shutdown is pending"} - case <-fin: - return nil - } -} - -// getReadTimeout is a helper func to use system timeout if server did not intend to change it. -func (srv *Server) getReadTimeout() time.Duration { - rtimeout := dnsTimeout - if srv.ReadTimeout != 0 { - rtimeout = srv.ReadTimeout - } - return rtimeout -} - -// serveTCP starts a TCP listener for the server. -// Each request is handled in a separate goroutine. -func (srv *Server) serveTCP(l net.Listener) error { - defer l.Close() - - if srv.NotifyStartedFunc != nil { - srv.NotifyStartedFunc() - } - - reader := Reader(&defaultReader{srv}) - if srv.DecorateReader != nil { - reader = srv.DecorateReader(reader) - } - - handler := srv.Handler - if handler == nil { - handler = DefaultServeMux - } - rtimeout := srv.getReadTimeout() - // deadline is not used here - for { - rw, err := l.Accept() - if err != nil { - if neterr, ok := err.(net.Error); ok && neterr.Temporary() { - continue - } - return err - } - m, err := reader.ReadTCP(rw, rtimeout) - srv.lock.RLock() - if !srv.started { - srv.lock.RUnlock() - return nil - } - srv.lock.RUnlock() - if err != nil { - continue - } - srv.inFlight.Add(1) - go srv.serve(rw.RemoteAddr(), handler, m, nil, nil, rw) - } -} - -// serveUDP starts a UDP listener for the server. -// Each request is handled in a separate goroutine. -func (srv *Server) serveUDP(l *net.UDPConn) error { - defer l.Close() - - if srv.NotifyStartedFunc != nil { - srv.NotifyStartedFunc() - } - - reader := Reader(&defaultReader{srv}) - if srv.DecorateReader != nil { - reader = srv.DecorateReader(reader) - } - - handler := srv.Handler - if handler == nil { - handler = DefaultServeMux - } - rtimeout := srv.getReadTimeout() - // deadline is not used here - for { - m, s, err := reader.ReadUDP(l, rtimeout) - srv.lock.RLock() - if !srv.started { - srv.lock.RUnlock() - return nil - } - srv.lock.RUnlock() - if err != nil { - continue - } - srv.inFlight.Add(1) - go srv.serve(s.RemoteAddr(), handler, m, l, s, nil) - } -} - -// Serve a new connection. -func (srv *Server) serve(a net.Addr, h Handler, m []byte, u *net.UDPConn, s *SessionUDP, t net.Conn) { - defer srv.inFlight.Done() - - w := &response{tsigSecret: srv.TsigSecret, udp: u, tcp: t, remoteAddr: a, udpSession: s} - if srv.DecorateWriter != nil { - w.writer = srv.DecorateWriter(w) - } else { - w.writer = w - } - - q := 0 // counter for the amount of TCP queries we get - - reader := Reader(&defaultReader{srv}) - if srv.DecorateReader != nil { - reader = srv.DecorateReader(reader) - } -Redo: - req := new(Msg) - err := req.Unpack(m) - if err != nil { // Send a FormatError back - x := new(Msg) - x.SetRcodeFormatError(req) - w.WriteMsg(x) - goto Exit - } - if !srv.Unsafe && req.Response { - goto Exit - } - - w.tsigStatus = nil - if w.tsigSecret != nil { - if t := req.IsTsig(); t != nil { - secret := t.Hdr.Name - if _, ok := w.tsigSecret[secret]; !ok { - w.tsigStatus = ErrKeyAlg - } - w.tsigStatus = TsigVerify(m, w.tsigSecret[secret], "", false) - w.tsigTimersOnly = false - w.tsigRequestMAC = req.Extra[len(req.Extra)-1].(*TSIG).MAC - } - } - h.ServeDNS(w, req) // Writes back to the client - -Exit: - if w.tcp == nil { - return - } - // TODO(miek): make this number configurable? - if q > maxTCPQueries { // close socket after this many queries - w.Close() - return - } - - if w.hijacked { - return // client calls Close() - } - if u != nil { // UDP, "close" and return - w.Close() - return - } - idleTimeout := tcpIdleTimeout - if srv.IdleTimeout != nil { - idleTimeout = srv.IdleTimeout() - } - m, err = reader.ReadTCP(w.tcp, idleTimeout) - if err == nil { - q++ - goto Redo - } - w.Close() - return -} - -func (srv *Server) readTCP(conn net.Conn, timeout time.Duration) ([]byte, error) { - conn.SetReadDeadline(time.Now().Add(timeout)) - l := make([]byte, 2) - n, err := conn.Read(l) - if err != nil || n != 2 { - if err != nil { - return nil, err - } - return nil, ErrShortRead - } - length := binary.BigEndian.Uint16(l) - if length == 0 { - return nil, ErrShortRead - } - m := make([]byte, int(length)) - n, err = conn.Read(m[:int(length)]) - if err != nil || n == 0 { - if err != nil { - return nil, err - } - return nil, ErrShortRead - } - i := n - for i < int(length) { - j, err := conn.Read(m[i:int(length)]) - if err != nil { - return nil, err - } - i += j - } - n = i - m = m[:n] - return m, nil -} - -func (srv *Server) readUDP(conn *net.UDPConn, timeout time.Duration) ([]byte, *SessionUDP, error) { - conn.SetReadDeadline(time.Now().Add(timeout)) - m := make([]byte, srv.UDPSize) - n, s, err := ReadFromSessionUDP(conn, m) - if err != nil || n == 0 { - if err != nil { - return nil, nil, err - } - return nil, nil, ErrShortRead - } - m = m[:n] - return m, s, nil -} - -// WriteMsg implements the ResponseWriter.WriteMsg method. -func (w *response) WriteMsg(m *Msg) (err error) { - var data []byte - if w.tsigSecret != nil { // if no secrets, dont check for the tsig (which is a longer check) - if t := m.IsTsig(); t != nil { - data, w.tsigRequestMAC, err = TsigGenerate(m, w.tsigSecret[t.Hdr.Name], w.tsigRequestMAC, w.tsigTimersOnly) - if err != nil { - return err - } - _, err = w.writer.Write(data) - return err - } - } - data, err = m.Pack() - if err != nil { - return err - } - _, err = w.writer.Write(data) - return err -} - -// Write implements the ResponseWriter.Write method. -func (w *response) Write(m []byte) (int, error) { - switch { - case w.udp != nil: - n, err := WriteToSessionUDP(w.udp, m, w.udpSession) - return n, err - case w.tcp != nil: - lm := len(m) - if lm < 2 { - return 0, io.ErrShortBuffer - } - if lm > MaxMsgSize { - return 0, &Error{err: "message too large"} - } - l := make([]byte, 2, 2+lm) - binary.BigEndian.PutUint16(l, uint16(lm)) - m = append(l, m...) - - n, err := io.Copy(w.tcp, bytes.NewReader(m)) - return int(n), err - } - panic("not reached") -} - -// LocalAddr implements the ResponseWriter.LocalAddr method. -func (w *response) LocalAddr() net.Addr { - if w.tcp != nil { - return w.tcp.LocalAddr() - } - return w.udp.LocalAddr() -} - -// RemoteAddr implements the ResponseWriter.RemoteAddr method. -func (w *response) RemoteAddr() net.Addr { return w.remoteAddr } - -// TsigStatus implements the ResponseWriter.TsigStatus method. -func (w *response) TsigStatus() error { return w.tsigStatus } - -// TsigTimersOnly implements the ResponseWriter.TsigTimersOnly method. -func (w *response) TsigTimersOnly(b bool) { w.tsigTimersOnly = b } - -// Hijack implements the ResponseWriter.Hijack method. -func (w *response) Hijack() { w.hijacked = true } - -// Close implements the ResponseWriter.Close method -func (w *response) Close() error { - // Can't close the udp conn, as that is actually the listener. - if w.tcp != nil { - e := w.tcp.Close() - w.tcp = nil - return e - } - return nil -} diff --git a/vendor/github.com/miekg/dns/sig0.go b/vendor/github.com/miekg/dns/sig0.go deleted file mode 100644 index f31e9e6843d..00000000000 --- a/vendor/github.com/miekg/dns/sig0.go +++ /dev/null @@ -1,218 +0,0 @@ -package dns - -import ( - "crypto" - "crypto/dsa" - "crypto/ecdsa" - "crypto/rsa" - "encoding/binary" - "math/big" - "strings" - "time" -) - -// Sign signs a dns.Msg. It fills the signature with the appropriate data. -// The SIG record should have the SignerName, KeyTag, Algorithm, Inception -// and Expiration set. -func (rr *SIG) Sign(k crypto.Signer, m *Msg) ([]byte, error) { - if k == nil { - return nil, ErrPrivKey - } - if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 { - return nil, ErrKey - } - rr.Header().Rrtype = TypeSIG - rr.Header().Class = ClassANY - rr.Header().Ttl = 0 - rr.Header().Name = "." - rr.OrigTtl = 0 - rr.TypeCovered = 0 - rr.Labels = 0 - - buf := make([]byte, m.Len()+rr.len()) - mbuf, err := m.PackBuffer(buf) - if err != nil { - return nil, err - } - if &buf[0] != &mbuf[0] { - return nil, ErrBuf - } - off, err := PackRR(rr, buf, len(mbuf), nil, false) - if err != nil { - return nil, err - } - buf = buf[:off:cap(buf)] - - hash, ok := AlgorithmToHash[rr.Algorithm] - if !ok { - return nil, ErrAlg - } - - hasher := hash.New() - // Write SIG rdata - hasher.Write(buf[len(mbuf)+1+2+2+4+2:]) - // Write message - hasher.Write(buf[:len(mbuf)]) - - signature, err := sign(k, hasher.Sum(nil), hash, rr.Algorithm) - if err != nil { - return nil, err - } - - rr.Signature = toBase64(signature) - - buf = append(buf, signature...) - if len(buf) > int(^uint16(0)) { - return nil, ErrBuf - } - // Adjust sig data length - rdoff := len(mbuf) + 1 + 2 + 2 + 4 - rdlen := binary.BigEndian.Uint16(buf[rdoff:]) - rdlen += uint16(len(signature)) - binary.BigEndian.PutUint16(buf[rdoff:], rdlen) - // Adjust additional count - adc := binary.BigEndian.Uint16(buf[10:]) - adc++ - binary.BigEndian.PutUint16(buf[10:], adc) - return buf, nil -} - -// Verify validates the message buf using the key k. -// It's assumed that buf is a valid message from which rr was unpacked. -func (rr *SIG) Verify(k *KEY, buf []byte) error { - if k == nil { - return ErrKey - } - if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 { - return ErrKey - } - - var hash crypto.Hash - switch rr.Algorithm { - case DSA, RSASHA1: - hash = crypto.SHA1 - case RSASHA256, ECDSAP256SHA256: - hash = crypto.SHA256 - case ECDSAP384SHA384: - hash = crypto.SHA384 - case RSASHA512: - hash = crypto.SHA512 - default: - return ErrAlg - } - hasher := hash.New() - - buflen := len(buf) - qdc := binary.BigEndian.Uint16(buf[4:]) - anc := binary.BigEndian.Uint16(buf[6:]) - auc := binary.BigEndian.Uint16(buf[8:]) - adc := binary.BigEndian.Uint16(buf[10:]) - offset := 12 - var err error - for i := uint16(0); i < qdc && offset < buflen; i++ { - _, offset, err = UnpackDomainName(buf, offset) - if err != nil { - return err - } - // Skip past Type and Class - offset += 2 + 2 - } - for i := uint16(1); i < anc+auc+adc && offset < buflen; i++ { - _, offset, err = UnpackDomainName(buf, offset) - if err != nil { - return err - } - // Skip past Type, Class and TTL - offset += 2 + 2 + 4 - if offset+1 >= buflen { - continue - } - var rdlen uint16 - rdlen = binary.BigEndian.Uint16(buf[offset:]) - offset += 2 - offset += int(rdlen) - } - if offset >= buflen { - return &Error{err: "overflowing unpacking signed message"} - } - - // offset should be just prior to SIG - bodyend := offset - // owner name SHOULD be root - _, offset, err = UnpackDomainName(buf, offset) - if err != nil { - return err - } - // Skip Type, Class, TTL, RDLen - offset += 2 + 2 + 4 + 2 - sigstart := offset - // Skip Type Covered, Algorithm, Labels, Original TTL - offset += 2 + 1 + 1 + 4 - if offset+4+4 >= buflen { - return &Error{err: "overflow unpacking signed message"} - } - expire := binary.BigEndian.Uint32(buf[offset:]) - offset += 4 - incept := binary.BigEndian.Uint32(buf[offset:]) - offset += 4 - now := uint32(time.Now().Unix()) - if now < incept || now > expire { - return ErrTime - } - // Skip key tag - offset += 2 - var signername string - signername, offset, err = UnpackDomainName(buf, offset) - if err != nil { - return err - } - // If key has come from the DNS name compression might - // have mangled the case of the name - if strings.ToLower(signername) != strings.ToLower(k.Header().Name) { - return &Error{err: "signer name doesn't match key name"} - } - sigend := offset - hasher.Write(buf[sigstart:sigend]) - hasher.Write(buf[:10]) - hasher.Write([]byte{ - byte((adc - 1) << 8), - byte(adc - 1), - }) - hasher.Write(buf[12:bodyend]) - - hashed := hasher.Sum(nil) - sig := buf[sigend:] - switch k.Algorithm { - case DSA: - pk := k.publicKeyDSA() - sig = sig[1:] - r := big.NewInt(0) - r.SetBytes(sig[:len(sig)/2]) - s := big.NewInt(0) - s.SetBytes(sig[len(sig)/2:]) - if pk != nil { - if dsa.Verify(pk, hashed, r, s) { - return nil - } - return ErrSig - } - case RSASHA1, RSASHA256, RSASHA512: - pk := k.publicKeyRSA() - if pk != nil { - return rsa.VerifyPKCS1v15(pk, hash, hashed, sig) - } - case ECDSAP256SHA256, ECDSAP384SHA384: - pk := k.publicKeyECDSA() - r := big.NewInt(0) - r.SetBytes(sig[:len(sig)/2]) - s := big.NewInt(0) - s.SetBytes(sig[len(sig)/2:]) - if pk != nil { - if ecdsa.Verify(pk, hashed, r, s) { - return nil - } - return ErrSig - } - } - return ErrKeyAlg -} diff --git a/vendor/github.com/miekg/dns/singleinflight.go b/vendor/github.com/miekg/dns/singleinflight.go deleted file mode 100644 index 9573c7d0b8c..00000000000 --- a/vendor/github.com/miekg/dns/singleinflight.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Adapted for dns package usage by Miek Gieben. - -package dns - -import "sync" -import "time" - -// call is an in-flight or completed singleflight.Do call -type call struct { - wg sync.WaitGroup - val *Msg - rtt time.Duration - err error - dups int -} - -// singleflight represents a class of work and forms a namespace in -// which units of work can be executed with duplicate suppression. -type singleflight struct { - sync.Mutex // protects m - m map[string]*call // lazily initialized -} - -// Do executes and returns the results of the given function, making -// sure that only one execution is in-flight for a given key at a -// time. If a duplicate comes in, the duplicate caller waits for the -// original to complete and receives the same results. -// The return value shared indicates whether v was given to multiple callers. -func (g *singleflight) Do(key string, fn func() (*Msg, time.Duration, error)) (v *Msg, rtt time.Duration, err error, shared bool) { - g.Lock() - if g.m == nil { - g.m = make(map[string]*call) - } - if c, ok := g.m[key]; ok { - c.dups++ - g.Unlock() - c.wg.Wait() - return c.val, c.rtt, c.err, true - } - c := new(call) - c.wg.Add(1) - g.m[key] = c - g.Unlock() - - c.val, c.rtt, c.err = fn() - c.wg.Done() - - g.Lock() - delete(g.m, key) - g.Unlock() - - return c.val, c.rtt, c.err, c.dups > 0 -} diff --git a/vendor/github.com/miekg/dns/smimea.go b/vendor/github.com/miekg/dns/smimea.go deleted file mode 100644 index 4e7ded4b386..00000000000 --- a/vendor/github.com/miekg/dns/smimea.go +++ /dev/null @@ -1,47 +0,0 @@ -package dns - -import ( - "crypto/sha256" - "crypto/x509" - "encoding/hex" -) - -// Sign creates a SMIMEA record from an SSL certificate. -func (r *SMIMEA) Sign(usage, selector, matchingType int, cert *x509.Certificate) (err error) { - r.Hdr.Rrtype = TypeSMIMEA - r.Usage = uint8(usage) - r.Selector = uint8(selector) - r.MatchingType = uint8(matchingType) - - r.Certificate, err = CertificateToDANE(r.Selector, r.MatchingType, cert) - if err != nil { - return err - } - return nil -} - -// Verify verifies a SMIMEA record against an SSL certificate. If it is OK -// a nil error is returned. -func (r *SMIMEA) Verify(cert *x509.Certificate) error { - c, err := CertificateToDANE(r.Selector, r.MatchingType, cert) - if err != nil { - return err // Not also ErrSig? - } - if r.Certificate == c { - return nil - } - return ErrSig // ErrSig, really? -} - -// SMIMEAName returns the ownername of a SMIMEA resource record as per the -// format specified in RFC 'draft-ietf-dane-smime-12' Section 2 and 3 -func SMIMEAName(email, domain string) (string, error) { - hasher := sha256.New() - hasher.Write([]byte(email)) - - // RFC Section 3: "The local-part is hashed using the SHA2-256 - // algorithm with the hash truncated to 28 octets and - // represented in its hexadecimal representation to become the - // left-most label in the prepared domain name" - return hex.EncodeToString(hasher.Sum(nil)[:28]) + "." + "_smimecert." + domain, nil -} diff --git a/vendor/github.com/miekg/dns/tlsa.go b/vendor/github.com/miekg/dns/tlsa.go deleted file mode 100644 index 431e2fb5afc..00000000000 --- a/vendor/github.com/miekg/dns/tlsa.go +++ /dev/null @@ -1,47 +0,0 @@ -package dns - -import ( - "crypto/x509" - "net" - "strconv" -) - -// Sign creates a TLSA record from an SSL certificate. -func (r *TLSA) Sign(usage, selector, matchingType int, cert *x509.Certificate) (err error) { - r.Hdr.Rrtype = TypeTLSA - r.Usage = uint8(usage) - r.Selector = uint8(selector) - r.MatchingType = uint8(matchingType) - - r.Certificate, err = CertificateToDANE(r.Selector, r.MatchingType, cert) - if err != nil { - return err - } - return nil -} - -// Verify verifies a TLSA record against an SSL certificate. If it is OK -// a nil error is returned. -func (r *TLSA) Verify(cert *x509.Certificate) error { - c, err := CertificateToDANE(r.Selector, r.MatchingType, cert) - if err != nil { - return err // Not also ErrSig? - } - if r.Certificate == c { - return nil - } - return ErrSig // ErrSig, really? -} - -// TLSAName returns the ownername of a TLSA resource record as per the -// rules specified in RFC 6698, Section 3. -func TLSAName(name, service, network string) (string, error) { - if !IsFqdn(name) { - return "", ErrFqdn - } - p, err := net.LookupPort(network, service) - if err != nil { - return "", err - } - return "_" + strconv.Itoa(p) + "._" + network + "." + name, nil -} diff --git a/vendor/github.com/miekg/dns/tsig.go b/vendor/github.com/miekg/dns/tsig.go deleted file mode 100644 index 24013096b02..00000000000 --- a/vendor/github.com/miekg/dns/tsig.go +++ /dev/null @@ -1,383 +0,0 @@ -package dns - -import ( - "crypto/hmac" - "crypto/md5" - "crypto/sha1" - "crypto/sha256" - "crypto/sha512" - "encoding/binary" - "encoding/hex" - "hash" - "strconv" - "strings" - "time" -) - -// HMAC hashing codes. These are transmitted as domain names. -const ( - HmacMD5 = "hmac-md5.sig-alg.reg.int." - HmacSHA1 = "hmac-sha1." - HmacSHA256 = "hmac-sha256." - HmacSHA512 = "hmac-sha512." -) - -// TSIG is the RR the holds the transaction signature of a message. -// See RFC 2845 and RFC 4635. -type TSIG struct { - Hdr RR_Header - Algorithm string `dns:"domain-name"` - TimeSigned uint64 `dns:"uint48"` - Fudge uint16 - MACSize uint16 - MAC string `dns:"size-hex:MACSize"` - OrigId uint16 - Error uint16 - OtherLen uint16 - OtherData string `dns:"size-hex:OtherLen"` -} - -// TSIG has no official presentation format, but this will suffice. - -func (rr *TSIG) String() string { - s := "\n;; TSIG PSEUDOSECTION:\n" - s += rr.Hdr.String() + - " " + rr.Algorithm + - " " + tsigTimeToString(rr.TimeSigned) + - " " + strconv.Itoa(int(rr.Fudge)) + - " " + strconv.Itoa(int(rr.MACSize)) + - " " + strings.ToUpper(rr.MAC) + - " " + strconv.Itoa(int(rr.OrigId)) + - " " + strconv.Itoa(int(rr.Error)) + // BIND prints NOERROR - " " + strconv.Itoa(int(rr.OtherLen)) + - " " + rr.OtherData - return s -} - -// The following values must be put in wireformat, so that the MAC can be calculated. -// RFC 2845, section 3.4.2. TSIG Variables. -type tsigWireFmt struct { - // From RR_Header - Name string `dns:"domain-name"` - Class uint16 - Ttl uint32 - // Rdata of the TSIG - Algorithm string `dns:"domain-name"` - TimeSigned uint64 `dns:"uint48"` - Fudge uint16 - // MACSize, MAC and OrigId excluded - Error uint16 - OtherLen uint16 - OtherData string `dns:"size-hex:OtherLen"` -} - -// If we have the MAC use this type to convert it to wiredata. Section 3.4.3. Request MAC -type macWireFmt struct { - MACSize uint16 - MAC string `dns:"size-hex:MACSize"` -} - -// 3.3. Time values used in TSIG calculations -type timerWireFmt struct { - TimeSigned uint64 `dns:"uint48"` - Fudge uint16 -} - -// TsigGenerate fills out the TSIG record attached to the message. -// The message should contain -// a "stub" TSIG RR with the algorithm, key name (owner name of the RR), -// time fudge (defaults to 300 seconds) and the current time -// The TSIG MAC is saved in that Tsig RR. -// When TsigGenerate is called for the first time requestMAC is set to the empty string and -// timersOnly is false. -// If something goes wrong an error is returned, otherwise it is nil. -func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, string, error) { - if m.IsTsig() == nil { - panic("dns: TSIG not last RR in additional") - } - // If we barf here, the caller is to blame - rawsecret, err := fromBase64([]byte(secret)) - if err != nil { - return nil, "", err - } - - rr := m.Extra[len(m.Extra)-1].(*TSIG) - m.Extra = m.Extra[0 : len(m.Extra)-1] // kill the TSIG from the msg - mbuf, err := m.Pack() - if err != nil { - return nil, "", err - } - buf := tsigBuffer(mbuf, rr, requestMAC, timersOnly) - - t := new(TSIG) - var h hash.Hash - switch strings.ToLower(rr.Algorithm) { - case HmacMD5: - h = hmac.New(md5.New, []byte(rawsecret)) - case HmacSHA1: - h = hmac.New(sha1.New, []byte(rawsecret)) - case HmacSHA256: - h = hmac.New(sha256.New, []byte(rawsecret)) - case HmacSHA512: - h = hmac.New(sha512.New, []byte(rawsecret)) - default: - return nil, "", ErrKeyAlg - } - h.Write(buf) - t.MAC = hex.EncodeToString(h.Sum(nil)) - t.MACSize = uint16(len(t.MAC) / 2) // Size is half! - - t.Hdr = RR_Header{Name: rr.Hdr.Name, Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0} - t.Fudge = rr.Fudge - t.TimeSigned = rr.TimeSigned - t.Algorithm = rr.Algorithm - t.OrigId = m.Id - - tbuf := make([]byte, t.len()) - if off, err := PackRR(t, tbuf, 0, nil, false); err == nil { - tbuf = tbuf[:off] // reset to actual size used - } else { - return nil, "", err - } - mbuf = append(mbuf, tbuf...) - // Update the ArCount directly in the buffer. - binary.BigEndian.PutUint16(mbuf[10:], uint16(len(m.Extra)+1)) - - return mbuf, t.MAC, nil -} - -// TsigVerify verifies the TSIG on a message. -// If the signature does not validate err contains the -// error, otherwise it is nil. -func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error { - rawsecret, err := fromBase64([]byte(secret)) - if err != nil { - return err - } - // Strip the TSIG from the incoming msg - stripped, tsig, err := stripTsig(msg) - if err != nil { - return err - } - - msgMAC, err := hex.DecodeString(tsig.MAC) - if err != nil { - return err - } - - buf := tsigBuffer(stripped, tsig, requestMAC, timersOnly) - - // Fudge factor works both ways. A message can arrive before it was signed because - // of clock skew. - now := uint64(time.Now().Unix()) - ti := now - tsig.TimeSigned - if now < tsig.TimeSigned { - ti = tsig.TimeSigned - now - } - if uint64(tsig.Fudge) < ti { - return ErrTime - } - - var h hash.Hash - switch strings.ToLower(tsig.Algorithm) { - case HmacMD5: - h = hmac.New(md5.New, rawsecret) - case HmacSHA1: - h = hmac.New(sha1.New, rawsecret) - case HmacSHA256: - h = hmac.New(sha256.New, rawsecret) - case HmacSHA512: - h = hmac.New(sha512.New, rawsecret) - default: - return ErrKeyAlg - } - h.Write(buf) - if !hmac.Equal(h.Sum(nil), msgMAC) { - return ErrSig - } - return nil -} - -// Create a wiredata buffer for the MAC calculation. -func tsigBuffer(msgbuf []byte, rr *TSIG, requestMAC string, timersOnly bool) []byte { - var buf []byte - if rr.TimeSigned == 0 { - rr.TimeSigned = uint64(time.Now().Unix()) - } - if rr.Fudge == 0 { - rr.Fudge = 300 // Standard (RFC) default. - } - - if requestMAC != "" { - m := new(macWireFmt) - m.MACSize = uint16(len(requestMAC) / 2) - m.MAC = requestMAC - buf = make([]byte, len(requestMAC)) // long enough - n, _ := packMacWire(m, buf) - buf = buf[:n] - } - - tsigvar := make([]byte, DefaultMsgSize) - if timersOnly { - tsig := new(timerWireFmt) - tsig.TimeSigned = rr.TimeSigned - tsig.Fudge = rr.Fudge - n, _ := packTimerWire(tsig, tsigvar) - tsigvar = tsigvar[:n] - } else { - tsig := new(tsigWireFmt) - tsig.Name = strings.ToLower(rr.Hdr.Name) - tsig.Class = ClassANY - tsig.Ttl = rr.Hdr.Ttl - tsig.Algorithm = strings.ToLower(rr.Algorithm) - tsig.TimeSigned = rr.TimeSigned - tsig.Fudge = rr.Fudge - tsig.Error = rr.Error - tsig.OtherLen = rr.OtherLen - tsig.OtherData = rr.OtherData - n, _ := packTsigWire(tsig, tsigvar) - tsigvar = tsigvar[:n] - } - - if requestMAC != "" { - x := append(buf, msgbuf...) - buf = append(x, tsigvar...) - } else { - buf = append(msgbuf, tsigvar...) - } - return buf -} - -// Strip the TSIG from the raw message. -func stripTsig(msg []byte) ([]byte, *TSIG, error) { - // Copied from msg.go's Unpack() Header, but modified. - var ( - dh Header - err error - ) - off, tsigoff := 0, 0 - - if dh, off, err = unpackMsgHdr(msg, off); err != nil { - return nil, nil, err - } - if dh.Arcount == 0 { - return nil, nil, ErrNoSig - } - - // Rcode, see msg.go Unpack() - if int(dh.Bits&0xF) == RcodeNotAuth { - return nil, nil, ErrAuth - } - - for i := 0; i < int(dh.Qdcount); i++ { - _, off, err = unpackQuestion(msg, off) - if err != nil { - return nil, nil, err - } - } - - _, off, err = unpackRRslice(int(dh.Ancount), msg, off) - if err != nil { - return nil, nil, err - } - _, off, err = unpackRRslice(int(dh.Nscount), msg, off) - if err != nil { - return nil, nil, err - } - - rr := new(TSIG) - var extra RR - for i := 0; i < int(dh.Arcount); i++ { - tsigoff = off - extra, off, err = UnpackRR(msg, off) - if err != nil { - return nil, nil, err - } - if extra.Header().Rrtype == TypeTSIG { - rr = extra.(*TSIG) - // Adjust Arcount. - arcount := binary.BigEndian.Uint16(msg[10:]) - binary.BigEndian.PutUint16(msg[10:], arcount-1) - break - } - } - if rr == nil { - return nil, nil, ErrNoSig - } - return msg[:tsigoff], rr, nil -} - -// Translate the TSIG time signed into a date. There is no -// need for RFC1982 calculations as this date is 48 bits. -func tsigTimeToString(t uint64) string { - ti := time.Unix(int64(t), 0).UTC() - return ti.Format("20060102150405") -} - -func packTsigWire(tw *tsigWireFmt, msg []byte) (int, error) { - // copied from zmsg.go TSIG packing - // RR_Header - off, err := PackDomainName(tw.Name, msg, 0, nil, false) - if err != nil { - return off, err - } - off, err = packUint16(tw.Class, msg, off) - if err != nil { - return off, err - } - off, err = packUint32(tw.Ttl, msg, off) - if err != nil { - return off, err - } - - off, err = PackDomainName(tw.Algorithm, msg, off, nil, false) - if err != nil { - return off, err - } - off, err = packUint48(tw.TimeSigned, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(tw.Fudge, msg, off) - if err != nil { - return off, err - } - - off, err = packUint16(tw.Error, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(tw.OtherLen, msg, off) - if err != nil { - return off, err - } - off, err = packStringHex(tw.OtherData, msg, off) - if err != nil { - return off, err - } - return off, nil -} - -func packMacWire(mw *macWireFmt, msg []byte) (int, error) { - off, err := packUint16(mw.MACSize, msg, 0) - if err != nil { - return off, err - } - off, err = packStringHex(mw.MAC, msg, off) - if err != nil { - return off, err - } - return off, nil -} - -func packTimerWire(tw *timerWireFmt, msg []byte) (int, error) { - off, err := packUint48(tw.TimeSigned, msg, 0) - if err != nil { - return off, err - } - off, err = packUint16(tw.Fudge, msg, off) - if err != nil { - return off, err - } - return off, nil -} diff --git a/vendor/github.com/miekg/dns/types.go b/vendor/github.com/miekg/dns/types.go deleted file mode 100644 index 57f065bc817..00000000000 --- a/vendor/github.com/miekg/dns/types.go +++ /dev/null @@ -1,1287 +0,0 @@ -package dns - -import ( - "fmt" - "net" - "strconv" - "strings" - "time" -) - -type ( - // Type is a DNS type. - Type uint16 - // Class is a DNS class. - Class uint16 - // Name is a DNS domain name. - Name string -) - -// Packet formats - -// Wire constants and supported types. -const ( - // valid RR_Header.Rrtype and Question.qtype - - TypeNone uint16 = 0 - TypeA uint16 = 1 - TypeNS uint16 = 2 - TypeMD uint16 = 3 - TypeMF uint16 = 4 - TypeCNAME uint16 = 5 - TypeSOA uint16 = 6 - TypeMB uint16 = 7 - TypeMG uint16 = 8 - TypeMR uint16 = 9 - TypeNULL uint16 = 10 - TypePTR uint16 = 12 - TypeHINFO uint16 = 13 - TypeMINFO uint16 = 14 - TypeMX uint16 = 15 - TypeTXT uint16 = 16 - TypeRP uint16 = 17 - TypeAFSDB uint16 = 18 - TypeX25 uint16 = 19 - TypeISDN uint16 = 20 - TypeRT uint16 = 21 - TypeNSAPPTR uint16 = 23 - TypeSIG uint16 = 24 - TypeKEY uint16 = 25 - TypePX uint16 = 26 - TypeGPOS uint16 = 27 - TypeAAAA uint16 = 28 - TypeLOC uint16 = 29 - TypeNXT uint16 = 30 - TypeEID uint16 = 31 - TypeNIMLOC uint16 = 32 - TypeSRV uint16 = 33 - TypeATMA uint16 = 34 - TypeNAPTR uint16 = 35 - TypeKX uint16 = 36 - TypeCERT uint16 = 37 - TypeDNAME uint16 = 39 - TypeOPT uint16 = 41 // EDNS - TypeDS uint16 = 43 - TypeSSHFP uint16 = 44 - TypeRRSIG uint16 = 46 - TypeNSEC uint16 = 47 - TypeDNSKEY uint16 = 48 - TypeDHCID uint16 = 49 - TypeNSEC3 uint16 = 50 - TypeNSEC3PARAM uint16 = 51 - TypeTLSA uint16 = 52 - TypeSMIMEA uint16 = 53 - TypeHIP uint16 = 55 - TypeNINFO uint16 = 56 - TypeRKEY uint16 = 57 - TypeTALINK uint16 = 58 - TypeCDS uint16 = 59 - TypeCDNSKEY uint16 = 60 - TypeOPENPGPKEY uint16 = 61 - TypeSPF uint16 = 99 - TypeUINFO uint16 = 100 - TypeUID uint16 = 101 - TypeGID uint16 = 102 - TypeUNSPEC uint16 = 103 - TypeNID uint16 = 104 - TypeL32 uint16 = 105 - TypeL64 uint16 = 106 - TypeLP uint16 = 107 - TypeEUI48 uint16 = 108 - TypeEUI64 uint16 = 109 - TypeURI uint16 = 256 - TypeCAA uint16 = 257 - TypeAVC uint16 = 258 - - TypeTKEY uint16 = 249 - TypeTSIG uint16 = 250 - - // valid Question.Qtype only - TypeIXFR uint16 = 251 - TypeAXFR uint16 = 252 - TypeMAILB uint16 = 253 - TypeMAILA uint16 = 254 - TypeANY uint16 = 255 - - TypeTA uint16 = 32768 - TypeDLV uint16 = 32769 - TypeReserved uint16 = 65535 - - // valid Question.Qclass - ClassINET = 1 - ClassCSNET = 2 - ClassCHAOS = 3 - ClassHESIOD = 4 - ClassNONE = 254 - ClassANY = 255 - - // Message Response Codes, see https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml - RcodeSuccess = 0 // NoError - No Error [DNS] - RcodeFormatError = 1 // FormErr - Format Error [DNS] - RcodeServerFailure = 2 // ServFail - Server Failure [DNS] - RcodeNameError = 3 // NXDomain - Non-Existent Domain [DNS] - RcodeNotImplemented = 4 // NotImp - Not Implemented [DNS] - RcodeRefused = 5 // Refused - Query Refused [DNS] - RcodeYXDomain = 6 // YXDomain - Name Exists when it should not [DNS Update] - RcodeYXRrset = 7 // YXRRSet - RR Set Exists when it should not [DNS Update] - RcodeNXRrset = 8 // NXRRSet - RR Set that should exist does not [DNS Update] - RcodeNotAuth = 9 // NotAuth - Server Not Authoritative for zone [DNS Update] - RcodeNotZone = 10 // NotZone - Name not contained in zone [DNS Update/TSIG] - RcodeBadSig = 16 // BADSIG - TSIG Signature Failure [TSIG] - RcodeBadVers = 16 // BADVERS - Bad OPT Version [EDNS0] - RcodeBadKey = 17 // BADKEY - Key not recognized [TSIG] - RcodeBadTime = 18 // BADTIME - Signature out of time window [TSIG] - RcodeBadMode = 19 // BADMODE - Bad TKEY Mode [TKEY] - RcodeBadName = 20 // BADNAME - Duplicate key name [TKEY] - RcodeBadAlg = 21 // BADALG - Algorithm not supported [TKEY] - RcodeBadTrunc = 22 // BADTRUNC - Bad Truncation [TSIG] - RcodeBadCookie = 23 // BADCOOKIE - Bad/missing Server Cookie [DNS Cookies] - - // Message Opcodes. There is no 3. - OpcodeQuery = 0 - OpcodeIQuery = 1 - OpcodeStatus = 2 - OpcodeNotify = 4 - OpcodeUpdate = 5 -) - -// Header is the wire format for the DNS packet header. -type Header struct { - Id uint16 - Bits uint16 - Qdcount, Ancount, Nscount, Arcount uint16 -} - -const ( - headerSize = 12 - - // Header.Bits - _QR = 1 << 15 // query/response (response=1) - _AA = 1 << 10 // authoritative - _TC = 1 << 9 // truncated - _RD = 1 << 8 // recursion desired - _RA = 1 << 7 // recursion available - _Z = 1 << 6 // Z - _AD = 1 << 5 // authticated data - _CD = 1 << 4 // checking disabled - - LOC_EQUATOR = 1 << 31 // RFC 1876, Section 2. - LOC_PRIMEMERIDIAN = 1 << 31 // RFC 1876, Section 2. - - LOC_HOURS = 60 * 1000 - LOC_DEGREES = 60 * LOC_HOURS - - LOC_ALTITUDEBASE = 100000 -) - -// Different Certificate Types, see RFC 4398, Section 2.1 -const ( - CertPKIX = 1 + iota - CertSPKI - CertPGP - CertIPIX - CertISPKI - CertIPGP - CertACPKIX - CertIACPKIX - CertURI = 253 - CertOID = 254 -) - -// CertTypeToString converts the Cert Type to its string representation. -// See RFC 4398 and RFC 6944. -var CertTypeToString = map[uint16]string{ - CertPKIX: "PKIX", - CertSPKI: "SPKI", - CertPGP: "PGP", - CertIPIX: "IPIX", - CertISPKI: "ISPKI", - CertIPGP: "IPGP", - CertACPKIX: "ACPKIX", - CertIACPKIX: "IACPKIX", - CertURI: "URI", - CertOID: "OID", -} - -// StringToCertType is the reverseof CertTypeToString. -var StringToCertType = reverseInt16(CertTypeToString) - -//go:generate go run types_generate.go - -// Question holds a DNS question. There can be multiple questions in the -// question section of a message. Usually there is just one. -type Question struct { - Name string `dns:"cdomain-name"` // "cdomain-name" specifies encoding (and may be compressed) - Qtype uint16 - Qclass uint16 -} - -func (q *Question) len() int { - return len(q.Name) + 1 + 2 + 2 -} - -func (q *Question) String() (s string) { - // prefix with ; (as in dig) - s = ";" + sprintName(q.Name) + "\t" - s += Class(q.Qclass).String() + "\t" - s += " " + Type(q.Qtype).String() - return s -} - -// ANY is a wildcard record. See RFC 1035, Section 3.2.3. ANY -// is named "*" there. -type ANY struct { - Hdr RR_Header - // Does not have any rdata -} - -func (rr *ANY) String() string { return rr.Hdr.String() } - -type CNAME struct { - Hdr RR_Header - Target string `dns:"cdomain-name"` -} - -func (rr *CNAME) String() string { return rr.Hdr.String() + sprintName(rr.Target) } - -type HINFO struct { - Hdr RR_Header - Cpu string - Os string -} - -func (rr *HINFO) String() string { - return rr.Hdr.String() + sprintTxt([]string{rr.Cpu, rr.Os}) -} - -type MB struct { - Hdr RR_Header - Mb string `dns:"cdomain-name"` -} - -func (rr *MB) String() string { return rr.Hdr.String() + sprintName(rr.Mb) } - -type MG struct { - Hdr RR_Header - Mg string `dns:"cdomain-name"` -} - -func (rr *MG) String() string { return rr.Hdr.String() + sprintName(rr.Mg) } - -type MINFO struct { - Hdr RR_Header - Rmail string `dns:"cdomain-name"` - Email string `dns:"cdomain-name"` -} - -func (rr *MINFO) String() string { - return rr.Hdr.String() + sprintName(rr.Rmail) + " " + sprintName(rr.Email) -} - -type MR struct { - Hdr RR_Header - Mr string `dns:"cdomain-name"` -} - -func (rr *MR) String() string { - return rr.Hdr.String() + sprintName(rr.Mr) -} - -type MF struct { - Hdr RR_Header - Mf string `dns:"cdomain-name"` -} - -func (rr *MF) String() string { - return rr.Hdr.String() + sprintName(rr.Mf) -} - -type MD struct { - Hdr RR_Header - Md string `dns:"cdomain-name"` -} - -func (rr *MD) String() string { - return rr.Hdr.String() + sprintName(rr.Md) -} - -type MX struct { - Hdr RR_Header - Preference uint16 - Mx string `dns:"cdomain-name"` -} - -func (rr *MX) String() string { - return rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) + " " + sprintName(rr.Mx) -} - -type AFSDB struct { - Hdr RR_Header - Subtype uint16 - Hostname string `dns:"cdomain-name"` -} - -func (rr *AFSDB) String() string { - return rr.Hdr.String() + strconv.Itoa(int(rr.Subtype)) + " " + sprintName(rr.Hostname) -} - -type X25 struct { - Hdr RR_Header - PSDNAddress string -} - -func (rr *X25) String() string { - return rr.Hdr.String() + rr.PSDNAddress -} - -type RT struct { - Hdr RR_Header - Preference uint16 - Host string `dns:"cdomain-name"` -} - -func (rr *RT) String() string { - return rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) + " " + sprintName(rr.Host) -} - -type NS struct { - Hdr RR_Header - Ns string `dns:"cdomain-name"` -} - -func (rr *NS) String() string { - return rr.Hdr.String() + sprintName(rr.Ns) -} - -type PTR struct { - Hdr RR_Header - Ptr string `dns:"cdomain-name"` -} - -func (rr *PTR) String() string { - return rr.Hdr.String() + sprintName(rr.Ptr) -} - -type RP struct { - Hdr RR_Header - Mbox string `dns:"domain-name"` - Txt string `dns:"domain-name"` -} - -func (rr *RP) String() string { - return rr.Hdr.String() + rr.Mbox + " " + sprintTxt([]string{rr.Txt}) -} - -type SOA struct { - Hdr RR_Header - Ns string `dns:"cdomain-name"` - Mbox string `dns:"cdomain-name"` - Serial uint32 - Refresh uint32 - Retry uint32 - Expire uint32 - Minttl uint32 -} - -func (rr *SOA) String() string { - return rr.Hdr.String() + sprintName(rr.Ns) + " " + sprintName(rr.Mbox) + - " " + strconv.FormatInt(int64(rr.Serial), 10) + - " " + strconv.FormatInt(int64(rr.Refresh), 10) + - " " + strconv.FormatInt(int64(rr.Retry), 10) + - " " + strconv.FormatInt(int64(rr.Expire), 10) + - " " + strconv.FormatInt(int64(rr.Minttl), 10) -} - -type TXT struct { - Hdr RR_Header - Txt []string `dns:"txt"` -} - -func (rr *TXT) String() string { return rr.Hdr.String() + sprintTxt(rr.Txt) } - -func sprintName(s string) string { - src := []byte(s) - dst := make([]byte, 0, len(src)) - for i := 0; i < len(src); { - if i+1 < len(src) && src[i] == '\\' && src[i+1] == '.' { - dst = append(dst, src[i:i+2]...) - i += 2 - } else { - b, n := nextByte(src, i) - if n == 0 { - i++ // dangling back slash - } else if b == '.' { - dst = append(dst, b) - } else { - dst = appendDomainNameByte(dst, b) - } - i += n - } - } - return string(dst) -} - -func sprintTxtOctet(s string) string { - src := []byte(s) - dst := make([]byte, 0, len(src)) - dst = append(dst, '"') - for i := 0; i < len(src); { - if i+1 < len(src) && src[i] == '\\' && src[i+1] == '.' { - dst = append(dst, src[i:i+2]...) - i += 2 - } else { - b, n := nextByte(src, i) - if n == 0 { - i++ // dangling back slash - } else if b == '.' { - dst = append(dst, b) - } else { - if b < ' ' || b > '~' { - dst = appendByte(dst, b) - } else { - dst = append(dst, b) - } - } - i += n - } - } - dst = append(dst, '"') - return string(dst) -} - -func sprintTxt(txt []string) string { - var out []byte - for i, s := range txt { - if i > 0 { - out = append(out, ` "`...) - } else { - out = append(out, '"') - } - bs := []byte(s) - for j := 0; j < len(bs); { - b, n := nextByte(bs, j) - if n == 0 { - break - } - out = appendTXTStringByte(out, b) - j += n - } - out = append(out, '"') - } - return string(out) -} - -func appendDomainNameByte(s []byte, b byte) []byte { - switch b { - case '.', ' ', '\'', '@', ';', '(', ')': // additional chars to escape - return append(s, '\\', b) - } - return appendTXTStringByte(s, b) -} - -func appendTXTStringByte(s []byte, b byte) []byte { - switch b { - case '"', '\\': - return append(s, '\\', b) - } - if b < ' ' || b > '~' { - return appendByte(s, b) - } - return append(s, b) -} - -func appendByte(s []byte, b byte) []byte { - var buf [3]byte - bufs := strconv.AppendInt(buf[:0], int64(b), 10) - s = append(s, '\\') - for i := 0; i < 3-len(bufs); i++ { - s = append(s, '0') - } - for _, r := range bufs { - s = append(s, r) - } - return s -} - -func nextByte(b []byte, offset int) (byte, int) { - if offset >= len(b) { - return 0, 0 - } - if b[offset] != '\\' { - // not an escape sequence - return b[offset], 1 - } - switch len(b) - offset { - case 1: // dangling escape - return 0, 0 - case 2, 3: // too short to be \ddd - default: // maybe \ddd - if isDigit(b[offset+1]) && isDigit(b[offset+2]) && isDigit(b[offset+3]) { - return dddToByte(b[offset+1:]), 4 - } - } - // not \ddd, just an RFC 1035 "quoted" character - return b[offset+1], 2 -} - -type SPF struct { - Hdr RR_Header - Txt []string `dns:"txt"` -} - -func (rr *SPF) String() string { return rr.Hdr.String() + sprintTxt(rr.Txt) } - -type AVC struct { - Hdr RR_Header - Txt []string `dns:"txt"` -} - -func (rr *AVC) String() string { return rr.Hdr.String() + sprintTxt(rr.Txt) } - -type SRV struct { - Hdr RR_Header - Priority uint16 - Weight uint16 - Port uint16 - Target string `dns:"domain-name"` -} - -func (rr *SRV) String() string { - return rr.Hdr.String() + - strconv.Itoa(int(rr.Priority)) + " " + - strconv.Itoa(int(rr.Weight)) + " " + - strconv.Itoa(int(rr.Port)) + " " + sprintName(rr.Target) -} - -type NAPTR struct { - Hdr RR_Header - Order uint16 - Preference uint16 - Flags string - Service string - Regexp string - Replacement string `dns:"domain-name"` -} - -func (rr *NAPTR) String() string { - return rr.Hdr.String() + - strconv.Itoa(int(rr.Order)) + " " + - strconv.Itoa(int(rr.Preference)) + " " + - "\"" + rr.Flags + "\" " + - "\"" + rr.Service + "\" " + - "\"" + rr.Regexp + "\" " + - rr.Replacement -} - -// The CERT resource record, see RFC 4398. -type CERT struct { - Hdr RR_Header - Type uint16 - KeyTag uint16 - Algorithm uint8 - Certificate string `dns:"base64"` -} - -func (rr *CERT) String() string { - var ( - ok bool - certtype, algorithm string - ) - if certtype, ok = CertTypeToString[rr.Type]; !ok { - certtype = strconv.Itoa(int(rr.Type)) - } - if algorithm, ok = AlgorithmToString[rr.Algorithm]; !ok { - algorithm = strconv.Itoa(int(rr.Algorithm)) - } - return rr.Hdr.String() + certtype + - " " + strconv.Itoa(int(rr.KeyTag)) + - " " + algorithm + - " " + rr.Certificate -} - -// The DNAME resource record, see RFC 2672. -type DNAME struct { - Hdr RR_Header - Target string `dns:"domain-name"` -} - -func (rr *DNAME) String() string { - return rr.Hdr.String() + sprintName(rr.Target) -} - -type A struct { - Hdr RR_Header - A net.IP `dns:"a"` -} - -func (rr *A) String() string { - if rr.A == nil { - return rr.Hdr.String() - } - return rr.Hdr.String() + rr.A.String() -} - -type AAAA struct { - Hdr RR_Header - AAAA net.IP `dns:"aaaa"` -} - -func (rr *AAAA) String() string { - if rr.AAAA == nil { - return rr.Hdr.String() - } - return rr.Hdr.String() + rr.AAAA.String() -} - -type PX struct { - Hdr RR_Header - Preference uint16 - Map822 string `dns:"domain-name"` - Mapx400 string `dns:"domain-name"` -} - -func (rr *PX) String() string { - return rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) + " " + sprintName(rr.Map822) + " " + sprintName(rr.Mapx400) -} - -type GPOS struct { - Hdr RR_Header - Longitude string - Latitude string - Altitude string -} - -func (rr *GPOS) String() string { - return rr.Hdr.String() + rr.Longitude + " " + rr.Latitude + " " + rr.Altitude -} - -type LOC struct { - Hdr RR_Header - Version uint8 - Size uint8 - HorizPre uint8 - VertPre uint8 - Latitude uint32 - Longitude uint32 - Altitude uint32 -} - -// cmToM takes a cm value expressed in RFC1876 SIZE mantissa/exponent -// format and returns a string in m (two decimals for the cm) -func cmToM(m, e uint8) string { - if e < 2 { - if e == 1 { - m *= 10 - } - - return fmt.Sprintf("0.%02d", m) - } - - s := fmt.Sprintf("%d", m) - for e > 2 { - s += "0" - e-- - } - return s -} - -func (rr *LOC) String() string { - s := rr.Hdr.String() - - lat := rr.Latitude - ns := "N" - if lat > LOC_EQUATOR { - lat = lat - LOC_EQUATOR - } else { - ns = "S" - lat = LOC_EQUATOR - lat - } - h := lat / LOC_DEGREES - lat = lat % LOC_DEGREES - m := lat / LOC_HOURS - lat = lat % LOC_HOURS - s += fmt.Sprintf("%02d %02d %0.3f %s ", h, m, (float64(lat) / 1000), ns) - - lon := rr.Longitude - ew := "E" - if lon > LOC_PRIMEMERIDIAN { - lon = lon - LOC_PRIMEMERIDIAN - } else { - ew = "W" - lon = LOC_PRIMEMERIDIAN - lon - } - h = lon / LOC_DEGREES - lon = lon % LOC_DEGREES - m = lon / LOC_HOURS - lon = lon % LOC_HOURS - s += fmt.Sprintf("%02d %02d %0.3f %s ", h, m, (float64(lon) / 1000), ew) - - var alt = float64(rr.Altitude) / 100 - alt -= LOC_ALTITUDEBASE - if rr.Altitude%100 != 0 { - s += fmt.Sprintf("%.2fm ", alt) - } else { - s += fmt.Sprintf("%.0fm ", alt) - } - - s += cmToM((rr.Size&0xf0)>>4, rr.Size&0x0f) + "m " - s += cmToM((rr.HorizPre&0xf0)>>4, rr.HorizPre&0x0f) + "m " - s += cmToM((rr.VertPre&0xf0)>>4, rr.VertPre&0x0f) + "m" - - return s -} - -// SIG is identical to RRSIG and nowadays only used for SIG(0), RFC2931. -type SIG struct { - RRSIG -} - -type RRSIG struct { - Hdr RR_Header - TypeCovered uint16 - Algorithm uint8 - Labels uint8 - OrigTtl uint32 - Expiration uint32 - Inception uint32 - KeyTag uint16 - SignerName string `dns:"domain-name"` - Signature string `dns:"base64"` -} - -func (rr *RRSIG) String() string { - s := rr.Hdr.String() - s += Type(rr.TypeCovered).String() - s += " " + strconv.Itoa(int(rr.Algorithm)) + - " " + strconv.Itoa(int(rr.Labels)) + - " " + strconv.FormatInt(int64(rr.OrigTtl), 10) + - " " + TimeToString(rr.Expiration) + - " " + TimeToString(rr.Inception) + - " " + strconv.Itoa(int(rr.KeyTag)) + - " " + sprintName(rr.SignerName) + - " " + rr.Signature - return s -} - -type NSEC struct { - Hdr RR_Header - NextDomain string `dns:"domain-name"` - TypeBitMap []uint16 `dns:"nsec"` -} - -func (rr *NSEC) String() string { - s := rr.Hdr.String() + sprintName(rr.NextDomain) - for i := 0; i < len(rr.TypeBitMap); i++ { - s += " " + Type(rr.TypeBitMap[i]).String() - } - return s -} - -func (rr *NSEC) len() int { - l := rr.Hdr.len() + len(rr.NextDomain) + 1 - lastwindow := uint32(2 ^ 32 + 1) - for _, t := range rr.TypeBitMap { - window := t / 256 - if uint32(window) != lastwindow { - l += 1 + 32 - } - lastwindow = uint32(window) - } - return l -} - -type DLV struct { - DS -} - -type CDS struct { - DS -} - -type DS struct { - Hdr RR_Header - KeyTag uint16 - Algorithm uint8 - DigestType uint8 - Digest string `dns:"hex"` -} - -func (rr *DS) String() string { - return rr.Hdr.String() + strconv.Itoa(int(rr.KeyTag)) + - " " + strconv.Itoa(int(rr.Algorithm)) + - " " + strconv.Itoa(int(rr.DigestType)) + - " " + strings.ToUpper(rr.Digest) -} - -type KX struct { - Hdr RR_Header - Preference uint16 - Exchanger string `dns:"domain-name"` -} - -func (rr *KX) String() string { - return rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) + - " " + sprintName(rr.Exchanger) -} - -type TA struct { - Hdr RR_Header - KeyTag uint16 - Algorithm uint8 - DigestType uint8 - Digest string `dns:"hex"` -} - -func (rr *TA) String() string { - return rr.Hdr.String() + strconv.Itoa(int(rr.KeyTag)) + - " " + strconv.Itoa(int(rr.Algorithm)) + - " " + strconv.Itoa(int(rr.DigestType)) + - " " + strings.ToUpper(rr.Digest) -} - -type TALINK struct { - Hdr RR_Header - PreviousName string `dns:"domain-name"` - NextName string `dns:"domain-name"` -} - -func (rr *TALINK) String() string { - return rr.Hdr.String() + - sprintName(rr.PreviousName) + " " + sprintName(rr.NextName) -} - -type SSHFP struct { - Hdr RR_Header - Algorithm uint8 - Type uint8 - FingerPrint string `dns:"hex"` -} - -func (rr *SSHFP) String() string { - return rr.Hdr.String() + strconv.Itoa(int(rr.Algorithm)) + - " " + strconv.Itoa(int(rr.Type)) + - " " + strings.ToUpper(rr.FingerPrint) -} - -type KEY struct { - DNSKEY -} - -type CDNSKEY struct { - DNSKEY -} - -type DNSKEY struct { - Hdr RR_Header - Flags uint16 - Protocol uint8 - Algorithm uint8 - PublicKey string `dns:"base64"` -} - -func (rr *DNSKEY) String() string { - return rr.Hdr.String() + strconv.Itoa(int(rr.Flags)) + - " " + strconv.Itoa(int(rr.Protocol)) + - " " + strconv.Itoa(int(rr.Algorithm)) + - " " + rr.PublicKey -} - -type RKEY struct { - Hdr RR_Header - Flags uint16 - Protocol uint8 - Algorithm uint8 - PublicKey string `dns:"base64"` -} - -func (rr *RKEY) String() string { - return rr.Hdr.String() + strconv.Itoa(int(rr.Flags)) + - " " + strconv.Itoa(int(rr.Protocol)) + - " " + strconv.Itoa(int(rr.Algorithm)) + - " " + rr.PublicKey -} - -type NSAPPTR struct { - Hdr RR_Header - Ptr string `dns:"domain-name"` -} - -func (rr *NSAPPTR) String() string { return rr.Hdr.String() + sprintName(rr.Ptr) } - -type NSEC3 struct { - Hdr RR_Header - Hash uint8 - Flags uint8 - Iterations uint16 - SaltLength uint8 - Salt string `dns:"size-hex:SaltLength"` - HashLength uint8 - NextDomain string `dns:"size-base32:HashLength"` - TypeBitMap []uint16 `dns:"nsec"` -} - -func (rr *NSEC3) String() string { - s := rr.Hdr.String() - s += strconv.Itoa(int(rr.Hash)) + - " " + strconv.Itoa(int(rr.Flags)) + - " " + strconv.Itoa(int(rr.Iterations)) + - " " + saltToString(rr.Salt) + - " " + rr.NextDomain - for i := 0; i < len(rr.TypeBitMap); i++ { - s += " " + Type(rr.TypeBitMap[i]).String() - } - return s -} - -func (rr *NSEC3) len() int { - l := rr.Hdr.len() + 6 + len(rr.Salt)/2 + 1 + len(rr.NextDomain) + 1 - lastwindow := uint32(2 ^ 32 + 1) - for _, t := range rr.TypeBitMap { - window := t / 256 - if uint32(window) != lastwindow { - l += 1 + 32 - } - lastwindow = uint32(window) - } - return l -} - -type NSEC3PARAM struct { - Hdr RR_Header - Hash uint8 - Flags uint8 - Iterations uint16 - SaltLength uint8 - Salt string `dns:"size-hex:SaltLength"` -} - -func (rr *NSEC3PARAM) String() string { - s := rr.Hdr.String() - s += strconv.Itoa(int(rr.Hash)) + - " " + strconv.Itoa(int(rr.Flags)) + - " " + strconv.Itoa(int(rr.Iterations)) + - " " + saltToString(rr.Salt) - return s -} - -type TKEY struct { - Hdr RR_Header - Algorithm string `dns:"domain-name"` - Inception uint32 - Expiration uint32 - Mode uint16 - Error uint16 - KeySize uint16 - Key string - OtherLen uint16 - OtherData string -} - -func (rr *TKEY) String() string { - // It has no presentation format - return "" -} - -// RFC3597 represents an unknown/generic RR. -type RFC3597 struct { - Hdr RR_Header - Rdata string `dns:"hex"` -} - -func (rr *RFC3597) String() string { - // Let's call it a hack - s := rfc3597Header(rr.Hdr) - - s += "\\# " + strconv.Itoa(len(rr.Rdata)/2) + " " + rr.Rdata - return s -} - -func rfc3597Header(h RR_Header) string { - var s string - - s += sprintName(h.Name) + "\t" - s += strconv.FormatInt(int64(h.Ttl), 10) + "\t" - s += "CLASS" + strconv.Itoa(int(h.Class)) + "\t" - s += "TYPE" + strconv.Itoa(int(h.Rrtype)) + "\t" - return s -} - -type URI struct { - Hdr RR_Header - Priority uint16 - Weight uint16 - Target string `dns:"octet"` -} - -func (rr *URI) String() string { - return rr.Hdr.String() + strconv.Itoa(int(rr.Priority)) + - " " + strconv.Itoa(int(rr.Weight)) + " " + sprintTxtOctet(rr.Target) -} - -type DHCID struct { - Hdr RR_Header - Digest string `dns:"base64"` -} - -func (rr *DHCID) String() string { return rr.Hdr.String() + rr.Digest } - -type TLSA struct { - Hdr RR_Header - Usage uint8 - Selector uint8 - MatchingType uint8 - Certificate string `dns:"hex"` -} - -func (rr *TLSA) String() string { - return rr.Hdr.String() + - strconv.Itoa(int(rr.Usage)) + - " " + strconv.Itoa(int(rr.Selector)) + - " " + strconv.Itoa(int(rr.MatchingType)) + - " " + rr.Certificate -} - -type SMIMEA struct { - Hdr RR_Header - Usage uint8 - Selector uint8 - MatchingType uint8 - Certificate string `dns:"hex"` -} - -func (rr *SMIMEA) String() string { - s := rr.Hdr.String() + - strconv.Itoa(int(rr.Usage)) + - " " + strconv.Itoa(int(rr.Selector)) + - " " + strconv.Itoa(int(rr.MatchingType)) - - // Every Nth char needs a space on this output. If we output - // this as one giant line, we can't read it can in because in some cases - // the cert length overflows scan.maxTok (2048). - sx := splitN(rr.Certificate, 1024) // conservative value here - s += " " + strings.Join(sx, " ") - return s -} - -type HIP struct { - Hdr RR_Header - HitLength uint8 - PublicKeyAlgorithm uint8 - PublicKeyLength uint16 - Hit string `dns:"size-hex:HitLength"` - PublicKey string `dns:"size-base64:PublicKeyLength"` - RendezvousServers []string `dns:"domain-name"` -} - -func (rr *HIP) String() string { - s := rr.Hdr.String() + - strconv.Itoa(int(rr.PublicKeyAlgorithm)) + - " " + rr.Hit + - " " + rr.PublicKey - for _, d := range rr.RendezvousServers { - s += " " + sprintName(d) - } - return s -} - -type NINFO struct { - Hdr RR_Header - ZSData []string `dns:"txt"` -} - -func (rr *NINFO) String() string { return rr.Hdr.String() + sprintTxt(rr.ZSData) } - -type NID struct { - Hdr RR_Header - Preference uint16 - NodeID uint64 -} - -func (rr *NID) String() string { - s := rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) - node := fmt.Sprintf("%0.16x", rr.NodeID) - s += " " + node[0:4] + ":" + node[4:8] + ":" + node[8:12] + ":" + node[12:16] - return s -} - -type L32 struct { - Hdr RR_Header - Preference uint16 - Locator32 net.IP `dns:"a"` -} - -func (rr *L32) String() string { - if rr.Locator32 == nil { - return rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) - } - return rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) + - " " + rr.Locator32.String() -} - -type L64 struct { - Hdr RR_Header - Preference uint16 - Locator64 uint64 -} - -func (rr *L64) String() string { - s := rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) - node := fmt.Sprintf("%0.16X", rr.Locator64) - s += " " + node[0:4] + ":" + node[4:8] + ":" + node[8:12] + ":" + node[12:16] - return s -} - -type LP struct { - Hdr RR_Header - Preference uint16 - Fqdn string `dns:"domain-name"` -} - -func (rr *LP) String() string { - return rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) + " " + sprintName(rr.Fqdn) -} - -type EUI48 struct { - Hdr RR_Header - Address uint64 `dns:"uint48"` -} - -func (rr *EUI48) String() string { return rr.Hdr.String() + euiToString(rr.Address, 48) } - -type EUI64 struct { - Hdr RR_Header - Address uint64 -} - -func (rr *EUI64) String() string { return rr.Hdr.String() + euiToString(rr.Address, 64) } - -type CAA struct { - Hdr RR_Header - Flag uint8 - Tag string - Value string `dns:"octet"` -} - -func (rr *CAA) String() string { - return rr.Hdr.String() + strconv.Itoa(int(rr.Flag)) + " " + rr.Tag + " " + sprintTxtOctet(rr.Value) -} - -type UID struct { - Hdr RR_Header - Uid uint32 -} - -func (rr *UID) String() string { return rr.Hdr.String() + strconv.FormatInt(int64(rr.Uid), 10) } - -type GID struct { - Hdr RR_Header - Gid uint32 -} - -func (rr *GID) String() string { return rr.Hdr.String() + strconv.FormatInt(int64(rr.Gid), 10) } - -type UINFO struct { - Hdr RR_Header - Uinfo string -} - -func (rr *UINFO) String() string { return rr.Hdr.String() + sprintTxt([]string{rr.Uinfo}) } - -type EID struct { - Hdr RR_Header - Endpoint string `dns:"hex"` -} - -func (rr *EID) String() string { return rr.Hdr.String() + strings.ToUpper(rr.Endpoint) } - -type NIMLOC struct { - Hdr RR_Header - Locator string `dns:"hex"` -} - -func (rr *NIMLOC) String() string { return rr.Hdr.String() + strings.ToUpper(rr.Locator) } - -type OPENPGPKEY struct { - Hdr RR_Header - PublicKey string `dns:"base64"` -} - -func (rr *OPENPGPKEY) String() string { return rr.Hdr.String() + rr.PublicKey } - -// TimeToString translates the RRSIG's incep. and expir. times to the -// string representation used when printing the record. -// It takes serial arithmetic (RFC 1982) into account. -func TimeToString(t uint32) string { - mod := ((int64(t) - time.Now().Unix()) / year68) - 1 - if mod < 0 { - mod = 0 - } - ti := time.Unix(int64(t)-(mod*year68), 0).UTC() - return ti.Format("20060102150405") -} - -// StringToTime translates the RRSIG's incep. and expir. times from -// string values like "20110403154150" to an 32 bit integer. -// It takes serial arithmetic (RFC 1982) into account. -func StringToTime(s string) (uint32, error) { - t, err := time.Parse("20060102150405", s) - if err != nil { - return 0, err - } - mod := (t.Unix() / year68) - 1 - if mod < 0 { - mod = 0 - } - return uint32(t.Unix() - (mod * year68)), nil -} - -// saltToString converts a NSECX salt to uppercase and returns "-" when it is empty. -func saltToString(s string) string { - if len(s) == 0 { - return "-" - } - return strings.ToUpper(s) -} - -func euiToString(eui uint64, bits int) (hex string) { - switch bits { - case 64: - hex = fmt.Sprintf("%16.16x", eui) - hex = hex[0:2] + "-" + hex[2:4] + "-" + hex[4:6] + "-" + hex[6:8] + - "-" + hex[8:10] + "-" + hex[10:12] + "-" + hex[12:14] + "-" + hex[14:16] - case 48: - hex = fmt.Sprintf("%12.12x", eui) - hex = hex[0:2] + "-" + hex[2:4] + "-" + hex[4:6] + "-" + hex[6:8] + - "-" + hex[8:10] + "-" + hex[10:12] - } - return -} - -// copyIP returns a copy of ip. -func copyIP(ip net.IP) net.IP { - p := make(net.IP, len(ip)) - copy(p, ip) - return p -} - -// SplitN splits a string into N sized string chunks. -// This might become an exported function once. -func splitN(s string, n int) []string { - if len(s) < n { - return []string{s} - } - sx := []string{} - p, i := 0, n - for { - if i <= len(s) { - sx = append(sx, s[p:i]) - } else { - sx = append(sx, s[p:]) - break - - } - p, i = p+n, i+n - } - - return sx -} diff --git a/vendor/github.com/miekg/dns/types_generate.go b/vendor/github.com/miekg/dns/types_generate.go deleted file mode 100644 index dd1310942ba..00000000000 --- a/vendor/github.com/miekg/dns/types_generate.go +++ /dev/null @@ -1,271 +0,0 @@ -//+build ignore - -// types_generate.go is meant to run with go generate. It will use -// go/{importer,types} to track down all the RR struct types. Then for each type -// it will generate conversion tables (TypeToRR and TypeToString) and banal -// methods (len, Header, copy) based on the struct tags. The generated source is -// written to ztypes.go, and is meant to be checked into git. -package main - -import ( - "bytes" - "fmt" - "go/format" - "go/importer" - "go/types" - "log" - "os" - "strings" - "text/template" -) - -var skipLen = map[string]struct{}{ - "NSEC": {}, - "NSEC3": {}, - "OPT": {}, -} - -var packageHdr = ` -// *** DO NOT MODIFY *** -// AUTOGENERATED BY go generate from type_generate.go - -package dns - -import ( - "encoding/base64" - "net" -) - -` - -var TypeToRR = template.Must(template.New("TypeToRR").Parse(` -// TypeToRR is a map of constructors for each RR type. -var TypeToRR = map[uint16]func() RR{ -{{range .}}{{if ne . "RFC3597"}} Type{{.}}: func() RR { return new({{.}}) }, -{{end}}{{end}} } - -`)) - -var typeToString = template.Must(template.New("typeToString").Parse(` -// TypeToString is a map of strings for each RR type. -var TypeToString = map[uint16]string{ -{{range .}}{{if ne . "NSAPPTR"}} Type{{.}}: "{{.}}", -{{end}}{{end}} TypeNSAPPTR: "NSAP-PTR", -} - -`)) - -var headerFunc = template.Must(template.New("headerFunc").Parse(` -// Header() functions -{{range .}} func (rr *{{.}}) Header() *RR_Header { return &rr.Hdr } -{{end}} - -`)) - -// getTypeStruct will take a type and the package scope, and return the -// (innermost) struct if the type is considered a RR type (currently defined as -// those structs beginning with a RR_Header, could be redefined as implementing -// the RR interface). The bool return value indicates if embedded structs were -// resolved. -func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) { - st, ok := t.Underlying().(*types.Struct) - if !ok { - return nil, false - } - if st.Field(0).Type() == scope.Lookup("RR_Header").Type() { - return st, false - } - if st.Field(0).Anonymous() { - st, _ := getTypeStruct(st.Field(0).Type(), scope) - return st, true - } - return nil, false -} - -func main() { - // Import and type-check the package - pkg, err := importer.Default().Import("github.com/miekg/dns") - fatalIfErr(err) - scope := pkg.Scope() - - // Collect constants like TypeX - var numberedTypes []string - for _, name := range scope.Names() { - o := scope.Lookup(name) - if o == nil || !o.Exported() { - continue - } - b, ok := o.Type().(*types.Basic) - if !ok || b.Kind() != types.Uint16 { - continue - } - if !strings.HasPrefix(o.Name(), "Type") { - continue - } - name := strings.TrimPrefix(o.Name(), "Type") - if name == "PrivateRR" { - continue - } - numberedTypes = append(numberedTypes, name) - } - - // Collect actual types (*X) - var namedTypes []string - for _, name := range scope.Names() { - o := scope.Lookup(name) - if o == nil || !o.Exported() { - continue - } - if st, _ := getTypeStruct(o.Type(), scope); st == nil { - continue - } - if name == "PrivateRR" { - continue - } - - // Check if corresponding TypeX exists - if scope.Lookup("Type"+o.Name()) == nil && o.Name() != "RFC3597" { - log.Fatalf("Constant Type%s does not exist.", o.Name()) - } - - namedTypes = append(namedTypes, o.Name()) - } - - b := &bytes.Buffer{} - b.WriteString(packageHdr) - - // Generate TypeToRR - fatalIfErr(TypeToRR.Execute(b, namedTypes)) - - // Generate typeToString - fatalIfErr(typeToString.Execute(b, numberedTypes)) - - // Generate headerFunc - fatalIfErr(headerFunc.Execute(b, namedTypes)) - - // Generate len() - fmt.Fprint(b, "// len() functions\n") - for _, name := range namedTypes { - if _, ok := skipLen[name]; ok { - continue - } - o := scope.Lookup(name) - st, isEmbedded := getTypeStruct(o.Type(), scope) - if isEmbedded { - continue - } - fmt.Fprintf(b, "func (rr *%s) len() int {\n", name) - fmt.Fprintf(b, "l := rr.Hdr.len()\n") - for i := 1; i < st.NumFields(); i++ { - o := func(s string) { fmt.Fprintf(b, s, st.Field(i).Name()) } - - if _, ok := st.Field(i).Type().(*types.Slice); ok { - switch st.Tag(i) { - case `dns:"-"`: - // ignored - case `dns:"cdomain-name"`, `dns:"domain-name"`, `dns:"txt"`: - o("for _, x := range rr.%s { l += len(x) + 1 }\n") - default: - log.Fatalln(name, st.Field(i).Name(), st.Tag(i)) - } - continue - } - - switch { - case st.Tag(i) == `dns:"-"`: - // ignored - case st.Tag(i) == `dns:"cdomain-name"`, st.Tag(i) == `dns:"domain-name"`: - o("l += len(rr.%s) + 1\n") - case st.Tag(i) == `dns:"octet"`: - o("l += len(rr.%s)\n") - case strings.HasPrefix(st.Tag(i), `dns:"size-base64`): - fallthrough - case st.Tag(i) == `dns:"base64"`: - o("l += base64.StdEncoding.DecodedLen(len(rr.%s))\n") - case strings.HasPrefix(st.Tag(i), `dns:"size-hex`): - fallthrough - case st.Tag(i) == `dns:"hex"`: - o("l += len(rr.%s)/2 + 1\n") - case st.Tag(i) == `dns:"a"`: - o("l += net.IPv4len // %s\n") - case st.Tag(i) == `dns:"aaaa"`: - o("l += net.IPv6len // %s\n") - case st.Tag(i) == `dns:"txt"`: - o("for _, t := range rr.%s { l += len(t) + 1 }\n") - case st.Tag(i) == `dns:"uint48"`: - o("l += 6 // %s\n") - case st.Tag(i) == "": - switch st.Field(i).Type().(*types.Basic).Kind() { - case types.Uint8: - o("l++ // %s\n") - case types.Uint16: - o("l += 2 // %s\n") - case types.Uint32: - o("l += 4 // %s\n") - case types.Uint64: - o("l += 8 // %s\n") - case types.String: - o("l += len(rr.%s) + 1\n") - default: - log.Fatalln(name, st.Field(i).Name()) - } - default: - log.Fatalln(name, st.Field(i).Name(), st.Tag(i)) - } - } - fmt.Fprintf(b, "return l }\n") - } - - // Generate copy() - fmt.Fprint(b, "// copy() functions\n") - for _, name := range namedTypes { - o := scope.Lookup(name) - st, isEmbedded := getTypeStruct(o.Type(), scope) - if isEmbedded { - continue - } - fmt.Fprintf(b, "func (rr *%s) copy() RR {\n", name) - fields := []string{"*rr.Hdr.copyHeader()"} - for i := 1; i < st.NumFields(); i++ { - f := st.Field(i).Name() - if sl, ok := st.Field(i).Type().(*types.Slice); ok { - t := sl.Underlying().String() - t = strings.TrimPrefix(t, "[]") - if strings.Contains(t, ".") { - splits := strings.Split(t, ".") - t = splits[len(splits)-1] - } - fmt.Fprintf(b, "%s := make([]%s, len(rr.%s)); copy(%s, rr.%s)\n", - f, t, f, f, f) - fields = append(fields, f) - continue - } - if st.Field(i).Type().String() == "net.IP" { - fields = append(fields, "copyIP(rr."+f+")") - continue - } - fields = append(fields, "rr."+f) - } - fmt.Fprintf(b, "return &%s{%s}\n", name, strings.Join(fields, ",")) - fmt.Fprintf(b, "}\n") - } - - // gofmt - res, err := format.Source(b.Bytes()) - if err != nil { - b.WriteTo(os.Stderr) - log.Fatal(err) - } - - // write result - f, err := os.Create("ztypes.go") - fatalIfErr(err) - defer f.Close() - f.Write(res) -} - -func fatalIfErr(err error) { - if err != nil { - log.Fatal(err) - } -} diff --git a/vendor/github.com/miekg/dns/udp.go b/vendor/github.com/miekg/dns/udp.go deleted file mode 100644 index af111b9a894..00000000000 --- a/vendor/github.com/miekg/dns/udp.go +++ /dev/null @@ -1,34 +0,0 @@ -// +build !windows - -package dns - -import ( - "net" -) - -// SessionUDP holds the remote address and the associated -// out-of-band data. -type SessionUDP struct { - raddr *net.UDPAddr - context []byte -} - -// RemoteAddr returns the remote network address. -func (s *SessionUDP) RemoteAddr() net.Addr { return s.raddr } - -// ReadFromSessionUDP acts just like net.UDPConn.ReadFrom(), but returns a session object instead of a -// net.UDPAddr. -func ReadFromSessionUDP(conn *net.UDPConn, b []byte) (int, *SessionUDP, error) { - oob := make([]byte, 40) - n, oobn, _, raddr, err := conn.ReadMsgUDP(b, oob) - if err != nil { - return n, nil, err - } - return n, &SessionUDP{raddr, oob[:oobn]}, err -} - -// WriteToSessionUDP acts just like net.UDPConn.WritetTo(), but uses a *SessionUDP instead of a net.Addr. -func WriteToSessionUDP(conn *net.UDPConn, b []byte, session *SessionUDP) (int, error) { - n, _, err := conn.WriteMsgUDP(b, session.context, session.raddr) - return n, err -} diff --git a/vendor/github.com/miekg/dns/udp_linux.go b/vendor/github.com/miekg/dns/udp_linux.go deleted file mode 100644 index 033df423998..00000000000 --- a/vendor/github.com/miekg/dns/udp_linux.go +++ /dev/null @@ -1,105 +0,0 @@ -// +build linux,!appengine - -package dns - -// See: -// * http://stackoverflow.com/questions/3062205/setting-the-source-ip-for-a-udp-socket and -// * http://blog.powerdns.com/2012/10/08/on-binding-datagram-udp-sockets-to-the-any-addresses/ -// -// Why do we need this: When listening on 0.0.0.0 with UDP so kernel decides what is the outgoing -// interface, this might not always be the correct one. This code will make sure the egress -// packet's interface matched the ingress' one. - -import ( - "net" - "syscall" -) - -// setUDPSocketOptions sets the UDP socket options. -// This function is implemented on a per platform basis. See udp_*.go for more details -func setUDPSocketOptions(conn *net.UDPConn) error { - sa, err := getUDPSocketName(conn) - if err != nil { - return err - } - switch sa.(type) { - case *syscall.SockaddrInet6: - v6only, err := getUDPSocketOptions6Only(conn) - if err != nil { - return err - } - setUDPSocketOptions6(conn) - if !v6only { - setUDPSocketOptions4(conn) - } - case *syscall.SockaddrInet4: - setUDPSocketOptions4(conn) - } - return nil -} - -// setUDPSocketOptions4 prepares the v4 socket for sessions. -func setUDPSocketOptions4(conn *net.UDPConn) error { - file, err := conn.File() - if err != nil { - return err - } - if err := syscall.SetsockoptInt(int(file.Fd()), syscall.IPPROTO_IP, syscall.IP_PKTINFO, 1); err != nil { - file.Close() - return err - } - // Calling File() above results in the connection becoming blocking, we must fix that. - // See https://github.com/miekg/dns/issues/279 - err = syscall.SetNonblock(int(file.Fd()), true) - if err != nil { - file.Close() - return err - } - file.Close() - return nil -} - -// setUDPSocketOptions6 prepares the v6 socket for sessions. -func setUDPSocketOptions6(conn *net.UDPConn) error { - file, err := conn.File() - if err != nil { - return err - } - if err := syscall.SetsockoptInt(int(file.Fd()), syscall.IPPROTO_IPV6, syscall.IPV6_RECVPKTINFO, 1); err != nil { - file.Close() - return err - } - err = syscall.SetNonblock(int(file.Fd()), true) - if err != nil { - file.Close() - return err - } - file.Close() - return nil -} - -// getUDPSocketOption6Only return true if the socket is v6 only and false when it is v4/v6 combined -// (dualstack). -func getUDPSocketOptions6Only(conn *net.UDPConn) (bool, error) { - file, err := conn.File() - if err != nil { - return false, err - } - // dual stack. See http://stackoverflow.com/questions/1618240/how-to-support-both-ipv4-and-ipv6-connections - v6only, err := syscall.GetsockoptInt(int(file.Fd()), syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY) - if err != nil { - file.Close() - return false, err - } - file.Close() - return v6only == 1, nil -} - -func getUDPSocketName(conn *net.UDPConn) (syscall.Sockaddr, error) { - file, err := conn.File() - if err != nil { - return nil, err - } - defer file.Close() - return syscall.Getsockname(int(file.Fd())) -} diff --git a/vendor/github.com/miekg/dns/udp_other.go b/vendor/github.com/miekg/dns/udp_other.go deleted file mode 100644 index 488a282b26a..00000000000 --- a/vendor/github.com/miekg/dns/udp_other.go +++ /dev/null @@ -1,15 +0,0 @@ -// +build !linux appengine - -package dns - -import ( - "net" -) - -// These do nothing. See udp_linux.go for an example of how to implement this. - -// We tried to adhire to some kind of naming scheme. -func setUDPSocketOptions(conn *net.UDPConn) error { return nil } -func setUDPSocketOptions4(conn *net.UDPConn) error { return nil } -func setUDPSocketOptions6(conn *net.UDPConn) error { return nil } -func getUDPSocketOptions6Only(conn *net.UDPConn) (bool, error) { return false, nil } diff --git a/vendor/github.com/miekg/dns/udp_windows.go b/vendor/github.com/miekg/dns/udp_windows.go deleted file mode 100644 index 51e532ac2a8..00000000000 --- a/vendor/github.com/miekg/dns/udp_windows.go +++ /dev/null @@ -1,29 +0,0 @@ -// +build windows - -package dns - -import "net" - -type SessionUDP struct { - raddr *net.UDPAddr -} - -func (s *SessionUDP) RemoteAddr() net.Addr { return s.raddr } - -// ReadFromSessionUDP acts just like net.UDPConn.ReadFrom(), but returns a session object instead of a -// net.UDPAddr. -func ReadFromSessionUDP(conn *net.UDPConn, b []byte) (int, *SessionUDP, error) { - n, raddr, err := conn.ReadFrom(b) - if err != nil { - return n, nil, err - } - session := &SessionUDP{raddr.(*net.UDPAddr)} - return n, session, err -} - -// WriteToSessionUDP acts just like net.UDPConn.WritetTo(), but uses a *SessionUDP instead of a net.Addr. -func WriteToSessionUDP(conn *net.UDPConn, b []byte, session *SessionUDP) (int, error) { - n, err := conn.WriteTo(b, session.raddr) - return n, err -} - diff --git a/vendor/github.com/miekg/dns/update.go b/vendor/github.com/miekg/dns/update.go deleted file mode 100644 index e90c5c968ec..00000000000 --- a/vendor/github.com/miekg/dns/update.go +++ /dev/null @@ -1,106 +0,0 @@ -package dns - -// NameUsed sets the RRs in the prereq section to -// "Name is in use" RRs. RFC 2136 section 2.4.4. -func (u *Msg) NameUsed(rr []RR) { - if u.Answer == nil { - u.Answer = make([]RR, 0, len(rr)) - } - for _, r := range rr { - u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: TypeANY, Class: ClassANY}}) - } -} - -// NameNotUsed sets the RRs in the prereq section to -// "Name is in not use" RRs. RFC 2136 section 2.4.5. -func (u *Msg) NameNotUsed(rr []RR) { - if u.Answer == nil { - u.Answer = make([]RR, 0, len(rr)) - } - for _, r := range rr { - u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: TypeANY, Class: ClassNONE}}) - } -} - -// Used sets the RRs in the prereq section to -// "RRset exists (value dependent -- with rdata)" RRs. RFC 2136 section 2.4.2. -func (u *Msg) Used(rr []RR) { - if len(u.Question) == 0 { - panic("dns: empty question section") - } - if u.Answer == nil { - u.Answer = make([]RR, 0, len(rr)) - } - for _, r := range rr { - r.Header().Class = u.Question[0].Qclass - u.Answer = append(u.Answer, r) - } -} - -// RRsetUsed sets the RRs in the prereq section to -// "RRset exists (value independent -- no rdata)" RRs. RFC 2136 section 2.4.1. -func (u *Msg) RRsetUsed(rr []RR) { - if u.Answer == nil { - u.Answer = make([]RR, 0, len(rr)) - } - for _, r := range rr { - u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: r.Header().Rrtype, Class: ClassANY}}) - } -} - -// RRsetNotUsed sets the RRs in the prereq section to -// "RRset does not exist" RRs. RFC 2136 section 2.4.3. -func (u *Msg) RRsetNotUsed(rr []RR) { - if u.Answer == nil { - u.Answer = make([]RR, 0, len(rr)) - } - for _, r := range rr { - u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: r.Header().Rrtype, Class: ClassNONE}}) - } -} - -// Insert creates a dynamic update packet that adds an complete RRset, see RFC 2136 section 2.5.1. -func (u *Msg) Insert(rr []RR) { - if len(u.Question) == 0 { - panic("dns: empty question section") - } - if u.Ns == nil { - u.Ns = make([]RR, 0, len(rr)) - } - for _, r := range rr { - r.Header().Class = u.Question[0].Qclass - u.Ns = append(u.Ns, r) - } -} - -// RemoveRRset creates a dynamic update packet that deletes an RRset, see RFC 2136 section 2.5.2. -func (u *Msg) RemoveRRset(rr []RR) { - if u.Ns == nil { - u.Ns = make([]RR, 0, len(rr)) - } - for _, r := range rr { - u.Ns = append(u.Ns, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: r.Header().Rrtype, Class: ClassANY}}) - } -} - -// RemoveName creates a dynamic update packet that deletes all RRsets of a name, see RFC 2136 section 2.5.3 -func (u *Msg) RemoveName(rr []RR) { - if u.Ns == nil { - u.Ns = make([]RR, 0, len(rr)) - } - for _, r := range rr { - u.Ns = append(u.Ns, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: TypeANY, Class: ClassANY}}) - } -} - -// Remove creates a dynamic update packet deletes RR from a RRSset, see RFC 2136 section 2.5.4 -func (u *Msg) Remove(rr []RR) { - if u.Ns == nil { - u.Ns = make([]RR, 0, len(rr)) - } - for _, r := range rr { - r.Header().Class = ClassNONE - r.Header().Ttl = 0 - u.Ns = append(u.Ns, r) - } -} diff --git a/vendor/github.com/miekg/dns/xfr.go b/vendor/github.com/miekg/dns/xfr.go deleted file mode 100644 index 576c5590afd..00000000000 --- a/vendor/github.com/miekg/dns/xfr.go +++ /dev/null @@ -1,255 +0,0 @@ -package dns - -import ( - "fmt" - "time" -) - -// Envelope is used when doing a zone transfer with a remote server. -type Envelope struct { - RR []RR // The set of RRs in the answer section of the xfr reply message. - Error error // If something went wrong, this contains the error. -} - -// A Transfer defines parameters that are used during a zone transfer. -type Transfer struct { - *Conn - DialTimeout time.Duration // net.DialTimeout, defaults to 2 seconds - ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds - WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds - TsigSecret map[string]string // Secret(s) for Tsig map[], zonename must be fully qualified - tsigTimersOnly bool -} - -// Think we need to away to stop the transfer - -// In performs an incoming transfer with the server in a. -// If you would like to set the source IP, or some other attribute -// of a Dialer for a Transfer, you can do so by specifying the attributes -// in the Transfer.Conn: -// -// d := net.Dialer{LocalAddr: transfer_source} -// con, err := d.Dial("tcp", master) -// dnscon := &dns.Conn{Conn:con} -// transfer = &dns.Transfer{Conn: dnscon} -// channel, err := transfer.In(message, master) -// -func (t *Transfer) In(q *Msg, a string) (env chan *Envelope, err error) { - timeout := dnsTimeout - if t.DialTimeout != 0 { - timeout = t.DialTimeout - } - if t.Conn == nil { - t.Conn, err = DialTimeout("tcp", a, timeout) - if err != nil { - return nil, err - } - } - if err := t.WriteMsg(q); err != nil { - return nil, err - } - env = make(chan *Envelope) - go func() { - if q.Question[0].Qtype == TypeAXFR { - go t.inAxfr(q.Id, env) - return - } - if q.Question[0].Qtype == TypeIXFR { - go t.inIxfr(q.Id, env) - return - } - }() - return env, nil -} - -func (t *Transfer) inAxfr(id uint16, c chan *Envelope) { - first := true - defer t.Close() - defer close(c) - timeout := dnsTimeout - if t.ReadTimeout != 0 { - timeout = t.ReadTimeout - } - for { - t.Conn.SetReadDeadline(time.Now().Add(timeout)) - in, err := t.ReadMsg() - if err != nil { - c <- &Envelope{nil, err} - return - } - if id != in.Id { - c <- &Envelope{in.Answer, ErrId} - return - } - if first { - if in.Rcode != RcodeSuccess { - c <- &Envelope{in.Answer, &Error{err: fmt.Sprintf(errXFR, in.Rcode)}} - return - } - if !isSOAFirst(in) { - c <- &Envelope{in.Answer, ErrSoa} - return - } - first = !first - // only one answer that is SOA, receive more - if len(in.Answer) == 1 { - t.tsigTimersOnly = true - c <- &Envelope{in.Answer, nil} - continue - } - } - - if !first { - t.tsigTimersOnly = true // Subsequent envelopes use this. - if isSOALast(in) { - c <- &Envelope{in.Answer, nil} - return - } - c <- &Envelope{in.Answer, nil} - } - } -} - -func (t *Transfer) inIxfr(id uint16, c chan *Envelope) { - serial := uint32(0) // The first serial seen is the current server serial - first := true - defer t.Close() - defer close(c) - timeout := dnsTimeout - if t.ReadTimeout != 0 { - timeout = t.ReadTimeout - } - for { - t.SetReadDeadline(time.Now().Add(timeout)) - in, err := t.ReadMsg() - if err != nil { - c <- &Envelope{nil, err} - return - } - if id != in.Id { - c <- &Envelope{in.Answer, ErrId} - return - } - if first { - if in.Rcode != RcodeSuccess { - c <- &Envelope{in.Answer, &Error{err: fmt.Sprintf(errXFR, in.Rcode)}} - return - } - // A single SOA RR signals "no changes" - if len(in.Answer) == 1 && isSOAFirst(in) { - c <- &Envelope{in.Answer, nil} - return - } - - // Check if the returned answer is ok - if !isSOAFirst(in) { - c <- &Envelope{in.Answer, ErrSoa} - return - } - // This serial is important - serial = in.Answer[0].(*SOA).Serial - first = !first - } - - // Now we need to check each message for SOA records, to see what we need to do - if !first { - t.tsigTimersOnly = true - // If the last record in the IXFR contains the servers' SOA, we should quit - if v, ok := in.Answer[len(in.Answer)-1].(*SOA); ok { - if v.Serial == serial { - c <- &Envelope{in.Answer, nil} - return - } - } - c <- &Envelope{in.Answer, nil} - } - } -} - -// Out performs an outgoing transfer with the client connecting in w. -// Basic use pattern: -// -// ch := make(chan *dns.Envelope) -// tr := new(dns.Transfer) -// go tr.Out(w, r, ch) -// ch <- &dns.Envelope{RR: []dns.RR{soa, rr1, rr2, rr3, soa}} -// close(ch) -// w.Hijack() -// // w.Close() // Client closes connection -// -// The server is responsible for sending the correct sequence of RRs through the -// channel ch. -func (t *Transfer) Out(w ResponseWriter, q *Msg, ch chan *Envelope) error { - for x := range ch { - r := new(Msg) - // Compress? - r.SetReply(q) - r.Authoritative = true - // assume it fits TODO(miek): fix - r.Answer = append(r.Answer, x.RR...) - if err := w.WriteMsg(r); err != nil { - return err - } - } - w.TsigTimersOnly(true) - return nil -} - -// ReadMsg reads a message from the transfer connection t. -func (t *Transfer) ReadMsg() (*Msg, error) { - m := new(Msg) - p := make([]byte, MaxMsgSize) - n, err := t.Read(p) - if err != nil && n == 0 { - return nil, err - } - p = p[:n] - if err := m.Unpack(p); err != nil { - return nil, err - } - if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil { - if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok { - return m, ErrSecret - } - // Need to work on the original message p, as that was used to calculate the tsig. - err = TsigVerify(p, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly) - t.tsigRequestMAC = ts.MAC - } - return m, err -} - -// WriteMsg writes a message through the transfer connection t. -func (t *Transfer) WriteMsg(m *Msg) (err error) { - var out []byte - if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil { - if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok { - return ErrSecret - } - out, t.tsigRequestMAC, err = TsigGenerate(m, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly) - } else { - out, err = m.Pack() - } - if err != nil { - return err - } - if _, err = t.Write(out); err != nil { - return err - } - return nil -} - -func isSOAFirst(in *Msg) bool { - if len(in.Answer) > 0 { - return in.Answer[0].Header().Rrtype == TypeSOA - } - return false -} - -func isSOALast(in *Msg) bool { - if len(in.Answer) > 0 { - return in.Answer[len(in.Answer)-1].Header().Rrtype == TypeSOA - } - return false -} - -const errXFR = "bad xfr rcode: %d" diff --git a/vendor/github.com/miekg/dns/zcompress.go b/vendor/github.com/miekg/dns/zcompress.go deleted file mode 100644 index b277978b96a..00000000000 --- a/vendor/github.com/miekg/dns/zcompress.go +++ /dev/null @@ -1,119 +0,0 @@ -// *** DO NOT MODIFY *** -// AUTOGENERATED BY go generate from compress_generate.go - -package dns - -func compressionLenHelperType(c map[string]int, r RR) { - switch x := r.(type) { - case *PTR: - compressionLenHelper(c, x.Ptr) - case *SOA: - compressionLenHelper(c, x.Ns) - compressionLenHelper(c, x.Mbox) - case *AFSDB: - compressionLenHelper(c, x.Hostname) - case *HIP: - for i := range x.RendezvousServers { - compressionLenHelper(c, x.RendezvousServers[i]) - } - case *LP: - compressionLenHelper(c, x.Fqdn) - case *CNAME: - compressionLenHelper(c, x.Target) - case *MB: - compressionLenHelper(c, x.Mb) - case *RP: - compressionLenHelper(c, x.Mbox) - compressionLenHelper(c, x.Txt) - case *RRSIG: - compressionLenHelper(c, x.SignerName) - case *MF: - compressionLenHelper(c, x.Mf) - case *MINFO: - compressionLenHelper(c, x.Rmail) - compressionLenHelper(c, x.Email) - case *SIG: - compressionLenHelper(c, x.SignerName) - case *SRV: - compressionLenHelper(c, x.Target) - case *TSIG: - compressionLenHelper(c, x.Algorithm) - case *KX: - compressionLenHelper(c, x.Exchanger) - case *MG: - compressionLenHelper(c, x.Mg) - case *NSAPPTR: - compressionLenHelper(c, x.Ptr) - case *PX: - compressionLenHelper(c, x.Map822) - compressionLenHelper(c, x.Mapx400) - case *DNAME: - compressionLenHelper(c, x.Target) - case *MR: - compressionLenHelper(c, x.Mr) - case *MX: - compressionLenHelper(c, x.Mx) - case *TKEY: - compressionLenHelper(c, x.Algorithm) - case *NSEC: - compressionLenHelper(c, x.NextDomain) - case *TALINK: - compressionLenHelper(c, x.PreviousName) - compressionLenHelper(c, x.NextName) - case *MD: - compressionLenHelper(c, x.Md) - case *NAPTR: - compressionLenHelper(c, x.Replacement) - case *NS: - compressionLenHelper(c, x.Ns) - case *RT: - compressionLenHelper(c, x.Host) - } -} - -func compressionLenSearchType(c map[string]int, r RR) (int, bool) { - switch x := r.(type) { - case *MG: - k1, ok1 := compressionLenSearch(c, x.Mg) - return k1, ok1 - case *PTR: - k1, ok1 := compressionLenSearch(c, x.Ptr) - return k1, ok1 - case *AFSDB: - k1, ok1 := compressionLenSearch(c, x.Hostname) - return k1, ok1 - case *MB: - k1, ok1 := compressionLenSearch(c, x.Mb) - return k1, ok1 - case *MD: - k1, ok1 := compressionLenSearch(c, x.Md) - return k1, ok1 - case *MF: - k1, ok1 := compressionLenSearch(c, x.Mf) - return k1, ok1 - case *NS: - k1, ok1 := compressionLenSearch(c, x.Ns) - return k1, ok1 - case *RT: - k1, ok1 := compressionLenSearch(c, x.Host) - return k1, ok1 - case *SOA: - k1, ok1 := compressionLenSearch(c, x.Ns) - k2, ok2 := compressionLenSearch(c, x.Mbox) - return k1 + k2, ok1 && ok2 - case *CNAME: - k1, ok1 := compressionLenSearch(c, x.Target) - return k1, ok1 - case *MINFO: - k1, ok1 := compressionLenSearch(c, x.Rmail) - k2, ok2 := compressionLenSearch(c, x.Email) - return k1 + k2, ok1 && ok2 - case *MR: - k1, ok1 := compressionLenSearch(c, x.Mr) - return k1, ok1 - case *MX: - k1, ok1 := compressionLenSearch(c, x.Mx) - return k1, ok1 - } - return 0, false -} diff --git a/vendor/github.com/miekg/dns/zmsg.go b/vendor/github.com/miekg/dns/zmsg.go deleted file mode 100644 index 418fb1fe3e2..00000000000 --- a/vendor/github.com/miekg/dns/zmsg.go +++ /dev/null @@ -1,3565 +0,0 @@ -// *** DO NOT MODIFY *** -// AUTOGENERATED BY go generate from msg_generate.go - -package dns - -// pack*() functions - -func (rr *A) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packDataA(rr.A, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *AAAA) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packDataAAAA(rr.AAAA, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *AFSDB) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint16(rr.Subtype, msg, off) - if err != nil { - return off, err - } - off, err = PackDomainName(rr.Hostname, msg, off, compression, compress) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *ANY) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *AVC) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packStringTxt(rr.Txt, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *CAA) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint8(rr.Flag, msg, off) - if err != nil { - return off, err - } - off, err = packString(rr.Tag, msg, off) - if err != nil { - return off, err - } - off, err = packStringOctet(rr.Value, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *CDNSKEY) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint16(rr.Flags, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.Protocol, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.Algorithm, msg, off) - if err != nil { - return off, err - } - off, err = packStringBase64(rr.PublicKey, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *CDS) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint16(rr.KeyTag, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.Algorithm, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.DigestType, msg, off) - if err != nil { - return off, err - } - off, err = packStringHex(rr.Digest, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *CERT) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint16(rr.Type, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(rr.KeyTag, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.Algorithm, msg, off) - if err != nil { - return off, err - } - off, err = packStringBase64(rr.Certificate, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *CNAME) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = PackDomainName(rr.Target, msg, off, compression, compress) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *DHCID) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packStringBase64(rr.Digest, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *DLV) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint16(rr.KeyTag, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.Algorithm, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.DigestType, msg, off) - if err != nil { - return off, err - } - off, err = packStringHex(rr.Digest, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *DNAME) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = PackDomainName(rr.Target, msg, off, compression, false) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *DNSKEY) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint16(rr.Flags, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.Protocol, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.Algorithm, msg, off) - if err != nil { - return off, err - } - off, err = packStringBase64(rr.PublicKey, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *DS) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint16(rr.KeyTag, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.Algorithm, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.DigestType, msg, off) - if err != nil { - return off, err - } - off, err = packStringHex(rr.Digest, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *EID) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packStringHex(rr.Endpoint, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *EUI48) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint48(rr.Address, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *EUI64) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint64(rr.Address, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *GID) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint32(rr.Gid, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *GPOS) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packString(rr.Longitude, msg, off) - if err != nil { - return off, err - } - off, err = packString(rr.Latitude, msg, off) - if err != nil { - return off, err - } - off, err = packString(rr.Altitude, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *HINFO) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packString(rr.Cpu, msg, off) - if err != nil { - return off, err - } - off, err = packString(rr.Os, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *HIP) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint8(rr.HitLength, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.PublicKeyAlgorithm, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(rr.PublicKeyLength, msg, off) - if err != nil { - return off, err - } - off, err = packStringHex(rr.Hit, msg, off) - if err != nil { - return off, err - } - off, err = packStringBase64(rr.PublicKey, msg, off) - if err != nil { - return off, err - } - off, err = packDataDomainNames(rr.RendezvousServers, msg, off, compression, compress) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *KEY) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint16(rr.Flags, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.Protocol, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.Algorithm, msg, off) - if err != nil { - return off, err - } - off, err = packStringBase64(rr.PublicKey, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *KX) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint16(rr.Preference, msg, off) - if err != nil { - return off, err - } - off, err = PackDomainName(rr.Exchanger, msg, off, compression, false) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *L32) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint16(rr.Preference, msg, off) - if err != nil { - return off, err - } - off, err = packDataA(rr.Locator32, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *L64) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint16(rr.Preference, msg, off) - if err != nil { - return off, err - } - off, err = packUint64(rr.Locator64, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *LOC) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint8(rr.Version, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.Size, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.HorizPre, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.VertPre, msg, off) - if err != nil { - return off, err - } - off, err = packUint32(rr.Latitude, msg, off) - if err != nil { - return off, err - } - off, err = packUint32(rr.Longitude, msg, off) - if err != nil { - return off, err - } - off, err = packUint32(rr.Altitude, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *LP) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint16(rr.Preference, msg, off) - if err != nil { - return off, err - } - off, err = PackDomainName(rr.Fqdn, msg, off, compression, false) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *MB) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = PackDomainName(rr.Mb, msg, off, compression, compress) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *MD) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = PackDomainName(rr.Md, msg, off, compression, compress) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *MF) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = PackDomainName(rr.Mf, msg, off, compression, compress) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *MG) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = PackDomainName(rr.Mg, msg, off, compression, compress) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *MINFO) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = PackDomainName(rr.Rmail, msg, off, compression, compress) - if err != nil { - return off, err - } - off, err = PackDomainName(rr.Email, msg, off, compression, compress) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *MR) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = PackDomainName(rr.Mr, msg, off, compression, compress) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *MX) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint16(rr.Preference, msg, off) - if err != nil { - return off, err - } - off, err = PackDomainName(rr.Mx, msg, off, compression, compress) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *NAPTR) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint16(rr.Order, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(rr.Preference, msg, off) - if err != nil { - return off, err - } - off, err = packString(rr.Flags, msg, off) - if err != nil { - return off, err - } - off, err = packString(rr.Service, msg, off) - if err != nil { - return off, err - } - off, err = packString(rr.Regexp, msg, off) - if err != nil { - return off, err - } - off, err = PackDomainName(rr.Replacement, msg, off, compression, false) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *NID) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint16(rr.Preference, msg, off) - if err != nil { - return off, err - } - off, err = packUint64(rr.NodeID, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *NIMLOC) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packStringHex(rr.Locator, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *NINFO) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packStringTxt(rr.ZSData, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *NS) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = PackDomainName(rr.Ns, msg, off, compression, compress) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *NSAPPTR) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = PackDomainName(rr.Ptr, msg, off, compression, false) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *NSEC) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = PackDomainName(rr.NextDomain, msg, off, compression, false) - if err != nil { - return off, err - } - off, err = packDataNsec(rr.TypeBitMap, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *NSEC3) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint8(rr.Hash, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.Flags, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(rr.Iterations, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.SaltLength, msg, off) - if err != nil { - return off, err - } - // Only pack salt if value is not "-", i.e. empty - if rr.Salt != "-" { - off, err = packStringHex(rr.Salt, msg, off) - if err != nil { - return off, err - } - } - off, err = packUint8(rr.HashLength, msg, off) - if err != nil { - return off, err - } - off, err = packStringBase32(rr.NextDomain, msg, off) - if err != nil { - return off, err - } - off, err = packDataNsec(rr.TypeBitMap, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *NSEC3PARAM) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint8(rr.Hash, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.Flags, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(rr.Iterations, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.SaltLength, msg, off) - if err != nil { - return off, err - } - // Only pack salt if value is not "-", i.e. empty - if rr.Salt != "-" { - off, err = packStringHex(rr.Salt, msg, off) - if err != nil { - return off, err - } - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *OPENPGPKEY) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packStringBase64(rr.PublicKey, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *OPT) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packDataOpt(rr.Option, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *PTR) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = PackDomainName(rr.Ptr, msg, off, compression, compress) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *PX) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint16(rr.Preference, msg, off) - if err != nil { - return off, err - } - off, err = PackDomainName(rr.Map822, msg, off, compression, false) - if err != nil { - return off, err - } - off, err = PackDomainName(rr.Mapx400, msg, off, compression, false) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *RFC3597) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packStringHex(rr.Rdata, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *RKEY) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint16(rr.Flags, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.Protocol, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.Algorithm, msg, off) - if err != nil { - return off, err - } - off, err = packStringBase64(rr.PublicKey, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *RP) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = PackDomainName(rr.Mbox, msg, off, compression, false) - if err != nil { - return off, err - } - off, err = PackDomainName(rr.Txt, msg, off, compression, false) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *RRSIG) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint16(rr.TypeCovered, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.Algorithm, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.Labels, msg, off) - if err != nil { - return off, err - } - off, err = packUint32(rr.OrigTtl, msg, off) - if err != nil { - return off, err - } - off, err = packUint32(rr.Expiration, msg, off) - if err != nil { - return off, err - } - off, err = packUint32(rr.Inception, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(rr.KeyTag, msg, off) - if err != nil { - return off, err - } - off, err = PackDomainName(rr.SignerName, msg, off, compression, false) - if err != nil { - return off, err - } - off, err = packStringBase64(rr.Signature, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *RT) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint16(rr.Preference, msg, off) - if err != nil { - return off, err - } - off, err = PackDomainName(rr.Host, msg, off, compression, compress) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *SIG) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint16(rr.TypeCovered, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.Algorithm, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.Labels, msg, off) - if err != nil { - return off, err - } - off, err = packUint32(rr.OrigTtl, msg, off) - if err != nil { - return off, err - } - off, err = packUint32(rr.Expiration, msg, off) - if err != nil { - return off, err - } - off, err = packUint32(rr.Inception, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(rr.KeyTag, msg, off) - if err != nil { - return off, err - } - off, err = PackDomainName(rr.SignerName, msg, off, compression, false) - if err != nil { - return off, err - } - off, err = packStringBase64(rr.Signature, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *SMIMEA) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint8(rr.Usage, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.Selector, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.MatchingType, msg, off) - if err != nil { - return off, err - } - off, err = packStringHex(rr.Certificate, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *SOA) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = PackDomainName(rr.Ns, msg, off, compression, compress) - if err != nil { - return off, err - } - off, err = PackDomainName(rr.Mbox, msg, off, compression, compress) - if err != nil { - return off, err - } - off, err = packUint32(rr.Serial, msg, off) - if err != nil { - return off, err - } - off, err = packUint32(rr.Refresh, msg, off) - if err != nil { - return off, err - } - off, err = packUint32(rr.Retry, msg, off) - if err != nil { - return off, err - } - off, err = packUint32(rr.Expire, msg, off) - if err != nil { - return off, err - } - off, err = packUint32(rr.Minttl, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *SPF) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packStringTxt(rr.Txt, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *SRV) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint16(rr.Priority, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(rr.Weight, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(rr.Port, msg, off) - if err != nil { - return off, err - } - off, err = PackDomainName(rr.Target, msg, off, compression, false) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *SSHFP) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint8(rr.Algorithm, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.Type, msg, off) - if err != nil { - return off, err - } - off, err = packStringHex(rr.FingerPrint, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *TA) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint16(rr.KeyTag, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.Algorithm, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.DigestType, msg, off) - if err != nil { - return off, err - } - off, err = packStringHex(rr.Digest, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *TALINK) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = PackDomainName(rr.PreviousName, msg, off, compression, false) - if err != nil { - return off, err - } - off, err = PackDomainName(rr.NextName, msg, off, compression, false) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *TKEY) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = PackDomainName(rr.Algorithm, msg, off, compression, false) - if err != nil { - return off, err - } - off, err = packUint32(rr.Inception, msg, off) - if err != nil { - return off, err - } - off, err = packUint32(rr.Expiration, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(rr.Mode, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(rr.Error, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(rr.KeySize, msg, off) - if err != nil { - return off, err - } - off, err = packString(rr.Key, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(rr.OtherLen, msg, off) - if err != nil { - return off, err - } - off, err = packString(rr.OtherData, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *TLSA) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint8(rr.Usage, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.Selector, msg, off) - if err != nil { - return off, err - } - off, err = packUint8(rr.MatchingType, msg, off) - if err != nil { - return off, err - } - off, err = packStringHex(rr.Certificate, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *TSIG) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = PackDomainName(rr.Algorithm, msg, off, compression, false) - if err != nil { - return off, err - } - off, err = packUint48(rr.TimeSigned, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(rr.Fudge, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(rr.MACSize, msg, off) - if err != nil { - return off, err - } - off, err = packStringHex(rr.MAC, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(rr.OrigId, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(rr.Error, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(rr.OtherLen, msg, off) - if err != nil { - return off, err - } - off, err = packStringHex(rr.OtherData, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *TXT) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packStringTxt(rr.Txt, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *UID) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint32(rr.Uid, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *UINFO) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packString(rr.Uinfo, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *URI) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packUint16(rr.Priority, msg, off) - if err != nil { - return off, err - } - off, err = packUint16(rr.Weight, msg, off) - if err != nil { - return off, err - } - off, err = packStringOctet(rr.Target, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -func (rr *X25) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { - off, err := rr.Hdr.pack(msg, off, compression, compress) - if err != nil { - return off, err - } - headerEnd := off - off, err = packString(rr.PSDNAddress, msg, off) - if err != nil { - return off, err - } - rr.Header().Rdlength = uint16(off - headerEnd) - return off, nil -} - -// unpack*() functions - -func unpackA(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(A) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.A, off, err = unpackDataA(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackAAAA(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(AAAA) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.AAAA, off, err = unpackDataAAAA(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackAFSDB(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(AFSDB) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Subtype, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Hostname, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackANY(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(ANY) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - return rr, off, err -} - -func unpackAVC(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(AVC) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Txt, off, err = unpackStringTxt(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackCAA(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(CAA) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Flag, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Tag, off, err = unpackString(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Value, off, err = unpackStringOctet(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackCDNSKEY(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(CDNSKEY) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Flags, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Protocol, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Algorithm, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.PublicKey, off, err = unpackStringBase64(msg, off, rdStart+int(rr.Hdr.Rdlength)) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackCDS(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(CDS) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.KeyTag, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Algorithm, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.DigestType, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Digest, off, err = unpackStringHex(msg, off, rdStart+int(rr.Hdr.Rdlength)) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackCERT(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(CERT) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Type, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.KeyTag, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Algorithm, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Certificate, off, err = unpackStringBase64(msg, off, rdStart+int(rr.Hdr.Rdlength)) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackCNAME(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(CNAME) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Target, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackDHCID(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(DHCID) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Digest, off, err = unpackStringBase64(msg, off, rdStart+int(rr.Hdr.Rdlength)) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackDLV(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(DLV) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.KeyTag, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Algorithm, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.DigestType, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Digest, off, err = unpackStringHex(msg, off, rdStart+int(rr.Hdr.Rdlength)) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackDNAME(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(DNAME) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Target, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackDNSKEY(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(DNSKEY) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Flags, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Protocol, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Algorithm, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.PublicKey, off, err = unpackStringBase64(msg, off, rdStart+int(rr.Hdr.Rdlength)) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackDS(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(DS) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.KeyTag, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Algorithm, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.DigestType, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Digest, off, err = unpackStringHex(msg, off, rdStart+int(rr.Hdr.Rdlength)) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackEID(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(EID) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Endpoint, off, err = unpackStringHex(msg, off, rdStart+int(rr.Hdr.Rdlength)) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackEUI48(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(EUI48) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Address, off, err = unpackUint48(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackEUI64(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(EUI64) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Address, off, err = unpackUint64(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackGID(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(GID) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Gid, off, err = unpackUint32(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackGPOS(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(GPOS) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Longitude, off, err = unpackString(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Latitude, off, err = unpackString(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Altitude, off, err = unpackString(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackHINFO(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(HINFO) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Cpu, off, err = unpackString(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Os, off, err = unpackString(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackHIP(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(HIP) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.HitLength, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.PublicKeyAlgorithm, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.PublicKeyLength, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Hit, off, err = unpackStringHex(msg, off, off+int(rr.HitLength)) - if err != nil { - return rr, off, err - } - rr.PublicKey, off, err = unpackStringBase64(msg, off, off+int(rr.PublicKeyLength)) - if err != nil { - return rr, off, err - } - rr.RendezvousServers, off, err = unpackDataDomainNames(msg, off, rdStart+int(rr.Hdr.Rdlength)) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackKEY(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(KEY) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Flags, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Protocol, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Algorithm, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.PublicKey, off, err = unpackStringBase64(msg, off, rdStart+int(rr.Hdr.Rdlength)) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackKX(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(KX) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Preference, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Exchanger, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackL32(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(L32) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Preference, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Locator32, off, err = unpackDataA(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackL64(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(L64) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Preference, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Locator64, off, err = unpackUint64(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackLOC(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(LOC) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Version, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Size, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.HorizPre, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.VertPre, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Latitude, off, err = unpackUint32(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Longitude, off, err = unpackUint32(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Altitude, off, err = unpackUint32(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackLP(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(LP) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Preference, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Fqdn, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackMB(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(MB) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Mb, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackMD(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(MD) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Md, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackMF(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(MF) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Mf, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackMG(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(MG) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Mg, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackMINFO(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(MINFO) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Rmail, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Email, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackMR(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(MR) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Mr, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackMX(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(MX) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Preference, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Mx, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackNAPTR(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(NAPTR) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Order, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Preference, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Flags, off, err = unpackString(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Service, off, err = unpackString(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Regexp, off, err = unpackString(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Replacement, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackNID(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(NID) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Preference, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.NodeID, off, err = unpackUint64(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackNIMLOC(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(NIMLOC) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Locator, off, err = unpackStringHex(msg, off, rdStart+int(rr.Hdr.Rdlength)) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackNINFO(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(NINFO) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.ZSData, off, err = unpackStringTxt(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackNS(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(NS) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Ns, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackNSAPPTR(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(NSAPPTR) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Ptr, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackNSEC(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(NSEC) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.NextDomain, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.TypeBitMap, off, err = unpackDataNsec(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackNSEC3(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(NSEC3) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Hash, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Flags, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Iterations, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.SaltLength, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Salt, off, err = unpackStringHex(msg, off, off+int(rr.SaltLength)) - if err != nil { - return rr, off, err - } - rr.HashLength, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.NextDomain, off, err = unpackStringBase32(msg, off, off+int(rr.HashLength)) - if err != nil { - return rr, off, err - } - rr.TypeBitMap, off, err = unpackDataNsec(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackNSEC3PARAM(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(NSEC3PARAM) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Hash, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Flags, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Iterations, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.SaltLength, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Salt, off, err = unpackStringHex(msg, off, off+int(rr.SaltLength)) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackOPENPGPKEY(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(OPENPGPKEY) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.PublicKey, off, err = unpackStringBase64(msg, off, rdStart+int(rr.Hdr.Rdlength)) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackOPT(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(OPT) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Option, off, err = unpackDataOpt(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackPTR(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(PTR) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Ptr, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackPX(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(PX) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Preference, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Map822, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Mapx400, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackRFC3597(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(RFC3597) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Rdata, off, err = unpackStringHex(msg, off, rdStart+int(rr.Hdr.Rdlength)) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackRKEY(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(RKEY) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Flags, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Protocol, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Algorithm, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.PublicKey, off, err = unpackStringBase64(msg, off, rdStart+int(rr.Hdr.Rdlength)) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackRP(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(RP) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Mbox, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Txt, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackRRSIG(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(RRSIG) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.TypeCovered, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Algorithm, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Labels, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.OrigTtl, off, err = unpackUint32(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Expiration, off, err = unpackUint32(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Inception, off, err = unpackUint32(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.KeyTag, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.SignerName, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Signature, off, err = unpackStringBase64(msg, off, rdStart+int(rr.Hdr.Rdlength)) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackRT(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(RT) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Preference, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Host, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackSIG(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(SIG) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.TypeCovered, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Algorithm, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Labels, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.OrigTtl, off, err = unpackUint32(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Expiration, off, err = unpackUint32(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Inception, off, err = unpackUint32(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.KeyTag, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.SignerName, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Signature, off, err = unpackStringBase64(msg, off, rdStart+int(rr.Hdr.Rdlength)) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackSMIMEA(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(SMIMEA) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Usage, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Selector, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.MatchingType, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Certificate, off, err = unpackStringHex(msg, off, rdStart+int(rr.Hdr.Rdlength)) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackSOA(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(SOA) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Ns, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Mbox, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Serial, off, err = unpackUint32(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Refresh, off, err = unpackUint32(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Retry, off, err = unpackUint32(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Expire, off, err = unpackUint32(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Minttl, off, err = unpackUint32(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackSPF(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(SPF) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Txt, off, err = unpackStringTxt(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackSRV(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(SRV) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Priority, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Weight, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Port, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Target, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackSSHFP(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(SSHFP) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Algorithm, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Type, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.FingerPrint, off, err = unpackStringHex(msg, off, rdStart+int(rr.Hdr.Rdlength)) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackTA(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(TA) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.KeyTag, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Algorithm, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.DigestType, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Digest, off, err = unpackStringHex(msg, off, rdStart+int(rr.Hdr.Rdlength)) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackTALINK(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(TALINK) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.PreviousName, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.NextName, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackTKEY(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(TKEY) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Algorithm, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Inception, off, err = unpackUint32(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Expiration, off, err = unpackUint32(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Mode, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Error, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.KeySize, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Key, off, err = unpackString(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.OtherLen, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.OtherData, off, err = unpackString(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackTLSA(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(TLSA) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Usage, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Selector, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.MatchingType, off, err = unpackUint8(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Certificate, off, err = unpackStringHex(msg, off, rdStart+int(rr.Hdr.Rdlength)) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackTSIG(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(TSIG) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Algorithm, off, err = UnpackDomainName(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.TimeSigned, off, err = unpackUint48(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Fudge, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.MACSize, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.MAC, off, err = unpackStringHex(msg, off, off+int(rr.MACSize)) - if err != nil { - return rr, off, err - } - rr.OrigId, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Error, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.OtherLen, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.OtherData, off, err = unpackStringHex(msg, off, off+int(rr.OtherLen)) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackTXT(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(TXT) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Txt, off, err = unpackStringTxt(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackUID(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(UID) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Uid, off, err = unpackUint32(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackUINFO(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(UINFO) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Uinfo, off, err = unpackString(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackURI(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(URI) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.Priority, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Weight, off, err = unpackUint16(msg, off) - if err != nil { - return rr, off, err - } - if off == len(msg) { - return rr, off, nil - } - rr.Target, off, err = unpackStringOctet(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -func unpackX25(h RR_Header, msg []byte, off int) (RR, int, error) { - rr := new(X25) - rr.Hdr = h - if noRdata(h) { - return rr, off, nil - } - var err error - rdStart := off - _ = rdStart - - rr.PSDNAddress, off, err = unpackString(msg, off) - if err != nil { - return rr, off, err - } - return rr, off, err -} - -var typeToUnpack = map[uint16]func(RR_Header, []byte, int) (RR, int, error){ - TypeA: unpackA, - TypeAAAA: unpackAAAA, - TypeAFSDB: unpackAFSDB, - TypeANY: unpackANY, - TypeAVC: unpackAVC, - TypeCAA: unpackCAA, - TypeCDNSKEY: unpackCDNSKEY, - TypeCDS: unpackCDS, - TypeCERT: unpackCERT, - TypeCNAME: unpackCNAME, - TypeDHCID: unpackDHCID, - TypeDLV: unpackDLV, - TypeDNAME: unpackDNAME, - TypeDNSKEY: unpackDNSKEY, - TypeDS: unpackDS, - TypeEID: unpackEID, - TypeEUI48: unpackEUI48, - TypeEUI64: unpackEUI64, - TypeGID: unpackGID, - TypeGPOS: unpackGPOS, - TypeHINFO: unpackHINFO, - TypeHIP: unpackHIP, - TypeKEY: unpackKEY, - TypeKX: unpackKX, - TypeL32: unpackL32, - TypeL64: unpackL64, - TypeLOC: unpackLOC, - TypeLP: unpackLP, - TypeMB: unpackMB, - TypeMD: unpackMD, - TypeMF: unpackMF, - TypeMG: unpackMG, - TypeMINFO: unpackMINFO, - TypeMR: unpackMR, - TypeMX: unpackMX, - TypeNAPTR: unpackNAPTR, - TypeNID: unpackNID, - TypeNIMLOC: unpackNIMLOC, - TypeNINFO: unpackNINFO, - TypeNS: unpackNS, - TypeNSAPPTR: unpackNSAPPTR, - TypeNSEC: unpackNSEC, - TypeNSEC3: unpackNSEC3, - TypeNSEC3PARAM: unpackNSEC3PARAM, - TypeOPENPGPKEY: unpackOPENPGPKEY, - TypeOPT: unpackOPT, - TypePTR: unpackPTR, - TypePX: unpackPX, - TypeRKEY: unpackRKEY, - TypeRP: unpackRP, - TypeRRSIG: unpackRRSIG, - TypeRT: unpackRT, - TypeSIG: unpackSIG, - TypeSMIMEA: unpackSMIMEA, - TypeSOA: unpackSOA, - TypeSPF: unpackSPF, - TypeSRV: unpackSRV, - TypeSSHFP: unpackSSHFP, - TypeTA: unpackTA, - TypeTALINK: unpackTALINK, - TypeTKEY: unpackTKEY, - TypeTLSA: unpackTLSA, - TypeTSIG: unpackTSIG, - TypeTXT: unpackTXT, - TypeUID: unpackUID, - TypeUINFO: unpackUINFO, - TypeURI: unpackURI, - TypeX25: unpackX25, -} diff --git a/vendor/github.com/miekg/dns/ztypes.go b/vendor/github.com/miekg/dns/ztypes.go deleted file mode 100644 index 3e534f12e01..00000000000 --- a/vendor/github.com/miekg/dns/ztypes.go +++ /dev/null @@ -1,857 +0,0 @@ -// *** DO NOT MODIFY *** -// AUTOGENERATED BY go generate from type_generate.go - -package dns - -import ( - "encoding/base64" - "net" -) - -// TypeToRR is a map of constructors for each RR type. -var TypeToRR = map[uint16]func() RR{ - TypeA: func() RR { return new(A) }, - TypeAAAA: func() RR { return new(AAAA) }, - TypeAFSDB: func() RR { return new(AFSDB) }, - TypeANY: func() RR { return new(ANY) }, - TypeAVC: func() RR { return new(AVC) }, - TypeCAA: func() RR { return new(CAA) }, - TypeCDNSKEY: func() RR { return new(CDNSKEY) }, - TypeCDS: func() RR { return new(CDS) }, - TypeCERT: func() RR { return new(CERT) }, - TypeCNAME: func() RR { return new(CNAME) }, - TypeDHCID: func() RR { return new(DHCID) }, - TypeDLV: func() RR { return new(DLV) }, - TypeDNAME: func() RR { return new(DNAME) }, - TypeDNSKEY: func() RR { return new(DNSKEY) }, - TypeDS: func() RR { return new(DS) }, - TypeEID: func() RR { return new(EID) }, - TypeEUI48: func() RR { return new(EUI48) }, - TypeEUI64: func() RR { return new(EUI64) }, - TypeGID: func() RR { return new(GID) }, - TypeGPOS: func() RR { return new(GPOS) }, - TypeHINFO: func() RR { return new(HINFO) }, - TypeHIP: func() RR { return new(HIP) }, - TypeKEY: func() RR { return new(KEY) }, - TypeKX: func() RR { return new(KX) }, - TypeL32: func() RR { return new(L32) }, - TypeL64: func() RR { return new(L64) }, - TypeLOC: func() RR { return new(LOC) }, - TypeLP: func() RR { return new(LP) }, - TypeMB: func() RR { return new(MB) }, - TypeMD: func() RR { return new(MD) }, - TypeMF: func() RR { return new(MF) }, - TypeMG: func() RR { return new(MG) }, - TypeMINFO: func() RR { return new(MINFO) }, - TypeMR: func() RR { return new(MR) }, - TypeMX: func() RR { return new(MX) }, - TypeNAPTR: func() RR { return new(NAPTR) }, - TypeNID: func() RR { return new(NID) }, - TypeNIMLOC: func() RR { return new(NIMLOC) }, - TypeNINFO: func() RR { return new(NINFO) }, - TypeNS: func() RR { return new(NS) }, - TypeNSAPPTR: func() RR { return new(NSAPPTR) }, - TypeNSEC: func() RR { return new(NSEC) }, - TypeNSEC3: func() RR { return new(NSEC3) }, - TypeNSEC3PARAM: func() RR { return new(NSEC3PARAM) }, - TypeOPENPGPKEY: func() RR { return new(OPENPGPKEY) }, - TypeOPT: func() RR { return new(OPT) }, - TypePTR: func() RR { return new(PTR) }, - TypePX: func() RR { return new(PX) }, - TypeRKEY: func() RR { return new(RKEY) }, - TypeRP: func() RR { return new(RP) }, - TypeRRSIG: func() RR { return new(RRSIG) }, - TypeRT: func() RR { return new(RT) }, - TypeSIG: func() RR { return new(SIG) }, - TypeSMIMEA: func() RR { return new(SMIMEA) }, - TypeSOA: func() RR { return new(SOA) }, - TypeSPF: func() RR { return new(SPF) }, - TypeSRV: func() RR { return new(SRV) }, - TypeSSHFP: func() RR { return new(SSHFP) }, - TypeTA: func() RR { return new(TA) }, - TypeTALINK: func() RR { return new(TALINK) }, - TypeTKEY: func() RR { return new(TKEY) }, - TypeTLSA: func() RR { return new(TLSA) }, - TypeTSIG: func() RR { return new(TSIG) }, - TypeTXT: func() RR { return new(TXT) }, - TypeUID: func() RR { return new(UID) }, - TypeUINFO: func() RR { return new(UINFO) }, - TypeURI: func() RR { return new(URI) }, - TypeX25: func() RR { return new(X25) }, -} - -// TypeToString is a map of strings for each RR type. -var TypeToString = map[uint16]string{ - TypeA: "A", - TypeAAAA: "AAAA", - TypeAFSDB: "AFSDB", - TypeANY: "ANY", - TypeATMA: "ATMA", - TypeAVC: "AVC", - TypeAXFR: "AXFR", - TypeCAA: "CAA", - TypeCDNSKEY: "CDNSKEY", - TypeCDS: "CDS", - TypeCERT: "CERT", - TypeCNAME: "CNAME", - TypeDHCID: "DHCID", - TypeDLV: "DLV", - TypeDNAME: "DNAME", - TypeDNSKEY: "DNSKEY", - TypeDS: "DS", - TypeEID: "EID", - TypeEUI48: "EUI48", - TypeEUI64: "EUI64", - TypeGID: "GID", - TypeGPOS: "GPOS", - TypeHINFO: "HINFO", - TypeHIP: "HIP", - TypeISDN: "ISDN", - TypeIXFR: "IXFR", - TypeKEY: "KEY", - TypeKX: "KX", - TypeL32: "L32", - TypeL64: "L64", - TypeLOC: "LOC", - TypeLP: "LP", - TypeMAILA: "MAILA", - TypeMAILB: "MAILB", - TypeMB: "MB", - TypeMD: "MD", - TypeMF: "MF", - TypeMG: "MG", - TypeMINFO: "MINFO", - TypeMR: "MR", - TypeMX: "MX", - TypeNAPTR: "NAPTR", - TypeNID: "NID", - TypeNIMLOC: "NIMLOC", - TypeNINFO: "NINFO", - TypeNS: "NS", - TypeNSEC: "NSEC", - TypeNSEC3: "NSEC3", - TypeNSEC3PARAM: "NSEC3PARAM", - TypeNULL: "NULL", - TypeNXT: "NXT", - TypeNone: "None", - TypeOPENPGPKEY: "OPENPGPKEY", - TypeOPT: "OPT", - TypePTR: "PTR", - TypePX: "PX", - TypeRKEY: "RKEY", - TypeRP: "RP", - TypeRRSIG: "RRSIG", - TypeRT: "RT", - TypeReserved: "Reserved", - TypeSIG: "SIG", - TypeSMIMEA: "SMIMEA", - TypeSOA: "SOA", - TypeSPF: "SPF", - TypeSRV: "SRV", - TypeSSHFP: "SSHFP", - TypeTA: "TA", - TypeTALINK: "TALINK", - TypeTKEY: "TKEY", - TypeTLSA: "TLSA", - TypeTSIG: "TSIG", - TypeTXT: "TXT", - TypeUID: "UID", - TypeUINFO: "UINFO", - TypeUNSPEC: "UNSPEC", - TypeURI: "URI", - TypeX25: "X25", - TypeNSAPPTR: "NSAP-PTR", -} - -// Header() functions -func (rr *A) Header() *RR_Header { return &rr.Hdr } -func (rr *AAAA) Header() *RR_Header { return &rr.Hdr } -func (rr *AFSDB) Header() *RR_Header { return &rr.Hdr } -func (rr *ANY) Header() *RR_Header { return &rr.Hdr } -func (rr *AVC) Header() *RR_Header { return &rr.Hdr } -func (rr *CAA) Header() *RR_Header { return &rr.Hdr } -func (rr *CDNSKEY) Header() *RR_Header { return &rr.Hdr } -func (rr *CDS) Header() *RR_Header { return &rr.Hdr } -func (rr *CERT) Header() *RR_Header { return &rr.Hdr } -func (rr *CNAME) Header() *RR_Header { return &rr.Hdr } -func (rr *DHCID) Header() *RR_Header { return &rr.Hdr } -func (rr *DLV) Header() *RR_Header { return &rr.Hdr } -func (rr *DNAME) Header() *RR_Header { return &rr.Hdr } -func (rr *DNSKEY) Header() *RR_Header { return &rr.Hdr } -func (rr *DS) Header() *RR_Header { return &rr.Hdr } -func (rr *EID) Header() *RR_Header { return &rr.Hdr } -func (rr *EUI48) Header() *RR_Header { return &rr.Hdr } -func (rr *EUI64) Header() *RR_Header { return &rr.Hdr } -func (rr *GID) Header() *RR_Header { return &rr.Hdr } -func (rr *GPOS) Header() *RR_Header { return &rr.Hdr } -func (rr *HINFO) Header() *RR_Header { return &rr.Hdr } -func (rr *HIP) Header() *RR_Header { return &rr.Hdr } -func (rr *KEY) Header() *RR_Header { return &rr.Hdr } -func (rr *KX) Header() *RR_Header { return &rr.Hdr } -func (rr *L32) Header() *RR_Header { return &rr.Hdr } -func (rr *L64) Header() *RR_Header { return &rr.Hdr } -func (rr *LOC) Header() *RR_Header { return &rr.Hdr } -func (rr *LP) Header() *RR_Header { return &rr.Hdr } -func (rr *MB) Header() *RR_Header { return &rr.Hdr } -func (rr *MD) Header() *RR_Header { return &rr.Hdr } -func (rr *MF) Header() *RR_Header { return &rr.Hdr } -func (rr *MG) Header() *RR_Header { return &rr.Hdr } -func (rr *MINFO) Header() *RR_Header { return &rr.Hdr } -func (rr *MR) Header() *RR_Header { return &rr.Hdr } -func (rr *MX) Header() *RR_Header { return &rr.Hdr } -func (rr *NAPTR) Header() *RR_Header { return &rr.Hdr } -func (rr *NID) Header() *RR_Header { return &rr.Hdr } -func (rr *NIMLOC) Header() *RR_Header { return &rr.Hdr } -func (rr *NINFO) Header() *RR_Header { return &rr.Hdr } -func (rr *NS) Header() *RR_Header { return &rr.Hdr } -func (rr *NSAPPTR) Header() *RR_Header { return &rr.Hdr } -func (rr *NSEC) Header() *RR_Header { return &rr.Hdr } -func (rr *NSEC3) Header() *RR_Header { return &rr.Hdr } -func (rr *NSEC3PARAM) Header() *RR_Header { return &rr.Hdr } -func (rr *OPENPGPKEY) Header() *RR_Header { return &rr.Hdr } -func (rr *OPT) Header() *RR_Header { return &rr.Hdr } -func (rr *PTR) Header() *RR_Header { return &rr.Hdr } -func (rr *PX) Header() *RR_Header { return &rr.Hdr } -func (rr *RFC3597) Header() *RR_Header { return &rr.Hdr } -func (rr *RKEY) Header() *RR_Header { return &rr.Hdr } -func (rr *RP) Header() *RR_Header { return &rr.Hdr } -func (rr *RRSIG) Header() *RR_Header { return &rr.Hdr } -func (rr *RT) Header() *RR_Header { return &rr.Hdr } -func (rr *SIG) Header() *RR_Header { return &rr.Hdr } -func (rr *SMIMEA) Header() *RR_Header { return &rr.Hdr } -func (rr *SOA) Header() *RR_Header { return &rr.Hdr } -func (rr *SPF) Header() *RR_Header { return &rr.Hdr } -func (rr *SRV) Header() *RR_Header { return &rr.Hdr } -func (rr *SSHFP) Header() *RR_Header { return &rr.Hdr } -func (rr *TA) Header() *RR_Header { return &rr.Hdr } -func (rr *TALINK) Header() *RR_Header { return &rr.Hdr } -func (rr *TKEY) Header() *RR_Header { return &rr.Hdr } -func (rr *TLSA) Header() *RR_Header { return &rr.Hdr } -func (rr *TSIG) Header() *RR_Header { return &rr.Hdr } -func (rr *TXT) Header() *RR_Header { return &rr.Hdr } -func (rr *UID) Header() *RR_Header { return &rr.Hdr } -func (rr *UINFO) Header() *RR_Header { return &rr.Hdr } -func (rr *URI) Header() *RR_Header { return &rr.Hdr } -func (rr *X25) Header() *RR_Header { return &rr.Hdr } - -// len() functions -func (rr *A) len() int { - l := rr.Hdr.len() - l += net.IPv4len // A - return l -} -func (rr *AAAA) len() int { - l := rr.Hdr.len() - l += net.IPv6len // AAAA - return l -} -func (rr *AFSDB) len() int { - l := rr.Hdr.len() - l += 2 // Subtype - l += len(rr.Hostname) + 1 - return l -} -func (rr *ANY) len() int { - l := rr.Hdr.len() - return l -} -func (rr *AVC) len() int { - l := rr.Hdr.len() - for _, x := range rr.Txt { - l += len(x) + 1 - } - return l -} -func (rr *CAA) len() int { - l := rr.Hdr.len() - l++ // Flag - l += len(rr.Tag) + 1 - l += len(rr.Value) - return l -} -func (rr *CERT) len() int { - l := rr.Hdr.len() - l += 2 // Type - l += 2 // KeyTag - l++ // Algorithm - l += base64.StdEncoding.DecodedLen(len(rr.Certificate)) - return l -} -func (rr *CNAME) len() int { - l := rr.Hdr.len() - l += len(rr.Target) + 1 - return l -} -func (rr *DHCID) len() int { - l := rr.Hdr.len() - l += base64.StdEncoding.DecodedLen(len(rr.Digest)) - return l -} -func (rr *DNAME) len() int { - l := rr.Hdr.len() - l += len(rr.Target) + 1 - return l -} -func (rr *DNSKEY) len() int { - l := rr.Hdr.len() - l += 2 // Flags - l++ // Protocol - l++ // Algorithm - l += base64.StdEncoding.DecodedLen(len(rr.PublicKey)) - return l -} -func (rr *DS) len() int { - l := rr.Hdr.len() - l += 2 // KeyTag - l++ // Algorithm - l++ // DigestType - l += len(rr.Digest)/2 + 1 - return l -} -func (rr *EID) len() int { - l := rr.Hdr.len() - l += len(rr.Endpoint)/2 + 1 - return l -} -func (rr *EUI48) len() int { - l := rr.Hdr.len() - l += 6 // Address - return l -} -func (rr *EUI64) len() int { - l := rr.Hdr.len() - l += 8 // Address - return l -} -func (rr *GID) len() int { - l := rr.Hdr.len() - l += 4 // Gid - return l -} -func (rr *GPOS) len() int { - l := rr.Hdr.len() - l += len(rr.Longitude) + 1 - l += len(rr.Latitude) + 1 - l += len(rr.Altitude) + 1 - return l -} -func (rr *HINFO) len() int { - l := rr.Hdr.len() - l += len(rr.Cpu) + 1 - l += len(rr.Os) + 1 - return l -} -func (rr *HIP) len() int { - l := rr.Hdr.len() - l++ // HitLength - l++ // PublicKeyAlgorithm - l += 2 // PublicKeyLength - l += len(rr.Hit)/2 + 1 - l += base64.StdEncoding.DecodedLen(len(rr.PublicKey)) - for _, x := range rr.RendezvousServers { - l += len(x) + 1 - } - return l -} -func (rr *KX) len() int { - l := rr.Hdr.len() - l += 2 // Preference - l += len(rr.Exchanger) + 1 - return l -} -func (rr *L32) len() int { - l := rr.Hdr.len() - l += 2 // Preference - l += net.IPv4len // Locator32 - return l -} -func (rr *L64) len() int { - l := rr.Hdr.len() - l += 2 // Preference - l += 8 // Locator64 - return l -} -func (rr *LOC) len() int { - l := rr.Hdr.len() - l++ // Version - l++ // Size - l++ // HorizPre - l++ // VertPre - l += 4 // Latitude - l += 4 // Longitude - l += 4 // Altitude - return l -} -func (rr *LP) len() int { - l := rr.Hdr.len() - l += 2 // Preference - l += len(rr.Fqdn) + 1 - return l -} -func (rr *MB) len() int { - l := rr.Hdr.len() - l += len(rr.Mb) + 1 - return l -} -func (rr *MD) len() int { - l := rr.Hdr.len() - l += len(rr.Md) + 1 - return l -} -func (rr *MF) len() int { - l := rr.Hdr.len() - l += len(rr.Mf) + 1 - return l -} -func (rr *MG) len() int { - l := rr.Hdr.len() - l += len(rr.Mg) + 1 - return l -} -func (rr *MINFO) len() int { - l := rr.Hdr.len() - l += len(rr.Rmail) + 1 - l += len(rr.Email) + 1 - return l -} -func (rr *MR) len() int { - l := rr.Hdr.len() - l += len(rr.Mr) + 1 - return l -} -func (rr *MX) len() int { - l := rr.Hdr.len() - l += 2 // Preference - l += len(rr.Mx) + 1 - return l -} -func (rr *NAPTR) len() int { - l := rr.Hdr.len() - l += 2 // Order - l += 2 // Preference - l += len(rr.Flags) + 1 - l += len(rr.Service) + 1 - l += len(rr.Regexp) + 1 - l += len(rr.Replacement) + 1 - return l -} -func (rr *NID) len() int { - l := rr.Hdr.len() - l += 2 // Preference - l += 8 // NodeID - return l -} -func (rr *NIMLOC) len() int { - l := rr.Hdr.len() - l += len(rr.Locator)/2 + 1 - return l -} -func (rr *NINFO) len() int { - l := rr.Hdr.len() - for _, x := range rr.ZSData { - l += len(x) + 1 - } - return l -} -func (rr *NS) len() int { - l := rr.Hdr.len() - l += len(rr.Ns) + 1 - return l -} -func (rr *NSAPPTR) len() int { - l := rr.Hdr.len() - l += len(rr.Ptr) + 1 - return l -} -func (rr *NSEC3PARAM) len() int { - l := rr.Hdr.len() - l++ // Hash - l++ // Flags - l += 2 // Iterations - l++ // SaltLength - l += len(rr.Salt)/2 + 1 - return l -} -func (rr *OPENPGPKEY) len() int { - l := rr.Hdr.len() - l += base64.StdEncoding.DecodedLen(len(rr.PublicKey)) - return l -} -func (rr *PTR) len() int { - l := rr.Hdr.len() - l += len(rr.Ptr) + 1 - return l -} -func (rr *PX) len() int { - l := rr.Hdr.len() - l += 2 // Preference - l += len(rr.Map822) + 1 - l += len(rr.Mapx400) + 1 - return l -} -func (rr *RFC3597) len() int { - l := rr.Hdr.len() - l += len(rr.Rdata)/2 + 1 - return l -} -func (rr *RKEY) len() int { - l := rr.Hdr.len() - l += 2 // Flags - l++ // Protocol - l++ // Algorithm - l += base64.StdEncoding.DecodedLen(len(rr.PublicKey)) - return l -} -func (rr *RP) len() int { - l := rr.Hdr.len() - l += len(rr.Mbox) + 1 - l += len(rr.Txt) + 1 - return l -} -func (rr *RRSIG) len() int { - l := rr.Hdr.len() - l += 2 // TypeCovered - l++ // Algorithm - l++ // Labels - l += 4 // OrigTtl - l += 4 // Expiration - l += 4 // Inception - l += 2 // KeyTag - l += len(rr.SignerName) + 1 - l += base64.StdEncoding.DecodedLen(len(rr.Signature)) - return l -} -func (rr *RT) len() int { - l := rr.Hdr.len() - l += 2 // Preference - l += len(rr.Host) + 1 - return l -} -func (rr *SMIMEA) len() int { - l := rr.Hdr.len() - l++ // Usage - l++ // Selector - l++ // MatchingType - l += len(rr.Certificate)/2 + 1 - return l -} -func (rr *SOA) len() int { - l := rr.Hdr.len() - l += len(rr.Ns) + 1 - l += len(rr.Mbox) + 1 - l += 4 // Serial - l += 4 // Refresh - l += 4 // Retry - l += 4 // Expire - l += 4 // Minttl - return l -} -func (rr *SPF) len() int { - l := rr.Hdr.len() - for _, x := range rr.Txt { - l += len(x) + 1 - } - return l -} -func (rr *SRV) len() int { - l := rr.Hdr.len() - l += 2 // Priority - l += 2 // Weight - l += 2 // Port - l += len(rr.Target) + 1 - return l -} -func (rr *SSHFP) len() int { - l := rr.Hdr.len() - l++ // Algorithm - l++ // Type - l += len(rr.FingerPrint)/2 + 1 - return l -} -func (rr *TA) len() int { - l := rr.Hdr.len() - l += 2 // KeyTag - l++ // Algorithm - l++ // DigestType - l += len(rr.Digest)/2 + 1 - return l -} -func (rr *TALINK) len() int { - l := rr.Hdr.len() - l += len(rr.PreviousName) + 1 - l += len(rr.NextName) + 1 - return l -} -func (rr *TKEY) len() int { - l := rr.Hdr.len() - l += len(rr.Algorithm) + 1 - l += 4 // Inception - l += 4 // Expiration - l += 2 // Mode - l += 2 // Error - l += 2 // KeySize - l += len(rr.Key) + 1 - l += 2 // OtherLen - l += len(rr.OtherData) + 1 - return l -} -func (rr *TLSA) len() int { - l := rr.Hdr.len() - l++ // Usage - l++ // Selector - l++ // MatchingType - l += len(rr.Certificate)/2 + 1 - return l -} -func (rr *TSIG) len() int { - l := rr.Hdr.len() - l += len(rr.Algorithm) + 1 - l += 6 // TimeSigned - l += 2 // Fudge - l += 2 // MACSize - l += len(rr.MAC)/2 + 1 - l += 2 // OrigId - l += 2 // Error - l += 2 // OtherLen - l += len(rr.OtherData)/2 + 1 - return l -} -func (rr *TXT) len() int { - l := rr.Hdr.len() - for _, x := range rr.Txt { - l += len(x) + 1 - } - return l -} -func (rr *UID) len() int { - l := rr.Hdr.len() - l += 4 // Uid - return l -} -func (rr *UINFO) len() int { - l := rr.Hdr.len() - l += len(rr.Uinfo) + 1 - return l -} -func (rr *URI) len() int { - l := rr.Hdr.len() - l += 2 // Priority - l += 2 // Weight - l += len(rr.Target) - return l -} -func (rr *X25) len() int { - l := rr.Hdr.len() - l += len(rr.PSDNAddress) + 1 - return l -} - -// copy() functions -func (rr *A) copy() RR { - return &A{*rr.Hdr.copyHeader(), copyIP(rr.A)} -} -func (rr *AAAA) copy() RR { - return &AAAA{*rr.Hdr.copyHeader(), copyIP(rr.AAAA)} -} -func (rr *AFSDB) copy() RR { - return &AFSDB{*rr.Hdr.copyHeader(), rr.Subtype, rr.Hostname} -} -func (rr *ANY) copy() RR { - return &ANY{*rr.Hdr.copyHeader()} -} -func (rr *AVC) copy() RR { - Txt := make([]string, len(rr.Txt)) - copy(Txt, rr.Txt) - return &AVC{*rr.Hdr.copyHeader(), Txt} -} -func (rr *CAA) copy() RR { - return &CAA{*rr.Hdr.copyHeader(), rr.Flag, rr.Tag, rr.Value} -} -func (rr *CERT) copy() RR { - return &CERT{*rr.Hdr.copyHeader(), rr.Type, rr.KeyTag, rr.Algorithm, rr.Certificate} -} -func (rr *CNAME) copy() RR { - return &CNAME{*rr.Hdr.copyHeader(), rr.Target} -} -func (rr *DHCID) copy() RR { - return &DHCID{*rr.Hdr.copyHeader(), rr.Digest} -} -func (rr *DNAME) copy() RR { - return &DNAME{*rr.Hdr.copyHeader(), rr.Target} -} -func (rr *DNSKEY) copy() RR { - return &DNSKEY{*rr.Hdr.copyHeader(), rr.Flags, rr.Protocol, rr.Algorithm, rr.PublicKey} -} -func (rr *DS) copy() RR { - return &DS{*rr.Hdr.copyHeader(), rr.KeyTag, rr.Algorithm, rr.DigestType, rr.Digest} -} -func (rr *EID) copy() RR { - return &EID{*rr.Hdr.copyHeader(), rr.Endpoint} -} -func (rr *EUI48) copy() RR { - return &EUI48{*rr.Hdr.copyHeader(), rr.Address} -} -func (rr *EUI64) copy() RR { - return &EUI64{*rr.Hdr.copyHeader(), rr.Address} -} -func (rr *GID) copy() RR { - return &GID{*rr.Hdr.copyHeader(), rr.Gid} -} -func (rr *GPOS) copy() RR { - return &GPOS{*rr.Hdr.copyHeader(), rr.Longitude, rr.Latitude, rr.Altitude} -} -func (rr *HINFO) copy() RR { - return &HINFO{*rr.Hdr.copyHeader(), rr.Cpu, rr.Os} -} -func (rr *HIP) copy() RR { - RendezvousServers := make([]string, len(rr.RendezvousServers)) - copy(RendezvousServers, rr.RendezvousServers) - return &HIP{*rr.Hdr.copyHeader(), rr.HitLength, rr.PublicKeyAlgorithm, rr.PublicKeyLength, rr.Hit, rr.PublicKey, RendezvousServers} -} -func (rr *KX) copy() RR { - return &KX{*rr.Hdr.copyHeader(), rr.Preference, rr.Exchanger} -} -func (rr *L32) copy() RR { - return &L32{*rr.Hdr.copyHeader(), rr.Preference, copyIP(rr.Locator32)} -} -func (rr *L64) copy() RR { - return &L64{*rr.Hdr.copyHeader(), rr.Preference, rr.Locator64} -} -func (rr *LOC) copy() RR { - return &LOC{*rr.Hdr.copyHeader(), rr.Version, rr.Size, rr.HorizPre, rr.VertPre, rr.Latitude, rr.Longitude, rr.Altitude} -} -func (rr *LP) copy() RR { - return &LP{*rr.Hdr.copyHeader(), rr.Preference, rr.Fqdn} -} -func (rr *MB) copy() RR { - return &MB{*rr.Hdr.copyHeader(), rr.Mb} -} -func (rr *MD) copy() RR { - return &MD{*rr.Hdr.copyHeader(), rr.Md} -} -func (rr *MF) copy() RR { - return &MF{*rr.Hdr.copyHeader(), rr.Mf} -} -func (rr *MG) copy() RR { - return &MG{*rr.Hdr.copyHeader(), rr.Mg} -} -func (rr *MINFO) copy() RR { - return &MINFO{*rr.Hdr.copyHeader(), rr.Rmail, rr.Email} -} -func (rr *MR) copy() RR { - return &MR{*rr.Hdr.copyHeader(), rr.Mr} -} -func (rr *MX) copy() RR { - return &MX{*rr.Hdr.copyHeader(), rr.Preference, rr.Mx} -} -func (rr *NAPTR) copy() RR { - return &NAPTR{*rr.Hdr.copyHeader(), rr.Order, rr.Preference, rr.Flags, rr.Service, rr.Regexp, rr.Replacement} -} -func (rr *NID) copy() RR { - return &NID{*rr.Hdr.copyHeader(), rr.Preference, rr.NodeID} -} -func (rr *NIMLOC) copy() RR { - return &NIMLOC{*rr.Hdr.copyHeader(), rr.Locator} -} -func (rr *NINFO) copy() RR { - ZSData := make([]string, len(rr.ZSData)) - copy(ZSData, rr.ZSData) - return &NINFO{*rr.Hdr.copyHeader(), ZSData} -} -func (rr *NS) copy() RR { - return &NS{*rr.Hdr.copyHeader(), rr.Ns} -} -func (rr *NSAPPTR) copy() RR { - return &NSAPPTR{*rr.Hdr.copyHeader(), rr.Ptr} -} -func (rr *NSEC) copy() RR { - TypeBitMap := make([]uint16, len(rr.TypeBitMap)) - copy(TypeBitMap, rr.TypeBitMap) - return &NSEC{*rr.Hdr.copyHeader(), rr.NextDomain, TypeBitMap} -} -func (rr *NSEC3) copy() RR { - TypeBitMap := make([]uint16, len(rr.TypeBitMap)) - copy(TypeBitMap, rr.TypeBitMap) - return &NSEC3{*rr.Hdr.copyHeader(), rr.Hash, rr.Flags, rr.Iterations, rr.SaltLength, rr.Salt, rr.HashLength, rr.NextDomain, TypeBitMap} -} -func (rr *NSEC3PARAM) copy() RR { - return &NSEC3PARAM{*rr.Hdr.copyHeader(), rr.Hash, rr.Flags, rr.Iterations, rr.SaltLength, rr.Salt} -} -func (rr *OPENPGPKEY) copy() RR { - return &OPENPGPKEY{*rr.Hdr.copyHeader(), rr.PublicKey} -} -func (rr *OPT) copy() RR { - Option := make([]EDNS0, len(rr.Option)) - copy(Option, rr.Option) - return &OPT{*rr.Hdr.copyHeader(), Option} -} -func (rr *PTR) copy() RR { - return &PTR{*rr.Hdr.copyHeader(), rr.Ptr} -} -func (rr *PX) copy() RR { - return &PX{*rr.Hdr.copyHeader(), rr.Preference, rr.Map822, rr.Mapx400} -} -func (rr *RFC3597) copy() RR { - return &RFC3597{*rr.Hdr.copyHeader(), rr.Rdata} -} -func (rr *RKEY) copy() RR { - return &RKEY{*rr.Hdr.copyHeader(), rr.Flags, rr.Protocol, rr.Algorithm, rr.PublicKey} -} -func (rr *RP) copy() RR { - return &RP{*rr.Hdr.copyHeader(), rr.Mbox, rr.Txt} -} -func (rr *RRSIG) copy() RR { - return &RRSIG{*rr.Hdr.copyHeader(), rr.TypeCovered, rr.Algorithm, rr.Labels, rr.OrigTtl, rr.Expiration, rr.Inception, rr.KeyTag, rr.SignerName, rr.Signature} -} -func (rr *RT) copy() RR { - return &RT{*rr.Hdr.copyHeader(), rr.Preference, rr.Host} -} -func (rr *SMIMEA) copy() RR { - return &SMIMEA{*rr.Hdr.copyHeader(), rr.Usage, rr.Selector, rr.MatchingType, rr.Certificate} -} -func (rr *SOA) copy() RR { - return &SOA{*rr.Hdr.copyHeader(), rr.Ns, rr.Mbox, rr.Serial, rr.Refresh, rr.Retry, rr.Expire, rr.Minttl} -} -func (rr *SPF) copy() RR { - Txt := make([]string, len(rr.Txt)) - copy(Txt, rr.Txt) - return &SPF{*rr.Hdr.copyHeader(), Txt} -} -func (rr *SRV) copy() RR { - return &SRV{*rr.Hdr.copyHeader(), rr.Priority, rr.Weight, rr.Port, rr.Target} -} -func (rr *SSHFP) copy() RR { - return &SSHFP{*rr.Hdr.copyHeader(), rr.Algorithm, rr.Type, rr.FingerPrint} -} -func (rr *TA) copy() RR { - return &TA{*rr.Hdr.copyHeader(), rr.KeyTag, rr.Algorithm, rr.DigestType, rr.Digest} -} -func (rr *TALINK) copy() RR { - return &TALINK{*rr.Hdr.copyHeader(), rr.PreviousName, rr.NextName} -} -func (rr *TKEY) copy() RR { - return &TKEY{*rr.Hdr.copyHeader(), rr.Algorithm, rr.Inception, rr.Expiration, rr.Mode, rr.Error, rr.KeySize, rr.Key, rr.OtherLen, rr.OtherData} -} -func (rr *TLSA) copy() RR { - return &TLSA{*rr.Hdr.copyHeader(), rr.Usage, rr.Selector, rr.MatchingType, rr.Certificate} -} -func (rr *TSIG) copy() RR { - return &TSIG{*rr.Hdr.copyHeader(), rr.Algorithm, rr.TimeSigned, rr.Fudge, rr.MACSize, rr.MAC, rr.OrigId, rr.Error, rr.OtherLen, rr.OtherData} -} -func (rr *TXT) copy() RR { - Txt := make([]string, len(rr.Txt)) - copy(Txt, rr.Txt) - return &TXT{*rr.Hdr.copyHeader(), Txt} -} -func (rr *UID) copy() RR { - return &UID{*rr.Hdr.copyHeader(), rr.Uid} -} -func (rr *UINFO) copy() RR { - return &UINFO{*rr.Hdr.copyHeader(), rr.Uinfo} -} -func (rr *URI) copy() RR { - return &URI{*rr.Hdr.copyHeader(), rr.Priority, rr.Weight, rr.Target} -} -func (rr *X25) copy() RR { - return &X25{*rr.Hdr.copyHeader(), rr.PSDNAddress} -} diff --git a/vendor/github.com/naoina/go-stringutil/LICENSE b/vendor/github.com/naoina/go-stringutil/LICENSE deleted file mode 100644 index 0fff1c58b73..00000000000 --- a/vendor/github.com/naoina/go-stringutil/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2015 Naoya Inada - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/vendor/github.com/naoina/go-stringutil/da.go b/vendor/github.com/naoina/go-stringutil/da.go deleted file mode 100644 index 8fe65165962..00000000000 --- a/vendor/github.com/naoina/go-stringutil/da.go +++ /dev/null @@ -1,253 +0,0 @@ -package stringutil - -import ( - "fmt" - "sort" - "unicode/utf8" -) - -const ( - terminationCharacter = '#' -) - -func mustDoubleArray(da *doubleArray, err error) *doubleArray { - if err != nil { - panic(err) - } - return da -} - -func (da *doubleArray) Build(keys []string) error { - records := makeRecords(keys) - if err := da.build(records, 1, 0, make(map[int]struct{})); err != nil { - return err - } - return nil -} - -type doubleArray struct { - bc []baseCheck - node []int -} - -func newDoubleArray(keys []string) (*doubleArray, error) { - da := &doubleArray{ - bc: []baseCheck{0}, - node: []int{-1}, // A start index is adjusting to 1 because 0 will be used as a mark of non-existent node. - } - if err := da.Build(keys); err != nil { - return nil, err - } - return da, nil -} - -// baseCheck contains BASE, CHECK and Extra flags. -// From the top, 22bits of BASE, 2bits of Extra flags and 8bits of CHECK. -// -// BASE (22bit) | Extra flags (2bit) | CHECK (8bit) -// |----------------------|--|--------| -// 32 10 8 0 -type baseCheck uint32 - -func (bc baseCheck) Base() int { - return int(bc >> 10) -} - -func (bc *baseCheck) SetBase(base int) { - *bc |= baseCheck(base) << 10 -} - -func (bc baseCheck) Check() byte { - return byte(bc) -} - -func (bc *baseCheck) SetCheck(check byte) { - *bc |= baseCheck(check) -} - -func (bc baseCheck) IsEmpty() bool { - return bc&0xfffffcff == 0 -} - -func (da *doubleArray) Lookup(path string) (length int) { - idx := 1 - tmpIdx := idx - for i := 0; i < len(path); i++ { - c := path[i] - tmpIdx = da.nextIndex(da.bc[tmpIdx].Base(), c) - if tmpIdx >= len(da.bc) || da.bc[tmpIdx].Check() != c { - break - } - idx = tmpIdx - } - if next := da.nextIndex(da.bc[idx].Base(), terminationCharacter); next < len(da.bc) && da.bc[next].Check() == terminationCharacter { - return da.node[da.bc[next].Base()] - } - return -1 -} - -func (da *doubleArray) LookupByBytes(path []byte) (length int) { - idx := 1 - tmpIdx := idx - for i := 0; i < len(path); i++ { - c := path[i] - tmpIdx = da.nextIndex(da.bc[tmpIdx].Base(), c) - if tmpIdx >= len(da.bc) || da.bc[tmpIdx].Check() != c { - break - } - idx = tmpIdx - } - if next := da.nextIndex(da.bc[idx].Base(), terminationCharacter); next < len(da.bc) && da.bc[next].Check() == terminationCharacter { - return da.node[da.bc[next].Base()] - } - return -1 -} - -func (da *doubleArray) build(srcs []record, idx, depth int, usedBase map[int]struct{}) error { - sort.Stable(recordSlice(srcs)) - base, siblings, leaf, err := da.arrange(srcs, idx, depth, usedBase) - if err != nil { - return err - } - if leaf != nil { - da.bc[idx].SetBase(len(da.node)) - da.node = append(da.node, leaf.value) - } - for _, sib := range siblings { - da.setCheck(da.nextIndex(base, sib.c), sib.c) - } - for _, sib := range siblings { - if err := da.build(srcs[sib.start:sib.end], da.nextIndex(base, sib.c), depth+1, usedBase); err != nil { - return err - } - } - return nil -} - -func (da *doubleArray) setBase(i, base int) { - da.bc[i].SetBase(base) -} - -func (da *doubleArray) setCheck(i int, check byte) { - da.bc[i].SetCheck(check) -} - -func (da *doubleArray) findEmptyIndex(start int) int { - i := start - for ; i < len(da.bc); i++ { - if da.bc[i].IsEmpty() { - break - } - } - return i -} - -// findBase returns good BASE. -func (da *doubleArray) findBase(siblings []sibling, start int, usedBase map[int]struct{}) (base int) { - for idx, firstChar := start+1, siblings[0].c; ; idx = da.findEmptyIndex(idx + 1) { - base = da.nextIndex(idx, firstChar) - if _, used := usedBase[base]; used { - continue - } - i := 0 - for ; i < len(siblings); i++ { - next := da.nextIndex(base, siblings[i].c) - if len(da.bc) <= next { - da.bc = append(da.bc, make([]baseCheck, next-len(da.bc)+1)...) - } - if !da.bc[next].IsEmpty() { - break - } - } - if i == len(siblings) { - break - } - } - usedBase[base] = struct{}{} - return base -} - -func (da *doubleArray) arrange(records []record, idx, depth int, usedBase map[int]struct{}) (base int, siblings []sibling, leaf *record, err error) { - siblings, leaf, err = makeSiblings(records, depth) - if err != nil { - return -1, nil, nil, err - } - if len(siblings) < 1 { - return -1, nil, leaf, nil - } - base = da.findBase(siblings, idx, usedBase) - da.setBase(idx, base) - return base, siblings, leaf, err -} - -type sibling struct { - start int - end int - c byte -} - -func (da *doubleArray) nextIndex(base int, c byte) int { - return base ^ int(c) -} - -func makeSiblings(records []record, depth int) (sib []sibling, leaf *record, err error) { - var ( - pc byte - n int - ) - for i, r := range records { - if len(r.key) <= depth { - leaf = &r - continue - } - c := r.key[depth] - switch { - case pc < c: - sib = append(sib, sibling{start: i, c: c}) - case pc == c: - continue - default: - return nil, nil, fmt.Errorf("stringutil: BUG: records hasn't been sorted") - } - if n > 0 { - sib[n-1].end = i - } - pc = c - n++ - } - if n == 0 { - return nil, leaf, nil - } - sib[n-1].end = len(records) - return sib, leaf, nil -} - -type record struct { - key string - value int -} - -func makeRecords(srcs []string) (records []record) { - termChar := string(terminationCharacter) - for _, s := range srcs { - records = append(records, record{ - key: string(s + termChar), - value: utf8.RuneCountInString(s), - }) - } - return records -} - -type recordSlice []record - -func (rs recordSlice) Len() int { - return len(rs) -} - -func (rs recordSlice) Less(i, j int) bool { - return rs[i].key < rs[j].key -} - -func (rs recordSlice) Swap(i, j int) { - rs[i], rs[j] = rs[j], rs[i] -} diff --git a/vendor/github.com/naoina/go-stringutil/strings.go b/vendor/github.com/naoina/go-stringutil/strings.go deleted file mode 100644 index 881ca2c8f67..00000000000 --- a/vendor/github.com/naoina/go-stringutil/strings.go +++ /dev/null @@ -1,320 +0,0 @@ -package stringutil - -import ( - "sync" - "unicode" - "unicode/utf8" -) - -var ( - mu sync.Mutex - - // Based on https://github.com/golang/lint/blob/32a87160691b3c96046c0c678fe57c5bef761456/lint.go#L702 - commonInitialismMap = map[string]struct{}{ - "API": struct{}{}, - "ASCII": struct{}{}, - "CPU": struct{}{}, - "CSRF": struct{}{}, - "CSS": struct{}{}, - "DNS": struct{}{}, - "EOF": struct{}{}, - "GUID": struct{}{}, - "HTML": struct{}{}, - "HTTP": struct{}{}, - "HTTPS": struct{}{}, - "ID": struct{}{}, - "IP": struct{}{}, - "JSON": struct{}{}, - "LHS": struct{}{}, - "QPS": struct{}{}, - "RAM": struct{}{}, - "RHS": struct{}{}, - "RPC": struct{}{}, - "SLA": struct{}{}, - "SMTP": struct{}{}, - "SQL": struct{}{}, - "SSH": struct{}{}, - "TCP": struct{}{}, - "TLS": struct{}{}, - "TTL": struct{}{}, - "UDP": struct{}{}, - "UI": struct{}{}, - "UID": struct{}{}, - "UUID": struct{}{}, - "URI": struct{}{}, - "URL": struct{}{}, - "UTF8": struct{}{}, - "VM": struct{}{}, - "XML": struct{}{}, - "XSRF": struct{}{}, - "XSS": struct{}{}, - } - commonInitialisms = keys(commonInitialismMap) - commonInitialism = mustDoubleArray(newDoubleArray(commonInitialisms)) - longestLen = longestLength(commonInitialisms) - shortestLen = shortestLength(commonInitialisms, longestLen) -) - -// ToUpperCamelCase returns a copy of the string s with all Unicode letters mapped to their camel case. -// It will convert to upper case previous letter of '_' and first letter, and remove letter of '_'. -func ToUpperCamelCase(s string) string { - if s == "" { - return "" - } - upper := true - start := 0 - result := make([]byte, 0, len(s)) - var runeBuf [utf8.UTFMax]byte - var initialism []byte - for _, c := range s { - if c == '_' { - upper = true - candidate := string(result[start:]) - initialism = initialism[:0] - for _, r := range candidate { - if r < utf8.RuneSelf { - initialism = append(initialism, toUpperASCII(byte(r))) - } else { - n := utf8.EncodeRune(runeBuf[:], unicode.ToUpper(r)) - initialism = append(initialism, runeBuf[:n]...) - } - } - if length := commonInitialism.LookupByBytes(initialism); length > 0 { - result = append(result[:start], initialism...) - } - start = len(result) - continue - } - if upper { - if c < utf8.RuneSelf { - result = append(result, toUpperASCII(byte(c))) - } else { - n := utf8.EncodeRune(runeBuf[:], unicode.ToUpper(c)) - result = append(result, runeBuf[:n]...) - } - upper = false - continue - } - if c < utf8.RuneSelf { - result = append(result, byte(c)) - } else { - n := utf8.EncodeRune(runeBuf[:], c) - result = append(result, runeBuf[:n]...) - } - } - candidate := string(result[start:]) - initialism = initialism[:0] - for _, r := range candidate { - if r < utf8.RuneSelf { - initialism = append(initialism, toUpperASCII(byte(r))) - } else { - n := utf8.EncodeRune(runeBuf[:], unicode.ToUpper(r)) - initialism = append(initialism, runeBuf[:n]...) - } - } - if length := commonInitialism.LookupByBytes(initialism); length > 0 { - result = append(result[:start], initialism...) - } - return string(result) -} - -// ToUpperCamelCaseASCII is similar to ToUpperCamelCase, but optimized for -// only the ASCII characters. -// ToUpperCamelCaseASCII is faster than ToUpperCamelCase, but doesn't work if -// contains non-ASCII characters. -func ToUpperCamelCaseASCII(s string) string { - if s == "" { - return "" - } - upper := true - start := 0 - result := make([]byte, 0, len(s)) - var initialism []byte - for i := 0; i < len(s); i++ { - c := s[i] - if c == '_' { - upper = true - candidate := result[start:] - initialism = initialism[:0] - for _, b := range candidate { - initialism = append(initialism, toUpperASCII(b)) - } - if length := commonInitialism.LookupByBytes(initialism); length > 0 { - result = append(result[:start], initialism...) - } - start = len(result) - continue - } - if upper { - result = append(result, toUpperASCII(c)) - upper = false - continue - } - result = append(result, c) - } - candidate := result[start:] - initialism = initialism[:0] - for _, b := range candidate { - initialism = append(initialism, toUpperASCII(b)) - } - if length := commonInitialism.LookupByBytes(initialism); length > 0 { - result = append(result[:start], initialism...) - } - return string(result) -} - -// ToSnakeCase returns a copy of the string s with all Unicode letters mapped to their snake case. -// It will insert letter of '_' at position of previous letter of uppercase and all -// letters convert to lower case. -// ToSnakeCase does not insert '_' letter into a common initialism word like ID, URL and so on. -func ToSnakeCase(s string) string { - if s == "" { - return "" - } - result := make([]byte, 0, len(s)) - var runeBuf [utf8.UTFMax]byte - var j, skipCount int - for i, c := range s { - if i < skipCount { - continue - } - if unicode.IsUpper(c) { - if i != 0 { - result = append(result, '_') - } - next := nextIndex(j, len(s)) - if length := commonInitialism.Lookup(s[j:next]); length > 0 { - for _, r := range s[j : j+length] { - if r < utf8.RuneSelf { - result = append(result, toLowerASCII(byte(r))) - } else { - n := utf8.EncodeRune(runeBuf[:], unicode.ToLower(r)) - result = append(result, runeBuf[:n]...) - } - } - j += length - 1 - skipCount = i + length - continue - } - } - if c < utf8.RuneSelf { - result = append(result, toLowerASCII(byte(c))) - } else { - n := utf8.EncodeRune(runeBuf[:], unicode.ToLower(c)) - result = append(result, runeBuf[:n]...) - } - j++ - } - return string(result) -} - -// ToSnakeCaseASCII is similar to ToSnakeCase, but optimized for only the ASCII -// characters. -// ToSnakeCaseASCII is faster than ToSnakeCase, but doesn't work correctly if -// contains non-ASCII characters. -func ToSnakeCaseASCII(s string) string { - if s == "" { - return "" - } - result := make([]byte, 0, len(s)) - for i := 0; i < len(s); i++ { - c := s[i] - if isUpperASCII(c) { - if i != 0 { - result = append(result, '_') - } - if k := i + shortestLen - 1; k < len(s) && isUpperASCII(s[k]) { - if length := commonInitialism.Lookup(s[i:nextIndex(i, len(s))]); length > 0 { - for j, buf := 0, s[i:i+length]; j < len(buf); j++ { - result = append(result, toLowerASCII(buf[j])) - } - i += length - 1 - continue - } - } - } - result = append(result, toLowerASCII(c)) - } - return string(result) -} - -// AddCommonInitialism adds ss to list of common initialisms. -func AddCommonInitialism(ss ...string) { - mu.Lock() - defer mu.Unlock() - for _, s := range ss { - commonInitialismMap[s] = struct{}{} - } - commonInitialisms = keys(commonInitialismMap) - commonInitialism = mustDoubleArray(newDoubleArray(commonInitialisms)) - longestLen = longestLength(commonInitialisms) - shortestLen = shortestLength(commonInitialisms, longestLen) -} - -// DelCommonInitialism deletes ss from list of common initialisms. -func DelCommonInitialism(ss ...string) { - mu.Lock() - defer mu.Unlock() - for _, s := range ss { - delete(commonInitialismMap, s) - } - commonInitialisms = keys(commonInitialismMap) - commonInitialism = mustDoubleArray(newDoubleArray(commonInitialisms)) - longestLen = longestLength(commonInitialisms) - shortestLen = shortestLength(commonInitialisms, longestLen) -} - -func isUpperASCII(c byte) bool { - return 'A' <= c && c <= 'Z' -} - -func isLowerASCII(c byte) bool { - return 'a' <= c && c <= 'z' -} - -func toUpperASCII(c byte) byte { - if isLowerASCII(c) { - return c - ('a' - 'A') - } - return c -} - -func toLowerASCII(c byte) byte { - if isUpperASCII(c) { - return c + 'a' - 'A' - } - return c -} - -func nextIndex(i, maxlen int) int { - if n := i + longestLen; n < maxlen { - return n - } - return maxlen -} - -func keys(m map[string]struct{}) []string { - result := make([]string, 0, len(m)) - for k := range m { - result = append(result, k) - } - return result -} - -func shortestLength(strs []string, shortest int) int { - for _, s := range strs { - if candidate := utf8.RuneCountInString(s); candidate < shortest { - shortest = candidate - } - } - return shortest -} - -func longestLength(strs []string) (longest int) { - for _, s := range strs { - if candidate := utf8.RuneCountInString(s); candidate > longest { - longest = candidate - } - } - return longest -} diff --git a/vendor/github.com/naoina/toml/LICENSE b/vendor/github.com/naoina/toml/LICENSE deleted file mode 100644 index e65039ad84c..00000000000 --- a/vendor/github.com/naoina/toml/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2014 Naoya Inada - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/vendor/github.com/naoina/toml/ast/ast.go b/vendor/github.com/naoina/toml/ast/ast.go deleted file mode 100644 index 4868e2e1ae2..00000000000 --- a/vendor/github.com/naoina/toml/ast/ast.go +++ /dev/null @@ -1,192 +0,0 @@ -package ast - -import ( - "strconv" - "strings" - "time" -) - -type Position struct { - Begin int - End int -} - -type Value interface { - Pos() int - End() int - Source() string -} - -type String struct { - Position Position - Value string - Data []rune -} - -func (s *String) Pos() int { - return s.Position.Begin -} - -func (s *String) End() int { - return s.Position.End -} - -func (s *String) Source() string { - return string(s.Data) -} - -type Integer struct { - Position Position - Value string - Data []rune -} - -func (i *Integer) Pos() int { - return i.Position.Begin -} - -func (i *Integer) End() int { - return i.Position.End -} - -func (i *Integer) Source() string { - return string(i.Data) -} - -func (i *Integer) Int() (int64, error) { - return strconv.ParseInt(i.Value, 10, 64) -} - -type Float struct { - Position Position - Value string - Data []rune -} - -func (f *Float) Pos() int { - return f.Position.Begin -} - -func (f *Float) End() int { - return f.Position.End -} - -func (f *Float) Source() string { - return string(f.Data) -} - -func (f *Float) Float() (float64, error) { - return strconv.ParseFloat(f.Value, 64) -} - -type Boolean struct { - Position Position - Value string - Data []rune -} - -func (b *Boolean) Pos() int { - return b.Position.Begin -} - -func (b *Boolean) End() int { - return b.Position.End -} - -func (b *Boolean) Source() string { - return string(b.Data) -} - -func (b *Boolean) Boolean() (bool, error) { - return strconv.ParseBool(b.Value) -} - -type Datetime struct { - Position Position - Value string - Data []rune -} - -func (d *Datetime) Pos() int { - return d.Position.Begin -} - -func (d *Datetime) End() int { - return d.Position.End -} - -func (d *Datetime) Source() string { - return string(d.Data) -} - -func (d *Datetime) Time() (time.Time, error) { - switch { - case !strings.Contains(d.Value, ":"): - return time.Parse("2006-01-02", d.Value) - case !strings.Contains(d.Value, "-"): - return time.Parse("15:04:05.999999999", d.Value) - default: - return time.Parse(time.RFC3339Nano, d.Value) - } -} - -type Array struct { - Position Position - Value []Value - Data []rune -} - -func (a *Array) Pos() int { - return a.Position.Begin -} - -func (a *Array) End() int { - return a.Position.End -} - -func (a *Array) Source() string { - return string(a.Data) -} - -type TableType uint8 - -const ( - TableTypeNormal TableType = iota - TableTypeArray -) - -var tableTypes = [...]string{ - "normal", - "array", -} - -func (t TableType) String() string { - return tableTypes[t] -} - -type Table struct { - Position Position - Line int - Name string - Fields map[string]interface{} - Type TableType - Data []rune -} - -func (t *Table) Pos() int { - return t.Position.Begin -} - -func (t *Table) End() int { - return t.Position.End -} - -func (t *Table) Source() string { - return string(t.Data) -} - -type KeyValue struct { - Key string - Value Value - Line int -} diff --git a/vendor/github.com/naoina/toml/config.go b/vendor/github.com/naoina/toml/config.go deleted file mode 100644 index 06bb9493b89..00000000000 --- a/vendor/github.com/naoina/toml/config.go +++ /dev/null @@ -1,86 +0,0 @@ -package toml - -import ( - "fmt" - "io" - "reflect" - "strings" - - stringutil "github.com/naoina/go-stringutil" - "github.com/naoina/toml/ast" -) - -// Config contains options for encoding and decoding. -type Config struct { - // NormFieldName is used to match TOML keys to struct fields. The function runs for - // both input keys and struct field names and should return a string that makes the - // two match. You must set this field to use the decoder. - // - // Example: The function in the default config removes _ and lowercases all keys. This - // allows a key called 'api_key' to match the struct field 'APIKey' because both are - // normalized to 'apikey'. - // - // Note that NormFieldName is not used for fields which define a TOML - // key through the struct tag. - NormFieldName func(typ reflect.Type, keyOrField string) string - - // FieldToKey determines the TOML key of a struct field when encoding. - // You must set this field to use the encoder. - // - // Note that FieldToKey is not used for fields which define a TOML - // key through the struct tag. - FieldToKey func(typ reflect.Type, field string) string - - // MissingField, if non-nil, is called when the decoder encounters a key for which no - // matching struct field exists. The default behavior is to return an error. - MissingField func(typ reflect.Type, key string) error -} - -// DefaultConfig contains the default options for encoding and decoding. -// Snake case (i.e. 'foo_bar') is used for key names. -var DefaultConfig = Config{ - NormFieldName: defaultNormFieldName, - FieldToKey: snakeCase, -} - -func defaultNormFieldName(typ reflect.Type, s string) string { - return strings.Replace(strings.ToLower(s), "_", "", -1) -} - -func snakeCase(typ reflect.Type, s string) string { - return stringutil.ToSnakeCase(s) -} - -func defaultMissingField(typ reflect.Type, key string) error { - return fmt.Errorf("field corresponding to `%s' is not defined in %v", key, typ) -} - -// NewEncoder returns a new Encoder that writes to w. -// It is shorthand for DefaultConfig.NewEncoder(w). -func NewEncoder(w io.Writer) *Encoder { - return DefaultConfig.NewEncoder(w) -} - -// Marshal returns the TOML encoding of v. -// It is shorthand for DefaultConfig.Marshal(v). -func Marshal(v interface{}) ([]byte, error) { - return DefaultConfig.Marshal(v) -} - -// Unmarshal parses the TOML data and stores the result in the value pointed to by v. -// It is shorthand for DefaultConfig.Unmarshal(data, v). -func Unmarshal(data []byte, v interface{}) error { - return DefaultConfig.Unmarshal(data, v) -} - -// UnmarshalTable applies the contents of an ast.Table to the value pointed at by v. -// It is shorthand for DefaultConfig.UnmarshalTable(t, v). -func UnmarshalTable(t *ast.Table, v interface{}) error { - return DefaultConfig.UnmarshalTable(t, v) -} - -// NewDecoder returns a new Decoder that reads from r. -// It is shorthand for DefaultConfig.NewDecoder(r). -func NewDecoder(r io.Reader) *Decoder { - return DefaultConfig.NewDecoder(r) -} diff --git a/vendor/github.com/naoina/toml/decode.go b/vendor/github.com/naoina/toml/decode.go deleted file mode 100644 index b3c169eb1c6..00000000000 --- a/vendor/github.com/naoina/toml/decode.go +++ /dev/null @@ -1,478 +0,0 @@ -// Package toml encodes and decodes the TOML configuration format using reflection. -// -// This library is compatible with TOML version v0.4.0. -package toml - -import ( - "encoding" - "fmt" - "io" - "io/ioutil" - "reflect" - "strconv" - "strings" - "time" - - "github.com/naoina/toml/ast" -) - -const ( - tableSeparator = '.' -) - -var ( - escapeReplacer = strings.NewReplacer( - "\b", "\\n", - "\f", "\\f", - "\n", "\\n", - "\r", "\\r", - "\t", "\\t", - ) - underscoreReplacer = strings.NewReplacer( - "_", "", - ) -) - -var timeType = reflect.TypeOf(time.Time{}) - -// Unmarshal parses the TOML data and stores the result in the value pointed to by v. -// -// Unmarshal will mapped to v that according to following rules: -// -// TOML strings to string -// TOML integers to any int type -// TOML floats to float32 or float64 -// TOML booleans to bool -// TOML datetimes to time.Time -// TOML arrays to any type of slice -// TOML tables to struct or map -// TOML array tables to slice of struct or map -func (cfg *Config) Unmarshal(data []byte, v interface{}) error { - table, err := Parse(data) - if err != nil { - return err - } - if err := cfg.UnmarshalTable(table, v); err != nil { - return err - } - return nil -} - -// A Decoder reads and decodes TOML from an input stream. -type Decoder struct { - r io.Reader - cfg *Config -} - -// NewDecoder returns a new Decoder that reads from r. -// Note that it reads all from r before parsing it. -func (cfg *Config) NewDecoder(r io.Reader) *Decoder { - return &Decoder{r, cfg} -} - -// Decode parses the TOML data from its input and stores it in the value pointed to by v. -// See the documentation for Unmarshal for details about the conversion of TOML into a Go value. -func (d *Decoder) Decode(v interface{}) error { - b, err := ioutil.ReadAll(d.r) - if err != nil { - return err - } - return d.cfg.Unmarshal(b, v) -} - -// UnmarshalerRec may be implemented by types to customize their behavior when being -// unmarshaled from TOML. You can use it to implement custom validation or to set -// unexported fields. -// -// UnmarshalTOML receives a function that can be called to unmarshal the original TOML -// value into a field or variable. It is safe to call the function more than once if -// necessary. -type UnmarshalerRec interface { - UnmarshalTOML(fn func(interface{}) error) error -} - -// Unmarshaler can be used to capture and process raw TOML source of a table or value. -// UnmarshalTOML must copy the input if it wishes to retain it after returning. -// -// Note: this interface is retained for backwards compatibility. You probably want -// to implement encoding.TextUnmarshaler or UnmarshalerRec instead. -type Unmarshaler interface { - UnmarshalTOML(input []byte) error -} - -// UnmarshalTable applies the contents of an ast.Table to the value pointed at by v. -// -// UnmarshalTable will mapped to v that according to following rules: -// -// TOML strings to string -// TOML integers to any int type -// TOML floats to float32 or float64 -// TOML booleans to bool -// TOML datetimes to time.Time -// TOML arrays to any type of slice -// TOML tables to struct or map -// TOML array tables to slice of struct or map -func (cfg *Config) UnmarshalTable(t *ast.Table, v interface{}) error { - rv := reflect.ValueOf(v) - toplevelMap := rv.Kind() == reflect.Map - if (!toplevelMap && rv.Kind() != reflect.Ptr) || rv.IsNil() { - return &invalidUnmarshalError{reflect.TypeOf(v)} - } - return unmarshalTable(cfg, rv, t, toplevelMap) -} - -// used for UnmarshalerRec. -func unmarshalTableOrValue(cfg *Config, rv reflect.Value, av interface{}) error { - if (rv.Kind() != reflect.Ptr && rv.Kind() != reflect.Map) || rv.IsNil() { - return &invalidUnmarshalError{rv.Type()} - } - rv = indirect(rv) - - switch av.(type) { - case *ast.KeyValue, *ast.Table, []*ast.Table: - if err := unmarshalField(cfg, rv, av); err != nil { - return lineError(fieldLineNumber(av), err) - } - return nil - case ast.Value: - return setValue(cfg, rv, av.(ast.Value)) - default: - panic(fmt.Sprintf("BUG: unhandled AST node type %T", av)) - } -} - -// unmarshalTable unmarshals the fields of a table into a struct or map. -// -// toplevelMap is true when rv is an (unadressable) map given to UnmarshalTable. In this -// (special) case, the map is used as-is instead of creating a new map. -func unmarshalTable(cfg *Config, rv reflect.Value, t *ast.Table, toplevelMap bool) error { - rv = indirect(rv) - if err, ok := setUnmarshaler(cfg, rv, t); ok { - return lineError(t.Line, err) - } - switch { - case rv.Kind() == reflect.Struct: - fc := makeFieldCache(cfg, rv.Type()) - for key, fieldAst := range t.Fields { - fv, fieldName, err := fc.findField(cfg, rv, key) - if err != nil { - return lineError(fieldLineNumber(fieldAst), err) - } - if fv.IsValid() { - if err := unmarshalField(cfg, fv, fieldAst); err != nil { - return lineErrorField(fieldLineNumber(fieldAst), rv.Type().String()+"."+fieldName, err) - } - } - } - case rv.Kind() == reflect.Map || isEface(rv): - m := rv - if !toplevelMap { - if rv.Kind() == reflect.Interface { - m = reflect.ValueOf(make(map[string]interface{})) - } else { - m = reflect.MakeMap(rv.Type()) - } - } - elemtyp := m.Type().Elem() - for key, fieldAst := range t.Fields { - kv, err := unmarshalMapKey(m.Type().Key(), key) - if err != nil { - return lineError(fieldLineNumber(fieldAst), err) - } - fv := reflect.New(elemtyp).Elem() - if err := unmarshalField(cfg, fv, fieldAst); err != nil { - return lineError(fieldLineNumber(fieldAst), err) - } - m.SetMapIndex(kv, fv) - } - if !toplevelMap { - rv.Set(m) - } - default: - return lineError(t.Line, &unmarshalTypeError{"table", "struct or map", rv.Type()}) - } - return nil -} - -func fieldLineNumber(fieldAst interface{}) int { - switch av := fieldAst.(type) { - case *ast.KeyValue: - return av.Line - case *ast.Table: - return av.Line - case []*ast.Table: - return av[0].Line - default: - panic(fmt.Sprintf("BUG: unhandled node type %T", fieldAst)) - } -} - -func unmarshalField(cfg *Config, rv reflect.Value, fieldAst interface{}) error { - switch av := fieldAst.(type) { - case *ast.KeyValue: - return setValue(cfg, rv, av.Value) - case *ast.Table: - return unmarshalTable(cfg, rv, av, false) - case []*ast.Table: - rv = indirect(rv) - if err, ok := setUnmarshaler(cfg, rv, fieldAst); ok { - return err - } - var slice reflect.Value - switch { - case rv.Kind() == reflect.Slice: - slice = reflect.MakeSlice(rv.Type(), len(av), len(av)) - case isEface(rv): - slice = reflect.ValueOf(make([]interface{}, len(av))) - default: - return &unmarshalTypeError{"array table", "slice", rv.Type()} - } - for i, tbl := range av { - vv := reflect.New(slice.Type().Elem()).Elem() - if err := unmarshalTable(cfg, vv, tbl, false); err != nil { - return err - } - slice.Index(i).Set(vv) - } - rv.Set(slice) - default: - panic(fmt.Sprintf("BUG: unhandled AST node type %T", av)) - } - return nil -} - -func unmarshalMapKey(typ reflect.Type, key string) (reflect.Value, error) { - rv := reflect.New(typ).Elem() - if u, ok := rv.Addr().Interface().(encoding.TextUnmarshaler); ok { - return rv, u.UnmarshalText([]byte(key)) - } - switch typ.Kind() { - case reflect.String: - rv.SetString(key) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - i, err := strconv.ParseInt(key, 10, int(typ.Size()*8)) - if err != nil { - return rv, convertNumError(typ.Kind(), err) - } - rv.SetInt(i) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - i, err := strconv.ParseUint(key, 10, int(typ.Size()*8)) - if err != nil { - return rv, convertNumError(typ.Kind(), err) - } - rv.SetUint(i) - default: - return rv, fmt.Errorf("invalid map key type %s", typ) - } - return rv, nil -} - -func setValue(cfg *Config, lhs reflect.Value, val ast.Value) error { - lhs = indirect(lhs) - if err, ok := setUnmarshaler(cfg, lhs, val); ok { - return err - } - if err, ok := setTextUnmarshaler(lhs, val); ok { - return err - } - switch v := val.(type) { - case *ast.Integer: - return setInt(lhs, v) - case *ast.Float: - return setFloat(lhs, v) - case *ast.String: - return setString(lhs, v) - case *ast.Boolean: - return setBoolean(lhs, v) - case *ast.Datetime: - return setDatetime(lhs, v) - case *ast.Array: - return setArray(cfg, lhs, v) - default: - panic(fmt.Sprintf("BUG: unhandled node type %T", v)) - } -} - -func indirect(rv reflect.Value) reflect.Value { - for rv.Kind() == reflect.Ptr { - if rv.IsNil() { - rv.Set(reflect.New(rv.Type().Elem())) - } - rv = rv.Elem() - } - return rv -} - -func setUnmarshaler(cfg *Config, lhs reflect.Value, av interface{}) (error, bool) { - if lhs.CanAddr() { - if u, ok := lhs.Addr().Interface().(UnmarshalerRec); ok { - err := u.UnmarshalTOML(func(v interface{}) error { - return unmarshalTableOrValue(cfg, reflect.ValueOf(v), av) - }) - return err, true - } - if u, ok := lhs.Addr().Interface().(Unmarshaler); ok { - return u.UnmarshalTOML(unmarshalerSource(av)), true - } - } - return nil, false -} - -func unmarshalerSource(av interface{}) []byte { - var source []byte - switch av := av.(type) { - case []*ast.Table: - for i, tab := range av { - source = append(source, tab.Source()...) - if i != len(av)-1 { - source = append(source, '\n') - } - } - case ast.Value: - source = []byte(av.Source()) - default: - panic(fmt.Sprintf("BUG: unhandled node type %T", av)) - } - return source -} - -func setTextUnmarshaler(lhs reflect.Value, val ast.Value) (error, bool) { - if !lhs.CanAddr() { - return nil, false - } - u, ok := lhs.Addr().Interface().(encoding.TextUnmarshaler) - if !ok || lhs.Type() == timeType { - return nil, false - } - var data string - switch val := val.(type) { - case *ast.Array: - return &unmarshalTypeError{"array", "", lhs.Type()}, true - case *ast.String: - data = val.Value - default: - data = val.Source() - } - return u.UnmarshalText([]byte(data)), true -} - -func setInt(fv reflect.Value, v *ast.Integer) error { - k := fv.Kind() - switch { - case k >= reflect.Int && k <= reflect.Int64: - i, err := strconv.ParseInt(v.Value, 10, int(fv.Type().Size()*8)) - if err != nil { - return convertNumError(fv.Kind(), err) - } - fv.SetInt(i) - case k >= reflect.Uint && k <= reflect.Uintptr: - i, err := strconv.ParseUint(v.Value, 10, int(fv.Type().Size()*8)) - if err != nil { - return convertNumError(fv.Kind(), err) - } - fv.SetUint(i) - case isEface(fv): - i, err := strconv.ParseInt(v.Value, 10, 64) - if err != nil { - return convertNumError(reflect.Int64, err) - } - fv.Set(reflect.ValueOf(i)) - default: - return &unmarshalTypeError{"integer", "", fv.Type()} - } - return nil -} - -func setFloat(fv reflect.Value, v *ast.Float) error { - f, err := v.Float() - if err != nil { - return err - } - switch { - case fv.Kind() == reflect.Float32 || fv.Kind() == reflect.Float64: - if fv.OverflowFloat(f) { - return &overflowError{fv.Kind(), v.Value} - } - fv.SetFloat(f) - case isEface(fv): - fv.Set(reflect.ValueOf(f)) - default: - return &unmarshalTypeError{"float", "", fv.Type()} - } - return nil -} - -func setString(fv reflect.Value, v *ast.String) error { - switch { - case fv.Kind() == reflect.String: - fv.SetString(v.Value) - case isEface(fv): - fv.Set(reflect.ValueOf(v.Value)) - default: - return &unmarshalTypeError{"string", "", fv.Type()} - } - return nil -} - -func setBoolean(fv reflect.Value, v *ast.Boolean) error { - b, _ := v.Boolean() - switch { - case fv.Kind() == reflect.Bool: - fv.SetBool(b) - case isEface(fv): - fv.Set(reflect.ValueOf(b)) - default: - return &unmarshalTypeError{"boolean", "", fv.Type()} - } - return nil -} - -func setDatetime(rv reflect.Value, v *ast.Datetime) error { - t, err := v.Time() - if err != nil { - return err - } - if !timeType.AssignableTo(rv.Type()) { - return &unmarshalTypeError{"datetime", "", rv.Type()} - } - rv.Set(reflect.ValueOf(t)) - return nil -} - -func setArray(cfg *Config, rv reflect.Value, v *ast.Array) error { - var slicetyp reflect.Type - switch { - case rv.Kind() == reflect.Slice: - slicetyp = rv.Type() - case isEface(rv): - slicetyp = reflect.SliceOf(rv.Type()) - default: - return &unmarshalTypeError{"array", "slice", rv.Type()} - } - - if len(v.Value) == 0 { - // Ensure defined slices are always set to a non-nil value. - rv.Set(reflect.MakeSlice(slicetyp, 0, 0)) - return nil - } - - tomltyp := reflect.TypeOf(v.Value[0]) - slice := reflect.MakeSlice(slicetyp, len(v.Value), len(v.Value)) - typ := slicetyp.Elem() - for i, vv := range v.Value { - if i > 0 && tomltyp != reflect.TypeOf(vv) { - return errArrayMultiType - } - tmp := reflect.New(typ).Elem() - if err := setValue(cfg, tmp, vv); err != nil { - return err - } - slice.Index(i).Set(tmp) - } - rv.Set(slice) - return nil -} - -func isEface(rv reflect.Value) bool { - return rv.Kind() == reflect.Interface && rv.Type().NumMethod() == 0 -} diff --git a/vendor/github.com/naoina/toml/encode.go b/vendor/github.com/naoina/toml/encode.go deleted file mode 100644 index ae6bfd575fc..00000000000 --- a/vendor/github.com/naoina/toml/encode.go +++ /dev/null @@ -1,398 +0,0 @@ -package toml - -import ( - "bytes" - "encoding" - "fmt" - "io" - "reflect" - "sort" - "strconv" - "time" - - "github.com/naoina/toml/ast" -) - -const ( - tagOmitempty = "omitempty" - tagSkip = "-" -) - -// Marshal returns the TOML encoding of v. -// -// Struct values encode as TOML. Each exported struct field becomes a field of -// the TOML structure unless -// - the field's tag is "-", or -// - the field is empty and its tag specifies the "omitempty" option. -// -// The "toml" key in the struct field's tag value is the key name, followed by -// an optional comma and options. Examples: -// -// // Field is ignored by this package. -// Field int `toml:"-"` -// -// // Field appears in TOML as key "myName". -// Field int `toml:"myName"` -// -// // Field appears in TOML as key "myName" and the field is omitted from the -// // result of encoding if its value is empty. -// Field int `toml:"myName,omitempty"` -// -// // Field appears in TOML as key "field", but the field is skipped if -// // empty. Note the leading comma. -// Field int `toml:",omitempty"` -func (cfg *Config) Marshal(v interface{}) ([]byte, error) { - buf := new(bytes.Buffer) - err := cfg.NewEncoder(buf).Encode(v) - return buf.Bytes(), err -} - -// A Encoder writes TOML to an output stream. -type Encoder struct { - w io.Writer - cfg *Config -} - -// NewEncoder returns a new Encoder that writes to w. -func (cfg *Config) NewEncoder(w io.Writer) *Encoder { - return &Encoder{w, cfg} -} - -// Encode writes the TOML of v to the stream. -// See the documentation for Marshal for details about the conversion of Go values to TOML. -func (e *Encoder) Encode(v interface{}) error { - rv := reflect.ValueOf(v) - for rv.Kind() == reflect.Ptr { - if rv.IsNil() { - return &marshalNilError{rv.Type()} - } - rv = rv.Elem() - } - buf := &tableBuf{typ: ast.TableTypeNormal} - var err error - switch rv.Kind() { - case reflect.Struct: - err = buf.structFields(e.cfg, rv) - case reflect.Map: - err = buf.mapFields(e.cfg, rv) - default: - err = &marshalTableError{rv.Type()} - } - if err != nil { - return err - } - return buf.writeTo(e.w, "") -} - -// Marshaler can be implemented to override the encoding of TOML values. The returned text -// must be a simple TOML value (i.e. not a table) and is inserted into marshaler output. -// -// This interface exists for backwards-compatibility reasons. You probably want to -// implement encoding.TextMarshaler or MarshalerRec instead. -type Marshaler interface { - MarshalTOML() ([]byte, error) -} - -// MarshalerRec can be implemented to override the TOML encoding of a type. -// The returned value is marshaled in place of the receiver. -type MarshalerRec interface { - MarshalTOML() (interface{}, error) -} - -type tableBuf struct { - name string // already escaped / quoted - body []byte - children []*tableBuf - typ ast.TableType - arrayDepth int -} - -func (b *tableBuf) writeTo(w io.Writer, prefix string) error { - key := b.name // TODO: escape dots - if prefix != "" { - key = prefix + "." + key - } - - if b.name != "" { - head := "[" + key + "]" - if b.typ == ast.TableTypeArray { - head = "[" + head + "]" - } - head += "\n" - if _, err := io.WriteString(w, head); err != nil { - return err - } - } - if _, err := w.Write(b.body); err != nil { - return err - } - - for i, child := range b.children { - if len(b.body) > 0 || i > 0 { - if _, err := w.Write([]byte("\n")); err != nil { - return err - } - } - if err := child.writeTo(w, key); err != nil { - return err - } - } - return nil -} - -func (b *tableBuf) newChild(name string) *tableBuf { - child := &tableBuf{name: quoteName(name), typ: ast.TableTypeNormal} - if b.arrayDepth > 0 { - child.typ = ast.TableTypeArray - } - return child -} - -func (b *tableBuf) addChild(child *tableBuf) { - // Empty table elision: we can avoid writing a table that doesn't have any keys on its - // own. Array tables can't be elided because they define array elements (which would - // be missing if elided). - if len(child.body) == 0 && child.typ == ast.TableTypeNormal { - for _, gchild := range child.children { - gchild.name = child.name + "." + gchild.name - b.addChild(gchild) - } - return - } - b.children = append(b.children, child) -} - -func (b *tableBuf) structFields(cfg *Config, rv reflect.Value) error { - rt := rv.Type() - for i := 0; i < rv.NumField(); i++ { - ft := rt.Field(i) - if ft.PkgPath != "" && !ft.Anonymous { // not exported - continue - } - name, rest := extractTag(ft.Tag.Get(fieldTagName)) - if name == tagSkip { - continue - } - fv := rv.Field(i) - if rest == tagOmitempty && isEmptyValue(fv) { - continue - } - if name == "" { - name = cfg.FieldToKey(rt, ft.Name) - } - if err := b.field(cfg, name, fv); err != nil { - return err - } - } - return nil -} - -type mapKeyList []struct { - key string - value reflect.Value -} - -func (l mapKeyList) Len() int { return len(l) } -func (l mapKeyList) Swap(i, j int) { l[i], l[j] = l[j], l[i] } -func (l mapKeyList) Less(i, j int) bool { return l[i].key < l[j].key } - -func (b *tableBuf) mapFields(cfg *Config, rv reflect.Value) error { - keys := rv.MapKeys() - keylist := make(mapKeyList, len(keys)) - for i, key := range keys { - var err error - keylist[i].key, err = encodeMapKey(key) - if err != nil { - return err - } - keylist[i].value = rv.MapIndex(key) - } - sort.Sort(keylist) - - for _, kv := range keylist { - if err := b.field(cfg, kv.key, kv.value); err != nil { - return err - } - } - return nil -} - -func (b *tableBuf) field(cfg *Config, name string, rv reflect.Value) error { - off := len(b.body) - b.body = append(b.body, quoteName(name)...) - b.body = append(b.body, " = "...) - isTable, err := b.value(cfg, rv, name) - if isTable { - b.body = b.body[:off] // rub out "key =" - } else { - b.body = append(b.body, '\n') - } - return err -} - -func (b *tableBuf) value(cfg *Config, rv reflect.Value, name string) (bool, error) { - isMarshaler, isTable, err := b.marshaler(cfg, rv, name) - if isMarshaler { - return isTable, err - } - switch rv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - b.body = strconv.AppendInt(b.body, rv.Int(), 10) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - b.body = strconv.AppendUint(b.body, rv.Uint(), 10) - case reflect.Float32, reflect.Float64: - b.body = strconv.AppendFloat(b.body, rv.Float(), 'e', -1, 64) - case reflect.Bool: - b.body = strconv.AppendBool(b.body, rv.Bool()) - case reflect.String: - b.body = strconv.AppendQuote(b.body, rv.String()) - case reflect.Ptr, reflect.Interface: - if rv.IsNil() { - return false, &marshalNilError{rv.Type()} - } - return b.value(cfg, rv.Elem(), name) - case reflect.Slice, reflect.Array: - rvlen := rv.Len() - if rvlen == 0 { - b.body = append(b.body, '[', ']') - return false, nil - } - - b.arrayDepth++ - wroteElem := false - b.body = append(b.body, '[') - for i := 0; i < rvlen; i++ { - isTable, err := b.value(cfg, rv.Index(i), name) - if err != nil { - return isTable, err - } - wroteElem = wroteElem || !isTable - if wroteElem { - if i < rvlen-1 { - b.body = append(b.body, ',', ' ') - } else { - b.body = append(b.body, ']') - } - } - } - if !wroteElem { - b.body = b.body[:len(b.body)-1] // rub out '[' - } - b.arrayDepth-- - return !wroteElem, nil - case reflect.Struct: - child := b.newChild(name) - err := child.structFields(cfg, rv) - b.addChild(child) - return true, err - case reflect.Map: - child := b.newChild(name) - err := child.mapFields(cfg, rv) - b.addChild(child) - return true, err - default: - return false, fmt.Errorf("toml: marshal: unsupported type %v", rv.Kind()) - } - return false, nil -} - -func (b *tableBuf) marshaler(cfg *Config, rv reflect.Value, name string) (handled, isTable bool, err error) { - switch t := rv.Interface().(type) { - case encoding.TextMarshaler: - enc, err := t.MarshalText() - if err != nil { - return true, false, err - } - b.body = encodeTextMarshaler(b.body, string(enc)) - return true, false, nil - case MarshalerRec: - newval, err := t.MarshalTOML() - if err != nil { - return true, false, err - } - isTable, err = b.value(cfg, reflect.ValueOf(newval), name) - return true, isTable, err - case Marshaler: - enc, err := t.MarshalTOML() - if err != nil { - return true, false, err - } - b.body = append(b.body, enc...) - return true, false, nil - } - return false, false, nil -} - -func encodeTextMarshaler(buf []byte, v string) []byte { - // Emit the value without quotes if possible. - if v == "true" || v == "false" { - return append(buf, v...) - } else if _, err := time.Parse(time.RFC3339Nano, v); err == nil { - return append(buf, v...) - } else if _, err := strconv.ParseInt(v, 10, 64); err == nil { - return append(buf, v...) - } else if _, err := strconv.ParseUint(v, 10, 64); err == nil { - return append(buf, v...) - } else if _, err := strconv.ParseFloat(v, 64); err == nil { - return append(buf, v...) - } - return strconv.AppendQuote(buf, v) -} - -func encodeMapKey(rv reflect.Value) (string, error) { - if rv.Kind() == reflect.String { - return rv.String(), nil - } - if tm, ok := rv.Interface().(encoding.TextMarshaler); ok { - b, err := tm.MarshalText() - return string(b), err - } - switch rv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return strconv.FormatInt(rv.Int(), 10), nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return strconv.FormatUint(rv.Uint(), 10), nil - } - return "", fmt.Errorf("toml: invalid map key type %v", rv.Type()) -} - -func isEmptyValue(v reflect.Value) bool { - switch v.Kind() { - case reflect.Array: - // encoding/json treats all arrays with non-zero length as non-empty. We check the - // array content here because zero-length arrays are almost never used. - len := v.Len() - for i := 0; i < len; i++ { - if !isEmptyValue(v.Index(i)) { - return false - } - } - return true - case reflect.Map, reflect.Slice, reflect.String: - return v.Len() == 0 - case reflect.Bool: - return !v.Bool() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return v.Int() == 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return v.Uint() == 0 - case reflect.Float32, reflect.Float64: - return v.Float() == 0 - case reflect.Interface, reflect.Ptr: - return v.IsNil() - } - return false -} - -func quoteName(s string) string { - if len(s) == 0 { - return strconv.Quote(s) - } - for _, r := range s { - if r >= '0' && r <= '9' || r >= 'A' && r <= 'Z' || r >= 'a' && r <= 'z' || r == '-' || r == '_' { - continue - } - return strconv.Quote(s) - } - return s -} diff --git a/vendor/github.com/naoina/toml/error.go b/vendor/github.com/naoina/toml/error.go deleted file mode 100644 index cb73b5e0aa4..00000000000 --- a/vendor/github.com/naoina/toml/error.go +++ /dev/null @@ -1,107 +0,0 @@ -package toml - -import ( - "errors" - "fmt" - "reflect" - "strconv" -) - -var ( - errArrayMultiType = errors.New("array can't contain multiple types") -) - -// LineError is returned by Unmarshal, UnmarshalTable and Parse -// if the error is local to a line. -type LineError struct { - Line int - StructField string - Err error -} - -func (err *LineError) Error() string { - field := "" - if err.StructField != "" { - field = "(" + err.StructField + ") " - } - return fmt.Sprintf("line %d: %s%v", err.Line, field, err.Err) -} - -func lineError(line int, err error) error { - if err == nil { - return nil - } - if _, ok := err.(*LineError); ok { - return err - } - return &LineError{Line: line, Err: err} -} - -func lineErrorField(line int, field string, err error) error { - if lerr, ok := err.(*LineError); ok { - return lerr - } else if err != nil { - err = &LineError{Line: line, StructField: field, Err: err} - } - return err -} - -type overflowError struct { - kind reflect.Kind - v string -} - -func (err *overflowError) Error() string { - return fmt.Sprintf("value %s is out of range for %v", err.v, err.kind) -} - -func convertNumError(kind reflect.Kind, err error) error { - if numerr, ok := err.(*strconv.NumError); ok && numerr.Err == strconv.ErrRange { - return &overflowError{kind, numerr.Num} - } - return err -} - -type invalidUnmarshalError struct { - typ reflect.Type -} - -func (err *invalidUnmarshalError) Error() string { - if err.typ == nil { - return "toml: Unmarshal(nil)" - } - if err.typ.Kind() != reflect.Ptr { - return "toml: Unmarshal(non-pointer " + err.typ.String() + ")" - } - return "toml: Unmarshal(nil " + err.typ.String() + ")" -} - -type unmarshalTypeError struct { - what string - want string - typ reflect.Type -} - -func (err *unmarshalTypeError) Error() string { - msg := fmt.Sprintf("cannot unmarshal TOML %s into %s", err.what, err.typ) - if err.want != "" { - msg += " (need " + err.want + ")" - } - return msg -} - -type marshalNilError struct { - typ reflect.Type -} - -func (err *marshalNilError) Error() string { - return fmt.Sprintf("toml: cannot marshal nil %s", err.typ) -} - -type marshalTableError struct { - typ reflect.Type -} - -func (err *marshalTableError) Error() string { - return fmt.Sprintf("toml: cannot marshal %s as table, want struct or map type", err.typ) -} diff --git a/vendor/github.com/naoina/toml/parse.go b/vendor/github.com/naoina/toml/parse.go deleted file mode 100644 index de9108566b8..00000000000 --- a/vendor/github.com/naoina/toml/parse.go +++ /dev/null @@ -1,362 +0,0 @@ -package toml - -import ( - "errors" - "fmt" - "strconv" - "strings" - - "github.com/naoina/toml/ast" -) - -// The parser is generated by github.com/pointlander/peg. To regenerate it, do: -// -// go get -u github.com/pointlander/peg -// go generate . - -//go:generate peg -switch -inline parse.peg - -var errParse = errors.New("invalid TOML syntax") - -// Parse returns an AST representation of TOML. -// The toplevel is represented by a table. -func Parse(data []byte) (*ast.Table, error) { - d := &parseState{p: &tomlParser{Buffer: string(data)}} - d.init() - - if err := d.parse(); err != nil { - return nil, err - } - - return d.p.toml.table, nil -} - -type parseState struct { - p *tomlParser -} - -func (d *parseState) init() { - d.p.Init() - d.p.toml.init(d.p.buffer) -} - -func (d *parseState) parse() error { - if err := d.p.Parse(); err != nil { - if err, ok := err.(*parseError); ok { - return lineError(err.Line(), errParse) - } - return err - } - return d.execute() -} - -func (d *parseState) execute() (err error) { - defer func() { - if e := recover(); e != nil { - lerr, ok := e.(*LineError) - if !ok { - panic(e) - } - err = lerr - } - }() - d.p.Execute() - return nil -} - -func (e *parseError) Line() int { - tokens := []token32{e.max} - positions, p := make([]int, 2*len(tokens)), 0 - for _, token := range tokens { - positions[p], p = int(token.begin), p+1 - positions[p], p = int(token.end), p+1 - } - for _, t := range translatePositions(e.p.buffer, positions) { - if e.p.line < t.line { - e.p.line = t.line - } - } - return e.p.line -} - -type stack struct { - key string - table *ast.Table -} - -type array struct { - parent *array - child *array - current *ast.Array - line int -} - -type toml struct { - table *ast.Table - line int - currentTable *ast.Table - s string - key string - tableKeys []string - val ast.Value - arr *array - stack []*stack - skip bool -} - -func (p *toml) init(data []rune) { - p.line = 1 - p.table = p.newTable(ast.TableTypeNormal, "") - p.table.Position.End = len(data) - 1 - p.table.Data = data[:len(data)-1] // truncate the end_symbol added by PEG parse generator. - p.currentTable = p.table -} - -func (p *toml) Error(err error) { - panic(lineError(p.line, err)) -} - -func (p *tomlParser) SetTime(begin, end int) { - p.val = &ast.Datetime{ - Position: ast.Position{Begin: begin, End: end}, - Data: p.buffer[begin:end], - Value: string(p.buffer[begin:end]), - } -} - -func (p *tomlParser) SetFloat64(begin, end int) { - p.val = &ast.Float{ - Position: ast.Position{Begin: begin, End: end}, - Data: p.buffer[begin:end], - Value: underscoreReplacer.Replace(string(p.buffer[begin:end])), - } -} - -func (p *tomlParser) SetInt64(begin, end int) { - p.val = &ast.Integer{ - Position: ast.Position{Begin: begin, End: end}, - Data: p.buffer[begin:end], - Value: underscoreReplacer.Replace(string(p.buffer[begin:end])), - } -} - -func (p *tomlParser) SetString(begin, end int) { - p.val = &ast.String{ - Position: ast.Position{Begin: begin, End: end}, - Data: p.buffer[begin:end], - Value: p.s, - } - p.s = "" -} - -func (p *tomlParser) SetBool(begin, end int) { - p.val = &ast.Boolean{ - Position: ast.Position{Begin: begin, End: end}, - Data: p.buffer[begin:end], - Value: string(p.buffer[begin:end]), - } -} - -func (p *tomlParser) StartArray() { - if p.arr == nil { - p.arr = &array{line: p.line, current: &ast.Array{}} - return - } - p.arr.child = &array{parent: p.arr, line: p.line, current: &ast.Array{}} - p.arr = p.arr.child -} - -func (p *tomlParser) AddArrayVal() { - if p.arr.current == nil { - p.arr.current = &ast.Array{} - } - p.arr.current.Value = append(p.arr.current.Value, p.val) -} - -func (p *tomlParser) SetArray(begin, end int) { - p.arr.current.Position = ast.Position{Begin: begin, End: end} - p.arr.current.Data = p.buffer[begin:end] - p.val = p.arr.current - p.arr = p.arr.parent -} - -func (p *toml) SetTable(buf []rune, begin, end int) { - rawName := string(buf[begin:end]) - p.setTable(p.table, rawName, p.tableKeys) - p.tableKeys = nil -} - -func (p *toml) setTable(parent *ast.Table, name string, names []string) { - parent, err := p.lookupTable(parent, names[:len(names)-1]) - if err != nil { - p.Error(err) - } - last := names[len(names)-1] - tbl := p.newTable(ast.TableTypeNormal, last) - switch v := parent.Fields[last].(type) { - case nil: - parent.Fields[last] = tbl - case []*ast.Table: - p.Error(fmt.Errorf("table `%s' is in conflict with array table in line %d", name, v[0].Line)) - case *ast.Table: - if (v.Position == ast.Position{}) { - // This table was created as an implicit parent. - // Replace it with the real defined table. - tbl.Fields = v.Fields - parent.Fields[last] = tbl - } else { - p.Error(fmt.Errorf("table `%s' is in conflict with table in line %d", name, v.Line)) - } - case *ast.KeyValue: - p.Error(fmt.Errorf("table `%s' is in conflict with line %d", name, v.Line)) - default: - p.Error(fmt.Errorf("BUG: table `%s' is in conflict but it's unknown type `%T'", last, v)) - } - p.currentTable = tbl -} - -func (p *toml) newTable(typ ast.TableType, name string) *ast.Table { - return &ast.Table{ - Line: p.line, - Name: name, - Type: typ, - Fields: make(map[string]interface{}), - } -} - -func (p *tomlParser) SetTableString(begin, end int) { - p.currentTable.Data = p.buffer[begin:end] - p.currentTable.Position.Begin = begin - p.currentTable.Position.End = end -} - -func (p *toml) SetArrayTable(buf []rune, begin, end int) { - rawName := string(buf[begin:end]) - p.setArrayTable(p.table, rawName, p.tableKeys) - p.tableKeys = nil -} - -func (p *toml) setArrayTable(parent *ast.Table, name string, names []string) { - parent, err := p.lookupTable(parent, names[:len(names)-1]) - if err != nil { - p.Error(err) - } - last := names[len(names)-1] - tbl := p.newTable(ast.TableTypeArray, last) - switch v := parent.Fields[last].(type) { - case nil: - parent.Fields[last] = []*ast.Table{tbl} - case []*ast.Table: - parent.Fields[last] = append(v, tbl) - case *ast.Table: - p.Error(fmt.Errorf("array table `%s' is in conflict with table in line %d", name, v.Line)) - case *ast.KeyValue: - p.Error(fmt.Errorf("array table `%s' is in conflict with line %d", name, v.Line)) - default: - p.Error(fmt.Errorf("BUG: array table `%s' is in conflict but it's unknown type `%T'", name, v)) - } - p.currentTable = tbl -} - -func (p *toml) StartInlineTable() { - p.skip = false - p.stack = append(p.stack, &stack{p.key, p.currentTable}) - names := []string{p.key} - if p.arr == nil { - p.setTable(p.currentTable, names[0], names) - } else { - p.setArrayTable(p.currentTable, names[0], names) - } -} - -func (p *toml) EndInlineTable() { - st := p.stack[len(p.stack)-1] - p.key, p.currentTable = st.key, st.table - p.stack[len(p.stack)-1] = nil - p.stack = p.stack[:len(p.stack)-1] - p.skip = true -} - -func (p *toml) AddLineCount(i int) { - p.line += i -} - -func (p *toml) SetKey(buf []rune, begin, end int) { - p.key = string(buf[begin:end]) - if len(p.key) > 0 && p.key[0] == '"' { - p.key = p.unquote(p.key) - } -} - -func (p *toml) AddTableKey() { - p.tableKeys = append(p.tableKeys, p.key) -} - -func (p *toml) AddKeyValue() { - if p.skip { - p.skip = false - return - } - if val, exists := p.currentTable.Fields[p.key]; exists { - switch v := val.(type) { - case *ast.Table: - p.Error(fmt.Errorf("key `%s' is in conflict with table in line %d", p.key, v.Line)) - case *ast.KeyValue: - p.Error(fmt.Errorf("key `%s' is in conflict with line %xd", p.key, v.Line)) - default: - p.Error(fmt.Errorf("BUG: key `%s' is in conflict but it's unknown type `%T'", p.key, v)) - } - } - p.currentTable.Fields[p.key] = &ast.KeyValue{Key: p.key, Value: p.val, Line: p.line} -} - -func (p *toml) SetBasicString(buf []rune, begin, end int) { - p.s = p.unquote(string(buf[begin:end])) -} - -func (p *toml) SetMultilineString() { - p.s = p.unquote(`"` + escapeReplacer.Replace(strings.TrimLeft(p.s, "\r\n")) + `"`) -} - -func (p *toml) AddMultilineBasicBody(buf []rune, begin, end int) { - p.s += string(buf[begin:end]) -} - -func (p *toml) SetLiteralString(buf []rune, begin, end int) { - p.s = string(buf[begin:end]) -} - -func (p *toml) SetMultilineLiteralString(buf []rune, begin, end int) { - p.s = strings.TrimLeft(string(buf[begin:end]), "\r\n") -} - -func (p *toml) unquote(s string) string { - s, err := strconv.Unquote(s) - if err != nil { - p.Error(err) - } - return s -} - -func (p *toml) lookupTable(t *ast.Table, keys []string) (*ast.Table, error) { - for _, s := range keys { - val, exists := t.Fields[s] - if !exists { - tbl := p.newTable(ast.TableTypeNormal, s) - t.Fields[s] = tbl - t = tbl - continue - } - switch v := val.(type) { - case *ast.Table: - t = v - case []*ast.Table: - t = v[len(v)-1] - case *ast.KeyValue: - return nil, fmt.Errorf("key `%s' is in conflict with line %d", s, v.Line) - default: - return nil, fmt.Errorf("BUG: key `%s' is in conflict but it's unknown type `%T'", s, v) - } - } - return t, nil -} diff --git a/vendor/github.com/naoina/toml/parse.peg.go b/vendor/github.com/naoina/toml/parse.peg.go deleted file mode 100644 index d768b00a8e9..00000000000 --- a/vendor/github.com/naoina/toml/parse.peg.go +++ /dev/null @@ -1,2579 +0,0 @@ -package toml - -import ( - "fmt" - "math" - "sort" - "strconv" -) - -const endSymbol rune = 1114112 - -/* The rule types inferred from the grammar are below. */ -type pegRule uint8 - -const ( - ruleUnknown pegRule = iota - ruleTOML - ruleExpression - rulenewline - rulews - rulewsnl - rulecomment - rulekeyval - rulekey - rulebareKey - rulequotedKey - ruleval - ruletable - rulestdTable - rulearrayTable - ruleinlineTable - ruleinlineTableKeyValues - ruletableKey - ruletableKeyComp - ruletableKeySep - ruleinlineTableValSep - ruleinteger - ruleint - rulefloat - rulefrac - ruleexp - rulestring - rulebasicString - rulebasicChar - ruleescaped - rulebasicUnescaped - ruleescape - rulemlBasicString - rulemlBasicBody - ruleliteralString - ruleliteralChar - rulemlLiteralString - rulemlLiteralBody - rulemlLiteralChar - rulehexdigit - rulehexQuad - ruleboolean - ruledateFullYear - ruledateMonth - ruledateMDay - ruletimeHour - ruletimeMinute - ruletimeSecond - ruletimeSecfrac - ruletimeNumoffset - ruletimeOffset - rulepartialTime - rulefullDate - rulefullTime - ruledatetime - ruledigit - ruledigitDual - ruledigitQuad - rulearray - rulearrayValues - rulearraySep - ruleAction0 - rulePegText - ruleAction1 - ruleAction2 - ruleAction3 - ruleAction4 - ruleAction5 - ruleAction6 - ruleAction7 - ruleAction8 - ruleAction9 - ruleAction10 - ruleAction11 - ruleAction12 - ruleAction13 - ruleAction14 - ruleAction15 - ruleAction16 - ruleAction17 - ruleAction18 - ruleAction19 - ruleAction20 - ruleAction21 - ruleAction22 - ruleAction23 - ruleAction24 - ruleAction25 -) - -var rul3s = [...]string{ - "Unknown", - "TOML", - "Expression", - "newline", - "ws", - "wsnl", - "comment", - "keyval", - "key", - "bareKey", - "quotedKey", - "val", - "table", - "stdTable", - "arrayTable", - "inlineTable", - "inlineTableKeyValues", - "tableKey", - "tableKeyComp", - "tableKeySep", - "inlineTableValSep", - "integer", - "int", - "float", - "frac", - "exp", - "string", - "basicString", - "basicChar", - "escaped", - "basicUnescaped", - "escape", - "mlBasicString", - "mlBasicBody", - "literalString", - "literalChar", - "mlLiteralString", - "mlLiteralBody", - "mlLiteralChar", - "hexdigit", - "hexQuad", - "boolean", - "dateFullYear", - "dateMonth", - "dateMDay", - "timeHour", - "timeMinute", - "timeSecond", - "timeSecfrac", - "timeNumoffset", - "timeOffset", - "partialTime", - "fullDate", - "fullTime", - "datetime", - "digit", - "digitDual", - "digitQuad", - "array", - "arrayValues", - "arraySep", - "Action0", - "PegText", - "Action1", - "Action2", - "Action3", - "Action4", - "Action5", - "Action6", - "Action7", - "Action8", - "Action9", - "Action10", - "Action11", - "Action12", - "Action13", - "Action14", - "Action15", - "Action16", - "Action17", - "Action18", - "Action19", - "Action20", - "Action21", - "Action22", - "Action23", - "Action24", - "Action25", -} - -type token32 struct { - pegRule - begin, end uint32 -} - -func (t *token32) String() string { - return fmt.Sprintf("\x1B[34m%v\x1B[m %v %v", rul3s[t.pegRule], t.begin, t.end) -} - -type node32 struct { - token32 - up, next *node32 -} - -func (node *node32) print(pretty bool, buffer string) { - var print func(node *node32, depth int) - print = func(node *node32, depth int) { - for node != nil { - for c := 0; c < depth; c++ { - fmt.Printf(" ") - } - rule := rul3s[node.pegRule] - quote := strconv.Quote(string(([]rune(buffer)[node.begin:node.end]))) - if !pretty { - fmt.Printf("%v %v\n", rule, quote) - } else { - fmt.Printf("\x1B[34m%v\x1B[m %v\n", rule, quote) - } - if node.up != nil { - print(node.up, depth+1) - } - node = node.next - } - } - print(node, 0) -} - -func (node *node32) Print(buffer string) { - node.print(false, buffer) -} - -func (node *node32) PrettyPrint(buffer string) { - node.print(true, buffer) -} - -type tokens32 struct { - tree []token32 -} - -func (t *tokens32) Trim(length uint32) { - t.tree = t.tree[:length] -} - -func (t *tokens32) Print() { - for _, token := range t.tree { - fmt.Println(token.String()) - } -} - -func (t *tokens32) AST() *node32 { - type element struct { - node *node32 - down *element - } - tokens := t.Tokens() - var stack *element - for _, token := range tokens { - if token.begin == token.end { - continue - } - node := &node32{token32: token} - for stack != nil && stack.node.begin >= token.begin && stack.node.end <= token.end { - stack.node.next = node.up - node.up = stack.node - stack = stack.down - } - stack = &element{node: node, down: stack} - } - if stack != nil { - return stack.node - } - return nil -} - -func (t *tokens32) PrintSyntaxTree(buffer string) { - t.AST().Print(buffer) -} - -func (t *tokens32) PrettyPrintSyntaxTree(buffer string) { - t.AST().PrettyPrint(buffer) -} - -func (t *tokens32) Add(rule pegRule, begin, end, index uint32) { - if tree := t.tree; int(index) >= len(tree) { - expanded := make([]token32, 2*len(tree)) - copy(expanded, tree) - t.tree = expanded - } - t.tree[index] = token32{ - pegRule: rule, - begin: begin, - end: end, - } -} - -func (t *tokens32) Tokens() []token32 { - return t.tree -} - -type tomlParser struct { - toml - - Buffer string - buffer []rune - rules [88]func() bool - parse func(rule ...int) error - reset func() - Pretty bool - tokens32 -} - -func (p *tomlParser) Parse(rule ...int) error { - return p.parse(rule...) -} - -func (p *tomlParser) Reset() { - p.reset() -} - -type textPosition struct { - line, symbol int -} - -type textPositionMap map[int]textPosition - -func translatePositions(buffer []rune, positions []int) textPositionMap { - length, translations, j, line, symbol := len(positions), make(textPositionMap, len(positions)), 0, 1, 0 - sort.Ints(positions) - -search: - for i, c := range buffer { - if c == '\n' { - line, symbol = line+1, 0 - } else { - symbol++ - } - if i == positions[j] { - translations[positions[j]] = textPosition{line, symbol} - for j++; j < length; j++ { - if i != positions[j] { - continue search - } - } - break search - } - } - - return translations -} - -type parseError struct { - p *tomlParser - max token32 -} - -func (e *parseError) Error() string { - tokens, error := []token32{e.max}, "\n" - positions, p := make([]int, 2*len(tokens)), 0 - for _, token := range tokens { - positions[p], p = int(token.begin), p+1 - positions[p], p = int(token.end), p+1 - } - translations := translatePositions(e.p.buffer, positions) - format := "parse error near %v (line %v symbol %v - line %v symbol %v):\n%v\n" - if e.p.Pretty { - format = "parse error near \x1B[34m%v\x1B[m (line %v symbol %v - line %v symbol %v):\n%v\n" - } - for _, token := range tokens { - begin, end := int(token.begin), int(token.end) - error += fmt.Sprintf(format, - rul3s[token.pegRule], - translations[begin].line, translations[begin].symbol, - translations[end].line, translations[end].symbol, - strconv.Quote(string(e.p.buffer[begin:end]))) - } - - return error -} - -func (p *tomlParser) PrintSyntaxTree() { - if p.Pretty { - p.tokens32.PrettyPrintSyntaxTree(p.Buffer) - } else { - p.tokens32.PrintSyntaxTree(p.Buffer) - } -} - -func (p *tomlParser) Execute() { - buffer, _buffer, text, begin, end := p.Buffer, p.buffer, "", 0, 0 - for _, token := range p.Tokens() { - switch token.pegRule { - - case rulePegText: - begin, end = int(token.begin), int(token.end) - text = string(_buffer[begin:end]) - - case ruleAction0: - _ = buffer - case ruleAction1: - p.SetTableString(begin, end) - case ruleAction2: - p.AddLineCount(end - begin) - case ruleAction3: - p.AddLineCount(end - begin) - case ruleAction4: - p.AddKeyValue() - case ruleAction5: - p.SetKey(p.buffer, begin, end) - case ruleAction6: - p.SetKey(p.buffer, begin, end) - case ruleAction7: - p.SetTime(begin, end) - case ruleAction8: - p.SetFloat64(begin, end) - case ruleAction9: - p.SetInt64(begin, end) - case ruleAction10: - p.SetString(begin, end) - case ruleAction11: - p.SetBool(begin, end) - case ruleAction12: - p.SetArray(begin, end) - case ruleAction13: - p.SetTable(p.buffer, begin, end) - case ruleAction14: - p.SetArrayTable(p.buffer, begin, end) - case ruleAction15: - p.StartInlineTable() - case ruleAction16: - p.EndInlineTable() - case ruleAction17: - p.AddTableKey() - case ruleAction18: - p.SetBasicString(p.buffer, begin, end) - case ruleAction19: - p.SetMultilineString() - case ruleAction20: - p.AddMultilineBasicBody(p.buffer, begin, end) - case ruleAction21: - p.SetLiteralString(p.buffer, begin, end) - case ruleAction22: - p.SetMultilineLiteralString(p.buffer, begin, end) - case ruleAction23: - p.StartArray() - case ruleAction24: - p.AddArrayVal() - case ruleAction25: - p.AddArrayVal() - - } - } - _, _, _, _, _ = buffer, _buffer, text, begin, end -} - -func (p *tomlParser) Init() { - var ( - max token32 - position, tokenIndex uint32 - buffer []rune - ) - p.reset = func() { - max = token32{} - position, tokenIndex = 0, 0 - - p.buffer = []rune(p.Buffer) - if len(p.buffer) == 0 || p.buffer[len(p.buffer)-1] != endSymbol { - p.buffer = append(p.buffer, endSymbol) - } - buffer = p.buffer - } - p.reset() - - _rules := p.rules - tree := tokens32{tree: make([]token32, math.MaxInt16)} - p.parse = func(rule ...int) error { - r := 1 - if len(rule) > 0 { - r = rule[0] - } - matches := p.rules[r]() - p.tokens32 = tree - if matches { - p.Trim(tokenIndex) - return nil - } - return &parseError{p, max} - } - - add := func(rule pegRule, begin uint32) { - tree.Add(rule, begin, position, tokenIndex) - tokenIndex++ - if begin != position && position > max.end { - max = token32{rule, begin, position} - } - } - - matchDot := func() bool { - if buffer[position] != endSymbol { - position++ - return true - } - return false - } - - /*matchChar := func(c byte) bool { - if buffer[position] == c { - position++ - return true - } - return false - }*/ - - /*matchRange := func(lower byte, upper byte) bool { - if c := buffer[position]; c >= lower && c <= upper { - position++ - return true - } - return false - }*/ - - _rules = [...]func() bool{ - nil, - /* 0 TOML <- <(Expression (newline Expression)* newline? !. Action0)> */ - func() bool { - position0, tokenIndex0 := position, tokenIndex - { - position1 := position - if !_rules[ruleExpression]() { - goto l0 - } - l2: - { - position3, tokenIndex3 := position, tokenIndex - if !_rules[rulenewline]() { - goto l3 - } - if !_rules[ruleExpression]() { - goto l3 - } - goto l2 - l3: - position, tokenIndex = position3, tokenIndex3 - } - { - position4, tokenIndex4 := position, tokenIndex - if !_rules[rulenewline]() { - goto l4 - } - goto l5 - l4: - position, tokenIndex = position4, tokenIndex4 - } - l5: - { - position6, tokenIndex6 := position, tokenIndex - if !matchDot() { - goto l6 - } - goto l0 - l6: - position, tokenIndex = position6, tokenIndex6 - } - { - add(ruleAction0, position) - } - add(ruleTOML, position1) - } - return true - l0: - position, tokenIndex = position0, tokenIndex0 - return false - }, - /* 1 Expression <- <((<(ws table ws comment? (wsnl keyval ws comment?)*)> Action1) / (ws keyval ws comment?) / (ws comment?) / ws)> */ - func() bool { - position8, tokenIndex8 := position, tokenIndex - { - position9 := position - { - position10, tokenIndex10 := position, tokenIndex - { - position12 := position - if !_rules[rulews]() { - goto l11 - } - { - position13 := position - { - position14, tokenIndex14 := position, tokenIndex - { - position16 := position - if buffer[position] != rune('[') { - goto l15 - } - position++ - if !_rules[rulews]() { - goto l15 - } - { - position17 := position - if !_rules[ruletableKey]() { - goto l15 - } - add(rulePegText, position17) - } - if !_rules[rulews]() { - goto l15 - } - if buffer[position] != rune(']') { - goto l15 - } - position++ - { - add(ruleAction13, position) - } - add(rulestdTable, position16) - } - goto l14 - l15: - position, tokenIndex = position14, tokenIndex14 - { - position19 := position - if buffer[position] != rune('[') { - goto l11 - } - position++ - if buffer[position] != rune('[') { - goto l11 - } - position++ - if !_rules[rulews]() { - goto l11 - } - { - position20 := position - if !_rules[ruletableKey]() { - goto l11 - } - add(rulePegText, position20) - } - if !_rules[rulews]() { - goto l11 - } - if buffer[position] != rune(']') { - goto l11 - } - position++ - if buffer[position] != rune(']') { - goto l11 - } - position++ - { - add(ruleAction14, position) - } - add(rulearrayTable, position19) - } - } - l14: - add(ruletable, position13) - } - if !_rules[rulews]() { - goto l11 - } - { - position22, tokenIndex22 := position, tokenIndex - if !_rules[rulecomment]() { - goto l22 - } - goto l23 - l22: - position, tokenIndex = position22, tokenIndex22 - } - l23: - l24: - { - position25, tokenIndex25 := position, tokenIndex - if !_rules[rulewsnl]() { - goto l25 - } - if !_rules[rulekeyval]() { - goto l25 - } - if !_rules[rulews]() { - goto l25 - } - { - position26, tokenIndex26 := position, tokenIndex - if !_rules[rulecomment]() { - goto l26 - } - goto l27 - l26: - position, tokenIndex = position26, tokenIndex26 - } - l27: - goto l24 - l25: - position, tokenIndex = position25, tokenIndex25 - } - add(rulePegText, position12) - } - { - add(ruleAction1, position) - } - goto l10 - l11: - position, tokenIndex = position10, tokenIndex10 - if !_rules[rulews]() { - goto l29 - } - if !_rules[rulekeyval]() { - goto l29 - } - if !_rules[rulews]() { - goto l29 - } - { - position30, tokenIndex30 := position, tokenIndex - if !_rules[rulecomment]() { - goto l30 - } - goto l31 - l30: - position, tokenIndex = position30, tokenIndex30 - } - l31: - goto l10 - l29: - position, tokenIndex = position10, tokenIndex10 - if !_rules[rulews]() { - goto l32 - } - { - position33, tokenIndex33 := position, tokenIndex - if !_rules[rulecomment]() { - goto l33 - } - goto l34 - l33: - position, tokenIndex = position33, tokenIndex33 - } - l34: - goto l10 - l32: - position, tokenIndex = position10, tokenIndex10 - if !_rules[rulews]() { - goto l8 - } - } - l10: - add(ruleExpression, position9) - } - return true - l8: - position, tokenIndex = position8, tokenIndex8 - return false - }, - /* 2 newline <- <(<('\r' / '\n')+> Action2)> */ - func() bool { - position35, tokenIndex35 := position, tokenIndex - { - position36 := position - { - position37 := position - { - position40, tokenIndex40 := position, tokenIndex - if buffer[position] != rune('\r') { - goto l41 - } - position++ - goto l40 - l41: - position, tokenIndex = position40, tokenIndex40 - if buffer[position] != rune('\n') { - goto l35 - } - position++ - } - l40: - l38: - { - position39, tokenIndex39 := position, tokenIndex - { - position42, tokenIndex42 := position, tokenIndex - if buffer[position] != rune('\r') { - goto l43 - } - position++ - goto l42 - l43: - position, tokenIndex = position42, tokenIndex42 - if buffer[position] != rune('\n') { - goto l39 - } - position++ - } - l42: - goto l38 - l39: - position, tokenIndex = position39, tokenIndex39 - } - add(rulePegText, position37) - } - { - add(ruleAction2, position) - } - add(rulenewline, position36) - } - return true - l35: - position, tokenIndex = position35, tokenIndex35 - return false - }, - /* 3 ws <- <(' ' / '\t')*> */ - func() bool { - { - position46 := position - l47: - { - position48, tokenIndex48 := position, tokenIndex - { - position49, tokenIndex49 := position, tokenIndex - if buffer[position] != rune(' ') { - goto l50 - } - position++ - goto l49 - l50: - position, tokenIndex = position49, tokenIndex49 - if buffer[position] != rune('\t') { - goto l48 - } - position++ - } - l49: - goto l47 - l48: - position, tokenIndex = position48, tokenIndex48 - } - add(rulews, position46) - } - return true - }, - /* 4 wsnl <- <((&('\t') '\t') | (&(' ') ' ') | (&('\n' | '\r') (<('\r' / '\n')> Action3)))*> */ - func() bool { - { - position52 := position - l53: - { - position54, tokenIndex54 := position, tokenIndex - { - switch buffer[position] { - case '\t': - if buffer[position] != rune('\t') { - goto l54 - } - position++ - break - case ' ': - if buffer[position] != rune(' ') { - goto l54 - } - position++ - break - default: - { - position56 := position - { - position57, tokenIndex57 := position, tokenIndex - if buffer[position] != rune('\r') { - goto l58 - } - position++ - goto l57 - l58: - position, tokenIndex = position57, tokenIndex57 - if buffer[position] != rune('\n') { - goto l54 - } - position++ - } - l57: - add(rulePegText, position56) - } - { - add(ruleAction3, position) - } - break - } - } - - goto l53 - l54: - position, tokenIndex = position54, tokenIndex54 - } - add(rulewsnl, position52) - } - return true - }, - /* 5 comment <- <('#' <('\t' / [ -\U0010ffff])*>)> */ - func() bool { - position60, tokenIndex60 := position, tokenIndex - { - position61 := position - if buffer[position] != rune('#') { - goto l60 - } - position++ - { - position62 := position - l63: - { - position64, tokenIndex64 := position, tokenIndex - { - position65, tokenIndex65 := position, tokenIndex - if buffer[position] != rune('\t') { - goto l66 - } - position++ - goto l65 - l66: - position, tokenIndex = position65, tokenIndex65 - if c := buffer[position]; c < rune(' ') || c > rune('\U0010ffff') { - goto l64 - } - position++ - } - l65: - goto l63 - l64: - position, tokenIndex = position64, tokenIndex64 - } - add(rulePegText, position62) - } - add(rulecomment, position61) - } - return true - l60: - position, tokenIndex = position60, tokenIndex60 - return false - }, - /* 6 keyval <- <(key ws '=' ws val Action4)> */ - func() bool { - position67, tokenIndex67 := position, tokenIndex - { - position68 := position - if !_rules[rulekey]() { - goto l67 - } - if !_rules[rulews]() { - goto l67 - } - if buffer[position] != rune('=') { - goto l67 - } - position++ - if !_rules[rulews]() { - goto l67 - } - if !_rules[ruleval]() { - goto l67 - } - { - add(ruleAction4, position) - } - add(rulekeyval, position68) - } - return true - l67: - position, tokenIndex = position67, tokenIndex67 - return false - }, - /* 7 key <- <(bareKey / quotedKey)> */ - func() bool { - position70, tokenIndex70 := position, tokenIndex - { - position71 := position - { - position72, tokenIndex72 := position, tokenIndex - { - position74 := position - { - position75 := position - { - switch buffer[position] { - case '_': - if buffer[position] != rune('_') { - goto l73 - } - position++ - break - case '-': - if buffer[position] != rune('-') { - goto l73 - } - position++ - break - case 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z': - if c := buffer[position]; c < rune('a') || c > rune('z') { - goto l73 - } - position++ - break - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l73 - } - position++ - break - default: - if c := buffer[position]; c < rune('A') || c > rune('Z') { - goto l73 - } - position++ - break - } - } - - l76: - { - position77, tokenIndex77 := position, tokenIndex - { - switch buffer[position] { - case '_': - if buffer[position] != rune('_') { - goto l77 - } - position++ - break - case '-': - if buffer[position] != rune('-') { - goto l77 - } - position++ - break - case 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z': - if c := buffer[position]; c < rune('a') || c > rune('z') { - goto l77 - } - position++ - break - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l77 - } - position++ - break - default: - if c := buffer[position]; c < rune('A') || c > rune('Z') { - goto l77 - } - position++ - break - } - } - - goto l76 - l77: - position, tokenIndex = position77, tokenIndex77 - } - add(rulePegText, position75) - } - { - add(ruleAction5, position) - } - add(rulebareKey, position74) - } - goto l72 - l73: - position, tokenIndex = position72, tokenIndex72 - { - position81 := position - { - position82 := position - if buffer[position] != rune('"') { - goto l70 - } - position++ - l83: - { - position84, tokenIndex84 := position, tokenIndex - if !_rules[rulebasicChar]() { - goto l84 - } - goto l83 - l84: - position, tokenIndex = position84, tokenIndex84 - } - if buffer[position] != rune('"') { - goto l70 - } - position++ - add(rulePegText, position82) - } - { - add(ruleAction6, position) - } - add(rulequotedKey, position81) - } - } - l72: - add(rulekey, position71) - } - return true - l70: - position, tokenIndex = position70, tokenIndex70 - return false - }, - /* 8 bareKey <- <(<((&('_') '_') | (&('-') '-') | (&('a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z') [a-z]) | (&('0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9') [0-9]) | (&('A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z') [A-Z]))+> Action5)> */ - nil, - /* 9 quotedKey <- <(<('"' basicChar* '"')> Action6)> */ - nil, - /* 10 val <- <(( Action7) / ( Action8) / ((&('{') inlineTable) | (&('[') ( Action12)) | (&('f' | 't') ( Action11)) | (&('"' | '\'') ( Action10)) | (&('+' | '-' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9') ( Action9))))> */ - func() bool { - position88, tokenIndex88 := position, tokenIndex - { - position89 := position - { - position90, tokenIndex90 := position, tokenIndex - { - position92 := position - { - position93 := position - { - position94, tokenIndex94 := position, tokenIndex - { - position96 := position - { - position97 := position - { - position98 := position - if !_rules[ruledigitDual]() { - goto l95 - } - if !_rules[ruledigitDual]() { - goto l95 - } - add(ruledigitQuad, position98) - } - add(ruledateFullYear, position97) - } - if buffer[position] != rune('-') { - goto l95 - } - position++ - { - position99 := position - if !_rules[ruledigitDual]() { - goto l95 - } - add(ruledateMonth, position99) - } - if buffer[position] != rune('-') { - goto l95 - } - position++ - { - position100 := position - if !_rules[ruledigitDual]() { - goto l95 - } - add(ruledateMDay, position100) - } - add(rulefullDate, position96) - } - { - position101, tokenIndex101 := position, tokenIndex - if buffer[position] != rune('T') { - goto l101 - } - position++ - { - position103 := position - if !_rules[rulepartialTime]() { - goto l101 - } - { - position104 := position - { - position105, tokenIndex105 := position, tokenIndex - if buffer[position] != rune('Z') { - goto l106 - } - position++ - goto l105 - l106: - position, tokenIndex = position105, tokenIndex105 - { - position107 := position - { - position108, tokenIndex108 := position, tokenIndex - if buffer[position] != rune('-') { - goto l109 - } - position++ - goto l108 - l109: - position, tokenIndex = position108, tokenIndex108 - if buffer[position] != rune('+') { - goto l101 - } - position++ - } - l108: - if !_rules[ruletimeHour]() { - goto l101 - } - if buffer[position] != rune(':') { - goto l101 - } - position++ - if !_rules[ruletimeMinute]() { - goto l101 - } - add(ruletimeNumoffset, position107) - } - } - l105: - add(ruletimeOffset, position104) - } - add(rulefullTime, position103) - } - goto l102 - l101: - position, tokenIndex = position101, tokenIndex101 - } - l102: - goto l94 - l95: - position, tokenIndex = position94, tokenIndex94 - if !_rules[rulepartialTime]() { - goto l91 - } - } - l94: - add(ruledatetime, position93) - } - add(rulePegText, position92) - } - { - add(ruleAction7, position) - } - goto l90 - l91: - position, tokenIndex = position90, tokenIndex90 - { - position112 := position - { - position113 := position - if !_rules[ruleinteger]() { - goto l111 - } - { - position114, tokenIndex114 := position, tokenIndex - if !_rules[rulefrac]() { - goto l115 - } - { - position116, tokenIndex116 := position, tokenIndex - if !_rules[ruleexp]() { - goto l116 - } - goto l117 - l116: - position, tokenIndex = position116, tokenIndex116 - } - l117: - goto l114 - l115: - position, tokenIndex = position114, tokenIndex114 - { - position118, tokenIndex118 := position, tokenIndex - if !_rules[rulefrac]() { - goto l118 - } - goto l119 - l118: - position, tokenIndex = position118, tokenIndex118 - } - l119: - if !_rules[ruleexp]() { - goto l111 - } - } - l114: - add(rulefloat, position113) - } - add(rulePegText, position112) - } - { - add(ruleAction8, position) - } - goto l90 - l111: - position, tokenIndex = position90, tokenIndex90 - { - switch buffer[position] { - case '{': - { - position122 := position - if buffer[position] != rune('{') { - goto l88 - } - position++ - { - add(ruleAction15, position) - } - if !_rules[rulews]() { - goto l88 - } - { - position124 := position - l125: - { - position126, tokenIndex126 := position, tokenIndex - if !_rules[rulekeyval]() { - goto l126 - } - { - position127, tokenIndex127 := position, tokenIndex - { - position129 := position - if !_rules[rulews]() { - goto l127 - } - if buffer[position] != rune(',') { - goto l127 - } - position++ - if !_rules[rulews]() { - goto l127 - } - add(ruleinlineTableValSep, position129) - } - goto l128 - l127: - position, tokenIndex = position127, tokenIndex127 - } - l128: - goto l125 - l126: - position, tokenIndex = position126, tokenIndex126 - } - add(ruleinlineTableKeyValues, position124) - } - if !_rules[rulews]() { - goto l88 - } - if buffer[position] != rune('}') { - goto l88 - } - position++ - { - add(ruleAction16, position) - } - add(ruleinlineTable, position122) - } - break - case '[': - { - position131 := position - { - position132 := position - if buffer[position] != rune('[') { - goto l88 - } - position++ - { - add(ruleAction23, position) - } - if !_rules[rulewsnl]() { - goto l88 - } - { - position134, tokenIndex134 := position, tokenIndex - { - position136 := position - if !_rules[ruleval]() { - goto l134 - } - { - add(ruleAction24, position) - } - l138: - { - position139, tokenIndex139 := position, tokenIndex - if !_rules[rulewsnl]() { - goto l139 - } - { - position140, tokenIndex140 := position, tokenIndex - if !_rules[rulecomment]() { - goto l140 - } - goto l141 - l140: - position, tokenIndex = position140, tokenIndex140 - } - l141: - if !_rules[rulewsnl]() { - goto l139 - } - if !_rules[rulearraySep]() { - goto l139 - } - if !_rules[rulewsnl]() { - goto l139 - } - { - position142, tokenIndex142 := position, tokenIndex - if !_rules[rulecomment]() { - goto l142 - } - goto l143 - l142: - position, tokenIndex = position142, tokenIndex142 - } - l143: - if !_rules[rulewsnl]() { - goto l139 - } - if !_rules[ruleval]() { - goto l139 - } - { - add(ruleAction25, position) - } - goto l138 - l139: - position, tokenIndex = position139, tokenIndex139 - } - if !_rules[rulewsnl]() { - goto l134 - } - { - position145, tokenIndex145 := position, tokenIndex - if !_rules[rulearraySep]() { - goto l145 - } - goto l146 - l145: - position, tokenIndex = position145, tokenIndex145 - } - l146: - if !_rules[rulewsnl]() { - goto l134 - } - { - position147, tokenIndex147 := position, tokenIndex - if !_rules[rulecomment]() { - goto l147 - } - goto l148 - l147: - position, tokenIndex = position147, tokenIndex147 - } - l148: - add(rulearrayValues, position136) - } - goto l135 - l134: - position, tokenIndex = position134, tokenIndex134 - } - l135: - if !_rules[rulewsnl]() { - goto l88 - } - if buffer[position] != rune(']') { - goto l88 - } - position++ - add(rulearray, position132) - } - add(rulePegText, position131) - } - { - add(ruleAction12, position) - } - break - case 'f', 't': - { - position150 := position - { - position151 := position - { - position152, tokenIndex152 := position, tokenIndex - if buffer[position] != rune('t') { - goto l153 - } - position++ - if buffer[position] != rune('r') { - goto l153 - } - position++ - if buffer[position] != rune('u') { - goto l153 - } - position++ - if buffer[position] != rune('e') { - goto l153 - } - position++ - goto l152 - l153: - position, tokenIndex = position152, tokenIndex152 - if buffer[position] != rune('f') { - goto l88 - } - position++ - if buffer[position] != rune('a') { - goto l88 - } - position++ - if buffer[position] != rune('l') { - goto l88 - } - position++ - if buffer[position] != rune('s') { - goto l88 - } - position++ - if buffer[position] != rune('e') { - goto l88 - } - position++ - } - l152: - add(ruleboolean, position151) - } - add(rulePegText, position150) - } - { - add(ruleAction11, position) - } - break - case '"', '\'': - { - position155 := position - { - position156 := position - { - position157, tokenIndex157 := position, tokenIndex - { - position159 := position - if buffer[position] != rune('\'') { - goto l158 - } - position++ - if buffer[position] != rune('\'') { - goto l158 - } - position++ - if buffer[position] != rune('\'') { - goto l158 - } - position++ - { - position160 := position - { - position161 := position - l162: - { - position163, tokenIndex163 := position, tokenIndex - { - position164, tokenIndex164 := position, tokenIndex - if buffer[position] != rune('\'') { - goto l164 - } - position++ - if buffer[position] != rune('\'') { - goto l164 - } - position++ - if buffer[position] != rune('\'') { - goto l164 - } - position++ - goto l163 - l164: - position, tokenIndex = position164, tokenIndex164 - } - { - position165, tokenIndex165 := position, tokenIndex - { - position167 := position - { - position168, tokenIndex168 := position, tokenIndex - if buffer[position] != rune('\t') { - goto l169 - } - position++ - goto l168 - l169: - position, tokenIndex = position168, tokenIndex168 - if c := buffer[position]; c < rune(' ') || c > rune('\U0010ffff') { - goto l166 - } - position++ - } - l168: - add(rulemlLiteralChar, position167) - } - goto l165 - l166: - position, tokenIndex = position165, tokenIndex165 - if !_rules[rulenewline]() { - goto l163 - } - } - l165: - goto l162 - l163: - position, tokenIndex = position163, tokenIndex163 - } - add(rulemlLiteralBody, position161) - } - add(rulePegText, position160) - } - if buffer[position] != rune('\'') { - goto l158 - } - position++ - if buffer[position] != rune('\'') { - goto l158 - } - position++ - if buffer[position] != rune('\'') { - goto l158 - } - position++ - { - add(ruleAction22, position) - } - add(rulemlLiteralString, position159) - } - goto l157 - l158: - position, tokenIndex = position157, tokenIndex157 - { - position172 := position - if buffer[position] != rune('\'') { - goto l171 - } - position++ - { - position173 := position - l174: - { - position175, tokenIndex175 := position, tokenIndex - { - position176 := position - { - switch buffer[position] { - case '\t': - if buffer[position] != rune('\t') { - goto l175 - } - position++ - break - case ' ', '!', '"', '#', '$', '%', '&': - if c := buffer[position]; c < rune(' ') || c > rune('&') { - goto l175 - } - position++ - break - default: - if c := buffer[position]; c < rune('(') || c > rune('\U0010ffff') { - goto l175 - } - position++ - break - } - } - - add(ruleliteralChar, position176) - } - goto l174 - l175: - position, tokenIndex = position175, tokenIndex175 - } - add(rulePegText, position173) - } - if buffer[position] != rune('\'') { - goto l171 - } - position++ - { - add(ruleAction21, position) - } - add(ruleliteralString, position172) - } - goto l157 - l171: - position, tokenIndex = position157, tokenIndex157 - { - position180 := position - if buffer[position] != rune('"') { - goto l179 - } - position++ - if buffer[position] != rune('"') { - goto l179 - } - position++ - if buffer[position] != rune('"') { - goto l179 - } - position++ - { - position181 := position - l182: - { - position183, tokenIndex183 := position, tokenIndex - { - position184, tokenIndex184 := position, tokenIndex - { - position186 := position - { - position187, tokenIndex187 := position, tokenIndex - if !_rules[rulebasicChar]() { - goto l188 - } - goto l187 - l188: - position, tokenIndex = position187, tokenIndex187 - if !_rules[rulenewline]() { - goto l185 - } - } - l187: - add(rulePegText, position186) - } - { - add(ruleAction20, position) - } - goto l184 - l185: - position, tokenIndex = position184, tokenIndex184 - if !_rules[ruleescape]() { - goto l183 - } - if !_rules[rulenewline]() { - goto l183 - } - if !_rules[rulewsnl]() { - goto l183 - } - } - l184: - goto l182 - l183: - position, tokenIndex = position183, tokenIndex183 - } - add(rulemlBasicBody, position181) - } - if buffer[position] != rune('"') { - goto l179 - } - position++ - if buffer[position] != rune('"') { - goto l179 - } - position++ - if buffer[position] != rune('"') { - goto l179 - } - position++ - { - add(ruleAction19, position) - } - add(rulemlBasicString, position180) - } - goto l157 - l179: - position, tokenIndex = position157, tokenIndex157 - { - position191 := position - { - position192 := position - if buffer[position] != rune('"') { - goto l88 - } - position++ - l193: - { - position194, tokenIndex194 := position, tokenIndex - if !_rules[rulebasicChar]() { - goto l194 - } - goto l193 - l194: - position, tokenIndex = position194, tokenIndex194 - } - if buffer[position] != rune('"') { - goto l88 - } - position++ - add(rulePegText, position192) - } - { - add(ruleAction18, position) - } - add(rulebasicString, position191) - } - } - l157: - add(rulestring, position156) - } - add(rulePegText, position155) - } - { - add(ruleAction10, position) - } - break - default: - { - position197 := position - if !_rules[ruleinteger]() { - goto l88 - } - add(rulePegText, position197) - } - { - add(ruleAction9, position) - } - break - } - } - - } - l90: - add(ruleval, position89) - } - return true - l88: - position, tokenIndex = position88, tokenIndex88 - return false - }, - /* 11 table <- <(stdTable / arrayTable)> */ - nil, - /* 12 stdTable <- <('[' ws ws ']' Action13)> */ - nil, - /* 13 arrayTable <- <('[' '[' ws ws (']' ']') Action14)> */ - nil, - /* 14 inlineTable <- <('{' Action15 ws inlineTableKeyValues ws '}' Action16)> */ - nil, - /* 15 inlineTableKeyValues <- <(keyval inlineTableValSep?)*> */ - nil, - /* 16 tableKey <- <(tableKeyComp (tableKeySep tableKeyComp)*)> */ - func() bool { - position204, tokenIndex204 := position, tokenIndex - { - position205 := position - if !_rules[ruletableKeyComp]() { - goto l204 - } - l206: - { - position207, tokenIndex207 := position, tokenIndex - { - position208 := position - if !_rules[rulews]() { - goto l207 - } - if buffer[position] != rune('.') { - goto l207 - } - position++ - if !_rules[rulews]() { - goto l207 - } - add(ruletableKeySep, position208) - } - if !_rules[ruletableKeyComp]() { - goto l207 - } - goto l206 - l207: - position, tokenIndex = position207, tokenIndex207 - } - add(ruletableKey, position205) - } - return true - l204: - position, tokenIndex = position204, tokenIndex204 - return false - }, - /* 17 tableKeyComp <- <(key Action17)> */ - func() bool { - position209, tokenIndex209 := position, tokenIndex - { - position210 := position - if !_rules[rulekey]() { - goto l209 - } - { - add(ruleAction17, position) - } - add(ruletableKeyComp, position210) - } - return true - l209: - position, tokenIndex = position209, tokenIndex209 - return false - }, - /* 18 tableKeySep <- <(ws '.' ws)> */ - nil, - /* 19 inlineTableValSep <- <(ws ',' ws)> */ - nil, - /* 20 integer <- <(('-' / '+')? int)> */ - func() bool { - position214, tokenIndex214 := position, tokenIndex - { - position215 := position - { - position216, tokenIndex216 := position, tokenIndex - { - position218, tokenIndex218 := position, tokenIndex - if buffer[position] != rune('-') { - goto l219 - } - position++ - goto l218 - l219: - position, tokenIndex = position218, tokenIndex218 - if buffer[position] != rune('+') { - goto l216 - } - position++ - } - l218: - goto l217 - l216: - position, tokenIndex = position216, tokenIndex216 - } - l217: - { - position220 := position - { - position221, tokenIndex221 := position, tokenIndex - if c := buffer[position]; c < rune('1') || c > rune('9') { - goto l222 - } - position++ - { - position225, tokenIndex225 := position, tokenIndex - if !_rules[ruledigit]() { - goto l226 - } - goto l225 - l226: - position, tokenIndex = position225, tokenIndex225 - if buffer[position] != rune('_') { - goto l222 - } - position++ - if !_rules[ruledigit]() { - goto l222 - } - } - l225: - l223: - { - position224, tokenIndex224 := position, tokenIndex - { - position227, tokenIndex227 := position, tokenIndex - if !_rules[ruledigit]() { - goto l228 - } - goto l227 - l228: - position, tokenIndex = position227, tokenIndex227 - if buffer[position] != rune('_') { - goto l224 - } - position++ - if !_rules[ruledigit]() { - goto l224 - } - } - l227: - goto l223 - l224: - position, tokenIndex = position224, tokenIndex224 - } - goto l221 - l222: - position, tokenIndex = position221, tokenIndex221 - if !_rules[ruledigit]() { - goto l214 - } - } - l221: - add(ruleint, position220) - } - add(ruleinteger, position215) - } - return true - l214: - position, tokenIndex = position214, tokenIndex214 - return false - }, - /* 21 int <- <(([1-9] (digit / ('_' digit))+) / digit)> */ - nil, - /* 22 float <- <(integer ((frac exp?) / (frac? exp)))> */ - nil, - /* 23 frac <- <('.' digit (digit / ('_' digit))*)> */ - func() bool { - position231, tokenIndex231 := position, tokenIndex - { - position232 := position - if buffer[position] != rune('.') { - goto l231 - } - position++ - if !_rules[ruledigit]() { - goto l231 - } - l233: - { - position234, tokenIndex234 := position, tokenIndex - { - position235, tokenIndex235 := position, tokenIndex - if !_rules[ruledigit]() { - goto l236 - } - goto l235 - l236: - position, tokenIndex = position235, tokenIndex235 - if buffer[position] != rune('_') { - goto l234 - } - position++ - if !_rules[ruledigit]() { - goto l234 - } - } - l235: - goto l233 - l234: - position, tokenIndex = position234, tokenIndex234 - } - add(rulefrac, position232) - } - return true - l231: - position, tokenIndex = position231, tokenIndex231 - return false - }, - /* 24 exp <- <(('e' / 'E') ('-' / '+')? digit (digit / ('_' digit))*)> */ - func() bool { - position237, tokenIndex237 := position, tokenIndex - { - position238 := position - { - position239, tokenIndex239 := position, tokenIndex - if buffer[position] != rune('e') { - goto l240 - } - position++ - goto l239 - l240: - position, tokenIndex = position239, tokenIndex239 - if buffer[position] != rune('E') { - goto l237 - } - position++ - } - l239: - { - position241, tokenIndex241 := position, tokenIndex - { - position243, tokenIndex243 := position, tokenIndex - if buffer[position] != rune('-') { - goto l244 - } - position++ - goto l243 - l244: - position, tokenIndex = position243, tokenIndex243 - if buffer[position] != rune('+') { - goto l241 - } - position++ - } - l243: - goto l242 - l241: - position, tokenIndex = position241, tokenIndex241 - } - l242: - if !_rules[ruledigit]() { - goto l237 - } - l245: - { - position246, tokenIndex246 := position, tokenIndex - { - position247, tokenIndex247 := position, tokenIndex - if !_rules[ruledigit]() { - goto l248 - } - goto l247 - l248: - position, tokenIndex = position247, tokenIndex247 - if buffer[position] != rune('_') { - goto l246 - } - position++ - if !_rules[ruledigit]() { - goto l246 - } - } - l247: - goto l245 - l246: - position, tokenIndex = position246, tokenIndex246 - } - add(ruleexp, position238) - } - return true - l237: - position, tokenIndex = position237, tokenIndex237 - return false - }, - /* 25 string <- <(mlLiteralString / literalString / mlBasicString / basicString)> */ - nil, - /* 26 basicString <- <(<('"' basicChar* '"')> Action18)> */ - nil, - /* 27 basicChar <- <(basicUnescaped / escaped)> */ - func() bool { - position251, tokenIndex251 := position, tokenIndex - { - position252 := position - { - position253, tokenIndex253 := position, tokenIndex - { - position255 := position - { - switch buffer[position] { - case ' ', '!': - if c := buffer[position]; c < rune(' ') || c > rune('!') { - goto l254 - } - position++ - break - case '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[': - if c := buffer[position]; c < rune('#') || c > rune('[') { - goto l254 - } - position++ - break - default: - if c := buffer[position]; c < rune(']') || c > rune('\U0010ffff') { - goto l254 - } - position++ - break - } - } - - add(rulebasicUnescaped, position255) - } - goto l253 - l254: - position, tokenIndex = position253, tokenIndex253 - { - position257 := position - if !_rules[ruleescape]() { - goto l251 - } - { - switch buffer[position] { - case 'U': - if buffer[position] != rune('U') { - goto l251 - } - position++ - if !_rules[rulehexQuad]() { - goto l251 - } - if !_rules[rulehexQuad]() { - goto l251 - } - break - case 'u': - if buffer[position] != rune('u') { - goto l251 - } - position++ - if !_rules[rulehexQuad]() { - goto l251 - } - break - case '\\': - if buffer[position] != rune('\\') { - goto l251 - } - position++ - break - case '/': - if buffer[position] != rune('/') { - goto l251 - } - position++ - break - case '"': - if buffer[position] != rune('"') { - goto l251 - } - position++ - break - case 'r': - if buffer[position] != rune('r') { - goto l251 - } - position++ - break - case 'f': - if buffer[position] != rune('f') { - goto l251 - } - position++ - break - case 'n': - if buffer[position] != rune('n') { - goto l251 - } - position++ - break - case 't': - if buffer[position] != rune('t') { - goto l251 - } - position++ - break - default: - if buffer[position] != rune('b') { - goto l251 - } - position++ - break - } - } - - add(ruleescaped, position257) - } - } - l253: - add(rulebasicChar, position252) - } - return true - l251: - position, tokenIndex = position251, tokenIndex251 - return false - }, - /* 28 escaped <- <(escape ((&('U') ('U' hexQuad hexQuad)) | (&('u') ('u' hexQuad)) | (&('\\') '\\') | (&('/') '/') | (&('"') '"') | (&('r') 'r') | (&('f') 'f') | (&('n') 'n') | (&('t') 't') | (&('b') 'b')))> */ - nil, - /* 29 basicUnescaped <- <((&(' ' | '!') [ -!]) | (&('#' | '$' | '%' | '&' | '\'' | '(' | ')' | '*' | '+' | ',' | '-' | '.' | '/' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | ':' | ';' | '<' | '=' | '>' | '?' | '@' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z' | '[') [#-[]) | (&(']' | '^' | '_' | '`' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z' | '{' | '|' | '}' | '~' | '\u007f' | '\u0080' | '\u0081' | '\u0082' | '\u0083' | '\u0084' | '\u0085' | '\u0086' | '\u0087' | '\u0088' | '\u0089' | '\u008a' | '\u008b' | '\u008c' | '\u008d' | '\u008e' | '\u008f' | '\u0090' | '\u0091' | '\u0092' | '\u0093' | '\u0094' | '\u0095' | '\u0096' | '\u0097' | '\u0098' | '\u0099' | '\u009a' | '\u009b' | '\u009c' | '\u009d' | '\u009e' | '\u009f' | '\u00a0' | '¡' | '¢' | '£' | '¤' | '¥' | '¦' | '§' | '¨' | '©' | 'ª' | '«' | '¬' | '\u00ad' | '®' | '¯' | '°' | '±' | '²' | '³' | '´' | 'µ' | '¶' | '·' | '¸' | '¹' | 'º' | '»' | '¼' | '½' | '¾' | '¿' | 'À' | 'Á' | 'Â' | 'Ã' | 'Ä' | 'Å' | 'Æ' | 'Ç' | 'È' | 'É' | 'Ê' | 'Ë' | 'Ì' | 'Í' | 'Î' | 'Ï' | 'Ð' | 'Ñ' | 'Ò' | 'Ó' | 'Ô' | 'Õ' | 'Ö' | '×' | 'Ø' | 'Ù' | 'Ú' | 'Û' | 'Ü' | 'Ý' | 'Þ' | 'ß' | 'à' | 'á' | 'â' | 'ã' | 'ä' | 'å' | 'æ' | 'ç' | 'è' | 'é' | 'ê' | 'ë' | 'ì' | 'í' | 'î' | 'ï' | 'ð' | 'ñ' | 'ò' | 'ó' | 'ô' | 'õ' | 'ö' | '÷' | 'ø' | 'ù' | 'ú' | 'û' | 'ü' | 'ý' | 'þ' | 'ÿ') []-\U0010ffff]))> */ - nil, - /* 30 escape <- <'\\'> */ - func() bool { - position261, tokenIndex261 := position, tokenIndex - { - position262 := position - if buffer[position] != rune('\\') { - goto l261 - } - position++ - add(ruleescape, position262) - } - return true - l261: - position, tokenIndex = position261, tokenIndex261 - return false - }, - /* 31 mlBasicString <- <('"' '"' '"' mlBasicBody ('"' '"' '"') Action19)> */ - nil, - /* 32 mlBasicBody <- <((<(basicChar / newline)> Action20) / (escape newline wsnl))*> */ - nil, - /* 33 literalString <- <('\'' '\'' Action21)> */ - nil, - /* 34 literalChar <- <((&('\t') '\t') | (&(' ' | '!' | '"' | '#' | '$' | '%' | '&') [ -&]) | (&('(' | ')' | '*' | '+' | ',' | '-' | '.' | '/' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | ':' | ';' | '<' | '=' | '>' | '?' | '@' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z' | '[' | '\\' | ']' | '^' | '_' | '`' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z' | '{' | '|' | '}' | '~' | '\u007f' | '\u0080' | '\u0081' | '\u0082' | '\u0083' | '\u0084' | '\u0085' | '\u0086' | '\u0087' | '\u0088' | '\u0089' | '\u008a' | '\u008b' | '\u008c' | '\u008d' | '\u008e' | '\u008f' | '\u0090' | '\u0091' | '\u0092' | '\u0093' | '\u0094' | '\u0095' | '\u0096' | '\u0097' | '\u0098' | '\u0099' | '\u009a' | '\u009b' | '\u009c' | '\u009d' | '\u009e' | '\u009f' | '\u00a0' | '¡' | '¢' | '£' | '¤' | '¥' | '¦' | '§' | '¨' | '©' | 'ª' | '«' | '¬' | '\u00ad' | '®' | '¯' | '°' | '±' | '²' | '³' | '´' | 'µ' | '¶' | '·' | '¸' | '¹' | 'º' | '»' | '¼' | '½' | '¾' | '¿' | 'À' | 'Á' | 'Â' | 'Ã' | 'Ä' | 'Å' | 'Æ' | 'Ç' | 'È' | 'É' | 'Ê' | 'Ë' | 'Ì' | 'Í' | 'Î' | 'Ï' | 'Ð' | 'Ñ' | 'Ò' | 'Ó' | 'Ô' | 'Õ' | 'Ö' | '×' | 'Ø' | 'Ù' | 'Ú' | 'Û' | 'Ü' | 'Ý' | 'Þ' | 'ß' | 'à' | 'á' | 'â' | 'ã' | 'ä' | 'å' | 'æ' | 'ç' | 'è' | 'é' | 'ê' | 'ë' | 'ì' | 'í' | 'î' | 'ï' | 'ð' | 'ñ' | 'ò' | 'ó' | 'ô' | 'õ' | 'ö' | '÷' | 'ø' | 'ù' | 'ú' | 'û' | 'ü' | 'ý' | 'þ' | 'ÿ') [(-\U0010ffff]))> */ - nil, - /* 35 mlLiteralString <- <('\'' '\'' '\'' ('\'' '\'' '\'') Action22)> */ - nil, - /* 36 mlLiteralBody <- <(!('\'' '\'' '\'') (mlLiteralChar / newline))*> */ - nil, - /* 37 mlLiteralChar <- <('\t' / [ -\U0010ffff])> */ - nil, - /* 38 hexdigit <- <((&('a' | 'b' | 'c' | 'd' | 'e' | 'f') [a-f]) | (&('A' | 'B' | 'C' | 'D' | 'E' | 'F') [A-F]) | (&('0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9') [0-9]))> */ - func() bool { - position270, tokenIndex270 := position, tokenIndex - { - position271 := position - { - switch buffer[position] { - case 'a', 'b', 'c', 'd', 'e', 'f': - if c := buffer[position]; c < rune('a') || c > rune('f') { - goto l270 - } - position++ - break - case 'A', 'B', 'C', 'D', 'E', 'F': - if c := buffer[position]; c < rune('A') || c > rune('F') { - goto l270 - } - position++ - break - default: - if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l270 - } - position++ - break - } - } - - add(rulehexdigit, position271) - } - return true - l270: - position, tokenIndex = position270, tokenIndex270 - return false - }, - /* 39 hexQuad <- <(hexdigit hexdigit hexdigit hexdigit)> */ - func() bool { - position273, tokenIndex273 := position, tokenIndex - { - position274 := position - if !_rules[rulehexdigit]() { - goto l273 - } - if !_rules[rulehexdigit]() { - goto l273 - } - if !_rules[rulehexdigit]() { - goto l273 - } - if !_rules[rulehexdigit]() { - goto l273 - } - add(rulehexQuad, position274) - } - return true - l273: - position, tokenIndex = position273, tokenIndex273 - return false - }, - /* 40 boolean <- <(('t' 'r' 'u' 'e') / ('f' 'a' 'l' 's' 'e'))> */ - nil, - /* 41 dateFullYear <- */ - nil, - /* 42 dateMonth <- */ - nil, - /* 43 dateMDay <- */ - nil, - /* 44 timeHour <- */ - func() bool { - position279, tokenIndex279 := position, tokenIndex - { - position280 := position - if !_rules[ruledigitDual]() { - goto l279 - } - add(ruletimeHour, position280) - } - return true - l279: - position, tokenIndex = position279, tokenIndex279 - return false - }, - /* 45 timeMinute <- */ - func() bool { - position281, tokenIndex281 := position, tokenIndex - { - position282 := position - if !_rules[ruledigitDual]() { - goto l281 - } - add(ruletimeMinute, position282) - } - return true - l281: - position, tokenIndex = position281, tokenIndex281 - return false - }, - /* 46 timeSecond <- */ - nil, - /* 47 timeSecfrac <- <('.' digit+)> */ - nil, - /* 48 timeNumoffset <- <(('-' / '+') timeHour ':' timeMinute)> */ - nil, - /* 49 timeOffset <- <('Z' / timeNumoffset)> */ - nil, - /* 50 partialTime <- <(timeHour ':' timeMinute ':' timeSecond timeSecfrac?)> */ - func() bool { - position287, tokenIndex287 := position, tokenIndex - { - position288 := position - if !_rules[ruletimeHour]() { - goto l287 - } - if buffer[position] != rune(':') { - goto l287 - } - position++ - if !_rules[ruletimeMinute]() { - goto l287 - } - if buffer[position] != rune(':') { - goto l287 - } - position++ - { - position289 := position - if !_rules[ruledigitDual]() { - goto l287 - } - add(ruletimeSecond, position289) - } - { - position290, tokenIndex290 := position, tokenIndex - { - position292 := position - if buffer[position] != rune('.') { - goto l290 - } - position++ - if !_rules[ruledigit]() { - goto l290 - } - l293: - { - position294, tokenIndex294 := position, tokenIndex - if !_rules[ruledigit]() { - goto l294 - } - goto l293 - l294: - position, tokenIndex = position294, tokenIndex294 - } - add(ruletimeSecfrac, position292) - } - goto l291 - l290: - position, tokenIndex = position290, tokenIndex290 - } - l291: - add(rulepartialTime, position288) - } - return true - l287: - position, tokenIndex = position287, tokenIndex287 - return false - }, - /* 51 fullDate <- <(dateFullYear '-' dateMonth '-' dateMDay)> */ - nil, - /* 52 fullTime <- <(partialTime timeOffset)> */ - nil, - /* 53 datetime <- <((fullDate ('T' fullTime)?) / partialTime)> */ - nil, - /* 54 digit <- <[0-9]> */ - func() bool { - position298, tokenIndex298 := position, tokenIndex - { - position299 := position - if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l298 - } - position++ - add(ruledigit, position299) - } - return true - l298: - position, tokenIndex = position298, tokenIndex298 - return false - }, - /* 55 digitDual <- <(digit digit)> */ - func() bool { - position300, tokenIndex300 := position, tokenIndex - { - position301 := position - if !_rules[ruledigit]() { - goto l300 - } - if !_rules[ruledigit]() { - goto l300 - } - add(ruledigitDual, position301) - } - return true - l300: - position, tokenIndex = position300, tokenIndex300 - return false - }, - /* 56 digitQuad <- <(digitDual digitDual)> */ - nil, - /* 57 array <- <('[' Action23 wsnl arrayValues? wsnl ']')> */ - nil, - /* 58 arrayValues <- <(val Action24 (wsnl comment? wsnl arraySep wsnl comment? wsnl val Action25)* wsnl arraySep? wsnl comment?)> */ - nil, - /* 59 arraySep <- <','> */ - func() bool { - position305, tokenIndex305 := position, tokenIndex - { - position306 := position - if buffer[position] != rune(',') { - goto l305 - } - position++ - add(rulearraySep, position306) - } - return true - l305: - position, tokenIndex = position305, tokenIndex305 - return false - }, - /* 61 Action0 <- <{ _ = buffer }> */ - nil, - nil, - /* 63 Action1 <- <{ p.SetTableString(begin, end) }> */ - nil, - /* 64 Action2 <- <{ p.AddLineCount(end - begin) }> */ - nil, - /* 65 Action3 <- <{ p.AddLineCount(end - begin) }> */ - nil, - /* 66 Action4 <- <{ p.AddKeyValue() }> */ - nil, - /* 67 Action5 <- <{ p.SetKey(p.buffer, begin, end) }> */ - nil, - /* 68 Action6 <- <{ p.SetKey(p.buffer, begin, end) }> */ - nil, - /* 69 Action7 <- <{ p.SetTime(begin, end) }> */ - nil, - /* 70 Action8 <- <{ p.SetFloat64(begin, end) }> */ - nil, - /* 71 Action9 <- <{ p.SetInt64(begin, end) }> */ - nil, - /* 72 Action10 <- <{ p.SetString(begin, end) }> */ - nil, - /* 73 Action11 <- <{ p.SetBool(begin, end) }> */ - nil, - /* 74 Action12 <- <{ p.SetArray(begin, end) }> */ - nil, - /* 75 Action13 <- <{ p.SetTable(p.buffer, begin, end) }> */ - nil, - /* 76 Action14 <- <{ p.SetArrayTable(p.buffer, begin, end) }> */ - nil, - /* 77 Action15 <- <{ p.StartInlineTable() }> */ - nil, - /* 78 Action16 <- <{ p.EndInlineTable() }> */ - nil, - /* 79 Action17 <- <{ p.AddTableKey() }> */ - nil, - /* 80 Action18 <- <{ p.SetBasicString(p.buffer, begin, end) }> */ - nil, - /* 81 Action19 <- <{ p.SetMultilineString() }> */ - nil, - /* 82 Action20 <- <{ p.AddMultilineBasicBody(p.buffer, begin, end) }> */ - nil, - /* 83 Action21 <- <{ p.SetLiteralString(p.buffer, begin, end) }> */ - nil, - /* 84 Action22 <- <{ p.SetMultilineLiteralString(p.buffer, begin, end) }> */ - nil, - /* 85 Action23 <- <{ p.StartArray() }> */ - nil, - /* 86 Action24 <- <{ p.AddArrayVal() }> */ - nil, - /* 87 Action25 <- <{ p.AddArrayVal() }> */ - nil, - } - p.rules = _rules -} diff --git a/vendor/github.com/naoina/toml/util.go b/vendor/github.com/naoina/toml/util.go deleted file mode 100644 index f882f4e5f83..00000000000 --- a/vendor/github.com/naoina/toml/util.go +++ /dev/null @@ -1,65 +0,0 @@ -package toml - -import ( - "fmt" - "reflect" - "strings" -) - -const fieldTagName = "toml" - -// fieldCache maps normalized field names to their position in a struct. -type fieldCache struct { - named map[string]fieldInfo // fields with an explicit name in tag - auto map[string]fieldInfo // fields with auto-assigned normalized names -} - -type fieldInfo struct { - index []int - name string - ignored bool -} - -func makeFieldCache(cfg *Config, rt reflect.Type) fieldCache { - named, auto := make(map[string]fieldInfo), make(map[string]fieldInfo) - for i := 0; i < rt.NumField(); i++ { - ft := rt.Field(i) - // skip unexported fields - if ft.PkgPath != "" && !ft.Anonymous { - continue - } - col, _ := extractTag(ft.Tag.Get(fieldTagName)) - info := fieldInfo{index: ft.Index, name: ft.Name, ignored: col == "-"} - if col == "" || col == "-" { - auto[cfg.NormFieldName(rt, ft.Name)] = info - } else { - named[col] = info - } - } - return fieldCache{named, auto} -} - -func (fc fieldCache) findField(cfg *Config, rv reflect.Value, name string) (reflect.Value, string, error) { - info, found := fc.named[name] - if !found { - info, found = fc.auto[cfg.NormFieldName(rv.Type(), name)] - } - if !found { - if cfg.MissingField == nil { - return reflect.Value{}, "", fmt.Errorf("field corresponding to `%s' is not defined in %v", name, rv.Type()) - } else { - return reflect.Value{}, "", cfg.MissingField(rv.Type(), name) - } - } else if info.ignored { - return reflect.Value{}, "", fmt.Errorf("field corresponding to `%s' in %v cannot be set through TOML", name, rv.Type()) - } - return rv.FieldByIndex(info.index), info.name, nil -} - -func extractTag(tag string) (col, rest string) { - tags := strings.SplitN(tag, ",", 2) - if len(tags) == 2 { - return strings.TrimSpace(tags[0]), strings.TrimSpace(tags[1]) - } - return strings.TrimSpace(tags[0]), "" -} diff --git a/vendor/github.com/nu7hatch/gouuid/COPYING b/vendor/github.com/nu7hatch/gouuid/COPYING deleted file mode 100644 index d7849fd8fb8..00000000000 --- a/vendor/github.com/nu7hatch/gouuid/COPYING +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (C) 2011 by Krzysztof Kowalik - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/nu7hatch/gouuid/uuid.go b/vendor/github.com/nu7hatch/gouuid/uuid.go deleted file mode 100644 index ac9623b729f..00000000000 --- a/vendor/github.com/nu7hatch/gouuid/uuid.go +++ /dev/null @@ -1,173 +0,0 @@ -// This package provides immutable UUID structs and the functions -// NewV3, NewV4, NewV5 and Parse() for generating versions 3, 4 -// and 5 UUIDs as specified in RFC 4122. -// -// Copyright (C) 2011 by Krzysztof Kowalik -package uuid - -import ( - "crypto/md5" - "crypto/rand" - "crypto/sha1" - "encoding/hex" - "errors" - "fmt" - "hash" - "regexp" -) - -// The UUID reserved variants. -const ( - ReservedNCS byte = 0x80 - ReservedRFC4122 byte = 0x40 - ReservedMicrosoft byte = 0x20 - ReservedFuture byte = 0x00 -) - -// The following standard UUIDs are for use with NewV3() or NewV5(). -var ( - NamespaceDNS, _ = ParseHex("6ba7b810-9dad-11d1-80b4-00c04fd430c8") - NamespaceURL, _ = ParseHex("6ba7b811-9dad-11d1-80b4-00c04fd430c8") - NamespaceOID, _ = ParseHex("6ba7b812-9dad-11d1-80b4-00c04fd430c8") - NamespaceX500, _ = ParseHex("6ba7b814-9dad-11d1-80b4-00c04fd430c8") -) - -// Pattern used to parse hex string representation of the UUID. -// FIXME: do something to consider both brackets at one time, -// current one allows to parse string with only one opening -// or closing bracket. -const hexPattern = "^(urn\\:uuid\\:)?\\{?([a-z0-9]{8})-([a-z0-9]{4})-" + - "([1-5][a-z0-9]{3})-([a-z0-9]{4})-([a-z0-9]{12})\\}?$" - -var re = regexp.MustCompile(hexPattern) - -// A UUID representation compliant with specification in -// RFC 4122 document. -type UUID [16]byte - -// ParseHex creates a UUID object from given hex string -// representation. Function accepts UUID string in following -// formats: -// -// uuid.ParseHex("6ba7b814-9dad-11d1-80b4-00c04fd430c8") -// uuid.ParseHex("{6ba7b814-9dad-11d1-80b4-00c04fd430c8}") -// uuid.ParseHex("urn:uuid:6ba7b814-9dad-11d1-80b4-00c04fd430c8") -// -func ParseHex(s string) (u *UUID, err error) { - md := re.FindStringSubmatch(s) - if md == nil { - err = errors.New("Invalid UUID string") - return - } - hash := md[2] + md[3] + md[4] + md[5] + md[6] - b, err := hex.DecodeString(hash) - if err != nil { - return - } - u = new(UUID) - copy(u[:], b) - return -} - -// Parse creates a UUID object from given bytes slice. -func Parse(b []byte) (u *UUID, err error) { - if len(b) != 16 { - err = errors.New("Given slice is not valid UUID sequence") - return - } - u = new(UUID) - copy(u[:], b) - return -} - -// Generate a UUID based on the MD5 hash of a namespace identifier -// and a name. -func NewV3(ns *UUID, name []byte) (u *UUID, err error) { - if ns == nil { - err = errors.New("Invalid namespace UUID") - return - } - u = new(UUID) - // Set all bits to MD5 hash generated from namespace and name. - u.setBytesFromHash(md5.New(), ns[:], name) - u.setVariant(ReservedRFC4122) - u.setVersion(3) - return -} - -// Generate a random UUID. -func NewV4() (u *UUID, err error) { - u = new(UUID) - // Set all bits to randomly (or pseudo-randomly) chosen values. - _, err = rand.Read(u[:]) - if err != nil { - return - } - u.setVariant(ReservedRFC4122) - u.setVersion(4) - return -} - -// Generate a UUID based on the SHA-1 hash of a namespace identifier -// and a name. -func NewV5(ns *UUID, name []byte) (u *UUID, err error) { - u = new(UUID) - // Set all bits to truncated SHA1 hash generated from namespace - // and name. - u.setBytesFromHash(sha1.New(), ns[:], name) - u.setVariant(ReservedRFC4122) - u.setVersion(5) - return -} - -// Generate a MD5 hash of a namespace and a name, and copy it to the -// UUID slice. -func (u *UUID) setBytesFromHash(hash hash.Hash, ns, name []byte) { - hash.Write(ns[:]) - hash.Write(name) - copy(u[:], hash.Sum([]byte{})[:16]) -} - -// Set the two most significant bits (bits 6 and 7) of the -// clock_seq_hi_and_reserved to zero and one, respectively. -func (u *UUID) setVariant(v byte) { - switch v { - case ReservedNCS: - u[8] = (u[8] | ReservedNCS) & 0xBF - case ReservedRFC4122: - u[8] = (u[8] | ReservedRFC4122) & 0x7F - case ReservedMicrosoft: - u[8] = (u[8] | ReservedMicrosoft) & 0x3F - } -} - -// Variant returns the UUID Variant, which determines the internal -// layout of the UUID. This will be one of the constants: RESERVED_NCS, -// RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE. -func (u *UUID) Variant() byte { - if u[8]&ReservedNCS == ReservedNCS { - return ReservedNCS - } else if u[8]&ReservedRFC4122 == ReservedRFC4122 { - return ReservedRFC4122 - } else if u[8]&ReservedMicrosoft == ReservedMicrosoft { - return ReservedMicrosoft - } - return ReservedFuture -} - -// Set the four most significant bits (bits 12 through 15) of the -// time_hi_and_version field to the 4-bit version number. -func (u *UUID) setVersion(v byte) { - u[6] = (u[6] & 0xF) | (v << 4) -} - -// Version returns a version number of the algorithm used to -// generate the UUID sequence. -func (u *UUID) Version() uint { - return uint(u[6] >> 4) -} - -// Returns unparsed version of the generated UUID sequence. -func (u *UUID) String() string { - return fmt.Sprintf("%x-%x-%x-%x-%x", u[0:4], u[4:6], u[6:8], u[8:10], u[10:]) -} diff --git a/vendor/github.com/russross/blackfriday/LICENSE.txt b/vendor/github.com/russross/blackfriday/LICENSE.txt deleted file mode 100644 index 2885af3602d..00000000000 --- a/vendor/github.com/russross/blackfriday/LICENSE.txt +++ /dev/null @@ -1,29 +0,0 @@ -Blackfriday is distributed under the Simplified BSD License: - -> Copyright © 2011 Russ Ross -> All rights reserved. -> -> Redistribution and use in source and binary forms, with or without -> modification, are permitted provided that the following conditions -> are met: -> -> 1. Redistributions of source code must retain the above copyright -> notice, this list of conditions and the following disclaimer. -> -> 2. Redistributions in binary form must reproduce the above -> copyright notice, this list of conditions and the following -> disclaimer in the documentation and/or other materials provided with -> the distribution. -> -> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -> "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -> LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -> FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -> COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -> INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -> BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -> LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -> LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -> ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -> POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/russross/blackfriday/block.go b/vendor/github.com/russross/blackfriday/block.go deleted file mode 100644 index 7fc731d5488..00000000000 --- a/vendor/github.com/russross/blackfriday/block.go +++ /dev/null @@ -1,1450 +0,0 @@ -// -// Blackfriday Markdown Processor -// Available at http://github.com/russross/blackfriday -// -// Copyright © 2011 Russ Ross . -// Distributed under the Simplified BSD License. -// See README.md for details. -// - -// -// Functions to parse block-level elements. -// - -package blackfriday - -import ( - "bytes" - "unicode" -) - -// Parse block-level data. -// Note: this function and many that it calls assume that -// the input buffer ends with a newline. -func (p *parser) block(out *bytes.Buffer, data []byte) { - if len(data) == 0 || data[len(data)-1] != '\n' { - panic("block input is missing terminating newline") - } - - // this is called recursively: enforce a maximum depth - if p.nesting >= p.maxNesting { - return - } - p.nesting++ - - // parse out one block-level construct at a time - for len(data) > 0 { - // prefixed header: - // - // # Header 1 - // ## Header 2 - // ... - // ###### Header 6 - if p.isPrefixHeader(data) { - data = data[p.prefixHeader(out, data):] - continue - } - - // block of preformatted HTML: - // - //
    - // ... - //
    - if data[0] == '<' { - if i := p.html(out, data, true); i > 0 { - data = data[i:] - continue - } - } - - // title block - // - // % stuff - // % more stuff - // % even more stuff - if p.flags&EXTENSION_TITLEBLOCK != 0 { - if data[0] == '%' { - if i := p.titleBlock(out, data, true); i > 0 { - data = data[i:] - continue - } - } - } - - // blank lines. note: returns the # of bytes to skip - if i := p.isEmpty(data); i > 0 { - data = data[i:] - continue - } - - // indented code block: - // - // func max(a, b int) int { - // if a > b { - // return a - // } - // return b - // } - if p.codePrefix(data) > 0 { - data = data[p.code(out, data):] - continue - } - - // fenced code block: - // - // ``` go - // func fact(n int) int { - // if n <= 1 { - // return n - // } - // return n * fact(n-1) - // } - // ``` - if p.flags&EXTENSION_FENCED_CODE != 0 { - if i := p.fencedCodeBlock(out, data, true); i > 0 { - data = data[i:] - continue - } - } - - // horizontal rule: - // - // ------ - // or - // ****** - // or - // ______ - if p.isHRule(data) { - p.r.HRule(out) - var i int - for i = 0; data[i] != '\n'; i++ { - } - data = data[i:] - continue - } - - // block quote: - // - // > A big quote I found somewhere - // > on the web - if p.quotePrefix(data) > 0 { - data = data[p.quote(out, data):] - continue - } - - // table: - // - // Name | Age | Phone - // ------|-----|--------- - // Bob | 31 | 555-1234 - // Alice | 27 | 555-4321 - if p.flags&EXTENSION_TABLES != 0 { - if i := p.table(out, data); i > 0 { - data = data[i:] - continue - } - } - - // an itemized/unordered list: - // - // * Item 1 - // * Item 2 - // - // also works with + or - - if p.uliPrefix(data) > 0 { - data = data[p.list(out, data, 0):] - continue - } - - // a numbered/ordered list: - // - // 1. Item 1 - // 2. Item 2 - if p.oliPrefix(data) > 0 { - data = data[p.list(out, data, LIST_TYPE_ORDERED):] - continue - } - - // definition lists: - // - // Term 1 - // : Definition a - // : Definition b - // - // Term 2 - // : Definition c - if p.flags&EXTENSION_DEFINITION_LISTS != 0 { - if p.dliPrefix(data) > 0 { - data = data[p.list(out, data, LIST_TYPE_DEFINITION):] - continue - } - } - - // anything else must look like a normal paragraph - // note: this finds underlined headers, too - data = data[p.paragraph(out, data):] - } - - p.nesting-- -} - -func (p *parser) isPrefixHeader(data []byte) bool { - if data[0] != '#' { - return false - } - - if p.flags&EXTENSION_SPACE_HEADERS != 0 { - level := 0 - for level < 6 && data[level] == '#' { - level++ - } - if data[level] != ' ' { - return false - } - } - return true -} - -func (p *parser) prefixHeader(out *bytes.Buffer, data []byte) int { - level := 0 - for level < 6 && data[level] == '#' { - level++ - } - i := skipChar(data, level, ' ') - end := skipUntilChar(data, i, '\n') - skip := end - id := "" - if p.flags&EXTENSION_HEADER_IDS != 0 { - j, k := 0, 0 - // find start/end of header id - for j = i; j < end-1 && (data[j] != '{' || data[j+1] != '#'); j++ { - } - for k = j + 1; k < end && data[k] != '}'; k++ { - } - // extract header id iff found - if j < end && k < end { - id = string(data[j+2 : k]) - end = j - skip = k + 1 - for end > 0 && data[end-1] == ' ' { - end-- - } - } - } - for end > 0 && data[end-1] == '#' { - if isBackslashEscaped(data, end-1) { - break - } - end-- - } - for end > 0 && data[end-1] == ' ' { - end-- - } - if end > i { - if id == "" && p.flags&EXTENSION_AUTO_HEADER_IDS != 0 { - id = SanitizedAnchorName(string(data[i:end])) - } - work := func() bool { - p.inline(out, data[i:end]) - return true - } - p.r.Header(out, work, level, id) - } - return skip -} - -func (p *parser) isUnderlinedHeader(data []byte) int { - // test of level 1 header - if data[0] == '=' { - i := skipChar(data, 1, '=') - i = skipChar(data, i, ' ') - if data[i] == '\n' { - return 1 - } else { - return 0 - } - } - - // test of level 2 header - if data[0] == '-' { - i := skipChar(data, 1, '-') - i = skipChar(data, i, ' ') - if data[i] == '\n' { - return 2 - } else { - return 0 - } - } - - return 0 -} - -func (p *parser) titleBlock(out *bytes.Buffer, data []byte, doRender bool) int { - if data[0] != '%' { - return 0 - } - splitData := bytes.Split(data, []byte("\n")) - var i int - for idx, b := range splitData { - if !bytes.HasPrefix(b, []byte("%")) { - i = idx // - 1 - break - } - } - - data = bytes.Join(splitData[0:i], []byte("\n")) - p.r.TitleBlock(out, data) - - return len(data) -} - -func (p *parser) html(out *bytes.Buffer, data []byte, doRender bool) int { - var i, j int - - // identify the opening tag - if data[0] != '<' { - return 0 - } - curtag, tagfound := p.htmlFindTag(data[1:]) - - // handle special cases - if !tagfound { - // check for an HTML comment - if size := p.htmlComment(out, data, doRender); size > 0 { - return size - } - - // check for an
    tag - if size := p.htmlHr(out, data, doRender); size > 0 { - return size - } - - // check for HTML CDATA - if size := p.htmlCDATA(out, data, doRender); size > 0 { - return size - } - - // no special case recognized - return 0 - } - - // look for an unindented matching closing tag - // followed by a blank line - found := false - /* - closetag := []byte("\n") - j = len(curtag) + 1 - for !found { - // scan for a closing tag at the beginning of a line - if skip := bytes.Index(data[j:], closetag); skip >= 0 { - j += skip + len(closetag) - } else { - break - } - - // see if it is the only thing on the line - if skip := p.isEmpty(data[j:]); skip > 0 { - // see if it is followed by a blank line/eof - j += skip - if j >= len(data) { - found = true - i = j - } else { - if skip := p.isEmpty(data[j:]); skip > 0 { - j += skip - found = true - i = j - } - } - } - } - */ - - // if not found, try a second pass looking for indented match - // but not if tag is "ins" or "del" (following original Markdown.pl) - if !found && curtag != "ins" && curtag != "del" { - i = 1 - for i < len(data) { - i++ - for i < len(data) && !(data[i-1] == '<' && data[i] == '/') { - i++ - } - - if i+2+len(curtag) >= len(data) { - break - } - - j = p.htmlFindEnd(curtag, data[i-1:]) - - if j > 0 { - i += j - 1 - found = true - break - } - } - } - - if !found { - return 0 - } - - // the end of the block has been found - if doRender { - // trim newlines - end := i - for end > 0 && data[end-1] == '\n' { - end-- - } - p.r.BlockHtml(out, data[:end]) - } - - return i -} - -func (p *parser) renderHTMLBlock(out *bytes.Buffer, data []byte, start int, doRender bool) int { - // html block needs to end with a blank line - if i := p.isEmpty(data[start:]); i > 0 { - size := start + i - if doRender { - // trim trailing newlines - end := size - for end > 0 && data[end-1] == '\n' { - end-- - } - p.r.BlockHtml(out, data[:end]) - } - return size - } - return 0 -} - -// HTML comment, lax form -func (p *parser) htmlComment(out *bytes.Buffer, data []byte, doRender bool) int { - i := p.inlineHTMLComment(out, data) - return p.renderHTMLBlock(out, data, i, doRender) -} - -// HTML CDATA section -func (p *parser) htmlCDATA(out *bytes.Buffer, data []byte, doRender bool) int { - const cdataTag = "') { - i++ - } - i++ - // no end-of-comment marker - if i >= len(data) { - return 0 - } - return p.renderHTMLBlock(out, data, i, doRender) -} - -// HR, which is the only self-closing block tag considered -func (p *parser) htmlHr(out *bytes.Buffer, data []byte, doRender bool) int { - if data[0] != '<' || (data[1] != 'h' && data[1] != 'H') || (data[2] != 'r' && data[2] != 'R') { - return 0 - } - if data[3] != ' ' && data[3] != '/' && data[3] != '>' { - // not an
    tag after all; at least not a valid one - return 0 - } - - i := 3 - for data[i] != '>' && data[i] != '\n' { - i++ - } - - if data[i] == '>' { - return p.renderHTMLBlock(out, data, i+1, doRender) - } - - return 0 -} - -func (p *parser) htmlFindTag(data []byte) (string, bool) { - i := 0 - for isalnum(data[i]) { - i++ - } - key := string(data[:i]) - if _, ok := blockTags[key]; ok { - return key, true - } - return "", false -} - -func (p *parser) htmlFindEnd(tag string, data []byte) int { - // assume data[0] == '<' && data[1] == '/' already tested - - // check if tag is a match - closetag := []byte("") - if !bytes.HasPrefix(data, closetag) { - return 0 - } - i := len(closetag) - - // check that the rest of the line is blank - skip := 0 - if skip = p.isEmpty(data[i:]); skip == 0 { - return 0 - } - i += skip - skip = 0 - - if i >= len(data) { - return i - } - - if p.flags&EXTENSION_LAX_HTML_BLOCKS != 0 { - return i - } - if skip = p.isEmpty(data[i:]); skip == 0 { - // following line must be blank - return 0 - } - - return i + skip -} - -func (*parser) isEmpty(data []byte) int { - // it is okay to call isEmpty on an empty buffer - if len(data) == 0 { - return 0 - } - - var i int - for i = 0; i < len(data) && data[i] != '\n'; i++ { - if data[i] != ' ' && data[i] != '\t' { - return 0 - } - } - return i + 1 -} - -func (*parser) isHRule(data []byte) bool { - i := 0 - - // skip up to three spaces - for i < 3 && data[i] == ' ' { - i++ - } - - // look at the hrule char - if data[i] != '*' && data[i] != '-' && data[i] != '_' { - return false - } - c := data[i] - - // the whole line must be the char or whitespace - n := 0 - for data[i] != '\n' { - switch { - case data[i] == c: - n++ - case data[i] != ' ': - return false - } - i++ - } - - return n >= 3 -} - -// isFenceLine checks if there's a fence line (e.g., ``` or ``` go) at the beginning of data, -// and returns the end index if so, or 0 otherwise. It also returns the marker found. -// If syntax is not nil, it gets set to the syntax specified in the fence line. -// A final newline is mandatory to recognize the fence line, unless newlineOptional is true. -func isFenceLine(data []byte, syntax *string, oldmarker string, newlineOptional bool) (end int, marker string) { - i, size := 0, 0 - - // skip up to three spaces - for i < len(data) && i < 3 && data[i] == ' ' { - i++ - } - - // check for the marker characters: ~ or ` - if i >= len(data) { - return 0, "" - } - if data[i] != '~' && data[i] != '`' { - return 0, "" - } - - c := data[i] - - // the whole line must be the same char or whitespace - for i < len(data) && data[i] == c { - size++ - i++ - } - - // the marker char must occur at least 3 times - if size < 3 { - return 0, "" - } - marker = string(data[i-size : i]) - - // if this is the end marker, it must match the beginning marker - if oldmarker != "" && marker != oldmarker { - return 0, "" - } - - // TODO(shurcooL): It's probably a good idea to simplify the 2 code paths here - // into one, always get the syntax, and discard it if the caller doesn't care. - if syntax != nil { - syn := 0 - i = skipChar(data, i, ' ') - - if i >= len(data) { - if newlineOptional && i == len(data) { - return i, marker - } - return 0, "" - } - - syntaxStart := i - - if data[i] == '{' { - i++ - syntaxStart++ - - for i < len(data) && data[i] != '}' && data[i] != '\n' { - syn++ - i++ - } - - if i >= len(data) || data[i] != '}' { - return 0, "" - } - - // strip all whitespace at the beginning and the end - // of the {} block - for syn > 0 && isspace(data[syntaxStart]) { - syntaxStart++ - syn-- - } - - for syn > 0 && isspace(data[syntaxStart+syn-1]) { - syn-- - } - - i++ - } else { - for i < len(data) && !isspace(data[i]) { - syn++ - i++ - } - } - - *syntax = string(data[syntaxStart : syntaxStart+syn]) - } - - i = skipChar(data, i, ' ') - if i >= len(data) || data[i] != '\n' { - if newlineOptional && i == len(data) { - return i, marker - } - return 0, "" - } - - return i + 1, marker // Take newline into account. -} - -// fencedCodeBlock returns the end index if data contains a fenced code block at the beginning, -// or 0 otherwise. It writes to out if doRender is true, otherwise it has no side effects. -// If doRender is true, a final newline is mandatory to recognize the fenced code block. -func (p *parser) fencedCodeBlock(out *bytes.Buffer, data []byte, doRender bool) int { - var syntax string - beg, marker := isFenceLine(data, &syntax, "", false) - if beg == 0 || beg >= len(data) { - return 0 - } - - var work bytes.Buffer - - for { - // safe to assume beg < len(data) - - // check for the end of the code block - newlineOptional := !doRender - fenceEnd, _ := isFenceLine(data[beg:], nil, marker, newlineOptional) - if fenceEnd != 0 { - beg += fenceEnd - break - } - - // copy the current line - end := skipUntilChar(data, beg, '\n') + 1 - - // did we reach the end of the buffer without a closing marker? - if end >= len(data) { - return 0 - } - - // verbatim copy to the working buffer - if doRender { - work.Write(data[beg:end]) - } - beg = end - } - - if doRender { - p.r.BlockCode(out, work.Bytes(), syntax) - } - - return beg -} - -func (p *parser) table(out *bytes.Buffer, data []byte) int { - var header bytes.Buffer - i, columns := p.tableHeader(&header, data) - if i == 0 { - return 0 - } - - var body bytes.Buffer - - for i < len(data) { - pipes, rowStart := 0, i - for ; data[i] != '\n'; i++ { - if data[i] == '|' { - pipes++ - } - } - - if pipes == 0 { - i = rowStart - break - } - - // include the newline in data sent to tableRow - i++ - p.tableRow(&body, data[rowStart:i], columns, false) - } - - p.r.Table(out, header.Bytes(), body.Bytes(), columns) - - return i -} - -// check if the specified position is preceded by an odd number of backslashes -func isBackslashEscaped(data []byte, i int) bool { - backslashes := 0 - for i-backslashes-1 >= 0 && data[i-backslashes-1] == '\\' { - backslashes++ - } - return backslashes&1 == 1 -} - -func (p *parser) tableHeader(out *bytes.Buffer, data []byte) (size int, columns []int) { - i := 0 - colCount := 1 - for i = 0; data[i] != '\n'; i++ { - if data[i] == '|' && !isBackslashEscaped(data, i) { - colCount++ - } - } - - // doesn't look like a table header - if colCount == 1 { - return - } - - // include the newline in the data sent to tableRow - header := data[:i+1] - - // column count ignores pipes at beginning or end of line - if data[0] == '|' { - colCount-- - } - if i > 2 && data[i-1] == '|' && !isBackslashEscaped(data, i-1) { - colCount-- - } - - columns = make([]int, colCount) - - // move on to the header underline - i++ - if i >= len(data) { - return - } - - if data[i] == '|' && !isBackslashEscaped(data, i) { - i++ - } - i = skipChar(data, i, ' ') - - // each column header is of form: / *:?-+:? *|/ with # dashes + # colons >= 3 - // and trailing | optional on last column - col := 0 - for data[i] != '\n' { - dashes := 0 - - if data[i] == ':' { - i++ - columns[col] |= TABLE_ALIGNMENT_LEFT - dashes++ - } - for data[i] == '-' { - i++ - dashes++ - } - if data[i] == ':' { - i++ - columns[col] |= TABLE_ALIGNMENT_RIGHT - dashes++ - } - for data[i] == ' ' { - i++ - } - - // end of column test is messy - switch { - case dashes < 3: - // not a valid column - return - - case data[i] == '|' && !isBackslashEscaped(data, i): - // marker found, now skip past trailing whitespace - col++ - i++ - for data[i] == ' ' { - i++ - } - - // trailing junk found after last column - if col >= colCount && data[i] != '\n' { - return - } - - case (data[i] != '|' || isBackslashEscaped(data, i)) && col+1 < colCount: - // something else found where marker was required - return - - case data[i] == '\n': - // marker is optional for the last column - col++ - - default: - // trailing junk found after last column - return - } - } - if col != colCount { - return - } - - p.tableRow(out, header, columns, true) - size = i + 1 - return -} - -func (p *parser) tableRow(out *bytes.Buffer, data []byte, columns []int, header bool) { - i, col := 0, 0 - var rowWork bytes.Buffer - - if data[i] == '|' && !isBackslashEscaped(data, i) { - i++ - } - - for col = 0; col < len(columns) && i < len(data); col++ { - for data[i] == ' ' { - i++ - } - - cellStart := i - - for (data[i] != '|' || isBackslashEscaped(data, i)) && data[i] != '\n' { - i++ - } - - cellEnd := i - - // skip the end-of-cell marker, possibly taking us past end of buffer - i++ - - for cellEnd > cellStart && data[cellEnd-1] == ' ' { - cellEnd-- - } - - var cellWork bytes.Buffer - p.inline(&cellWork, data[cellStart:cellEnd]) - - if header { - p.r.TableHeaderCell(&rowWork, cellWork.Bytes(), columns[col]) - } else { - p.r.TableCell(&rowWork, cellWork.Bytes(), columns[col]) - } - } - - // pad it out with empty columns to get the right number - for ; col < len(columns); col++ { - if header { - p.r.TableHeaderCell(&rowWork, nil, columns[col]) - } else { - p.r.TableCell(&rowWork, nil, columns[col]) - } - } - - // silently ignore rows with too many cells - - p.r.TableRow(out, rowWork.Bytes()) -} - -// returns blockquote prefix length -func (p *parser) quotePrefix(data []byte) int { - i := 0 - for i < 3 && data[i] == ' ' { - i++ - } - if data[i] == '>' { - if data[i+1] == ' ' { - return i + 2 - } - return i + 1 - } - return 0 -} - -// blockquote ends with at least one blank line -// followed by something without a blockquote prefix -func (p *parser) terminateBlockquote(data []byte, beg, end int) bool { - if p.isEmpty(data[beg:]) <= 0 { - return false - } - if end >= len(data) { - return true - } - return p.quotePrefix(data[end:]) == 0 && p.isEmpty(data[end:]) == 0 -} - -// parse a blockquote fragment -func (p *parser) quote(out *bytes.Buffer, data []byte) int { - var raw bytes.Buffer - beg, end := 0, 0 - for beg < len(data) { - end = beg - // Step over whole lines, collecting them. While doing that, check for - // fenced code and if one's found, incorporate it altogether, - // irregardless of any contents inside it - for data[end] != '\n' { - if p.flags&EXTENSION_FENCED_CODE != 0 { - if i := p.fencedCodeBlock(out, data[end:], false); i > 0 { - // -1 to compensate for the extra end++ after the loop: - end += i - 1 - break - } - } - end++ - } - end++ - - if pre := p.quotePrefix(data[beg:]); pre > 0 { - // skip the prefix - beg += pre - } else if p.terminateBlockquote(data, beg, end) { - break - } - - // this line is part of the blockquote - raw.Write(data[beg:end]) - beg = end - } - - var cooked bytes.Buffer - p.block(&cooked, raw.Bytes()) - p.r.BlockQuote(out, cooked.Bytes()) - return end -} - -// returns prefix length for block code -func (p *parser) codePrefix(data []byte) int { - if data[0] == ' ' && data[1] == ' ' && data[2] == ' ' && data[3] == ' ' { - return 4 - } - return 0 -} - -func (p *parser) code(out *bytes.Buffer, data []byte) int { - var work bytes.Buffer - - i := 0 - for i < len(data) { - beg := i - for data[i] != '\n' { - i++ - } - i++ - - blankline := p.isEmpty(data[beg:i]) > 0 - if pre := p.codePrefix(data[beg:i]); pre > 0 { - beg += pre - } else if !blankline { - // non-empty, non-prefixed line breaks the pre - i = beg - break - } - - // verbatim copy to the working buffeu - if blankline { - work.WriteByte('\n') - } else { - work.Write(data[beg:i]) - } - } - - // trim all the \n off the end of work - workbytes := work.Bytes() - eol := len(workbytes) - for eol > 0 && workbytes[eol-1] == '\n' { - eol-- - } - if eol != len(workbytes) { - work.Truncate(eol) - } - - work.WriteByte('\n') - - p.r.BlockCode(out, work.Bytes(), "") - - return i -} - -// returns unordered list item prefix -func (p *parser) uliPrefix(data []byte) int { - i := 0 - - // start with up to 3 spaces - for i < 3 && data[i] == ' ' { - i++ - } - - // need a *, +, or - followed by a space - if (data[i] != '*' && data[i] != '+' && data[i] != '-') || - data[i+1] != ' ' { - return 0 - } - return i + 2 -} - -// returns ordered list item prefix -func (p *parser) oliPrefix(data []byte) int { - i := 0 - - // start with up to 3 spaces - for i < 3 && data[i] == ' ' { - i++ - } - - // count the digits - start := i - for data[i] >= '0' && data[i] <= '9' { - i++ - } - - // we need >= 1 digits followed by a dot and a space - if start == i || data[i] != '.' || data[i+1] != ' ' { - return 0 - } - return i + 2 -} - -// returns definition list item prefix -func (p *parser) dliPrefix(data []byte) int { - i := 0 - - // need a : followed by a spaces - if data[i] != ':' || data[i+1] != ' ' { - return 0 - } - for data[i] == ' ' { - i++ - } - return i + 2 -} - -// parse ordered or unordered list block -func (p *parser) list(out *bytes.Buffer, data []byte, flags int) int { - i := 0 - flags |= LIST_ITEM_BEGINNING_OF_LIST - work := func() bool { - for i < len(data) { - skip := p.listItem(out, data[i:], &flags) - i += skip - - if skip == 0 || flags&LIST_ITEM_END_OF_LIST != 0 { - break - } - flags &= ^LIST_ITEM_BEGINNING_OF_LIST - } - return true - } - - p.r.List(out, work, flags) - return i -} - -// Parse a single list item. -// Assumes initial prefix is already removed if this is a sublist. -func (p *parser) listItem(out *bytes.Buffer, data []byte, flags *int) int { - // keep track of the indentation of the first line - itemIndent := 0 - for itemIndent < 3 && data[itemIndent] == ' ' { - itemIndent++ - } - - i := p.uliPrefix(data) - if i == 0 { - i = p.oliPrefix(data) - } - if i == 0 { - i = p.dliPrefix(data) - // reset definition term flag - if i > 0 { - *flags &= ^LIST_TYPE_TERM - } - } - if i == 0 { - // if in defnition list, set term flag and continue - if *flags&LIST_TYPE_DEFINITION != 0 { - *flags |= LIST_TYPE_TERM - } else { - return 0 - } - } - - // skip leading whitespace on first line - for data[i] == ' ' { - i++ - } - - // find the end of the line - line := i - for i > 0 && data[i-1] != '\n' { - i++ - } - - // get working buffer - var raw bytes.Buffer - - // put the first line into the working buffer - raw.Write(data[line:i]) - line = i - - // process the following lines - containsBlankLine := false - sublist := 0 - -gatherlines: - for line < len(data) { - i++ - - // find the end of this line - for data[i-1] != '\n' { - i++ - } - - // if it is an empty line, guess that it is part of this item - // and move on to the next line - if p.isEmpty(data[line:i]) > 0 { - containsBlankLine = true - raw.Write(data[line:i]) - line = i - continue - } - - // calculate the indentation - indent := 0 - for indent < 4 && line+indent < i && data[line+indent] == ' ' { - indent++ - } - - chunk := data[line+indent : i] - - // evaluate how this line fits in - switch { - // is this a nested list item? - case (p.uliPrefix(chunk) > 0 && !p.isHRule(chunk)) || - p.oliPrefix(chunk) > 0 || - p.dliPrefix(chunk) > 0: - - if containsBlankLine { - // end the list if the type changed after a blank line - if indent <= itemIndent && - ((*flags&LIST_TYPE_ORDERED != 0 && p.uliPrefix(chunk) > 0) || - (*flags&LIST_TYPE_ORDERED == 0 && p.oliPrefix(chunk) > 0)) { - - *flags |= LIST_ITEM_END_OF_LIST - break gatherlines - } - *flags |= LIST_ITEM_CONTAINS_BLOCK - } - - // to be a nested list, it must be indented more - // if not, it is the next item in the same list - if indent <= itemIndent { - break gatherlines - } - - // is this the first item in the nested list? - if sublist == 0 { - sublist = raw.Len() - } - - // is this a nested prefix header? - case p.isPrefixHeader(chunk): - // if the header is not indented, it is not nested in the list - // and thus ends the list - if containsBlankLine && indent < 4 { - *flags |= LIST_ITEM_END_OF_LIST - break gatherlines - } - *flags |= LIST_ITEM_CONTAINS_BLOCK - - // anything following an empty line is only part - // of this item if it is indented 4 spaces - // (regardless of the indentation of the beginning of the item) - case containsBlankLine && indent < 4: - if *flags&LIST_TYPE_DEFINITION != 0 && i < len(data)-1 { - // is the next item still a part of this list? - next := i - for data[next] != '\n' { - next++ - } - for next < len(data)-1 && data[next] == '\n' { - next++ - } - if i < len(data)-1 && data[i] != ':' && data[next] != ':' { - *flags |= LIST_ITEM_END_OF_LIST - } - } else { - *flags |= LIST_ITEM_END_OF_LIST - } - break gatherlines - - // a blank line means this should be parsed as a block - case containsBlankLine: - *flags |= LIST_ITEM_CONTAINS_BLOCK - } - - containsBlankLine = false - - // add the line into the working buffer without prefix - raw.Write(data[line+indent : i]) - - line = i - } - - // If reached end of data, the Renderer.ListItem call we're going to make below - // is definitely the last in the list. - if line >= len(data) { - *flags |= LIST_ITEM_END_OF_LIST - } - - rawBytes := raw.Bytes() - - // render the contents of the list item - var cooked bytes.Buffer - if *flags&LIST_ITEM_CONTAINS_BLOCK != 0 && *flags&LIST_TYPE_TERM == 0 { - // intermediate render of block item, except for definition term - if sublist > 0 { - p.block(&cooked, rawBytes[:sublist]) - p.block(&cooked, rawBytes[sublist:]) - } else { - p.block(&cooked, rawBytes) - } - } else { - // intermediate render of inline item - if sublist > 0 { - p.inline(&cooked, rawBytes[:sublist]) - p.block(&cooked, rawBytes[sublist:]) - } else { - p.inline(&cooked, rawBytes) - } - } - - // render the actual list item - cookedBytes := cooked.Bytes() - parsedEnd := len(cookedBytes) - - // strip trailing newlines - for parsedEnd > 0 && cookedBytes[parsedEnd-1] == '\n' { - parsedEnd-- - } - p.r.ListItem(out, cookedBytes[:parsedEnd], *flags) - - return line -} - -// render a single paragraph that has already been parsed out -func (p *parser) renderParagraph(out *bytes.Buffer, data []byte) { - if len(data) == 0 { - return - } - - // trim leading spaces - beg := 0 - for data[beg] == ' ' { - beg++ - } - - // trim trailing newline - end := len(data) - 1 - - // trim trailing spaces - for end > beg && data[end-1] == ' ' { - end-- - } - - work := func() bool { - p.inline(out, data[beg:end]) - return true - } - p.r.Paragraph(out, work) -} - -func (p *parser) paragraph(out *bytes.Buffer, data []byte) int { - // prev: index of 1st char of previous line - // line: index of 1st char of current line - // i: index of cursor/end of current line - var prev, line, i int - - // keep going until we find something to mark the end of the paragraph - for i < len(data) { - // mark the beginning of the current line - prev = line - current := data[i:] - line = i - - // did we find a blank line marking the end of the paragraph? - if n := p.isEmpty(current); n > 0 { - // did this blank line followed by a definition list item? - if p.flags&EXTENSION_DEFINITION_LISTS != 0 { - if i < len(data)-1 && data[i+1] == ':' { - return p.list(out, data[prev:], LIST_TYPE_DEFINITION) - } - } - - p.renderParagraph(out, data[:i]) - return i + n - } - - // an underline under some text marks a header, so our paragraph ended on prev line - if i > 0 { - if level := p.isUnderlinedHeader(current); level > 0 { - // render the paragraph - p.renderParagraph(out, data[:prev]) - - // ignore leading and trailing whitespace - eol := i - 1 - for prev < eol && data[prev] == ' ' { - prev++ - } - for eol > prev && data[eol-1] == ' ' { - eol-- - } - - // render the header - // this ugly double closure avoids forcing variables onto the heap - work := func(o *bytes.Buffer, pp *parser, d []byte) func() bool { - return func() bool { - pp.inline(o, d) - return true - } - }(out, p, data[prev:eol]) - - id := "" - if p.flags&EXTENSION_AUTO_HEADER_IDS != 0 { - id = SanitizedAnchorName(string(data[prev:eol])) - } - - p.r.Header(out, work, level, id) - - // find the end of the underline - for data[i] != '\n' { - i++ - } - return i - } - } - - // if the next line starts a block of HTML, then the paragraph ends here - if p.flags&EXTENSION_LAX_HTML_BLOCKS != 0 { - if data[i] == '<' && p.html(out, current, false) > 0 { - // rewind to before the HTML block - p.renderParagraph(out, data[:i]) - return i - } - } - - // if there's a prefixed header or a horizontal rule after this, paragraph is over - if p.isPrefixHeader(current) || p.isHRule(current) { - p.renderParagraph(out, data[:i]) - return i - } - - // if there's a fenced code block, paragraph is over - if p.flags&EXTENSION_FENCED_CODE != 0 { - if p.fencedCodeBlock(out, current, false) > 0 { - p.renderParagraph(out, data[:i]) - return i - } - } - - // if there's a definition list item, prev line is a definition term - if p.flags&EXTENSION_DEFINITION_LISTS != 0 { - if p.dliPrefix(current) != 0 { - return p.list(out, data[prev:], LIST_TYPE_DEFINITION) - } - } - - // if there's a list after this, paragraph is over - if p.flags&EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK != 0 { - if p.uliPrefix(current) != 0 || - p.oliPrefix(current) != 0 || - p.quotePrefix(current) != 0 || - p.codePrefix(current) != 0 { - p.renderParagraph(out, data[:i]) - return i - } - } - - // otherwise, scan to the beginning of the next line - for data[i] != '\n' { - i++ - } - i++ - } - - p.renderParagraph(out, data[:i]) - return i -} - -// SanitizedAnchorName returns a sanitized anchor name for the given text. -// -// It implements the algorithm specified in the package comment. -func SanitizedAnchorName(text string) string { - var anchorName []rune - futureDash := false - for _, r := range text { - switch { - case unicode.IsLetter(r) || unicode.IsNumber(r): - if futureDash && len(anchorName) > 0 { - anchorName = append(anchorName, '-') - } - futureDash = false - anchorName = append(anchorName, unicode.ToLower(r)) - default: - futureDash = true - } - } - return string(anchorName) -} diff --git a/vendor/github.com/russross/blackfriday/doc.go b/vendor/github.com/russross/blackfriday/doc.go deleted file mode 100644 index 9656c42a191..00000000000 --- a/vendor/github.com/russross/blackfriday/doc.go +++ /dev/null @@ -1,32 +0,0 @@ -// Package blackfriday is a Markdown processor. -// -// It translates plain text with simple formatting rules into HTML or LaTeX. -// -// Sanitized Anchor Names -// -// Blackfriday includes an algorithm for creating sanitized anchor names -// corresponding to a given input text. This algorithm is used to create -// anchors for headings when EXTENSION_AUTO_HEADER_IDS is enabled. The -// algorithm is specified below, so that other packages can create -// compatible anchor names and links to those anchors. -// -// The algorithm iterates over the input text, interpreted as UTF-8, -// one Unicode code point (rune) at a time. All runes that are letters (category L) -// or numbers (category N) are considered valid characters. They are mapped to -// lower case, and included in the output. All other runes are considered -// invalid characters. Invalid characters that preceed the first valid character, -// as well as invalid character that follow the last valid character -// are dropped completely. All other sequences of invalid characters -// between two valid characters are replaced with a single dash character '-'. -// -// SanitizedAnchorName exposes this functionality, and can be used to -// create compatible links to the anchor names generated by blackfriday. -// This algorithm is also implemented in a small standalone package at -// github.com/shurcooL/sanitized_anchor_name. It can be useful for clients -// that want a small package and don't need full functionality of blackfriday. -package blackfriday - -// NOTE: Keep Sanitized Anchor Name algorithm in sync with package -// github.com/shurcooL/sanitized_anchor_name. -// Otherwise, users of sanitized_anchor_name will get anchor names -// that are incompatible with those generated by blackfriday. diff --git a/vendor/github.com/russross/blackfriday/html.go b/vendor/github.com/russross/blackfriday/html.go deleted file mode 100644 index 74e67ee82bd..00000000000 --- a/vendor/github.com/russross/blackfriday/html.go +++ /dev/null @@ -1,949 +0,0 @@ -// -// Blackfriday Markdown Processor -// Available at http://github.com/russross/blackfriday -// -// Copyright © 2011 Russ Ross . -// Distributed under the Simplified BSD License. -// See README.md for details. -// - -// -// -// HTML rendering backend -// -// - -package blackfriday - -import ( - "bytes" - "fmt" - "regexp" - "strconv" - "strings" -) - -// Html renderer configuration options. -const ( - HTML_SKIP_HTML = 1 << iota // skip preformatted HTML blocks - HTML_SKIP_STYLE // skip embedded