diff --git a/.github/ISSUE_TEMPLATE/01_bug.md b/.github/ISSUE_TEMPLATE/01_bug.md new file mode 100644 index 0000000..0c12e2d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/01_bug.md @@ -0,0 +1,43 @@ +--- +name: "\U0001F6A8 Bug" +about: Did you come across a bug or unexpected behaviour differing from the docs? +labels: bug +--- + + + +## Describe the bug + + + +## Expected behaviour + + + +## Steps to reproduce the issue + + + + + +## Technical details + +- Host Machine OS (Windows/Linux/Mac): + +## Possible Fix + + + +## Additional context + + diff --git a/.github/ISSUE_TEMPLATE/02_feature_request.md b/.github/ISSUE_TEMPLATE/02_feature_request.md new file mode 100644 index 0000000..ad11b6b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/02_feature_request.md @@ -0,0 +1,31 @@ +--- +name: "\U0001F381 Feature Request" +about: Do you have an idea for a new feature? +labels: feature request +--- + + + +## Feature description + + + +## Problem and motivation + + + +## Is this something you're interested in working on + + diff --git a/.github/ISSUE_TEMPLATE/03_enhancement.md b/.github/ISSUE_TEMPLATE/03_enhancement.md new file mode 100644 index 0000000..bbdc3b7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/03_enhancement.md @@ -0,0 +1,30 @@ +--- +name: "\u23F1\uFE0F Enhancement" +about: Do you have an idea for an enhancement? +labels: enhancement +--- + + + +## Current Implementation + + + +## Suggested Enhancement + + + +## Expected Benefits + + diff --git a/.github/ISSUE_TEMPLATE/04_question.md b/.github/ISSUE_TEMPLATE/04_question.md new file mode 100644 index 0000000..2be9b92 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/04_question.md @@ -0,0 +1,20 @@ +--- +name: "\U00002753 Question" +about: If you have questions about pieces of the code or documentation for this component, please post them here. +labels: question +--- + + + +## Your Question + + + +* Source File: +* Line(s): +* Question: diff --git a/.github/ISSUE_TEMPLATE/05_other.md b/.github/ISSUE_TEMPLATE/05_other.md new file mode 100644 index 0000000..102f771 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/05_other.md @@ -0,0 +1,12 @@ +--- +name: "\U0001F4AC Other" +about: For conceptual questions, please consider opening an issue in the documentation repository. +labels: other +--- + + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..3ba13e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.github/workflows/ci-dockerfile.yml b/.github/workflows/ci-dockerfile.yml new file mode 100644 index 0000000..cdf701e --- /dev/null +++ b/.github/workflows/ci-dockerfile.yml @@ -0,0 +1,21 @@ +name: ci-dockerfile +on: + push: + branches: + - master + paths: + - Dockerfile + pull_request: + types: + - opened + - synchronize + - reopened + paths: + - Dockerfile +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: docker pull hadolint/hadolint + - run: docker run --rm --interactive hadolint/hadolint < Dockerfile diff --git a/.github/workflows/ci-master.yml b/.github/workflows/ci-master.yml new file mode 100644 index 0000000..bf81a7d --- /dev/null +++ b/.github/workflows/ci-master.yml @@ -0,0 +1,51 @@ +name: ci-master +on: + push: + branches: + - master +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: environment + run: | + export VERSION=$(git rev-parse --short ${GITHUB_SHA}) + echo "VERSION=${VERSION}" >> $GITHUB_ENV + - name: docker build + run: | + docker build \ + --tag docker.pkg.github.com/${GITHUB_REPOSITORY}/cwa-log-upload:latest \ + --tag docker.pkg.github.com/${GITHUB_REPOSITORY}/cwa-log-upload:${VERSION} \ + --tag ${TRUSTED_URL}/${TRUSTED_REPOSITORY}/cwa-log-upload:${VERSION} \ + . + env: + TRUSTED_URL: ${{ secrets.TRUSTED_URL }} + TRUSTED_REPOSITORY: ${{ secrets.TRUSTED_REPOSITORY }} + - name: docker push github + run: | + echo ${GITHUB_TOKEN} | docker login docker.pkg.github.com -u ${GITHUB_REPOSITORY_OWNER} --password-stdin + docker push docker.pkg.github.com/${GITHUB_REPOSITORY}/cwa-log-upload:latest + docker push docker.pkg.github.com/${GITHUB_REPOSITORY}/cwa-log-upload:${VERSION} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: docker push trusted + run: | + echo ${TRUSTED_TOKEN} | docker login ${TRUSTED_URL} -u ${TRUSTED_USER} --password-stdin + export DOCKER_CONTENT_TRUST=1 + export DOCKER_CONTENT_TRUST_SERVER=${TRUSTED_SERVER_URL} + export DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE=${TRUSTED_TOKEN} + export DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE=${TRUSTED_TOKEN} + gpg --quiet --batch --yes --decrypt --passphrase=${TRUSTED_KEY} \ + --output trusted.key trusted.key.gpg + chmod 600 trusted.key + docker trust key load trusted.key --name user + docker trust sign ${TRUSTED_URL}/${TRUSTED_REPOSITORY}/cwa-log-upload:${VERSION} + docker push ${TRUSTED_URL}/${TRUSTED_REPOSITORY}/cwa-log-upload:${VERSION} + env: + TRUSTED_KEY: ${{ secrets.TRUSTED_KEY }} + TRUSTED_URL: ${{ secrets.TRUSTED_URL }} + TRUSTED_SERVER_URL: ${{ secrets.TRUSTED_SERVER_URL }} + TRUSTED_REPOSITORY: ${{ secrets.TRUSTED_REPOSITORY }} + TRUSTED_USER: ${{ secrets.TRUSTED_USER }} + TRUSTED_TOKEN: ${{ secrets.TRUSTED_TOKEN }} diff --git a/.github/workflows/ci-pull-request.yml b/.github/workflows/ci-pull-request.yml new file mode 100644 index 0000000..20c9113 --- /dev/null +++ b/.github/workflows/ci-pull-request.yml @@ -0,0 +1,14 @@ +name: ci-pull-request +on: + pull_request: + types: + - opened + - synchronize + - reopened +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: docker build + run: docker build . diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a3e3bd2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ + +build.sh +trusted.key diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..10a8e59 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,8 @@ +# This file provides an overview of code owners in this repository. + +# Each line is a file pattern followed by one or more owners. +# The last matching pattern has the most precedence. +# For more details, read the following article on GitHub: https://help.github.com/articles/about-codeowners/. + +# These are the default owners for the whole content of this repository. The default owners are automatically added as reviewers when you open a pull request, unless different owners are specified in the file. +* @alstiefel @dfischer-tech @f11h @jhagestedt @lstelzne-tech @mlaue-tech @mschulte-tsi @tence @kreincke @ascheibal @lbenthins @ChristianFirmenich @BugBuster1701 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..d8d1b36 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,105 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for +everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity +and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, +or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take +appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, +issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for +moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing +the community in public spaces. Examples of representing our community include using an official e-mail address, posting +via an official social media account, or acting as an appointed representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible +for enforcement at +[cwa-opensource@telekom.de](mailto:cwa-opensource@telekom.de). All complaints will be reviewed and investigated promptly +and fairly. + +All community leaders are obligated to respect the privacy and security of the reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem +in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the +community. + +**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation +and an explanation of why the behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of actions. + +**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including +unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding +interactions in community spaces as well as external channels like social media. Violating these terms may lead to a +temporary or permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified +period of time. No public or private interaction with the people involved, including unsolicited interaction with those +enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate +behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired +by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e7f4378 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,97 @@ +# Contributing + +## Code of conduct + +All members of the project community must abide by the [Contributor Covenant, version 2.0](CODE_OF_CONDUCT.md). Only by +respecting each other can we develop a productive, collaborative community. Instances of abusive, harassing, or +otherwise unacceptable behavior may be reported by +contacting [cwa-opensource@telekom.de](mailto:cwa-opensource@telekom.de) and/or a project maintainer. + +We appreciate your courtesy of avoiding political questions here. Issues which are not related to the project itself +will be closed by our community managers. + +## Engaging in our project + +We use GitHub to manage reviews of pull requests. + +* If you are a new contributor, see: [Steps to Contribute](#steps-to-contribute) + +* If you have a trivial fix or improvement, go ahead and create a pull request, addressing (with `@...`) a suitable + maintainer of this repository (see [CODEOWNERS](CODEOWNERS) of the repository you want to contribute to) in the + description of the pull request. + +* If you plan to do something more involved, please reach out to us and send + an [email](mailto:cwa-opensource@telekom.de). This will avoid unnecessary work and surely give you and us a good deal + of inspiration. + +* Relevant coding style guidelines are available in the respective sub-repositories as they are programming + language-dependent. + +## Steps to Contribute + +Should you wish to work on an issue, please claim it first by commenting on the GitHub issue that you want to work on. +This is to prevent duplicated efforts from other contributors on the same issue. + +If you have questions about one of the issues, please comment on them, and one of the maintainers will clarify. + +We kindly ask you to follow the [Pull Request Checklist](#Pull-Request-Checklist) to ensure reviews can happen +accordingly. + +## Contributing Code + +You are welcome to contribute code in order to fix a bug or to implement a new feature. + +The following rule governs code contributions: + +* Contributions must be licensed under the [Apache 2.0 License](LICENSE) +* Newly created files must be opened by an instantiated version of the file 'templates/file-header.txt' +* At least if you add a new file to the repository, add your name into the contributor section of the file NOTICE ( + please respect the preset entry structure) + +## Contributing Documentation + +You are welcome to contribute documentation to the project. + +The following rule governs documentation contributions: + +* Contributions must be licensed under the same license as code, the [Apache 2.0 License](LICENSE) + +## Pull Request Checklist + +* Branch from the master branch and, if needed, rebase to the current master branch before submitting your pull request. + If it doesn't merge cleanly with master you may be asked to rebase your changes. + +* Commits should be as small as possible while ensuring that each commit is correct independently (i.e., each commit + should compile and pass tests). + +* Test your changes as thoroughly as possible before you commit them. Preferably, automate your test by unit/integration + tests. If tested manually, provide information about the test scope in the PR description (e.g. “Test passed: Upgrade + version from 0.42 to 0.42.23.”). + +* Create _Work In Progress [WIP]_ pull requests only if you need clarification or an explicit review before you can + continue your work item. + +* If your patch is not getting reviewed or you need a specific person to review it, you can @-reply a reviewer asking + for a review in the pull request or a comment, or you can ask for a review by contacting us + via [email](mailto:cwa-opensource@telekom.de). + +* Post review: + * If a review requires you to change your commit(s), please test the changes again. + * Amend the affected commit(s) and force push onto your branch. + * Set respective comments in your GitHub review to resolved. + * Create a general PR comment to notify the reviewers that your amendments are ready for another round of review. + +## Issues and Planning + +* We use GitHub issues to track bugs and enhancement requests. + +* Please provide as much context as possible when you open an issue. The information you provide must be comprehensive + enough to reproduce that issue for the assignee. Therefore, contributors may use but aren't restricted to the issue + template provided by the project maintainers. + +* When creating an issue, try using one of our issue templates which already contain some guidelines on which content is + expected to process the issue most efficiently. If no template applies, you can of course also create an issue from + scratch. + +* Please apply one or more applicable [labels](https://github.com/corona-warn-app/cwa-documentation/labels) to your + issue so that all community members are able to cluster the issues better. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4cf26c3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM maven:3-openjdk-11 as build + +COPY . . +RUN mvn clean install + + +FROM gcr.io/distroless/java-debian10:11 as run + +COPY --from=build ./target/*.jar /app.jar +#COPY scripts/Dpkg.java Dpkg.java +#RUN ["java", "Dpkg.java"] +USER 65534:65534 +CMD ["app.jar"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + 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/NOTICE b/NOTICE new file mode 100644 index 0000000..23a0b69 --- /dev/null +++ b/NOTICE @@ -0,0 +1,17 @@ +Copyright (c) 2020 Deutsche Telekom AG. + +This project is licensed under Apache License, Version 2.0; +you may not use them except in compliance with the License. + +Contributors: +------------- + +Karsten Reincke [kreincke], Deutsche Telekom AG +Daniel Eder [daniel-eder], T-Mobile International Austria GmbH +Dominik Fischer [dfischer-tech], T-Systems International GmbH +Julien Hagestedt [jhagestedt], T-Systems International GmbH +Maximilian Laue [mlaue-tech], T-Systems International GmbH +Andreas Scheibal [ascheibal], T-Systems International GmbH +Michael Schulte [mschulte-tsi], T-Systems International GmbH +Lars Stelzner [lstelzne-tech], T-Systems International GmbH +Andreas Mandel [amandel], T-Systems International GmbH diff --git a/README.md b/README.md new file mode 100644 index 0000000..dfee6d2 --- /dev/null +++ b/README.md @@ -0,0 +1,207 @@ +

+ Corona-Warn-App Log Upload +

+ +

+ + + +

+ +

+ Development • + Documentation • + Support • + Contribute • + Contributors • + Repositories • + Licensing +

+ +The goal of this project is to develop the official Corona-Warn-App for Germany based on the exposure notification API +from [Apple](https://www.apple.com/covid19/contacttracing/) +and [Google](https://www.google.com/covid19/exposurenotifications/). The apps (for both iOS and Android) use Bluetooth +technology to exchange anonymous encrypted data with other mobile phones (on which the app is also installed) in the +vicinity of an app user's phone. The data is stored locally on each user's device, preventing authorities or other +parties from accessing or controlling the data. This repository contains the **log upload** for the Corona-Warn-App. + +## Status + +![ci](https://github.com/corona-warn-app/cwa-log-upload/workflows/ci/badge.svg) +[![quality gate](https://sonarcloud.io/api/project_badges/measure?project=corona-warn-app_cwa-log-upload&metric=alert_status)](https://sonarcloud.io/dashboard?id=corona-warn-app_cwa-log-upload) +[![coverage](https://sonarcloud.io/api/project_badges/measure?project=corona-warn-app_cwa-log-upload&metric=coverage)](https://sonarcloud.io/dashboard?id=corona-warn-app_cwa-log-upload) +[![bugs](https://sonarcloud.io/api/project_badges/measure?project=corona-warn-app_cwa-log-upload&metric=bugs)](https://sonarcloud.io/dashboard?id=corona-warn-app_cwa-log-upload) + +## About this component + +The log upload service is the counterpart of the log upload in the app. It enables the App developers to analyse the log data uploaded to the CWA infrastructure to indentify the root cause of bugs that only occur in rare conditions are not easy to reproduce. The log upload and viewer service follows the privacy preserving paradigm of the corona warn app, only allowing authorized personell to access the logs. The means to restrict access are the same that also protect TeleTAN generation from abusive usage. + +## Development + +This component can be built locally in order to test the functionality of the interfaces and verify the concepts it is +build upon. There are two ways to build: + +- [Maven](https:///maven.apache.org) build - to run this component as spring application on your local machine +- [Docker](https://www.docker.com) build - to run it as docker container build from the provided docker + build [file](https://github.com/corona-warn-app/cwa-log-upload/blob/master/Dockerfile) + +### Prerequisites + +[Open JDK 11](https://openjdk.java.net) +[Maven](https://maven.apache.org) +*(optional)*: [Docker](https://www.docker.com) + +### Build + +Whether you cloned or downloaded the 'zipped' sources you will either find the sources in the chosen checkout-directory +or get a zip file with the source code, which you can expand to a folder of your choice. + +In either case open a terminal pointing to the directory you put the sources in. The local build process is described +afterwards depending on the way you choose. + +#### Maven based build + +For actively take part on the development this is the way you should choose. +Please check, whether following prerequisites are fulfilled + +- [Open JDK 11](https://openjdk.java.net) or a similar JDK 11 compatible VM +- [Maven](https://maven.apache.org) + +is installed on your machine. +You can then open a terminal pointing to the root directory of the log upload and do the following: + + mvn package + java -jar target/cwa-log-upload-0.0.1-SNAPSHOT.jar + +The log upload will start up and run locally on your machine available on port 8081. Please keep in mind, that you need +another component [cwa-verification-iam] the get this running in a sensable manner. + +#### Docker based build + +We recommend that you first check the prerequisites to ensure that + +- [Docker](https://www.docker.com) + +is installed on your machine. + +On the command line do the following: + +```bash +docker build -f|--file -t +docker run -p 127.0.0.1:8081:8081/tcp -it +``` + +or simply + +```bash +docker build --pull --rm -f "Dockerfile" -t cwa-logupload "." +docker run -p 127.0.0.1:8081:8081/tcp -it cwa-logupload +``` + +if you are in the root of the checked out repository. +The docker image will then run on your local machine on port 8081 assuming you configured docker for shared network +mode. + +## Code of Conduct + +This project has adopted the [Contributor Covenant](https://www.contributor-covenant.org/) in version 2.0 as our code of +conduct. Please see the details in our [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). All contributors must abide by the code +of conduct. + +## Working Language + +We are building this application for Germany. We want to be as open and transparent as possible, also to interested +parties in the global developer community who do not speak German. Later on this application might also serve as a +template for other projects outside of Germany. For these reasons, we decided to apply _English_ as the primary project +language. + +Consequently, all content will be made available primarily in English. We also ask all interested people to use English +as language to create issues, in their code (comments, documentation etc.) and when you send requests to us. The +application itself, documentation and all end-user facing content will - of course - be made available in German (and +probably other languages as well). We also try to make some developer documentation available in German, but please +understand that focussing on the _Lingua Franca_ of the global developer community makes the development of this +application as efficient as possible. + +## Documentation + +The full documentation for the Corona-Warn-App can be found in +the [cwa-documentation](https://github.com/corona-warn-app/cwa-documentation) repository. The documentation repository +contains technical documents, architecture information, and white papers related to this implementation. + +## Support and Feedback + +The following channels are available for discussions, feedback, and support requests: + +| Type | Channel | +| ------------------------ | ------------------------------------------------------ | +| **General +Discussion** | | +| **Concept +Feedback** | | +| **Log Upload +Issue** | | +| **Other +Requests** | | + +## How to Contribute + +Contribution and feedback is encouraged and always welcome. For more information about how to contribute, the project +structure, as well as additional contribution information, see our [Contribution Guidelines](./CONTRIBUTING.md). By +participating in this project, you agree to abide by its [Code of Conduct](./CODE_OF_CONDUCT.md) at all times. + +## Contributors + +The German government has asked SAP AG and Deutsche Telekom AG to develop the Corona-Warn-App for Germany as open source +software. Deutsche Telekom is providing the network and mobile technology and will operate and run the backend for the +app in a safe, scalable and stable manner. SAP is responsible for the app development, its framework and the underlying +platform. Therefore, development teams of SAP and Deutsche Telekom are contributing to this project. At the same time +our commitment to open source means that we are enabling -in fact encouraging- all interested parties to contribute and +become part of its developer community. + +## Repositories + +| Repository | Description | +| ------------------- | --------------------------------------------------------------------- | +| [cwa-documentation] | Project overview, general documentation, and white papers. | +| [cwa-app-ios] | Native iOS app using the Apple/Google exposure notification API. | +| [cwa-app-android] | Native Android app using the Apple/Google exposure notification API. | +| [cwa-wishlist] | Community feature requests. | +| [cwa-website] | The official website for the Corona-Warn-App | +| [cwa-server] | Backend implementation for the Apple/Google exposure notification API.| +| [cwa-verification-server] | Backend implementation of the verification process. | +| [cwa-verification-portal] | The portal to interact with the verification server | +| [cwa-verification-iam] | The identity and access management to interact with the verification server | +| [cwa-testresult-server] | Receives the test results from connected laboratories | + +[cwa-documentation]: https://github.com/corona-warn-app/cwa-documentation + +[cwa-app-ios]: https://github.com/corona-warn-app/cwa-app-ios + +[cwa-app-android]: https://github.com/corona-warn-app/cwa-app-android + +[cwa-wishlist]: https://github.com/corona-warn-app/cwa-wishlist + +[cwa-website]: https://github.com/corona-warn-app/cwa-website + +[cwa-server]: https://github.com/corona-warn-app/cwa-server + +[cwa-verification-server]: https://github.com/corona-warn-app/cwa-verification-server + +[cwa-verification-portal]: https://github.com/corona-warn-app/cwa-verification-portal + +[cwa-verification-iam]: https://github.com/corona-warn-app/cwa-verification-iam + +[cwa-testresult-server]: https://github.com/corona-warn-app/cwa-testresult-server + +## Licensing + +Copyright (c) 2020 Deutsche Telekom AG. + +Licensed under the **Apache License, Version 2.0** (the "License"); you may not use this file except in compliance with +the License. + +You may obtain a copy of the License at https://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](./LICENSE) for +the specific language governing permissions and limitations under the License. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..32a1ad8 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,11 @@ +# Reporting Security Vulnerabilities + +The Corona-Warn-App is built with security and data privacy in mind to ensure your data is safe. We are grateful for +security researchers and users reporting a vulnerability to us, first. To ensure that your request is handled in a +timely manner and non-disclosure of vulnerabilities can be assured, please follow the below guideline. + +**Please do not report security vulnerabilities directly on GitHub. GitHub Issues can be publicly seen and therefore +would result in a direct disclosure.** + +* Please address questions about data privacy, security concepts, and other media requests to the cert@telekom.de + mailbox. diff --git a/THIRD-PARTY-NOTICES b/THIRD-PARTY-NOTICES new file mode 100644 index 0000000..a5b8fa6 --- /dev/null +++ b/THIRD-PARTY-NOTICES @@ -0,0 +1,661 @@ +ThirdPartyNotices +----------------- +corona-warn-app/cwa-log-upload uses third-party software or other resources that +may be distributed under licenses different from corona-warn-app/cwa-log-upload +software. +In the event that we overlooked to list a required notice, please bring this +to our attention by contacting us via this email: +opensource@telekom.de + + +Components: +----------- + +Component: Apache Commons Codec +Licensor: The Apache Software Foundation +Website: https://commons.apache.org +License: Apache License 2.0 + +Component: H2Database +Licensor: H2 +Website: http://www.h2database.com/ +License: Mozilla Public License 2.0 + +Component: Liquibase +Licensor: Datical, Inc +Website: https://www.liquibase.org/ +License: Apache License 2.0 + +Component: Lombok +Licensor: The Project Lombok +Website: https://projectlombok.org/ +License: MIT license + +Component: Mockito +Licensor: Apache Software Foundation +Website: https://site.mockito.org/ +License: MIT License + +Component: Maven +Licensor: Apache Software Foundation +Website: https://maven.apache.org/ +License: Apache License 2.0 + +Component: PostgreSQL +Licensor: PostgreSQL +Website: https://www.postgresql.org/ +License: PostgreSQL License + +Component: Spring Boot +Licensor: VMWare Inc. +Website: https://spring.io/ +License: Apache License 2.0 + +Component: Spring Doc +Licensor: springdoc-openapi +Website: https://springdoc.org/ +License: Apache License 2.0 + +-------------------------------------------------------------------------------- +Apache License 2.0 (Apache Commons Codec, Maven, Spring Boot, Spring Doc) + + Apache License + Version 2.0, January 2004 + https://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 +-------------------------------------------------------------------------------- +BSD License (dom4j) + +Copyright 2001-2016 (C) MetaStuff, Ltd. and DOM4J contributors. All Rights Reserved. + +Redistribution and use of this software and associated documentation +("Software"), with or without modification, are permitted provided +that the following conditions are met: + +1. Redistributions of source code must retain copyright + statements and notices. Redistributions must also contain a + copy of this document. + +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. + +3. The name "DOM4J" must not be used to endorse or promote + products derived from this Software without prior written + permission of MetaStuff, Ltd. For written permission, + please contact dom4j-info@metastuff.com. + +4. Products derived from this Software may not be called "DOM4J" + nor may "DOM4J" appear in their names without prior written + permission of MetaStuff, Ltd. DOM4J is a registered + trademark of MetaStuff, Ltd. + +5. Due credit should be given to the DOM4J Project - https://dom4j.github.io/ + +THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESSED 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 +METASTUFF, LTD. OR ITS 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. +-------------------------------------------------------------------------------- +3-Clause BSD License (Protocol Buffers) + +Copyright 2008 Google Inc. 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. + +Code generated by the Protocol Buffer compiler is owned by the owner +of the input file used when generating it. This code is not +standalone and requires a support library to be linked with it. This +support library is itself covered by the above license. +-------------------------------------------------------------------------------- +PostgreSQL License (PostgreSQL) + +PostgreSQL Database Management System +(formerly known as Postgres, then as Postgres95) + +Portions Copyright © 1996-2020, The PostgreSQL Global Development Group + +Portions Copyright © 1994, The Regents of the University of California + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose, without fee, and without a written agreement is +hereby granted, provided that the above copyright notice and this paragraph and +the following two paragraphs appear in all copies. + +IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR +DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST +PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF +THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND +THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, +UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +-------------------------------------------------------------------------------- +Mozilla Public License 2.0 (H2Database) + +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 + +that the initial Contributor has attached the notice described in Exhibit B to +the Covered Software; or + +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: + +any file in Source Code Form that results from an addition to, deletion from, +or modification of the contents of Covered Software; or + +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: + +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 + +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: + +for any code that a Contributor has removed from Covered Software; or + +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 + +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: + +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 + +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 https://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. +-------------------------------------------------------------------------------- +MIT License (Lombok, Mockito) + +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/codestyle/checkstyle.xml b/codestyle/checkstyle.xml new file mode 100644 index 0000000..efd8abd --- /dev/null +++ b/codestyle/checkstyle.xml @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/compose/certs/bundle.p12 b/compose/certs/bundle.p12 new file mode 100644 index 0000000..81e4641 Binary files /dev/null and b/compose/certs/bundle.p12 differ diff --git a/compose/certs/ca-root.p12 b/compose/certs/ca-root.p12 new file mode 100644 index 0000000..29dd476 Binary files /dev/null and b/compose/certs/ca-root.p12 differ diff --git a/compose/certs/cert.crt b/compose/certs/cert.crt new file mode 100644 index 0000000..726f2e0 --- /dev/null +++ b/compose/certs/cert.crt @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE/TCCAuWgAwIBAgIUKmskqXg5AcxuyWV0W4EikHHD9fwwDQYJKoZIhvcNAQEL +BQAwgY4xCzAJBgNVBAYTAkRFMQ4wDAYDVQQIDAVIZXNzZTESMBAGA1UEBwwJRnJh +bmtmdXJ0MSUwIwYDVQQKDBxULVN5c3RlbXMgSW50ZXJuYXRpb25hbCBHbWJIMRow +GAYDVQQLDBFEaWdpdGFsIFNvbHV0aW9uczEYMBYGA1UEAwwPRVBTIERFViBST09U +IENBMB4XDTIwMTIwOTE4MzgwNloXDTIyMTEyOTE4MzgwNlowgaAxCzAJBgNVBAYT +AkRFMQ4wDAYDVQQIDAVIZXNzZTEaMBgGA1UEBwwRRnJhbmtmdXJ0IGFtIE1haW4x +JTAjBgNVBAoMHFQtU3lzdGVtcyBJbnRlcm5hdGlvbmFsIEdtYkgxGjAYBgNVBAsM +EURpZ2l0YWwgU29sdXRpb25zMSIwIAYDVQQDDBlFUFMgTG9jYWxob3N0IERldmVs +b3BtZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1J7ZAU3HGToX +jLQpUp0BC8JmmDjDyvmNQhoQZvIXlwLc6OTruCp5+le2zukiPB57ZVIWrjD7pkc7 +j21l3MsNjoolkX+33jL0jOBpNyvCwQHT/5hLy4jDWXYIvHFydb7kThC2vjwhn9jA ++DsL0csinMgMu1ysJDRFS/GcR+PSblGqkFWAfYmeROQdClD4DTo36sfuMQ6+vsnO +TsjEUaBOkoi0FYGSd5k6XP+zzYNGDjDU8jeZdaa3RvWVIKKli9Vx8kViYHPN0xTm +9OWt15+PHBu5U1ETNFXQkaVtpqAQvsoz8DqMYiptjn7G+KZ8+6T358q55SlhhN5+ +JZsERfby5wIDAQABoz8wPTALBgNVHQ8EBAMCBeAwEwYDVR0lBAwwCgYIKwYBBQUH +AwEwGQYDVR0RBBIwEIIJbG9jYWxob3N0ggNpYW0wDQYJKoZIhvcNAQELBQADggIB +AIr1JgyqFMGQ7dGOJJRN7RdVfYjj2uVRUFH/L41gzXr+FWF61KIaI9I9USdhGWT6 +RYvXsuPE28BHNrRAd34wS8cLYqGqszcIQ9SWVfBQ8qYn2JgrlutCkcH6oFe7lUkZ +JsaPHdufWVgWrqxhPZlg6Tq855eRTp4ip3Nw0skaI3210nTyqamO6Sbh0xWmbT9M +oP+ElBY8veAiUIDHJ7IM3V/wXl7lpMmDNa2JzZexLT9zg/jN8zqA1kJGD6lgc6uv +RLUbkbCv/G3w84DLEfMmSZfttfnHyTMhrNzI0LIJMWOJQXx+fH1PUAbDyPquNKJA +tDaYrcUDJmeck34KBcHi+qB+84ZfxCuJ1FlOSpsWfUUVBqMWbS6Cj7aCBBpSlVrg +SMts4gz40uCNIbBgVLop5jvTRyXyNNWYcrIGoKMou9ESr4OOXCSRtnX6WET7tN/K +wUU5jnhR/orIQgcv+3JelBXO6D4OkLfxHNoHo+eCYboQz4LhxlIGz29TdUrkNPNN +2s1FNZM0enRuP/OsC9ep/gX0gyP1yAe+br7RyVyNj9IukWB1xBQz9Dv8NpFbHzA1 +KZBfz7rI12m05QnS5m6C6lpQArrq9Rlr3elsDL7vjOg/368BRQWU99DKB6bMt7NN +z74aaDhWw8LDmvTLuKzjB2vCjnH5YcbX4ophI70oURq4 +-----END CERTIFICATE----- diff --git a/compose/certs/csr.pem b/compose/certs/csr.pem new file mode 100644 index 0000000..1ba10a7 --- /dev/null +++ b/compose/certs/csr.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIDNDCCAhwCAQAwgaAxCzAJBgNVBAYTAkRFMQ4wDAYDVQQIDAVIZXNzZTEaMBgG +A1UEBwwRRnJhbmtmdXJ0IGFtIE1haW4xJTAjBgNVBAoMHFQtU3lzdGVtcyBJbnRl +cm5hdGlvbmFsIEdtYkgxGjAYBgNVBAsMEURpZ2l0YWwgU29sdXRpb25zMSIwIAYD +VQQDDBlFUFMgTG9jYWxob3N0IERldmVsb3BtZW50MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA1J7ZAU3HGToXjLQpUp0BC8JmmDjDyvmNQhoQZvIXlwLc +6OTruCp5+le2zukiPB57ZVIWrjD7pkc7j21l3MsNjoolkX+33jL0jOBpNyvCwQHT +/5hLy4jDWXYIvHFydb7kThC2vjwhn9jA+DsL0csinMgMu1ysJDRFS/GcR+PSblGq +kFWAfYmeROQdClD4DTo36sfuMQ6+vsnOTsjEUaBOkoi0FYGSd5k6XP+zzYNGDjDU +8jeZdaa3RvWVIKKli9Vx8kViYHPN0xTm9OWt15+PHBu5U1ETNFXQkaVtpqAQvsoz +8DqMYiptjn7G+KZ8+6T358q55SlhhN5+JZsERfby5wIDAQABoE4wTAYJKoZIhvcN +AQkOMT8wPTALBgNVHQ8EBAMCBeAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwGQYDVR0R +BBIwEIIJbG9jYWxob3N0ggNpYW0wDQYJKoZIhvcNAQELBQADggEBAJLdlKhGm13L +RpXgvWqKHbRipBWQDilAorFw3LzPRtt8yqQibYR77cVVXGFwUDCbygcG3wmEHehl +6N++v9ZlcLryl3xgGg9La05vbzA5x1jMr5JnRtxxM8ndPZC8cTpYtaiPZ3bqc1NI +8D6vhy/mTpyeA2qTkuIYN6WWQKHfpEJTrGmnxKyi7/YmLS5YS0nCom2n40cQwhC6 +l+j408MTQ5YU3/pR1+IBb8+qF3OhyoceuSVQDW/bGbgOv2si1hm6iAiXRzgeAAWW +SnKPyR5HSEp+j+lsGU8kEjTAdvKWKeKQxHxKywVsA0Po1m54snlPkMJ47aXa1t8k +LIZREUE99eE= +-----END CERTIFICATE REQUEST----- diff --git a/compose/certs/private.pem b/compose/certs/private.pem new file mode 100644 index 0000000..66b8464 --- /dev/null +++ b/compose/certs/private.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDUntkBTccZOheM +tClSnQELwmaYOMPK+Y1CGhBm8heXAtzo5Ou4Knn6V7bO6SI8HntlUhauMPumRzuP +bWXcyw2OiiWRf7feMvSM4Gk3K8LBAdP/mEvLiMNZdgi8cXJ1vuROELa+PCGf2MD4 +OwvRyyKcyAy7XKwkNEVL8ZxH49JuUaqQVYB9iZ5E5B0KUPgNOjfqx+4xDr6+yc5O +yMRRoE6SiLQVgZJ3mTpc/7PNg0YOMNTyN5l1prdG9ZUgoqWL1XHyRWJgc83TFOb0 +5a3Xn48cG7lTURM0VdCRpW2moBC+yjPwOoxiKm2Ofsb4pnz7pPfnyrnlKWGE3n4l +mwRF9vLnAgMBAAECggEAHzQYwNo7R/aAkkO2dMtZ5fHnwJtEELvDAVp14cXRtHXV +GdDOzz5cnGLXD1KjlZMbpOkBLxs9M0/s68WwV1DtcmfPz6SgxVlRDoO4rUIc2Np9 +1FrzdLFjifOKoK09kQ4sz1AgoZaNkLnsyAFSYL2hHMU3KSAWm1GYgea5estyjIHn +HbHgg9CJL0VOV44nJIfNVFO5ezX1o10Dwn7WPogyLNKOZ5OX8EP4Gfbb/gRTu8Cb +3cmAGb7sSqAJMJH71lFAQfjLDN8bQ2lX1wZNLwTD4Tu7j59jNY2JK9carfouG8nq +P9za7OW3EXCmMj4Bxrw63qxOsM4Jd++w0ITmDrzAAQKBgQD30CkSYA+a5r4H5+la +EfLTVcBiRo9VLT1Xyl3dTK+Qf2fJgVun7o6Y1GWsxdBYyUKXO2FOzZ6V/V2JBsRH +ONEGXk+6fp3cKJEaPxTRuloPhZixzucrf/UJaQ8DxRiNrAI+1EUAb6ZkPng4x4WZ +euZSK4orscaMBsy4ZaQh/6bC5wKBgQDbpQ0fjPkOEqaLm/oY4StXsjoEW8skHeh5 +TGZpPsskCwSCnC+5We6scsAXMJLjC9rtbXHlbSDUUuatmG08g1MH5GuzVhfzMjDh +ukihM/NILAB6yPQGIQ+hfF6qDUOwsgB9jXoTwKwRo7daiBtMdawGs+qZq2iFzXaG +BhSgRlhQAQKBgQDoanFqy283TztgvM+tavH75QvW3+hj8Vb79E7OU7LA6czUPx9Q +Q7u3eFSXkrsR2kg7ADUGLHCUqZjE//Sr+4yG1YMfHMs/BUj5fbov7P+0WQ7ZjHxg +3tRY7BBp77tUe8NrW7gPbad3kuM8FVymko5a+HzQ+B2HjIUKWEF0pYmodwKBgGp8 +aZFbSj+9YQscUrMPL3Ez0hbc3e6jpVpxZNEvhVLzGHut9yE4bZMjiudzQDNdQwWK +4wYk0x69FmMhHAyTaoTQBDsMyU25jqNRKfcldZQO6SieFliGXMqgvlBZX/DhS4WT +OpUq+wEOS01T4VA/WGhjf6CCzojYJNczSXquwBABAoGBAOTJSpeTESZ+P7jDJr1J +lNdW/lUNJKexPIiv6uO2w/oYxETn5pPhI0G/2vNqmFeqzmzdhDrCwR/CMVKZ32Kv +riatYA7tH/QIollgathBMH3rG7i7T1u3UkwUJFmd1tQwQwpAbdPRBJVEPmAoVGNI +RjptdEMDdRvb0cIh/mkklecB +-----END PRIVATE KEY----- diff --git a/compose/docker-compose.yml b/compose/docker-compose.yml new file mode 100644 index 0000000..06b9dee --- /dev/null +++ b/compose/docker-compose.yml @@ -0,0 +1,39 @@ +version: '2' + +services: + iam: + image: cwa-iam + build: ../../cwa-verification-iam + ports: + - 8443:8443 + - 7443:7443 + volumes: + - ./certs/cert.crt:/etc/x509/https/tls.crt + - ./certs/private.pem:/etc/x509/https/tls.key + environment: + SERVER_PORT: 8443 + KEYCLOAK_USER: admin + KEYCLOAK_PASSWORD: admin + DB_VENDOR: postgres + DB_ADDR: iam_postgres + DB_PORT: 5432 + DB_USER: psqluser + DB_PASSWORD: psqlpassword + DB_DATABASE: iam + iam_postgres: + image: postgres + environment: + POSTGRES_PASSWORD: psqlpassword + POSTGRES_USER: psqluser + POSTGRES_DB: iam + minio: + image: minio/minio + command: server /data + volumes: + - bucket-data:/data + ports: + - 9000:9000 + +volumes: + bucket-data: + \ No newline at end of file diff --git a/doc/Overview.md b/doc/Overview.md new file mode 100644 index 0000000..2c10a14 --- /dev/null +++ b/doc/Overview.md @@ -0,0 +1,169 @@ +# Software Design Log upload service, Logviewer + + +## Introduction +This document describes the component Log upload service + Logviewer for the System “Corona Warn App”. In the world of the Corona Warn App the Log upload service + Log viewer helps to validate whether upload requests from the mobile App are valid or not. +This document links the overall system architecture with the software design of the Log upload service + Log viewer, it links user stories with implementation inside the Log upload service + Log viewer. +This document is intended to be read by people who want to get insights how verification works in detail, it is our guideline for implementation. + +# Overview +## Purpose of the Software System Component +The primary scope of the component is to enable users to upload logfiles collected on their devices to the cwa and give designated app developers access to them. + +## Context +The Log upload service + Log viewer works in conjunction with the Coronawarn.app Android and iPhone app. On user request, the phone collects runtime data, that are specific to the current phone that help to evaluate errors in rare conditions. + +## Core Entities +| Entity | Definition | +| ------------- |:-------------:| +| Log collection | component on the smart phone, that controlls and monitors the collection of log data and enables the user to transmit them | +| Log upload service | CWA component, that provides an interface for the logs to be uploaded and stores them on a object data storage in the open telekom cloud | +| TOTP | This is a onetime token that the clients sends in the http header. The log upload service can verify this token against the PPA Server for validity | +| Log viewer | The log viewer provides a log on interface to authenticate a user (app developer) and presents the uploaded logs, provides full text and regex search | +|CWA IAM| This is the identity and access management component, that is also used for the verification portal. It contains an additional group that will hold the developers who will have access to the log viewer | + +# Software Design + +## Privacy Constraints +The Log upload service receives zipped log data, which may contain certain information about the participation user + - ip address + - number of risk encounters + t.b.c + +### Measures +|ID| Measure |Comment| +| ------------- |:-------------:| -----:| +|M1| all data is enclosed in a zipped file and is stored in a obs bucket on the OTC, only a technical user can access this bucket | +|M2| periodical deletion of data | all data is deleted after an configurable amaout of time, the databse entries, as well as the corresponding OBS objects | +|M3| downloaded data is processed in the context of the webbrows, i. e. that the downloaded zipped logfiles will be deleted when the browser session is closed | + +## Measures to increase data privacy +### Separate Operation of log upload, log viewer Service and Authentification service +The log upload and log viewer will be handled as two seperate services. The may by that means have seperate routing and network configurtaion like ip whitelisting for the log viewer. + +### Logging +Logging will be reduced to indication of an successful data upload for the log upload service, and information of deletion The log viewer will have log + +## Important Assumptions + +## Actors +- **User/Coronawarn.app Nutzer**: Person, who is tested for SARS-CoV-2, is equipped with a smartphone, Corona Warn App installed +- **User/Backend Developer**: Person who reads log files to analyse seldom failure situations occuring on the corona.app user's phone + +## Big Picture - User Journeys +### User Journey Log upload + +Note: + +Steps: + + +### User Journey Log viewing + + Precondition: + +Steps + +## Supported User Stories + + +## Implemented Use Cases +### Use Case Log upload +API Endpoint: +- Method: POST /logs +Body: binary encoded multipart + +} +- Authentication: TOTP via http header +Steps: + +### Use Case Log view + +Steps + +### Use Case Delete data to keep data privacy high +Steps: +1. Delete all entities OBS objects older than 2 weeks +2. Delete all entities in database oder than 2 weeks + + + +# Data Model +The data model is persisted in a dedicated schema. +## Entity Logs +The entity logs represents the link to the zipped log files in OTC storage + +|Name| Not null| Type| Definition| +| ------------- |:-------------:| -----:| -----:| +|id| Y| String[32]| Primary key, row index| +|created_at| Y| Date| Timestamp when the entry is stored| +|filename| Y| String[100]| Filename | +|size| Y| long| Lenght of zipfile| +|hash |Y |String[32] | Hash | +|metadata| N| String[1000] | Metadata| + +### Data Deletion +All data is deleted after 14 days. + +# Security +## Authentication +### Authentication of users +|Role| Authentication |Comment| +| ------------- |:-------------:| -----:| +|Anonymous | None| | +|Corona Warn App Developer| TLS Client Certificate, 2nd factor | + +### Authorization of users +|Role| Authorization| Comment| +| ------------- |:-------------:| -----:| +|Anonymous| None| | +|Corona Warn App Developer| Authorization via username/password + TOTP | a user which is authenticated as Corona Warn App Developer is authorized to view all logs | + +## Threat Model +**_This chapter is still in work._** +### Threats +Based on STRIDE threat modelling, the threats below are anticipated: +|ID| Category| Name| Definition| +| ------------- |:-------------:| -----:| -----:| +|T1| Brute Force | Brute Force teleTAN| Try to guess a teleTAN via brute force attack.| +|T2| DDoS Attack| The API is attacked by a high number of requests, leading to an outage of the service| +|T3| Code injection| The payload and/or header contain code which is executed| +|T4| +|T5| Brute force attack| By a brute force attack a client wants to guess a valid GUID to create a valid TAN| +|T6| Steal secrets from logs | + +Categories follow STRIDE: +- Spoofing +- Tampering +- Repudiation +- Information disclosure (privacy breach or data leak) +- Denial of service +- Elevation of privilege + +### Measures + +|ID| Threat| Name| Definition| +| ------------- |:-------------:| -----:| -----:| +|MT1|||OTC DDoD Protection Infrastructure Level| +|MT2|||Strong input parameter verification, with 100% code coverage and very high amount of testing | +|MT3|||Enforcing TLS 1.2 and above | +|MT6|T2|| Use Open Telekom Cloud Anti-DDoS | +|MT7|T3|| Strict validation of http headers, body content | +|MT9|T5|| Use Throttling @ Code Level in API implementation to reduce the possible frequency of guessing attempts | +|MT10|T5|| Detect unusual load scenario and trigger warning for operation | +|MT11|T6|| Use only POST requests to avoid logging of secrets at infrastructure components| +|MT12||| Strict input validation, all REST input parameter are validated in a strict manner| + + + +## Used cryptographic algorithms +- Hashing of Registration Token: SHA-256, no salt, no pepper + +- Creating of Registration Token: the JAVA UUID generation (UUID.randomUUID()) is used, which relies on Java SecureRandom (see https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/security/SecureRandom.html ) is used. this random is considered as cryptographically strong random number generator +- Creating of TAN: the JAVA UUID generation (UUID.randomUUID()) is used, which relies on Java SecureRandom (see https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/security/SecureRandom.html ) is used. this random is considered as cryptographically strong random number generator +- Creating of teleTAN: string of random chars, as random java SecureRandom (see https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/security/SecureRandom.html ) is used. this random is considered as cryptographically strong random number generator + +## Complexity of secrets + + + diff --git a/doc/cwa-logviewer-deployment.png b/doc/cwa-logviewer-deployment.png new file mode 100644 index 0000000..98d573e Binary files /dev/null and b/doc/cwa-logviewer-deployment.png differ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..dae7e74 --- /dev/null +++ b/pom.xml @@ -0,0 +1,377 @@ + + + 4.0.0 + + app.coronawarn.logupload + cwa-log-upload + 1.2.0-SNAPSHOT + jar + + + cwa-log-upload + CWA Log Upload project. + + T-Systems International GmbH + + + https://www.coronawarn.app/ + + https://github.com/corona-warn-app/cwa-log-upload/actions?query=workflow%3Aci + + + https://github.com/corona-warn-app/cwa-log-upload/issues + + + https://github.com/corona-warn-app/cwa-log-upload + + + + + 11 + 11 + 11 + + UTF-8 + UTF-8 + + 2.4.2 + 2020.0.1 + 12.0.2 + 1.18.18 + 1.5.3 + 1.11.947 + 4.21.0 + + 3.1.2 + 3.8.0.2131 + 0.8.6 + 30.1-jre + Corona-Warn-App / cwa-log-upload + 2021 + apache_v2 + + **/LogUploadApplication.java, + **/SecurityConfig.java, + **/client/* + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + org.springframework.cloud + spring-cloud-dependencies + ${spring.cloud.version} + pom + import + + + org.keycloak.bom + keycloak-adapter-bom + ${keycloak.version} + pom + import + + + + + + + github + https://maven.pkg.github.com/corona-warn-app/cwa-log-upload + + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.cloud + spring-cloud-starter-vault-config + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.cloud + spring-cloud-starter-bootstrap + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.cloud + spring-cloud-starter-sleuth + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + com.google.guava + guava + + + + + io.github.openfeign + feign-httpclient + + + com.google.guava + guava + ${guava.version} + + + org.springframework.boot + spring-boot-starter-security + + + org.keycloak + keycloak-spring-boot-starter + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springdoc + springdoc-openapi-ui + ${springdoc.version} + + + com.c4-soft.springaddons + spring-security-oauth2-test-webmvc-addons + 2.4.1 + test + + + org.projectlombok + lombok + ${lombok.version} + + + io.micrometer + micrometer-core + + + io.micrometer + micrometer-registry-prometheus + + + org.springframework.session + spring-session-core + 2.3.0.RELEASE + + + org.liquibase + liquibase-core + + + org.postgresql + postgresql + runtime + + + com.h2database + h2 + runtime + + + com.amazonaws + aws-java-sdk-s3 + ${aws-s3.version} + + + org.springframework.boot + spring-boot-configuration-processor + true + + + net.javacrumbs.shedlock + shedlock-spring + ${shedlock.version} + + + net.javacrumbs.shedlock + shedlock-provider-jdbc-template + ${shedlock.version} + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${plugin.checkstyle.version} + + + org.sonarsource.scanner.maven + sonar-maven-plugin + ${plugin.sonar.version} + + + org.jacoco + jacoco-maven-plugin + ${plugin.jacoco.version} + + + + + + org.springframework.boot + spring-boot-maven-plugin + + 1000 + 60 + + + + + repackage + build-info + + + + pre-integration-test + + start + + + + post-integration-test + + stop + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + com.puppycrawl.tools + checkstyle + 8.40 + + + + codestyle/checkstyle.xml + target/**/* + UTF-8 + true + true + warning + true + false + + + + validate + validate + + check + + + + + + org.jacoco + jacoco-maven-plugin + + + + prepare-agent + + + + report + + report + + + + + + org.springdoc + springdoc-openapi-maven-plugin + 1.1 + + http://localhost:8085/v3/api-docs + + + + integration-test + + generate + + + + + + maven-surefire-plugin + 2.19.1 + + + org.junit.platform + junit-platform-surefire-provider + 1.0.1 + + + + + org.codehaus.mojo + license-maven-plugin + 2.0.0 + + **/*.java + ${project.organization.name} and all other contributors + ---license-start + ---license-end + --- + false + true + true + + + + + diff --git a/src/doc/Overview.md b/src/doc/Overview.md new file mode 100644 index 0000000..1664be6 --- /dev/null +++ b/src/doc/Overview.md @@ -0,0 +1,200 @@ +# Software Design Log upload service, Logviewer + + +## Introduction +This document describes the component Log upload service + Logviewer for the System “Corona Warn App”. In the world of the Corona Warn App the Log upload service + Log viewer helps to validate whether upload requests from the mobile App are valid or not. +This document links the overall system architecture with the software design of the Log upload service + Log viewer, it links user stories with implementation inside the Log upload service + Log viewer. +This document is intended to be read by people who want to get insights how verification works in detail, it is our guideline for implementation. + +# Overview +## Purpose of the Software System Component +The primary scope of the component is to enable users to upload logfiles collected on their devices to the cwa and give designated app developers access to them. + +## Context +The Log upload service + Log viewer works in conjunction with the Coronawarn.app Android and iPhone app. On user request, the phone collects runtime data, that are specific to the current phone that help to evaluate errors in rare conditions. + +## Core Entities +| Entity | Definition | +| ------------- |:-------------:| +| Log collection | component on the smart phone, that controlls and monitors the collection of log data and enables the user to transmit them | +| Log upload service | CWA component, that provides an interface for the logs to be uploaded and stores them on a object data storage in the open telekom cloud | +| TOTP | This is a onetime token that the clients sends in the http header. The log upload service can verify this token against the PPA Server for validity | +| Log viewer | The log viewer provides a log on interface to authenticate a user (app developer) and presents the uploaded logs, provides full text and regex search | +|CWA IAM| This is the identity and access management component, that is also used for the verification portal. It contains an additional group that will hold the developers who will have access to the log viewer | + +# Software Design + +## Privacy Constraints +The Log upload service receives zipped log data, which may contain certain information about the participation user + - ip address + - number of risk encounters + t.b.c + +### Measures +|ID| Measure |Comment| +| ------------- |:-------------:| -----:| +|M1| all data is enclosed in a zipped file and is stored in a obs bucket on the OTC, only a technical user can access this bucket | +|M2| periodical deletion of data | all data is deleted after an configurable amaout of time, the databse entries, as well as the corresponding OBS objects | +|M3| downloaded data is processed in the context of the webbrows, i. e. that the downloaded zipped logfiles will be deleted when the browser session is closed | + + +## Measures to increase data privacy +### Separate Operation of log upload, log viewer Service and Authentification service +The log upload and log viewer will be handled as two seperate services. The may by that means have seperate routing and network configurtaion like ip whitelisting for the log viewer. + +### Logging +Logging will be reduced to indication of an successful data upload for the log upload service, and information of deletion The log viewer will have log + +## Important Assumptions + + +## Actors +- **User/Coronawarn.app Nutzer**: Person, who is tested for SARS-CoV-2, is equipped with a smartphone, Corona Warn App installed +- **User/Backend Developer**: Person who reads log files to analyse seldom failure situations occuring on the corona.app user's phone + +## Big Picture - User Journeys +### User Journey Log upload + +Note: + +Steps: + + +### User Journey Log viewing + + Precondition: + +Steps + +## Supported User Stories + + +## Implemented Use Cases +### Use Case Log upload +API Endpoint: +- Method: POST /logs +Body: { +"key": "<< key >>", +"keyType": “teleTAN||hashedGUID” +} +- Authentication: TOTP via http header +Steps: + +### Use Case Log view + +Steps + +### Use Case Delete data to keep data privacy high +Steps: +1. Delete all entities OBS objects older than 2 weeks +2. Delete all entities in database oder than 2 weeks + + + +# Data Model +The data model is persisted in a dedicated schema. +## Entity Logs +The entity logs represents the link to the zipped log files in OTC storage + +|Name| Not null| Type| Definition| +| ------------- |:-------------:| -----:| -----:| +|id| Y| String[32]| Primary key, row index| +|created_at| Y| Date| Timestamp when the entry is stored| +|filename| Y| String[100]| Filename | +|size| Y| long| Lenght of zipfile| +|hash |Y |String[32] | Hash | +|metadata| N| String[1000] | Metadata| + +### Data Deletion +All data is deleted after 21 days. + +## Entity AppSession +The entity AppSession is a hashed GUID which was used in processing to generate a TAN. The entity basically marks a GUID hash as “used”. + +|Name| Not null| Type| Definition| +| ------------- |:-------------:| -----:| -----:| +|GUIDHash|| String[64]| The hashed GUID.| +|teleTANHash|| String[64]| The hashed teleTAN.| +|RegistrationTokenHash| Y| String[64]| Hash of the Registration Token.| +|TANcounter| Y|Int| Contains the number of TANs generated in the session| +|sourceOfTrust| Y| String [“hashedGUID”, “teleTAN”]|Defines the type of the Session| +|createdON| Y| Date |Date of creation| + +### Data Deletion +All data is deleted after 14 days. + +# Security +## Authentication +### Authentication of users +|Role| Authentication |Comment| +| ------------- |:-------------:| -----:| +|Anonymous | None| the app uses no authentication for communication with Verification Server| +|Corona Warn App Server| TLS Client Certificate, 2nd factor IP Range | +|Health Authority User| Signed JWT, verification of signature | +|Hotline User| Signed JWT, verification of signature | + + +### Authorization of users +|Role| Authorization| Comment| +| ------------- |:-------------:| -----:| +|Anonymous| None| the app uses no authorization for communication with Verification Server | +|Corona Warn App Backend User| Implicit authorization | a user which is authenticated as Corona Warn App Backend User is authorized as Corona Warn App Backend User | +|Health Authority User| Signed JWT, verification of signature | Signature contains role “c19healthauthority” | +|Hotline User| Signed JWT | verification of signature “c19hotline” | + +## Threat Model +**_This chapter is still in work._** +### Threats +Based on STRIDE threat modelling, the threats below are anticipated: +|ID| Category| Name| Definition| +| ------------- |:-------------:| -----:| -----:| +|T1| Brute Force | Brute Force teleTAN| Try to guess a teleTAN via brute force attack.| +|T2| DDoS Attack| The API is attacked by a high number of requests, leading to an outage of the service| +|T3| Code injection| The payload and/or header contain code which is executed| +|T4| +|T5| Brute force attack| By a brute force attack a client wants to guess a valid GUID to create a valid TAN| +|T6| Steal secrets from logs | + +Categories follow STRIDE: +- Spoofing +- Tampering +- Repudiation +- Information disclosure (privacy breach or data leak) +- Denial of service +- Elevation of privilege + +### Measures + +|ID| Threat| Name| Definition| +| ------------- |:-------------:| -----:| -----:| +|MT1|||OTC DDoD Protection Infrastructure Level| +|MT2|||Strong input parameter verification, with 100% code coverage and very high amount of testing | +|MT3|||Enforcing TLS 1.2 and above | +|MT6|T2|| Use Open Telekom Cloud Anti-DDoS | +|MT7|T3|| Strict validation of http headers, body content | +|MT9|T5|| Use Throttling @ Code Level in API implementation to reduce the possible frequency of guessing attempts | +|MT10|T5|| Detect unusual load scenario and trigger warning for operation | +|MT11|T6|| Use only POST requests to avoid logging of secrets at infrastructure components| +|MT12||| Strict input validation, all REST input parameter are validated in a strict manner| + + + +## Used cryptographic algorithms +- Hashing of GUID: SHA-256, no salt, no pepper +- Hashing of Registration Token: SHA-256, no salt, no pepper +- Hashing of TAN: SHA-256, no salt, no pepper +- Creating of Registration Token: the JAVA UUID generation (UUID.randomUUID()) is used, which relies on Java SecureRandom (see https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/security/SecureRandom.html ) is used. this random is considered as cryptographically strong random number generator +- Creating of TAN: the JAVA UUID generation (UUID.randomUUID()) is used, which relies on Java SecureRandom (see https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/security/SecureRandom.html ) is used. this random is considered as cryptographically strong random number generator +- Creating of teleTAN: string of random chars, as random java SecureRandom (see https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/security/SecureRandom.html ) is used. this random is considered as cryptographically strong random number generator + +## Complexity of secrets +- TAN: 128 bits +- Registration Token: 128 bits +- teleTAN: 44 bits (31 characters, length of 9) + +## Used Timeframes +TAN +- Lifespan of TAN is configured to 14 days + +teleTAN +- Lifespan of teleTAN is configured to 1h diff --git a/src/main/java/app/coronawarn/logupload/LogUploadApplication.java b/src/main/java/app/coronawarn/logupload/LogUploadApplication.java new file mode 100644 index 0000000..779c62e --- /dev/null +++ b/src/main/java/app/coronawarn/logupload/LogUploadApplication.java @@ -0,0 +1,69 @@ +/* + * Corona-Warn-App / cwa-log-upload + * + * (C) 2021, T-Systems International GmbH + * + * Deutsche Telekom AG and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.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 app.coronawarn.logupload; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.info.Info; +import org.apache.coyote.http11.AbstractHttp11Protocol; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Bean; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * The Spring Boot application class. + */ +@SpringBootApplication +@EnableConfigurationProperties +@EnableFeignClients +@EnableScheduling +@OpenAPIDefinition(info = @Info( + description = "API to upload and store log files in context of Corona Warn App.", + version = "v1.0", + title = "CWA Log Upload")) +public class LogUploadApplication { + + public static void main(String[] args) { + SpringApplication.run(LogUploadApplication.class, args); + } + + /** + * Enable the cipher suites from server to be preferred. + * + * @return the WebServerFactoryCustomizer with cipher suites configuration + */ + @Bean + @ConditionalOnProperty(value = "server.ssl.cipher.suites.order", havingValue = "true") + public WebServerFactoryCustomizer webServerFactoryCustomizer() { + return factory -> factory + .addConnectorCustomizers(connector -> + ((AbstractHttp11Protocol) connector.getProtocolHandler()) + .setUseServerCipherSuitesOrder(true) + ); + } + +} diff --git a/src/main/java/app/coronawarn/logupload/LogUploadHttpFilter.java b/src/main/java/app/coronawarn/logupload/LogUploadHttpFilter.java new file mode 100644 index 0000000..f1cbbb2 --- /dev/null +++ b/src/main/java/app/coronawarn/logupload/LogUploadHttpFilter.java @@ -0,0 +1,80 @@ +/* + * Corona-Warn-App / cwa-verification + * + * (C) 2021, T-Systems International GmbH + * + * Deutsche Telekom AG, SAP AG and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.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 app.coronawarn.logupload; + +import java.io.IOException; +import java.util.List; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class LogUploadHttpFilter implements Filter { + + private static final String X_FORWARDED_HOST_HEADER = "X-Forwarded-Host"; + + @Value("${host-header.whitelist}") + private List validHostHeaders; + + @Value("${pod.ip}") + private String podIp; + + @Value("${pod.port}") + private String podPort; + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + + HttpServletRequest request = (HttpServletRequest) servletRequest; + HttpServletResponse response = (HttpServletResponse) servletResponse; + + if (isHostHeaderValid(request)) { + filterChain.doFilter(request, response); + } else { + log.warn("Invalid Host Header"); + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + response.getOutputStream().flush(); + response.getOutputStream().println("Bad Request"); + } + } + + private boolean isHostHeaderValid(final HttpServletRequest request) { + final String host = request.getHeader(HttpHeaders.HOST); + final String xForwardedHost = request.getHeader(X_FORWARDED_HOST_HEADER); + if (xForwardedHost != null || host == null) { + return false; + } else { + return validHostHeaders.contains(host) || host.equals(podIp + ":" + podPort); + } + } + +} diff --git a/src/main/java/app/coronawarn/logupload/SecurityConfig.java b/src/main/java/app/coronawarn/logupload/SecurityConfig.java new file mode 100644 index 0000000..39120b9 --- /dev/null +++ b/src/main/java/app/coronawarn/logupload/SecurityConfig.java @@ -0,0 +1,138 @@ +/* + * Corona-Warn-App / cwa-log-upload + * + * (C) 2021, T-Systems International GmbH + * + * Deutsche Telekom AG and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.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 app.coronawarn.logupload; + +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver; +import org.keycloak.adapters.springsecurity.KeycloakSecurityComponents; +import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider; +import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter; +import org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper; +import org.springframework.security.core.session.SessionRegistryImpl; +import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy; +import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; +import org.springframework.session.MapSessionRepository; +import org.springframework.session.SessionRepository; +import org.springframework.session.web.http.CookieSerializer; +import org.springframework.session.web.http.DefaultCookieSerializer; + + +@Slf4j +@Configuration +@EnableWebSecurity +@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class) +@RequiredArgsConstructor +class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter { + + private static final String ROLE_C19LOG_INSPECTOR = "c19log_inspector"; + private static final String ACTUATOR_ROUTE = "/actuator/**"; + private static final String PUBLIC_API_ROUTE = "/api/**"; + private static final String PORTAL_ROUTE = "/portal/**"; + private static final String SWAGGER_ROUTE = "/v3/api-docs/**"; + + + private static final String SAMESITE_LAX = "Lax"; + private static final String OAUTH_TOKEN_REQUEST_STATE_COOKIE = "OAuth_Token_Request_State"; + private static final String SESSION_COOKIE = "SESSION"; + + private final LogUploadHttpFilter logUploadHttpFilter; + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) { + KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider(); + keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper()); + auth.authenticationProvider(keycloakAuthenticationProvider); + } + + @Bean + public KeycloakSpringBootConfigResolver keycloakConfigResolver() { + return new KeycloakSpringBootConfigResolver(); + } + + @Bean + @Override + protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { + return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + super.configure(http); + http + .addFilterBefore(logUploadHttpFilter, KeycloakAuthenticationProcessingFilter.class) + .headers().addHeaderWriter(this::addSameSiteToOAuthCookie) + .and() + .authorizeRequests() + .mvcMatchers(HttpMethod.GET, ACTUATOR_ROUTE).permitAll() + .mvcMatchers(HttpMethod.POST, PUBLIC_API_ROUTE).permitAll() + .mvcMatchers(HttpMethod.GET, SWAGGER_ROUTE).permitAll() + .mvcMatchers(HttpMethod.GET, PORTAL_ROUTE).hasRole(ROLE_C19LOG_INSPECTOR) + .mvcMatchers(HttpMethod.POST, PORTAL_ROUTE).hasRole(ROLE_C19LOG_INSPECTOR) + .anyRequest() + .denyAll() + .and() + .csrf().ignoringAntMatchers(PUBLIC_API_ROUTE, SWAGGER_ROUTE); + } + + @Bean + public CookieSerializer defaultCookieSerializer() { + DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer(); + cookieSerializer.setCookieName(SESSION_COOKIE); + cookieSerializer.setSameSite(SAMESITE_LAX); + cookieSerializer.setUseHttpOnlyCookie(true); + return cookieSerializer; + } + + @Bean + public SessionRepository sessionRepository() { + return new MapSessionRepository(new ConcurrentHashMap<>()); + } + + private void addSameSiteToOAuthCookie(final HttpServletRequest request, final HttpServletResponse response) { + final Collection setCookieValues = response.getHeaders(HttpHeaders.SET_COOKIE); + for (String setCookie : setCookieValues) { + if (setCookie.contains(OAUTH_TOKEN_REQUEST_STATE_COOKIE)) { + response.setHeader(HttpHeaders.SET_COOKIE, addSameSiteStrict(setCookie)); + } + } + } + + private String addSameSiteStrict(String setCookie) { + return setCookie + "; SameSite=" + SAMESITE_LAX; + } + +} diff --git a/src/main/java/app/coronawarn/logupload/client/ElsVerifyClient.java b/src/main/java/app/coronawarn/logupload/client/ElsVerifyClient.java new file mode 100644 index 0000000..2cc3a57 --- /dev/null +++ b/src/main/java/app/coronawarn/logupload/client/ElsVerifyClient.java @@ -0,0 +1,13 @@ +package app.coronawarn.logupload.client; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; + +@FeignClient(name = "els-verify", url = "${els-verify.url}", configuration = ElsVerifyClientConfig.class) +public interface ElsVerifyClient { + + @PostMapping(value = "/version/v1/els", + produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE) + ElsVerifyClientResponse verifyOtp(ElsVerifyClientRequest body); +} diff --git a/src/main/java/app/coronawarn/logupload/client/ElsVerifyClientConfig.java b/src/main/java/app/coronawarn/logupload/client/ElsVerifyClientConfig.java new file mode 100644 index 0000000..ccc586b --- /dev/null +++ b/src/main/java/app/coronawarn/logupload/client/ElsVerifyClientConfig.java @@ -0,0 +1,66 @@ +package app.coronawarn.logupload.client; + +import app.coronawarn.logupload.config.ElsVerifyConfig; +import feign.Client; +import feign.httpclient.ApacheHttpClient; +import java.io.IOException; +import java.security.GeneralSecurityException; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import lombok.RequiredArgsConstructor; +import org.apache.http.conn.ssl.DefaultHostnameVerifier; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.ssl.SSLContextBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.util.ResourceUtils; +import org.springframework.web.server.ResponseStatusException; + +@Component +@RequiredArgsConstructor +@Profile("api") + +public class ElsVerifyClientConfig { + + private final ElsVerifyConfig config; + + /** + * Build Http Client for accessing els-verify service. + */ + @Bean + public Client client() { + if (config.getTls().getEnabled()) { // Build HTTPS client + return new ApacheHttpClient( + HttpClientBuilder + .create() + .setSSLContext(getSslContext()) + .setSSLHostnameVerifier(getSslHostnameVerifier()) + .build() + ); + } else { // Build HTTP client + return new ApacheHttpClient(HttpClientBuilder.create().build()); + } + } + + private SSLContext getSslContext() { + try { + return SSLContextBuilder.create() + .loadTrustMaterial(ResourceUtils.getFile(config.getTls().getTrustStore()), + config.getTls().getTrustStorePassword().toCharArray()) + .loadKeyMaterial(ResourceUtils.getFile(config.getTls().getKeyStore()), + config.getTls().getKeyStorePassword().toCharArray(), + config.getTls().getKeyStorePassword().toCharArray()) + .build(); + + } catch (IOException | GeneralSecurityException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "The SSL context could not be loaded."); + } + } + + private HostnameVerifier getSslHostnameVerifier() { + return config.getTls().getHostnameVerify() ? new DefaultHostnameVerifier() : new NoopHostnameVerifier(); + } +} diff --git a/src/main/java/app/coronawarn/logupload/client/ElsVerifyClientRequest.java b/src/main/java/app/coronawarn/logupload/client/ElsVerifyClientRequest.java new file mode 100644 index 0000000..912dd2b --- /dev/null +++ b/src/main/java/app/coronawarn/logupload/client/ElsVerifyClientRequest.java @@ -0,0 +1,11 @@ +package app.coronawarn.logupload.client; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class ElsVerifyClientRequest { + + private String otp; +} diff --git a/src/main/java/app/coronawarn/logupload/client/ElsVerifyClientResponse.java b/src/main/java/app/coronawarn/logupload/client/ElsVerifyClientResponse.java new file mode 100644 index 0000000..e0eb360 --- /dev/null +++ b/src/main/java/app/coronawarn/logupload/client/ElsVerifyClientResponse.java @@ -0,0 +1,16 @@ +package app.coronawarn.logupload.client; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ElsVerifyClientResponse { + + private String otp; + private boolean strongClientIntegrityCheck; + private String state; + +} diff --git a/src/main/java/app/coronawarn/logupload/config/ElsVerifyConfig.java b/src/main/java/app/coronawarn/logupload/config/ElsVerifyConfig.java new file mode 100644 index 0000000..7df76b1 --- /dev/null +++ b/src/main/java/app/coronawarn/logupload/config/ElsVerifyConfig.java @@ -0,0 +1,45 @@ +/* + * Corona-Warn-App / cwa-log-upload + * + * (C) 2021, T-Systems International GmbH + * + * Deutsche Telekom AG and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.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 app.coronawarn.logupload.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties("els-verify") +@Data +public class ElsVerifyConfig { + + private String url; + private TlsConfig tls; + + @Data + public static class TlsConfig { + private Boolean enabled; + private Boolean hostnameVerify; + private String keyStore; + private String keyStorePassword; + private String trustStore; + private String trustStorePassword; + } +} diff --git a/src/main/java/app/coronawarn/logupload/config/LogUploadConfig.java b/src/main/java/app/coronawarn/logupload/config/LogUploadConfig.java new file mode 100644 index 0000000..6cf5075 --- /dev/null +++ b/src/main/java/app/coronawarn/logupload/config/LogUploadConfig.java @@ -0,0 +1,38 @@ +/* + * Corona-Warn-App / cwa-log-upload + * + * (C) 2021, T-Systems International GmbH + * + * Deutsche Telekom AG and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.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 app.coronawarn.logupload.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties("log-upload") +@Data +public class LogUploadConfig { + + private Integer logIdByteLength = 10; + private String keycloakPwResetUrl; + private Integer logEntityLifetime = 7; + private String cleanupCron = "0 0 0 * * *"; + +} diff --git a/src/main/java/app/coronawarn/logupload/config/LogUploadS3Config.java b/src/main/java/app/coronawarn/logupload/config/LogUploadS3Config.java new file mode 100644 index 0000000..82b4ea4 --- /dev/null +++ b/src/main/java/app/coronawarn/logupload/config/LogUploadS3Config.java @@ -0,0 +1,53 @@ +/* + * Corona-Warn-App / cwa-log-upload + * + * (C) 2021, T-Systems International GmbH + * + * Deutsche Telekom AG and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.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 app.coronawarn.logupload.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties("s3") +@Data +public class LogUploadS3Config { + + private String accessKey; + private String secretKey; + private String bucketName; + + private Region region; + + private ProxyConfig proxy; + + @Data + public static class Region { + private String name = ""; + private String endpoint; + } + + @Data + public static class ProxyConfig { + private Boolean enabled = Boolean.FALSE; + private String host; + private Integer port; + } +} diff --git a/src/main/java/app/coronawarn/logupload/config/S3StorageClient.java b/src/main/java/app/coronawarn/logupload/config/S3StorageClient.java new file mode 100644 index 0000000..a9d5dc6 --- /dev/null +++ b/src/main/java/app/coronawarn/logupload/config/S3StorageClient.java @@ -0,0 +1,69 @@ +/* + * Corona-Warn-App / cwa-log-upload + * + * (C) 2021, T-Systems International GmbH + * + * Deutsche Telekom AG and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.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 app.coronawarn.logupload.config; + +import com.amazonaws.ClientConfiguration; +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.client.builder.AwsClientBuilder; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@RequiredArgsConstructor +@Slf4j +public class S3StorageClient { + + private final LogUploadS3Config s3Config; + + /** + * Creates a Bean for accessing S3 storage depending on application configuration. + * + * @return Preconfigured AmazonS3 instance. + */ + @Bean + public AmazonS3 getStorage() { + ClientConfiguration clientConfig = new ClientConfiguration(); + clientConfig.setSignerOverride("AWSS3V4SignerType"); + + if (s3Config.getProxy().getEnabled()) { + log.info("Setting proxy for S3 connection."); + clientConfig.setProxyHost(s3Config.getProxy().getHost()); + clientConfig.setProxyPort(s3Config.getProxy().getPort()); + } + + AWSCredentials credentials = new BasicAWSCredentials(s3Config.getAccessKey(), s3Config.getSecretKey()); + + return AmazonS3ClientBuilder.standard() + .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration( + s3Config.getRegion().getEndpoint(), s3Config.getRegion().getName())) + .withPathStyleAccessEnabled(true) + .withClientConfiguration(clientConfig) + .withCredentials(new AWSStaticCredentialsProvider(credentials)) + .build(); + } +} diff --git a/src/main/java/app/coronawarn/logupload/controller/LogDownloadApiController.java b/src/main/java/app/coronawarn/logupload/controller/LogDownloadApiController.java new file mode 100644 index 0000000..6cfa5d5 --- /dev/null +++ b/src/main/java/app/coronawarn/logupload/controller/LogDownloadApiController.java @@ -0,0 +1,75 @@ +/* + * Corona-Warn-App / cwa-log-upload + * + * (C) 2021, T-Systems International GmbH + * + * Deutsche Telekom AG and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.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 app.coronawarn.logupload.controller; + +import app.coronawarn.logupload.service.FileStorageService; +import com.google.common.net.HttpHeaders; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Profile; +import org.springframework.core.io.InputStreamResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.server.ResponseStatusException; + +@Slf4j +@Controller +@RequestMapping(value = "/portal/api/") +@RequiredArgsConstructor +@Profile("portal") +public class LogDownloadApiController { + + private final FileStorageService storageService; + + /** + * Endpoint for downloading a log file. + * + * @param id the ID of the log file. + * @return ResponseEntity with binary data. + */ + @GetMapping("/logs/{id}") + public ResponseEntity downloadLogfile(@PathVariable("id") String id) { + FileStorageService.LogDownloadResponse logDownloadResponse; + + try { + logDownloadResponse = storageService.downloadFile(id); + } catch (FileStorageService.FileStoreException e) { + if (e.getReason() == FileStorageService.FileStoreException.Reason.FILE_NOT_FOUND) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND); + } else { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + Resource resource = new InputStreamResource(logDownloadResponse.getInputStream()); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=\"" + logDownloadResponse.getLogEntity().getFilename() + "\"") + .header(HttpHeaders.CONTENT_LENGTH, String.valueOf(logDownloadResponse.getLogEntity().getSize())) + .body(resource); + } +} diff --git a/src/main/java/app/coronawarn/logupload/controller/LogUploadApiController.java b/src/main/java/app/coronawarn/logupload/controller/LogUploadApiController.java new file mode 100644 index 0000000..5dcb737 --- /dev/null +++ b/src/main/java/app/coronawarn/logupload/controller/LogUploadApiController.java @@ -0,0 +1,135 @@ +/* + * Corona-Warn-App / cwa-log-upload + * + * (C) 2021, T-Systems International GmbH + * + * Deutsche Telekom AG and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.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 app.coronawarn.logupload.controller; + +import app.coronawarn.logupload.model.LogEntity; +import app.coronawarn.logupload.model.LogUploadResponse; +import app.coronawarn.logupload.service.FileStorageService; +import app.coronawarn.logupload.service.OtpService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import java.io.IOException; +import java.util.regex.Pattern; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Profile; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.server.ResponseStatusException; + +@Slf4j +@Controller +@RequestMapping("/api") +@RequiredArgsConstructor +@Profile("api") +public class LogUploadApiController { + + private final FileStorageService storageService; + + private final OtpService otpService; + + private static final Pattern UUID_PATTERN = + Pattern.compile("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[34][0-9a-fA-F]{3}-[89ab][0-9a-fA-F]{3}-[0-9a-fA-F]{12}"); + + /** + * Endpoint for uploading log files. + * + * @param file the multipart file upload + * @return id and hash of the uploaded log file. + */ + @Operation( + operationId = "uploadLogFile", + summary = "Uploads a log file from CWA to log-upload server.", + tags = {"PUBLIC"}, + method = "POST" + ) + @ApiResponses({ + @ApiResponse( + responseCode = "201", + description = "Object containing the ID and MD5 hash of the uploaded log.", + content = @Content(schema = @Schema(implementation = LogUploadResponse.class)) + ), + @ApiResponse( + responseCode = "500", + description = "The upload of the log file has failed.", + content = @Content(schema = @Schema(hidden = true)) + ) + }) + @PostMapping( + produces = MediaType.APPLICATION_JSON_VALUE, + consumes = MediaType.MULTIPART_FORM_DATA_VALUE, + value = "/logs" + ) + public ResponseEntity uploadLogFile( + @Parameter(description = "The file to upload", required = true) + @RequestParam("file") MultipartFile file, + @Parameter(description = "One Time Password to authorize upload", required = true) + @RequestHeader("cwa-otp") String otp + ) throws IOException { + + if (!UUID_PATTERN.matcher(otp).matches()) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid OTP format"); + } + + if (!otpService.verifyOtp(otp)) { + log.info("Unauthorized log upload"); + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "OTP is required for log upload."); + } + + log.info("Got file: {}, {}", file.getOriginalFilename(), file.getSize()); + + LogEntity logEntity; + + String filename = "unknown"; + + if (file.getOriginalFilename() != null) { + filename = file.getOriginalFilename().length() > 100 + ? file.getOriginalFilename().substring(0, 100) + : file.getOriginalFilename(); + } + + try { + logEntity = + storageService.storeFileStream(filename, file.getSize(), file.getInputStream()); + } catch (FileStorageService.FileStoreException e) { + log.error("Failed to save log file.", e); + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR); + } + + log.info("Saved log file in db with id {}", logEntity.getId()); + + return ResponseEntity + .status(HttpStatus.CREATED) + .body(new LogUploadResponse(logEntity.getId(), logEntity.getHash())); + } +} diff --git a/src/main/java/app/coronawarn/logupload/controller/LogUploadErrorController.java b/src/main/java/app/coronawarn/logupload/controller/LogUploadErrorController.java new file mode 100644 index 0000000..945f0fe --- /dev/null +++ b/src/main/java/app/coronawarn/logupload/controller/LogUploadErrorController.java @@ -0,0 +1,83 @@ +/* + * Corona-Warn-App / cwa-log-upload + * + * (C) 2021, T-Systems International GmbH + * + * Deutsche Telekom AG and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.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 app.coronawarn.logupload.controller; + +import javax.servlet.RequestDispatcher; +import javax.servlet.http.HttpServletRequest; +import org.springframework.context.annotation.Profile; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + * Controller handling errors. + */ +@Controller +@Profile("portal") +public class LogUploadErrorController { + + /** + * Error messages for the common problems like 'Not Found', 'Internal Error' + * 'Forbidden' and 'Too Many Requests'. + */ + private static final String ERROR_404 = "Die aufgerufene Seite konnte nicht gefunden werden."; + private static final String ERROR_403 = "Der Benutzer kann nicht authentifiziert werden."; + + /** + * The internal route to the portal error web site. + */ + private static final String ROUTE_ERROR = "/error"; + + /** + * The html Thymeleaf template for the error web site. + */ + private static final String TEMPLATE_ERROR = "error"; + + /** + * The Thymeleaf attribute used for displaying the error message. + */ + private static final String ATTR_ERROR_MSG = "message"; + + /** + * The Web GUI page request showing an Error message. + * + * @param request the original request + * @param model the thymleaf model to be filled with the error text + * @return the error template name + */ + @RequestMapping(value = ROUTE_ERROR, method = {RequestMethod.GET, RequestMethod.POST}) + public String handleError(HttpServletRequest request, Model model) { + Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); + if (status != null) { + int statusCode = Integer.parseInt(status.toString()); + + if (statusCode == HttpStatus.NOT_FOUND.value()) { + model.addAttribute(ATTR_ERROR_MSG, ERROR_404); + } else if (statusCode == HttpStatus.FORBIDDEN.value()) { + model.addAttribute(ATTR_ERROR_MSG, ERROR_403); + } + } + return TEMPLATE_ERROR; + } +} diff --git a/src/main/java/app/coronawarn/logupload/controller/LogUploadPortalController.java b/src/main/java/app/coronawarn/logupload/controller/LogUploadPortalController.java new file mode 100644 index 0000000..67a72f5 --- /dev/null +++ b/src/main/java/app/coronawarn/logupload/controller/LogUploadPortalController.java @@ -0,0 +1,147 @@ +/* + * Corona-Warn-App / cwa-log-upload + * + * (C) 2021, T-Systems International GmbH + * + * Deutsche Telekom AG and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.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 app.coronawarn.logupload.controller; + +import app.coronawarn.logupload.config.LogUploadConfig; +import app.coronawarn.logupload.model.LogEntity; +import app.coronawarn.logupload.service.LogService; +import java.security.Principal; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FileUtils; +import org.springframework.context.annotation.Profile; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; + +/** + * This class represents the WEB UI controller for the verification portal. It implements a very + * simple HTML interface with one submit button to get and show a newly generated TeleTAN. + */ +@Slf4j +@Controller +@RequiredArgsConstructor +@Profile("portal") +public class LogUploadPortalController { + + /** + * Routes. + */ + private static final String ROUTE_LOGOUT = "/portal/logout"; + private static final String ROUTE_START = "/portal/start"; + private static final String ROUTE_SEARCH = "/portal/search"; + private static final String ROUTE_INDEX = "/"; + + /** + * Template properties. + */ + private static final String ATTR_USER = "userName"; + private static final String ATTR_PW_RESET_URL = "pwResetUrl"; + private static final String ATTR_LOG_ID = "logId"; + private static final String ATTR_LOG_ENTITY = "logEntity"; + private static final String ATTR_LOG_FILESIZE_HR = "fileSizeHr"; + + /** + * Template names. + */ + private static final String TEMPLATE_START = "start"; + private static final String TEMPLATE_SEARCH = "search"; + private static final String TEMPLATE_NOT_FOUND = "not_found"; + + private final LogUploadConfig logUploadConfig; + private final LogService logService; + + /** + * The Get request to log out from the portal web site. + */ + @PostMapping(ROUTE_LOGOUT) + public String logout(HttpServletRequest request) { + try { + request.logout(); + } catch (ServletException e) { + log.error("Logout failed", e); + } + return "redirect:" + TEMPLATE_START; + } + + /** + * Request for start template. + */ + @GetMapping(ROUTE_START) + public String start(HttpServletRequest request, Model model) { + addUserDetailsToModel(request, model); + model.addAttribute(ATTR_LOG_ID, ""); + + return TEMPLATE_START; + } + + /** + * Request for search template. + */ + @PostMapping(ROUTE_SEARCH) + public String search(HttpServletRequest request, Model model, @ModelAttribute(ATTR_LOG_ID) String logId) { + addUserDetailsToModel(request, model); + + log.info("Got id {}", logId); + + LogEntity logEntity = logService.getLogEntity(logId); + + if (logEntity == null) { + return TEMPLATE_NOT_FOUND; + } + + model.addAttribute(ATTR_LOG_ENTITY, logEntity); + model.addAttribute(ATTR_LOG_FILESIZE_HR, FileUtils.byteCountToDisplaySize(logEntity.getSize())); + + return TEMPLATE_SEARCH; + } + + /** + * Request for index. + */ + @GetMapping(ROUTE_INDEX) + public ResponseEntity index() { + return ResponseEntity + .status(HttpStatus.FOUND) + .header(HttpHeaders.LOCATION, ROUTE_START) + .build(); + + } + + private void addUserDetailsToModel(HttpServletRequest request, Model model) { + Principal principal = request.getUserPrincipal(); + request.getUserPrincipal().getName(); + String user = principal.getName(); + + if (model != null) { + model.addAttribute(ATTR_USER, user); + model.addAttribute(ATTR_PW_RESET_URL, logUploadConfig.getKeycloakPwResetUrl()); + } + } +} diff --git a/src/main/java/app/coronawarn/logupload/exception/LogUploadException.java b/src/main/java/app/coronawarn/logupload/exception/LogUploadException.java new file mode 100644 index 0000000..df5fe99 --- /dev/null +++ b/src/main/java/app/coronawarn/logupload/exception/LogUploadException.java @@ -0,0 +1,46 @@ +/* + * Corona-Warn-App / cwa-verification + * + * (C) 2021, T-Systems International GmbH + * + * Deutsche Telekom AG, SAP AG and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.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 app.coronawarn.logupload.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +/** + * This class represents the Verification Portal Exception. + */ +@Getter +public class LogUploadException extends RuntimeException { + + private final HttpStatus httpStatus; + + /** + * The Constructor for the Exception class. + * + * @param httpStatus the state of the server + * @param message the message + */ + public LogUploadException(HttpStatus httpStatus, String message) { + super(message); + this.httpStatus = httpStatus; + } + +} diff --git a/src/main/java/app/coronawarn/logupload/model/LogEntity.java b/src/main/java/app/coronawarn/logupload/model/LogEntity.java new file mode 100644 index 0000000..b2c8efd --- /dev/null +++ b/src/main/java/app/coronawarn/logupload/model/LogEntity.java @@ -0,0 +1,60 @@ +/* + * Corona-Warn-App / cwa-verification + * + * (C) 2021, T-Systems International GmbH + * + * Deutsche Telekom AG, SAP AG and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.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 app.coronawarn.logupload.model; + +import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "log") +@Data +@AllArgsConstructor +@NoArgsConstructor +public class LogEntity { + + @Id + @Column(name = "id") + private String id; + + @Column(name = "createdAt") + private ZonedDateTime createdAt; + + @Column(name = "filename") + private String filename; + + @Column(name = "size") + private long size; + + @Column(name = "hash") + private String hash; + + @Column(name = "metadata") + private String metaData; + +} diff --git a/src/main/java/app/coronawarn/logupload/model/LogUploadResponse.java b/src/main/java/app/coronawarn/logupload/model/LogUploadResponse.java new file mode 100644 index 0000000..3fd8650 --- /dev/null +++ b/src/main/java/app/coronawarn/logupload/model/LogUploadResponse.java @@ -0,0 +1,37 @@ +/* + * Corona-Warn-App / cwa-verification + * + * (C) 2021, T-Systems International GmbH + * + * Deutsche Telekom AG, SAP AG and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.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 app.coronawarn.logupload.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class LogUploadResponse { + + private String id; + + private String hash; + +} diff --git a/src/main/java/app/coronawarn/logupload/repository/LogRepository.java b/src/main/java/app/coronawarn/logupload/repository/LogRepository.java new file mode 100644 index 0000000..c8fa3da --- /dev/null +++ b/src/main/java/app/coronawarn/logupload/repository/LogRepository.java @@ -0,0 +1,33 @@ +/* + * Corona-Warn-App / cwa-verification + * + * (C) 2021, T-Systems International GmbH + * + * Deutsche Telekom AG, SAP AG and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.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 app.coronawarn.logupload.repository; + +import app.coronawarn.logupload.model.LogEntity; +import java.time.ZonedDateTime; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface LogRepository extends JpaRepository { + + List findByCreatedAtBefore(ZonedDateTime before); + +} diff --git a/src/main/java/app/coronawarn/logupload/service/FileStorageService.java b/src/main/java/app/coronawarn/logupload/service/FileStorageService.java new file mode 100644 index 0000000..3383162 --- /dev/null +++ b/src/main/java/app/coronawarn/logupload/service/FileStorageService.java @@ -0,0 +1,169 @@ +/* + * Corona-Warn-App / cwa-verification + * + * (C) 2021, T-Systems International GmbH + * + * Deutsche Telekom AG, SAP AG and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.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 app.coronawarn.logupload.service; + +import app.coronawarn.logupload.config.LogUploadConfig; +import app.coronawarn.logupload.config.LogUploadS3Config; +import app.coronawarn.logupload.model.LogEntity; +import app.coronawarn.logupload.repository.LogRepository; +import com.amazonaws.SdkClientException; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectResult; +import com.amazonaws.services.s3.model.S3Object; +import java.io.InputStream; +import java.time.ZonedDateTime; +import java.util.Optional; +import java.util.Random; +import javax.xml.bind.DatatypeConverter; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class FileStorageService { + + private final LogUploadS3Config s3Config; + private final LogUploadConfig logUploadConfig; + private final AmazonS3 s3Client; + private final LogRepository logRepository; + + /** + * Stores the given FileStream in configured S3 bucket and creates an entry in LogFile database table. + * + * @param stream the FileInputStream of the file to store. + * @return an LogEntity object with information about the stored file. + */ + public LogEntity storeFileStream(String fileName, long size, InputStream stream) throws FileStoreException { + log.info("Persisting file stream"); + + String id; + Optional existingLogEntity; + + // Checking if generated ID already exists + do { + id = generateLogId(); + existingLogEntity = logRepository.findById(id); + } while (existingLogEntity.isPresent()); + + + PutObjectResult putObjectResult; + try { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(size); + + putObjectResult = s3Client.putObject(s3Config.getBucketName(), id, stream, metadata); + log.info("File stored to S3 with id {}", id); + } catch (SdkClientException e) { + log.error("Upload to S3 bucket failed.", e); + throw new FileStoreException(FileStoreException.Reason.S3_UPLOAD_FAILED); + } + + log.info("Storing LogEntity to database"); + LogEntity logEntity = + new LogEntity(id, ZonedDateTime.now(), fileName, size, putObjectResult.getContentMd5(), ""); + + return logRepository.save(logEntity); + } + + /** + * Method to download a log file from S3 bucket. This method returns + * an {@link InputStream} which can be used to get the file. + * + * @param id the ID of the file to download. + * @return {@link LogDownloadResponse} object containing the {@link InputStream} + * of the file and a {@link LogEntity} with meta-data of the file. + * @throws FileStoreException if anything went wrong during download. + */ + public LogDownloadResponse downloadFile(String id) throws FileStoreException { + Optional entity = logRepository.findById(id); + + if (entity.isEmpty()) { + throw new FileStoreException(FileStoreException.Reason.FILE_NOT_FOUND); + } + + S3Object s3Object; + try { + s3Object = s3Client.getObject(s3Config.getBucketName(), entity.get().getId()); + } catch (SdkClientException e) { + log.error("Failed to download log file from S3 bucket.", e); + throw new FileStoreException(FileStoreException.Reason.S3_DOWNLOAD_FAILED); + } + + log.info("Got file from S3 bucket with id {}", entity.get().getId()); + + return new LogDownloadResponse(entity.get(), s3Object.getObjectContent()); + } + + /** + * Delete a log file from S3 bucket and database. + * This method does not throw any exception if something goes wrong. + * In case of a misbehaviour only a information in the log file will be written. + * + * @param logEntity the {@link LogEntity} to be deleted. + */ + public void deleteFileSafe(LogEntity logEntity) { + try { + log.info("Deleting object with id {} from bucket", logEntity.getId()); + s3Client.deleteObject(s3Config.getBucketName(), logEntity.getId()); + } catch (SdkClientException e) { + log.error("Failed to delete log file from bucket.", e); + } + + log.info("Deleting entity for log with id {} from database", logEntity.getId()); + logRepository.delete(logEntity); + } + + private String generateLogId() { + byte[] bytes = new byte[logUploadConfig.getLogIdByteLength()]; + Random random = new Random(); + + random.nextBytes(bytes); + + return DatatypeConverter.printHexBinary(bytes); + } + + @Getter + @AllArgsConstructor + public static class LogDownloadResponse { + private final LogEntity logEntity; + private final InputStream inputStream; + } + + @Getter + public static class FileStoreException extends Exception { + private final Reason reason; + + public FileStoreException(Reason reason) { + super(); + this.reason = reason; + } + + public enum Reason { + S3_UPLOAD_FAILED, S3_DOWNLOAD_FAILED, S3_DELETE_FAILED, FILE_NOT_FOUND + } + } +} diff --git a/src/main/java/app/coronawarn/logupload/service/LogCleanupService.java b/src/main/java/app/coronawarn/logupload/service/LogCleanupService.java new file mode 100644 index 0000000..dd5afe5 --- /dev/null +++ b/src/main/java/app/coronawarn/logupload/service/LogCleanupService.java @@ -0,0 +1,46 @@ +package app.coronawarn.logupload.service; + +import app.coronawarn.logupload.config.LogUploadConfig; +import app.coronawarn.logupload.model.LogEntity; +import app.coronawarn.logupload.repository.LogRepository; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; +import org.springframework.context.annotation.Profile; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Slf4j +@Profile("api") +public class LogCleanupService { + + private final LogRepository logRepository; + private final LogUploadConfig config; + private final FileStorageService fileStorageService; + + /** + * Cleanup job for automated deletion of {@link LogEntity} older then a specified (e.g. 7 days) amount of time. + */ + @Scheduled(cron = "${log-upload.cleanup-cron}") + @SchedulerLock(name = "LogCleanupService_cleanup", lockAtLeastFor = "PT0S", + lockAtMostFor = "PT30M") + public void cleanup() { + + log.info("Starting log cleanup job"); + + ZonedDateTime threshold = ZonedDateTime.now().minus(config.getLogEntityLifetime(), ChronoUnit.DAYS); + + log.info("Threshold date is {}", threshold); + + List logEntities = logRepository.findByCreatedAtBefore(threshold); + logEntities.forEach(fileStorageService::deleteFileSafe); + + log.info("Finished log cleanup job"); + + } +} diff --git a/src/main/java/app/coronawarn/logupload/service/LogService.java b/src/main/java/app/coronawarn/logupload/service/LogService.java new file mode 100644 index 0000000..b048812 --- /dev/null +++ b/src/main/java/app/coronawarn/logupload/service/LogService.java @@ -0,0 +1,23 @@ +package app.coronawarn.logupload.service; + +import app.coronawarn.logupload.model.LogEntity; +import app.coronawarn.logupload.repository.LogRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class LogService { + + private final LogRepository logRepository; + + /** + * Searches for a Log with given id. + * + * @param id id to search for. + * @return the {@link LogEntity} or null if not found. + */ + public LogEntity getLogEntity(String id) { + return logRepository.findById(id).orElse(null); + } +} diff --git a/src/main/java/app/coronawarn/logupload/service/OtpService.java b/src/main/java/app/coronawarn/logupload/service/OtpService.java new file mode 100644 index 0000000..bc2cdf7 --- /dev/null +++ b/src/main/java/app/coronawarn/logupload/service/OtpService.java @@ -0,0 +1,47 @@ +package app.coronawarn.logupload.service; + +import app.coronawarn.logupload.client.ElsVerifyClient; +import app.coronawarn.logupload.client.ElsVerifyClientRequest; +import app.coronawarn.logupload.client.ElsVerifyClientResponse; +import feign.FeignException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.HttpStatus; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Slf4j +public class OtpService { + + private final ElsVerifyClient elsVerifyClient; + + /** + * Verifies whether the given one time password is valid. + * Also the redemption of the otp will happen. + * + * @param otp the one time password to check + * @return whether otp was valid. + */ + public boolean verifyOtp(String otp) { + log.info("Validating OTP"); + + ElsVerifyClientRequest request = new ElsVerifyClientRequest(otp); + ElsVerifyClientResponse response; + + try { + response = elsVerifyClient.verifyOtp(request); + } catch (FeignException e) { + if (e.status() == HttpStatus.SC_BAD_REQUEST) { + log.info("OTP is invalid"); + } else { + log.error("Could not redeem otp", e); + } + return false; + } + + log.info("Got OTP status: {}", response.getState()); + + return response.getState().equals("valid"); + } +} diff --git a/src/main/resources/application-cloud.yml b/src/main/resources/application-cloud.yml new file mode 100644 index 0000000..ec6f2e5 --- /dev/null +++ b/src/main/resources/application-cloud.yml @@ -0,0 +1,47 @@ +spring: + datasource: + driver-class-name: org.postgresql.Driver + url: jdbc:postgresql://${POSTGRESQL_SERVICE_HOST}:${POSTGRESQL_SERVICE_PORT}/${POSTGRESQL_DATABASE} + username: ${POSTGRESQL_USER} + password: ${POSTGRESQL_PASSWORD} + jpa: + database-platform: org.hibernate.dialect.PostgreSQLDialect +s3: + accessKey: ${S3_ACCESS_KEY} + secretKey: ${S3_SECRET_KEY} + bucketName: ${S3_BUCKET_NAME} + region: + name: ${S3_REGION_NAME} + endpoint: ${S3_REGION_ENDPOINT} + proxy: + enabled: false + host: "" + port: -1 +log-upload: + cleanup-cron: 0 0 0 * * * + log-entity-lifetime: 7 + log-id-byte-length: 10 + keycloak-pw-reset-url: https://localhost:8443/auth/realms/cwa/account/password +els-verify: + tls: + enabled: true + hostname-verify: false + key-store: ${LOG_UPLOAD_KEYSTORE_PATH} + key-store-password: ${LOG_UPLOAD_KEYSTORE_PASSWORD} + trust-store: ${ELS_VERIFY_TRUSTSTORE_PATH} + trust-store-password: ${ELS_VERIFY_TRUSTSTORE_PASSWORD} +server: + port: 8085 + ssl: + key-store: ${LOG_UPLOAD_KEYSTORE_PATH} + key-store-password: ${LOG_UPLOAD_KEYSTORE_PASSWORD} + enabled: true + protocol: TLS + enabled-protocols: TLSv1.2,TLSv1.3 + ciphers: + - TLS_AES_128_GCM_SHA256 + - TLS_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 + diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..ebae9e0 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,93 @@ +spring: + servlet: + multipart: + max-file-size: 60MB + max-request-size: 60MB + application: + name: cwa-log-upload + datasource: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:log + username: sa + password: '' + liquibase: + change-log: classpath:db/changelog.yml + mvc: + static-path-pattern: /portal/static/** +keycloak: + auth-server-url: https://localhost:8443/auth + principal-attribute: preferred_username + public-client: true + realm: cwa + resource: log-upload + disable-trust-manager: true + +server: + error: + whitelabel: + enabled: false + port: 8085 + servlet: + context-path: / + session: + timeout: 14400s + +management: + server: + port: 8085 + endpoint: + info: + enabled: true + health: + enabled: true + probes: + enabled: true + metrics: + enabled: true + prometheus: + enabled: true + endpoints: + enabled-by-default: false + web: + exposure: + include: info,health,metrics,prometheus + jmx: + exposure: + include: info,health,metrics,prometheus + metrics: + export: + prometheus: + enabled: true + +host-header: + whitelist: localhost:8085 + +pod: + ip: 127.0.0.1 + port: 8081 + +s3: + accessKey: "minioadmin" + secretKey: "minioadmin" + bucketName: "log-upload" + region: + name: "eu-de" + endpoint: "http://localhost:9000" + proxy: + enabled: false + host: "" + port: -1 +log-upload: + cleanup-cron: 0 0 0 * * * + log-entity-lifetime: 7 + log-id-byte-length: 10 + keycloak-pw-reset-url: https://localhost:8443/auth/realms/cwa/account/password +els-verify: + url: https://localhost:8103 + tls: + enabled: true + hostname-verify: false + key-store: /client.jks + key-store-password: 123456 + trust-store: /truststore.jks + trust-store-password: 123456 diff --git a/src/main/resources/bootstrap-cloud.yaml b/src/main/resources/bootstrap-cloud.yaml new file mode 100644 index 0000000..d9f50db --- /dev/null +++ b/src/main/resources/bootstrap-cloud.yaml @@ -0,0 +1,30 @@ +--- +spring: + application: + name: cwa-log-upload + cloud: + vault: + ssl: + trust-store: file:${SSL_VAULT_TRUSTSTORE_PATH} + trust-store-password: ${SSL_VAULT_TRUSTSTORE_PASSWORD} + enabled: true + generic: + enabled: false + kv: + enabled: true + backend: ${VAULT_BACKEND} + profile-separator: '/' + application-name: 'cwa-log-upload' + default-context: '' + profiles: cloud + fail-fast: true + authentication: KUBERNETES + kubernetes: + role: ${VAULT_ROLE} + kubernetes-path: kubernetes + service-account-token-file: /var/run/secrets/kubernetes.io/serviceaccount/token + uri: ${VAULT_URI} + connection-timeout: 5000 + read-timeout: 15000 + config: + order: -10 diff --git a/src/main/resources/bootstrap.yaml b/src/main/resources/bootstrap.yaml new file mode 100644 index 0000000..4eed0ce --- /dev/null +++ b/src/main/resources/bootstrap.yaml @@ -0,0 +1,5 @@ +--- +spring: + cloud: + vault: + enabled: false diff --git a/src/main/resources/db/changelog.yml b/src/main/resources/db/changelog.yml new file mode 100644 index 0000000..945dd83 --- /dev/null +++ b/src/main/resources/db/changelog.yml @@ -0,0 +1,4 @@ +databaseChangeLog: + - include: + file: changelog/v000-create-tables.yml + relativeToChangelogFile: true diff --git a/src/main/resources/db/changelog/v000-create-tables.yml b/src/main/resources/db/changelog/v000-create-tables.yml new file mode 100644 index 0000000..cdb964f --- /dev/null +++ b/src/main/resources/db/changelog/v000-create-tables.yml @@ -0,0 +1,40 @@ +databaseChangeLog: + - changeSet: + id: create-tables + author: f11h + changes: + - createTable: + tableName: log + columns: + - column: + name: id + type: varchar(32) + constraints: + unique: true + nullable: false + primaryKey: true + - column: + name: created_at + type: datetime(2) + constraints: + nullable: false + - column: + name: filename + type: varchar(100) + constraints: + nullable: false + - column: + name: size + type: long + constraints: + nullable: false + - column: + name: hash + type: varchar(32) + constraints: + nullable: false + - column: + name: metadata + type: varchar(1000) + constraints: + nullable: true \ No newline at end of file diff --git a/src/main/resources/static/css/codemirror.addons.css b/src/main/resources/static/css/codemirror.addons.css new file mode 100644 index 0000000..677c078 --- /dev/null +++ b/src/main/resources/static/css/codemirror.addons.css @@ -0,0 +1,32 @@ +.CodeMirror-dialog { + position: absolute; + left: 0; right: 0; + background: inherit; + z-index: 15; + padding: .1em .8em; + overflow: hidden; + color: inherit; +} + +.CodeMirror-dialog-top { + border-bottom: 1px solid #eee; + top: 0; +} + +.CodeMirror-dialog-bottom { + border-top: 1px solid #eee; + bottom: 0; +} + +.CodeMirror-dialog input { + border: none; + outline: none; + background: transparent; + width: 20em; + color: inherit; + font-family: monospace; +} + +.CodeMirror-dialog button { + font-size: 70%; +} diff --git a/src/main/resources/static/css/codemirror.css b/src/main/resources/static/css/codemirror.css new file mode 100644 index 0000000..df78709 --- /dev/null +++ b/src/main/resources/static/css/codemirror.css @@ -0,0 +1,351 @@ +/* BASICS */ + +.CodeMirror { + /* Set height, width, borders, and global font properties here */ + font-family: monospace; + height: 100%; + width: 100%; + color: black; + direction: ltr; +} + +/* PADDING */ + +.CodeMirror-lines { + padding: 4px 0; /* Vertical padding around content */ +} +.CodeMirror pre.CodeMirror-line, +.CodeMirror pre.CodeMirror-line-like { + padding: 0 4px; /* Horizontal padding of content */ +} + +.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + background-color: transparent; /* The little square between H and V scrollbars */ +} + +/* GUTTER */ + +.CodeMirror-gutters { + border-right: 1px solid #ddd; + background-color: #f7f7f7; + white-space: nowrap; +} +.CodeMirror-linenumbers {} +.CodeMirror-linenumber { + padding: 0 3px 0 5px; + min-width: 20px; + text-align: right; + color: #999; + white-space: nowrap; +} + +.CodeMirror-guttermarker { color: black; } +.CodeMirror-guttermarker-subtle { color: #999; } + +/* CURSOR */ + +.CodeMirror-cursor { + border-left: 1px solid black; + border-right: none; + width: 0; +} +/* Shown when moving in bi-directional text */ +.CodeMirror div.CodeMirror-secondarycursor { + border-left: 1px solid silver; +} +.cm-fat-cursor .CodeMirror-cursor { + width: auto; + border: 0 !important; + background: #7e7; +} +.cm-fat-cursor div.CodeMirror-cursors { + z-index: 1; +} +.cm-fat-cursor-mark { + background-color: rgba(20, 255, 20, 0.5); + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; +} +.cm-animate-fat-cursor { + width: auto; + border: 0; + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; + background-color: #7e7; +} +@-moz-keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} +@-webkit-keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} +@keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} + +/* Can style cursor different in overwrite (non-insert) mode */ +.CodeMirror-overwrite .CodeMirror-cursor {} + +.cm-tab { display: inline-block; text-decoration: inherit; } + +.CodeMirror-rulers { + position: absolute; + left: 0; right: 0; top: -50px; bottom: 0; + overflow: hidden; +} +.CodeMirror-ruler { + border-left: 1px solid #ccc; + top: 0; bottom: 0; + position: absolute; +} + +/* DEFAULT THEME */ + +.cm-s-default .cm-header {color: blue;} +.cm-s-default .cm-quote {color: #090;} +.cm-negative {color: #d44;} +.cm-positive {color: #292;} +.cm-header, .cm-strong {font-weight: bold;} +.cm-em {font-style: italic;} +.cm-link {text-decoration: underline;} +.cm-strikethrough {text-decoration: line-through;} + +.cm-s-default .cm-keyword {color: #708;} +.cm-s-default .cm-atom {color: #219;} +.cm-s-default .cm-number {color: #164;} +.cm-s-default .cm-def {color: #00f;} +.cm-s-default .cm-variable, +.cm-s-default .cm-punctuation, +.cm-s-default .cm-property, +.cm-s-default .cm-operator {} +.cm-s-default .cm-variable-2 {color: #05a;} +.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;} +.cm-s-default .cm-comment {color: #a50;} +.cm-s-default .cm-string {color: #a11;} +.cm-s-default .cm-string-2 {color: #f50;} +.cm-s-default .cm-meta {color: #555;} +.cm-s-default .cm-qualifier {color: #555;} +.cm-s-default .cm-builtin {color: #30a;} +.cm-s-default .cm-bracket {color: #997;} +.cm-s-default .cm-tag {color: #170;} +.cm-s-default .cm-attribute {color: #00c;} +.cm-s-default .cm-hr {color: #999;} +.cm-s-default .cm-link {color: #00c;} + +.cm-s-default .cm-error {color: #f00;} +.cm-invalidchar {color: #f00;} + +.CodeMirror-composing { border-bottom: 2px solid; } + +/* Default styles for common addons */ + +div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;} +div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;} +.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } +.CodeMirror-activeline-background {background: #e8f2ff;} + +/* STOP */ + +/* The rest of this file contains styles related to the mechanics of + the editor. You probably shouldn't touch them. */ + +.CodeMirror { + position: relative; + overflow: hidden; + background: white; +} + +.CodeMirror-scroll { + overflow: scroll !important; /* Things will break if this is overridden */ + /* 50px is the magic margin used to hide the element's real scrollbars */ + /* See overflow: hidden in .CodeMirror */ + margin-bottom: -50px; margin-right: -50px; + padding-bottom: 50px; + height: 100%; + outline: none; /* Prevent dragging from highlighting the element */ + position: relative; +} +.CodeMirror-sizer { + position: relative; + border-right: 50px solid transparent; +} + +/* The fake, visible scrollbars. Used to force redraw during scrolling + before actual scrolling happens, thus preventing shaking and + flickering artifacts. */ +.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + position: absolute; + z-index: 6; + display: none; + outline: none; +} +.CodeMirror-vscrollbar { + right: 0; top: 0; + overflow-x: hidden; + overflow-y: scroll; +} +.CodeMirror-hscrollbar { + bottom: 0; left: 0; + overflow-y: hidden; + overflow-x: scroll; +} +.CodeMirror-scrollbar-filler { + right: 0; bottom: 0; +} +.CodeMirror-gutter-filler { + left: 0; bottom: 0; +} + +.CodeMirror-gutters { + position: absolute; left: 0; top: 0; + min-height: 100%; + z-index: 3; +} +.CodeMirror-gutter { + white-space: normal; + height: 100%; + display: inline-block; + vertical-align: top; + margin-bottom: -50px; +} +.CodeMirror-gutter-wrapper { + position: absolute; + z-index: 4; + background: none !important; + border: none !important; +} +.CodeMirror-gutter-background { + position: absolute; + top: 0; bottom: 0; + z-index: 4; +} +.CodeMirror-gutter-elt { + position: absolute; + cursor: default; + z-index: 4; +} +.CodeMirror-gutter-wrapper ::selection { background-color: transparent } +.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent } + +.CodeMirror-lines { + cursor: text; + min-height: 1px; /* prevents collapsing before first draw */ +} +.CodeMirror pre.CodeMirror-line, +.CodeMirror pre.CodeMirror-line-like { + /* Reset some styles that the rest of the page might have set */ + -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; + border-width: 0; + background: transparent; + font-family: inherit; + font-size: inherit; + margin: 0; + white-space: pre; + word-wrap: normal; + line-height: inherit; + color: inherit; + z-index: 2; + position: relative; + overflow: visible; + -webkit-tap-highlight-color: transparent; + -webkit-font-variant-ligatures: contextual; + font-variant-ligatures: contextual; +} +.CodeMirror-wrap pre.CodeMirror-line, +.CodeMirror-wrap pre.CodeMirror-line-like { + word-wrap: break-word; + white-space: pre-wrap; + word-break: normal; +} + +.CodeMirror-linebackground { + position: absolute; + left: 0; right: 0; top: 0; bottom: 0; + z-index: 0; +} + +.CodeMirror-linewidget { + position: relative; + z-index: 2; + padding: 0.1px; /* Force widget margins to stay inside of the container */ +} + +.CodeMirror-widget {} + +.CodeMirror-rtl pre { direction: rtl; } + +.CodeMirror-code { + outline: none; +} + +/* Force content-box sizing for the elements where we expect it */ +.CodeMirror-scroll, +.CodeMirror-sizer, +.CodeMirror-gutter, +.CodeMirror-gutters, +.CodeMirror-linenumber { + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +.CodeMirror-measure { + position: absolute; + width: 100%; + height: 0; + overflow: hidden; + visibility: hidden; +} + +.CodeMirror-cursor { + position: absolute; + pointer-events: none; +} +.CodeMirror-measure pre { position: static; } + +div.CodeMirror-cursors { + visibility: hidden; + position: relative; + z-index: 3; +} +div.CodeMirror-dragcursors { + visibility: visible; +} + +.CodeMirror-focused div.CodeMirror-cursors { + visibility: visible; +} + +.CodeMirror-selected { background: #d9d9d9; } +.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } +.CodeMirror-crosshair { cursor: crosshair; } +.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } +.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } + +.cm-searching { + background-color: #ffa; + background-color: rgba(255, 255, 0, .4); +} + +/* Used to force a border model for a node */ +.cm-force-border { padding-right: .1px; } + +@media print { + /* Hide the cursor when printing */ + .CodeMirror div.CodeMirror-cursors { + visibility: hidden; + } +} + +/* See issue #2901 */ +.cm-tab-wrap-hack:after { content: ''; } + +/* Help users use markselection to safely style text background */ +span.CodeMirror-selectedtext { background: none; } \ No newline at end of file diff --git a/src/main/resources/static/css/cwa.css b/src/main/resources/static/css/cwa.css new file mode 100644 index 0000000..d653ce5 --- /dev/null +++ b/src/main/resources/static/css/cwa.css @@ -0,0 +1,245 @@ + +.header { + position: fixed; + top: 0px; + left: 0px; + height: 84px; + width: 100%; + background: #E20074; +} + +.header-logo { + position: fixed; + top: 24px; + left: 24px; +} + +.header-text { + position: fixed; + top: 37px; + right: 20px; +} + +.footer { + position: fixed; + bottom: 0px; + left: 0px; + height: 50px; + width: 100%; + border-top-style: solid; + border-width: 1px; + border-color: #757575; +} + +.footer-font { + font-family: "Telegrotesk Next Regular"; + font-size: 14px; + color: #757575; +} + +.footer-copyright { + position: fixed; + left: 24px; + bottom: 3px; +} + +.footer-imprint { + position: fixed; + right: 160px; + bottom: 3px; +} + +.footer-dp { + position: fixed; + right: 20px; + bottom: 3px; +} + +.footer-dp-logo { + position: fixed; + right: 100px; + bottom: 18px; +} + +.c19-logo { + position: fixed; + top: 120px; + left: calc(50% - 200px); + width: 400px; + font-family: "Telegrotesk Next Bold"; + font-size: 21px; + color: #262626; +} + +.text { + position: fixed; + left: calc(50% - 300px); + width: 600px; + text-align: center; + font-family: "Telegrotesk Next Regular"; + font-size: 18px; + color: #262626; +} + +.text-bold { + position: fixed; + left: calc(50% - 300px); + width: 600px; + text-align: center; + font-family: "Telegrotesk Next Bold"; + font-size: 18px; + color: #262626; +} + +.text-medium { + position: fixed; + left: calc(50% - 300px); + width: 600px; + text-align: center; + font-family: "Telegrotesk Next Thin"; + font-size: 36px; + color: #262626; +} + + +.text-big { + position: fixed; + left: calc(50% - 300px); + width: 600px; + text-align: center; + font-family: "Telegrotesk Next Thin"; + font-size: 48px; + color: #262626; +} + +.text-error { + position: fixed; + left: calc(50% - 300px); + width: 600px; + text-align: center; + font-family: "Telegrotesk Next Bold"; + font-size: 18px; + color: #E20074; +} + +.button { + position: fixed; + left: calc(50% - 250px); + width: 500px; + height: 40px; + font-family: "Telegrotesk Next Regular"; + font-size: 18px; + color: #FFFFFF; + background: #E20074; + border-width: 1px; + border-radius: 4px; + border-color: #E20074; +} + +.input { + position: fixed; + left: calc(50% - 250px); + width: 485px; + height: 45px; + line-height: 45px; + padding-left: 10px; + font-family: "Telegrotesk Next Regular"; + font-size: 18px; + color: #262626; + border-width: 1px; + border-radius: 4px; + border-color: #dddddd; +} + +.error { + position: fixed; + left: calc(50% - 250px); + width: 480px; + padding: 10px; + font-family: "Telegrotesk Next Bold"; + font-size: 14px; + color: #E20074; + background-color: rgba(226, 0, 74, 0.1); + border-width: 1px; + border-radius: 4px; + border-color: rgba(226, 0, 74, 0.1); +} + +.user { + position: fixed; + right: 40px; + top: 100px; + font-family: "Telegrotesk Next Regular"; + font-size: 18px; + color: #262626; +} + +.button-menu { + position: fixed; + right: 40px; + visibility: hidden; + font-family: "Telegrotesk Next Regular"; + font-size: 18px; + color: #262626; + border-width: 1px; + border-radius: 4px; + border-color: #dddddd; + background-color: #ffffff; +} + +.button-logout { + top: 150px; + width: 100px; +} + +.button-pw-reset { + top: 180px; + width: 140px; +} + +.show { + visibility: visible; +} + +.file-item-list { + list-style-type: none; + position: fixed; + left: calc(50% - 200px); + width: 400px; + padding: 0; + height: 400px; + overflow-y: auto; +} + +.file-item-list > li { + font-family: "Telegrotesk Next Regular"; + text-overflow: ellipsis; + padding: 5px 10px; + margin: 5px 20px 5px 5px; + border: 1px solid #69696955; + border-radius: 3px; + cursor: pointer; + + transition-property: font-weight, background-color; + transition-duration: 200ms; +} + +.file-item-list > li:hover { + background-color: lightgray; + font-weight: bold; +} + +@font-face { + font-family: 'Telegrotesk Next Regular'; + src: url('telegrotesknext-regular.woff'); +} + +@font-face { + font-family: 'Telegrotesk Next Thin'; + src: url('telegrotesknext-thin.woff'); +} + +@font-face { + font-family: 'Telegrotesk Next Bold'; + src: url('telegrotesknext-bold.woff'); +} diff --git a/src/main/resources/static/css/jspanel.min.css b/src/main/resources/static/css/jspanel.min.css new file mode 100644 index 0000000..0a61511 --- /dev/null +++ b/src/main/resources/static/css/jspanel.min.css @@ -0,0 +1 @@ +.default-bg,.secondary-bg{background-color:#b0bec5}.primary-bg{background-color:#01579b}.info-bg{background-color:#039be5}.success-bg{background-color:#2e7d32}.warning-bg{background-color:#f57f17}.danger-bg{background-color:#dd2c00}.light-bg{background-color:#e0e0e0}.dark-bg{background-color:#263238}.jsPanel{border:0;box-sizing:border-box;vertical-align:baseline;font-family:Roboto,"Open Sans",Lato,"Helvetica Neue",Arial,sans-serif;font-weight:400;display:flex;flex-direction:column;opacity:0;overflow:visible;position:absolute;z-index:100}.jsPanel .jsPanel-hdr{border:0;box-sizing:border-box;vertical-align:baseline;font-family:Roboto,"Open Sans",Lato,"Helvetica Neue",Arial,sans-serif;font-weight:400;display:flex;flex-direction:column;flex-shrink:0;line-height:normal}.jsPanel .jsPanel-content{border:0;box-sizing:border-box;vertical-align:baseline;font-family:Roboto,"Open Sans",Lato,"Helvetica Neue",Arial,sans-serif;font-weight:400;background:#fff;color:#000;font-size:1rem;position:relative;overflow-x:hidden;overflow-y:auto;flex-grow:1}.jsPanel .jsPanel-content pre{color:inherit}.jsPanel .jsPanel-ftr{flex-direction:row;justify-content:flex-end;flex-wrap:nowrap;align-items:center;display:none;box-sizing:border-box;font-size:1rem;height:auto;background:#f5f5f5;font-weight:400;color:#000;overflow:hidden}.jsPanel .jsPanel-ftr.active{display:flex;flex-shrink:0;margin:0;padding:3px 8px}.jsPanel-hdr.jsPanel-hdr-dark .jsPanel-btn:hover{background-color:rgba(255,255,255,.4)}.jsPanel-hdr.jsPanel-hdr-light .jsPanel-btn:hover{background-color:rgba(0,0,0,.15)}.jsPanel-hdr-toolbar{font-size:1rem}.jsPanel-headerbar{box-sizing:border-box;display:flex;flex-direction:row;flex-wrap:nowrap;align-items:center}.jsPanel-headerbar img{vertical-align:middle;max-height:38px}.jsPanel-titlebar{display:flex;align-items:center;font-size:1rem;flex:1 1 0;cursor:move;height:100%;overflow:hidden;user-select:none}.jsPanel-titlebar .jsPanel-title{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-variant:small-caps;font-weight:400;margin:0 5px 0 8px;min-width:0}.jsPanel-titlebar.jsPanel-rtl{flex-direction:row-reverse}.jsPanel-controlbar{display:flex;align-items:center;align-self:start;touch-action:none;margin:3px}.jsPanel-controlbar .jsPanel-btn{cursor:pointer;touch-action:none;border-radius:3px;border:0;padding:0;margin:0;background-color:transparent;box-shadow:none}.jsPanel-controlbar .jsPanel-btn i,.jsPanel-controlbar .jsPanel-btn span,.jsPanel-controlbar .jsPanel-btn svg.jsPanel-icon{vertical-align:middle}.jsPanel-controlbar .jsPanel-btn span.glyphicon{padding:0 2px}.jsPanel-controlbar .jsPanel-btn svg.svg-inline--fa{margin:2px 3px}.jsPanel-controlbar .jsPanel-btn-normalize{display:none}.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-xl span:not(.material-icons),.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-xl svg:not(.svg-inline--fa){width:2rem;height:2rem;margin:2px 3px}.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-xl .svg-inline--fa{font-size:2rem}.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-xl span.material-icons{font-size:2.2rem}.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-xl span[class^=fa]{width:auto;height:auto;font-size:2rem;margin:0 4px}.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-lg span:not(.material-icons),.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-lg svg:not(.svg-inline--fa){width:1.75rem;height:1.75rem;margin:2px 3px}.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-lg .svg-inline--fa{font-size:1.75rem}.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-lg span.material-icons{font-size:1.9rem}.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-lg span[class^=fa]{width:auto;height:auto;font-size:1.75rem}.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-md span:not(.material-icons),.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-md svg:not(.svg-inline--fa){width:1.5rem;height:1.5rem;margin:2px 3px}.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-md .svg-inline--fa{font-size:1.5rem}.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-md span.material-icons{font-size:1.6rem}.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-md span[class^=fa]{width:auto;height:auto;font-size:1.5rem}.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-sm span:not(.material-icons),.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-sm svg:not(.svg-inline--fa){width:1.25rem;height:1.25rem;margin:2px 3px}.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-sm .svg-inline--fa{font-size:1.25rem}.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-sm span.material-icons{font-size:1.3rem}.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-sm span[class^=fa]{width:auto;height:auto;font-size:1.25rem}.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-xs span:not(.material-icons),.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-xs svg:not(.svg-inline--fa){width:1rem;height:1rem;margin:1px 3px}.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-xs .svg-inline--fa{font-size:1rem}.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-xs span.material-icons{font-size:1rem}.jsPanel-controlbar .jsPanel-btn.jsPanel-btn-xs span[class^=fa]{width:auto;height:auto;font-size:1rem}.jsPanel-hdr-toolbar{display:none;width:auto;height:auto}.jsPanel-hdr-toolbar.active{box-sizing:border-box;display:flex;flex-direction:row;flex-wrap:nowrap;align-items:center;padding:3px 8px}.jsPanel-titlebar .jsPanel-title[dir=rtl]{margin:0 8px 0 5px}.jsPanel-hdr-toolbar[dir=rtl].active{padding:0 8px 0 8px}.jsPanel-content[dir=rtl]{text-align:right}.jsPanel-ftr[dir=rtl]{flex-direction:row}#jsPanel-replacement-container,.jsPanel-minimized-box,.jsPanel-minimized-container{display:flex;flex-flow:row wrap-reverse;background:transparent none repeat scroll 0 0;bottom:0;height:auto;left:0;position:fixed;width:auto;z-index:9998}#jsPanel-replacement-container .jsPanel-replacement,.jsPanel-minimized-box .jsPanel-replacement,.jsPanel-minimized-container .jsPanel-replacement{font-family:Roboto,"Open Sans",Lato,"Helvetica Neue",Arial,sans-serif;display:flex;align-items:center;width:200px;height:34px;margin:1px 1px 0 0;z-index:9999}#jsPanel-replacement-container .jsPanel-replacement .jsPanel-hdr,.jsPanel-minimized-box .jsPanel-replacement .jsPanel-hdr,.jsPanel-minimized-container .jsPanel-replacement .jsPanel-hdr{flex-grow:1;min-width:0;padding:0;height:34px;overflow:hidden}#jsPanel-replacement-container .jsPanel-replacement .jsPanel-hdr .jsPanel-headerlogo,.jsPanel-minimized-box .jsPanel-replacement .jsPanel-hdr .jsPanel-headerlogo,.jsPanel-minimized-container .jsPanel-replacement .jsPanel-hdr .jsPanel-headerlogo{max-width:50%;overflow:hidden}#jsPanel-replacement-container .jsPanel-replacement .jsPanel-hdr .jsPanel-headerlogo img,.jsPanel-minimized-box .jsPanel-replacement .jsPanel-hdr .jsPanel-headerlogo img,.jsPanel-minimized-container .jsPanel-replacement .jsPanel-hdr .jsPanel-headerlogo img{max-width:100px;max-height:34px}#jsPanel-replacement-container .jsPanel-replacement .jsPanel-titlebar,.jsPanel-minimized-box .jsPanel-replacement .jsPanel-titlebar,.jsPanel-minimized-container .jsPanel-replacement .jsPanel-titlebar{cursor:default;min-width:0}#jsPanel-replacement-container .jsPanel-replacement .jsPanel-btn.jsPanel-btn-normalize,.jsPanel-minimized-box .jsPanel-replacement .jsPanel-btn.jsPanel-btn-normalize,.jsPanel-minimized-container .jsPanel-replacement .jsPanel-btn.jsPanel-btn-normalize{display:block}.jsPanel-minimized-box,.jsPanel-minimized-container{position:absolute;width:100%;overflow:hidden}.flexOne{display:flex;flex-flow:row wrap}.jsPanel-resizeit-handle{display:block;font-size:.1px;position:absolute;touch-action:none}.jsPanel-resizeit-handle.jsPanel-resizeit-n{cursor:n-resize;height:12px;left:9px;top:-5px;width:calc(100% - 18px)}.jsPanel-resizeit-handle.jsPanel-resizeit-e{cursor:e-resize;height:calc(100% - 18px);right:-9px;top:9px;width:12px}.jsPanel-resizeit-handle.jsPanel-resizeit-s{bottom:-9px;cursor:s-resize;height:12px;left:9px;width:calc(100% - 18px)}.jsPanel-resizeit-handle.jsPanel-resizeit-w{cursor:w-resize;height:calc(100% - 18px);left:-9px;top:9px;width:12px}.jsPanel-resizeit-handle.jsPanel-resizeit-ne{cursor:ne-resize;height:18px;right:-9px;top:-9px;width:18px}.jsPanel-resizeit-handle.jsPanel-resizeit-se{bottom:-9px;cursor:se-resize;height:18px;right:-9px;width:18px}.jsPanel-resizeit-handle.jsPanel-resizeit-sw{bottom:-9px;cursor:sw-resize;height:18px;left:-9px;width:18px}.jsPanel-resizeit-handle.jsPanel-resizeit-nw{cursor:nw-resize;height:18px;left:-9px;top:-9px;width:18px}.jsPanel-drag-overlay{width:100%;height:100%;position:absolute;left:0;top:0}.jsPanel-error .jsPanel-content{border:0!important;padding-top:0!important;font-size:.9rem;text-align:center}.jsPanel-error .jsPanel-content p{margin:0 0 10px 0}.jsPanel-error .jsPanel-content mark{background:#e6e6fa;border-radius:.33rem;padding:0 8px;font-family:monospace}.jsPanel-error .jsPanel-content .jsPanel-error-content-separator{width:100%;height:1px;background-image:linear-gradient(90deg,#fff 0,#663399 50%,#fff 100%);margin-bottom:10px}.jsPanel-depth-1{box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23)}.jsPanel-depth-2{box-shadow:0 10px 20px rgba(0,0,0,.19),0 6px 6px rgba(0,0,0,.23)}.jsPanel-depth-3{box-shadow:0 14px 28px rgba(0,0,0,.25),0 10px 10px rgba(0,0,0,.22)}.jsPanel-depth-4{box-shadow:0 19px 38px rgba(0,0,0,.3),0 15px 12px rgba(0,0,0,.22)}.jsPanel-depth-5{box-shadow:0 24px 48px rgba(0,0,0,.3),0 20px 14px rgba(0,0,0,.22)}.jsPanel-snap-area{position:fixed;background:#000;opacity:.2;border:1px solid silver;box-shadow:0 14px 28px rgba(0,0,0,.5),0 10px 10px rgba(0,0,0,.5);z-index:9999}.jsPanel-snap-area-lb,.jsPanel-snap-area-lc,.jsPanel-snap-area-left-bottom,.jsPanel-snap-area-left-center,.jsPanel-snap-area-left-top,.jsPanel-snap-area-lt{left:0}.jsPanel-snap-area-cb,.jsPanel-snap-area-ct{left:37.5%}.jsPanel-snap-area-rb,.jsPanel-snap-area-rc,.jsPanel-snap-area-right-bottom,.jsPanel-snap-area-right-center,.jsPanel-snap-area-right-top,.jsPanel-snap-area-rt{right:0}.jsPanel-snap-area-center-top,.jsPanel-snap-area-ct,.jsPanel-snap-area-left-top,.jsPanel-snap-area-lt,.jsPanel-snap-area-right-top,.jsPanel-snap-area-rt{top:0}.jsPanel-snap-area-lc,.jsPanel-snap-area-rc{top:37.5%}.jsPanel-snap-area-cb,.jsPanel-snap-area-center-bottom,.jsPanel-snap-area-lb,.jsPanel-snap-area-left-bottom,.jsPanel-snap-area-rb,.jsPanel-snap-area-right-bottom{bottom:0}.jsPanel-snap-area-cb,.jsPanel-snap-area-ct{width:25%}.jsPanel-snap-area-lc,.jsPanel-snap-area-rc{height:25%}.jsPanel-snap-area-left-top,.jsPanel-snap-area-lt{border-bottom-right-radius:100%}.jsPanel-snap-area-right-top,.jsPanel-snap-area-rt{border-bottom-left-radius:100%}.jsPanel-snap-area-rb,.jsPanel-snap-area-right-bottom{border-top-left-radius:100%}.jsPanel-snap-area-lb,.jsPanel-snap-area-left-bottom{border-top-right-radius:100%}.jsPanel-connector-left-bottom-corner,.jsPanel-connector-left-top-corner,.jsPanel-connector-right-bottom-corner,.jsPanel-connector-right-top-corner{width:12px;height:12px;position:absolute;border-radius:50%}.jsPanel-connector-left-top-corner{left:calc(100% - 6px);top:calc(100% - 6px)}.jsPanel-connector-right-top-corner{left:-6px;top:calc(100% - 6px)}.jsPanel-connector-right-bottom-corner{left:-6px;top:-6px}.jsPanel-connector-left-bottom-corner{left:calc(100% - 6px);top:-6px}.jsPanel-connector-bottom,.jsPanel-connector-bottomleft,.jsPanel-connector-bottomright,.jsPanel-connector-left,.jsPanel-connector-leftbottom,.jsPanel-connector-lefttop,.jsPanel-connector-right,.jsPanel-connector-rightbottom,.jsPanel-connector-righttop,.jsPanel-connector-top,.jsPanel-connector-topleft,.jsPanel-connector-topright{width:0;height:0;position:absolute;border:12px solid transparent}.jsPanel-connector-top,.jsPanel-connector-topleft,.jsPanel-connector-topright{top:100%;border-bottom-width:0}.jsPanel-connector-top{left:calc(50% - 12px)}.jsPanel-connector-topleft{left:0}.jsPanel-connector-topright{left:calc(100% - 24px)}.jsPanel-connector-bottom,.jsPanel-connector-bottomleft,.jsPanel-connector-bottomright{top:-12px;border-top-width:0}.jsPanel-connector-bottom{left:calc(50% - 12px)}.jsPanel-connector-bottomleft{left:0}.jsPanel-connector-bottomright{left:calc(100% - 24px)}.jsPanel-connector-left,.jsPanel-connector-leftbottom,.jsPanel-connector-lefttop{left:100%;border-right-width:0}.jsPanel-connector-left{top:calc(50% - 12px)}.jsPanel-connector-lefttop{top:0}.jsPanel-connector-leftbottom{top:calc(100% - 24px)}.jsPanel-connector-right,.jsPanel-connector-rightbottom,.jsPanel-connector-righttop{left:-12px;border-left-width:0}.jsPanel-connector-right{top:calc(50% - 12px)}.jsPanel-connector-righttop{top:0}.jsPanel-connector-rightbottom{top:calc(100% - 24px)}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){#jsPanel-replacement-container .jsPanel-replacement .jsPanel-titlebar{max-width:105px}}@keyframes jsPanelFadeIn{from{opacity:0}to{opacity:1}}.jsPanelFadeIn{opacity:0;animation:jsPanelFadeIn ease-in 1;animation-fill-mode:forwards;animation-duration:.6s}@keyframes jsPanelFadeOut{from{opacity:1}to{opacity:0}}.jsPanelFadeOut{animation:jsPanelFadeOut ease-in 1;animation-fill-mode:forwards;animation-duration:.6s}@keyframes modalBackdropFadeIn{from{opacity:0}to{opacity:.65}}.jsPanel-modal-backdrop{animation:modalBackdropFadeIn ease-in 1;animation-fill-mode:forwards;animation-duration:750ms;background:#000;position:fixed;top:0;left:0;width:100%;height:100%}@keyframes modalBackdropFadeOut{from{opacity:.65}to{opacity:0}}.jsPanel-modal-backdrop-out{animation:modalBackdropFadeOut ease-in 1;animation-fill-mode:forwards;animation-duration:.4s}.jsPanel-modal-backdrop-multi{background:rgba(0,0,0,.15)}.jsPanel-content .jsPanel-iframe-overlay{position:absolute;top:0;width:100%;height:100%;background:0 0}.jsPanel-addCloseCtrl{position:absolute;top:0;right:0;width:.8rem;height:.8rem;margin:2px;cursor:pointer;line-height:.8rem;padding:0;z-index:100;border:0;background-color:transparent}.jsPanel-addCloseCtrl.rtl{right:unset;left:0}.jsPanel-progressbar{position:relative;width:100%;height:0;overflow:hidden}.jsPanel-progressbar .jsPanel-progressbar-slider{position:absolute;width:0;height:3px;background:#d3d3d3;right:0}.jsPanel-progressbar.active{height:3px}@keyframes progressbar{from{width:0}to{width:100%}}.jsPanel-content.jsPanel-content-noheader{border:none!important}body{-ms-overflow-style:scrollbar} \ No newline at end of file diff --git a/src/main/resources/static/css/telegrotesknext-bold.woff b/src/main/resources/static/css/telegrotesknext-bold.woff new file mode 100644 index 0000000..487172f Binary files /dev/null and b/src/main/resources/static/css/telegrotesknext-bold.woff differ diff --git a/src/main/resources/static/css/telegrotesknext-regular.woff b/src/main/resources/static/css/telegrotesknext-regular.woff new file mode 100644 index 0000000..3a8dd41 Binary files /dev/null and b/src/main/resources/static/css/telegrotesknext-regular.woff differ diff --git a/src/main/resources/static/css/telegrotesknext-thin.woff b/src/main/resources/static/css/telegrotesknext-thin.woff new file mode 100644 index 0000000..08f7042 Binary files /dev/null and b/src/main/resources/static/css/telegrotesknext-thin.woff differ diff --git a/src/main/resources/static/img/c-19_logo.png b/src/main/resources/static/img/c-19_logo.png new file mode 100644 index 0000000..53dc409 Binary files /dev/null and b/src/main/resources/static/img/c-19_logo.png differ diff --git a/src/main/resources/static/img/data_protect.png b/src/main/resources/static/img/data_protect.png new file mode 100644 index 0000000..3b7fd8c Binary files /dev/null and b/src/main/resources/static/img/data_protect.png differ diff --git a/src/main/resources/static/img/life_is_for_sharing.png b/src/main/resources/static/img/life_is_for_sharing.png new file mode 100644 index 0000000..6e85291 Binary files /dev/null and b/src/main/resources/static/img/life_is_for_sharing.png differ diff --git a/src/main/resources/static/img/telekom_web_logo.png b/src/main/resources/static/img/telekom_web_logo.png new file mode 100644 index 0000000..1bca0ef Binary files /dev/null and b/src/main/resources/static/img/telekom_web_logo.png differ diff --git a/src/main/resources/static/img/user.png b/src/main/resources/static/img/user.png new file mode 100644 index 0000000..a50dc71 Binary files /dev/null and b/src/main/resources/static/img/user.png differ diff --git a/src/main/resources/static/js/codemirror.addons.js b/src/main/resources/static/js/codemirror.addons.js new file mode 100644 index 0000000..21883ae --- /dev/null +++ b/src/main/resources/static/js/codemirror.addons.js @@ -0,0 +1,776 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +// Defines jumpToLine command. Uses dialog.js if present. + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../dialog/dialog")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../dialog/dialog"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + // default search panel location + CodeMirror.defineOption("search", {bottom: false}); + + function dialog(cm, text, shortText, deflt, f) { + if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true, bottom: cm.options.search.bottom}); + else f(prompt(shortText, deflt)); + } + + function getJumpDialog(cm) { + return cm.phrase("Jump to line:") + ' ' + cm.phrase("(Use line:column or scroll% syntax)") + ''; + } + + function interpretLine(cm, string) { + var num = Number(string) + if (/^[-+]/.test(string)) return cm.getCursor().line + num + else return num - 1 + } + + CodeMirror.commands.jumpToLine = function(cm) { + var cur = cm.getCursor(); + dialog(cm, getJumpDialog(cm), cm.phrase("Jump to line:"), (cur.line + 1) + ":" + cur.ch, function(posStr) { + if (!posStr) return; + + var match; + if (match = /^\s*([\+\-]?\d+)\s*\:\s*(\d+)\s*$/.exec(posStr)) { + cm.setCursor(interpretLine(cm, match[1]), Number(match[2])) + } else if (match = /^\s*([\+\-]?\d+(\.\d+)?)\%\s*/.exec(posStr)) { + var line = Math.round(cm.lineCount() * Number(match[1]) / 100); + if (/^[-+]/.test(match[1])) line = cur.line + line + 1; + cm.setCursor(line - 1, cur.ch); + } else if (match = /^\s*\:?\s*([\+\-]?\d+)\s*/.exec(posStr)) { + cm.setCursor(interpretLine(cm, match[1]), cur.ch); + } + }); + }; + + CodeMirror.keyMap["default"]["Alt-G"] = "jumpToLine"; +}); +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +// Define search commands. Depends on dialog.js or another +// implementation of the openDialog method. + +// Replace works a little oddly -- it will do the replace on the next +// Ctrl-G (or whatever is bound to findNext) press. You prevent a +// replace by making sure the match is no longer selected when hitting +// Ctrl-G. + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("./searchcursor"), require("../dialog/dialog")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "./searchcursor", "../dialog/dialog"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + // default search panel location + CodeMirror.defineOption("search", {bottom: false}); + + function searchOverlay(query, caseInsensitive) { + if (typeof query == "string") + query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), caseInsensitive ? "gi" : "g"); + else if (!query.global) + query = new RegExp(query.source, query.ignoreCase ? "gi" : "g"); + + return {token: function(stream) { + query.lastIndex = stream.pos; + var match = query.exec(stream.string); + if (match && match.index == stream.pos) { + stream.pos += match[0].length || 1; + return "searching"; + } else if (match) { + stream.pos = match.index; + } else { + stream.skipToEnd(); + } + }}; + } + + function SearchState() { + this.posFrom = this.posTo = this.lastQuery = this.query = null; + this.overlay = null; + } + + function getSearchState(cm) { + return cm.state.search || (cm.state.search = new SearchState()); + } + + function queryCaseInsensitive(query) { + return typeof query == "string" && query == query.toLowerCase(); + } + + function getSearchCursor(cm, query, pos) { + // Heuristic: if the query string is all lowercase, do a case insensitive search. + return cm.getSearchCursor(query, pos, {caseFold: queryCaseInsensitive(query), multiline: true}); + } + + function persistentDialog(cm, text, deflt, onEnter, onKeyDown) { + cm.openDialog(text, onEnter, { + value: deflt, + selectValueOnOpen: true, + closeOnEnter: false, + onClose: function() { clearSearch(cm); }, + onKeyDown: onKeyDown, + bottom: cm.options.search.bottom + }); + } + + function dialog(cm, text, shortText, deflt, f) { + if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true, bottom: cm.options.search.bottom}); + else f(prompt(shortText, deflt)); + } + + function confirmDialog(cm, text, shortText, fs) { + if (cm.openConfirm) cm.openConfirm(text, fs); + else if (confirm(shortText)) fs[0](); + } + + function parseString(string) { + return string.replace(/\\([nrt\\])/g, function(match, ch) { + if (ch == "n") return "\n" + if (ch == "r") return "\r" + if (ch == "t") return "\t" + if (ch == "\\") return "\\" + return match + }) + } + + function parseQuery(query) { + var isRE = query.match(/^\/(.*)\/([a-z]*)$/); + if (isRE) { + try { query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i"); } + catch(e) {} // Not a regular expression after all, do a string search + } else { + query = parseString(query) + } + if (typeof query == "string" ? query == "" : query.test("")) + query = /x^/; + return query; + } + + function startSearch(cm, state, query) { + state.queryText = query; + state.query = parseQuery(query); + cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query)); + state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query)); + cm.addOverlay(state.overlay); + if (cm.showMatchesOnScrollbar) { + if (state.annotate) { state.annotate.clear(); state.annotate = null; } + state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query)); + } + } + + function doSearch(cm, rev, persistent, immediate) { + var state = getSearchState(cm); + if (state.query) return findNext(cm, rev); + var q = cm.getSelection() || state.lastQuery; + if (q instanceof RegExp && q.source == "x^") q = null + if (persistent && cm.openDialog) { + var hiding = null + var searchNext = function(query, event) { + CodeMirror.e_stop(event); + if (!query) return; + if (query != state.queryText) { + startSearch(cm, state, query); + state.posFrom = state.posTo = cm.getCursor(); + } + if (hiding) hiding.style.opacity = 1 + findNext(cm, event.shiftKey, function(_, to) { + var dialog + if (to.line < 3 && document.querySelector && + (dialog = cm.display.wrapper.querySelector(".CodeMirror-dialog")) && + dialog.getBoundingClientRect().bottom - 4 > cm.cursorCoords(to, "window").top) + (hiding = dialog).style.opacity = .4 + }) + }; + persistentDialog(cm, getQueryDialog(cm), q, searchNext, function(event, query) { + var keyName = CodeMirror.keyName(event) + var extra = cm.getOption('extraKeys'), cmd = (extra && extra[keyName]) || CodeMirror.keyMap[cm.getOption("keyMap")][keyName] + if (cmd == "findNext" || cmd == "findPrev" || + cmd == "findPersistentNext" || cmd == "findPersistentPrev") { + CodeMirror.e_stop(event); + startSearch(cm, getSearchState(cm), query); + cm.execCommand(cmd); + } else if (cmd == "find" || cmd == "findPersistent") { + CodeMirror.e_stop(event); + searchNext(query, event); + } + }); + if (immediate && q) { + startSearch(cm, state, q); + findNext(cm, rev); + } + } else { + dialog(cm, getQueryDialog(cm), "Search for:", q, function(query) { + if (query && !state.query) cm.operation(function() { + startSearch(cm, state, query); + state.posFrom = state.posTo = cm.getCursor(); + findNext(cm, rev); + }); + }); + } + } + + function findNext(cm, rev, callback) {cm.operation(function() { + var state = getSearchState(cm); + var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo); + if (!cursor.find(rev)) { + cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0)); + if (!cursor.find(rev)) return; + } + cm.setSelection(cursor.from(), cursor.to()); + cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 20); + state.posFrom = cursor.from(); state.posTo = cursor.to(); + if (callback) callback(cursor.from(), cursor.to()) + });} + + function clearSearch(cm) {cm.operation(function() { + var state = getSearchState(cm); + state.lastQuery = state.query; + if (!state.query) return; + state.query = state.queryText = null; + cm.removeOverlay(state.overlay); + if (state.annotate) { state.annotate.clear(); state.annotate = null; } + });} + + + function getQueryDialog(cm) { + return '' + cm.phrase("Search:") + ' ' + cm.phrase("(Use /re/ syntax for regexp search)") + ''; + } + function getReplaceQueryDialog(cm) { + return ' ' + cm.phrase("(Use /re/ syntax for regexp search)") + ''; + } + function getReplacementQueryDialog(cm) { + return '' + cm.phrase("With:") + ' '; + } + function getDoReplaceConfirm(cm) { + return '' + cm.phrase("Replace?") + ' '; + } + + function replaceAll(cm, query, text) { + cm.operation(function() { + for (var cursor = getSearchCursor(cm, query); cursor.findNext();) { + if (typeof query != "string") { + var match = cm.getRange(cursor.from(), cursor.to()).match(query); + cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];})); + } else cursor.replace(text); + } + }); + } + + function replace(cm, all) { + if (cm.getOption("readOnly")) return; + var query = cm.getSelection() || getSearchState(cm).lastQuery; + var dialogText = '' + (all ? cm.phrase("Replace all:") : cm.phrase("Replace:")) + ''; + dialog(cm, dialogText + getReplaceQueryDialog(cm), dialogText, query, function(query) { + if (!query) return; + query = parseQuery(query); + dialog(cm, getReplacementQueryDialog(cm), cm.phrase("Replace with:"), "", function(text) { + text = parseString(text) + if (all) { + replaceAll(cm, query, text) + } else { + clearSearch(cm); + var cursor = getSearchCursor(cm, query, cm.getCursor("from")); + var advance = function() { + var start = cursor.from(), match; + if (!(match = cursor.findNext())) { + cursor = getSearchCursor(cm, query); + if (!(match = cursor.findNext()) || + (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return; + } + cm.setSelection(cursor.from(), cursor.to()); + cm.scrollIntoView({from: cursor.from(), to: cursor.to()}); + confirmDialog(cm, getDoReplaceConfirm(cm), cm.phrase("Replace?"), + [function() {doReplace(match);}, advance, + function() {replaceAll(cm, query, text)}]); + }; + var doReplace = function(match) { + cursor.replace(typeof query == "string" ? text : + text.replace(/\$(\d)/g, function(_, i) {return match[i];})); + advance(); + }; + advance(); + } + }); + }); + } + + CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);}; + CodeMirror.commands.findPersistent = function(cm) {clearSearch(cm); doSearch(cm, false, true);}; + CodeMirror.commands.findPersistentNext = function(cm) {doSearch(cm, false, true, true);}; + CodeMirror.commands.findPersistentPrev = function(cm) {doSearch(cm, true, true, true);}; + CodeMirror.commands.findNext = doSearch; + CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);}; + CodeMirror.commands.clearSearch = clearSearch; + CodeMirror.commands.replace = replace; + CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);}; +}); +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")) + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod) + else // Plain browser env + mod(CodeMirror) +})(function(CodeMirror) { + "use strict" + var Pos = CodeMirror.Pos + + function regexpFlags(regexp) { + var flags = regexp.flags + return flags != null ? flags : (regexp.ignoreCase ? "i" : "") + + (regexp.global ? "g" : "") + + (regexp.multiline ? "m" : "") + } + + function ensureFlags(regexp, flags) { + var current = regexpFlags(regexp), target = current + for (var i = 0; i < flags.length; i++) if (target.indexOf(flags.charAt(i)) == -1) + target += flags.charAt(i) + return current == target ? regexp : new RegExp(regexp.source, target) + } + + function maybeMultiline(regexp) { + return /\\s|\\n|\n|\\W|\\D|\[\^/.test(regexp.source) + } + + function searchRegexpForward(doc, regexp, start) { + regexp = ensureFlags(regexp, "g") + for (var line = start.line, ch = start.ch, last = doc.lastLine(); line <= last; line++, ch = 0) { + regexp.lastIndex = ch + var string = doc.getLine(line), match = regexp.exec(string) + if (match) + return {from: Pos(line, match.index), + to: Pos(line, match.index + match[0].length), + match: match} + } + } + + function searchRegexpForwardMultiline(doc, regexp, start) { + if (!maybeMultiline(regexp)) return searchRegexpForward(doc, regexp, start) + + regexp = ensureFlags(regexp, "gm") + var string, chunk = 1 + for (var line = start.line, last = doc.lastLine(); line <= last;) { + // This grows the search buffer in exponentially-sized chunks + // between matches, so that nearby matches are fast and don't + // require concatenating the whole document (in case we're + // searching for something that has tons of matches), but at the + // same time, the amount of retries is limited. + for (var i = 0; i < chunk; i++) { + if (line > last) break + var curLine = doc.getLine(line++) + string = string == null ? curLine : string + "\n" + curLine + } + chunk = chunk * 2 + regexp.lastIndex = start.ch + var match = regexp.exec(string) + if (match) { + var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n") + var startLine = start.line + before.length - 1, startCh = before[before.length - 1].length + return {from: Pos(startLine, startCh), + to: Pos(startLine + inside.length - 1, + inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length), + match: match} + } + } + } + + function lastMatchIn(string, regexp, endMargin) { + var match, from = 0 + while (from <= string.length) { + regexp.lastIndex = from + var newMatch = regexp.exec(string) + if (!newMatch) break + var end = newMatch.index + newMatch[0].length + if (end > string.length - endMargin) break + if (!match || end > match.index + match[0].length) + match = newMatch + from = newMatch.index + 1 + } + return match + } + + function searchRegexpBackward(doc, regexp, start) { + regexp = ensureFlags(regexp, "g") + for (var line = start.line, ch = start.ch, first = doc.firstLine(); line >= first; line--, ch = -1) { + var string = doc.getLine(line) + var match = lastMatchIn(string, regexp, ch < 0 ? 0 : string.length - ch) + if (match) + return {from: Pos(line, match.index), + to: Pos(line, match.index + match[0].length), + match: match} + } + } + + function searchRegexpBackwardMultiline(doc, regexp, start) { + if (!maybeMultiline(regexp)) return searchRegexpBackward(doc, regexp, start) + regexp = ensureFlags(regexp, "gm") + var string, chunkSize = 1, endMargin = doc.getLine(start.line).length - start.ch + for (var line = start.line, first = doc.firstLine(); line >= first;) { + for (var i = 0; i < chunkSize && line >= first; i++) { + var curLine = doc.getLine(line--) + string = string == null ? curLine : curLine + "\n" + string + } + chunkSize *= 2 + + var match = lastMatchIn(string, regexp, endMargin) + if (match) { + var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n") + var startLine = line + before.length, startCh = before[before.length - 1].length + return {from: Pos(startLine, startCh), + to: Pos(startLine + inside.length - 1, + inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length), + match: match} + } + } + } + + var doFold, noFold + if (String.prototype.normalize) { + doFold = function(str) { return str.normalize("NFD").toLowerCase() } + noFold = function(str) { return str.normalize("NFD") } + } else { + doFold = function(str) { return str.toLowerCase() } + noFold = function(str) { return str } + } + + // Maps a position in a case-folded line back to a position in the original line + // (compensating for codepoints increasing in number during folding) + function adjustPos(orig, folded, pos, foldFunc) { + if (orig.length == folded.length) return pos + for (var min = 0, max = pos + Math.max(0, orig.length - folded.length);;) { + if (min == max) return min + var mid = (min + max) >> 1 + var len = foldFunc(orig.slice(0, mid)).length + if (len == pos) return mid + else if (len > pos) max = mid + else min = mid + 1 + } + } + + function searchStringForward(doc, query, start, caseFold) { + // Empty string would match anything and never progress, so we + // define it to match nothing instead. + if (!query.length) return null + var fold = caseFold ? doFold : noFold + var lines = fold(query).split(/\r|\n\r?/) + + search: for (var line = start.line, ch = start.ch, last = doc.lastLine() + 1 - lines.length; line <= last; line++, ch = 0) { + var orig = doc.getLine(line).slice(ch), string = fold(orig) + if (lines.length == 1) { + var found = string.indexOf(lines[0]) + if (found == -1) continue search + var start = adjustPos(orig, string, found, fold) + ch + return {from: Pos(line, adjustPos(orig, string, found, fold) + ch), + to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold) + ch)} + } else { + var cutFrom = string.length - lines[0].length + if (string.slice(cutFrom) != lines[0]) continue search + for (var i = 1; i < lines.length - 1; i++) + if (fold(doc.getLine(line + i)) != lines[i]) continue search + var end = doc.getLine(line + lines.length - 1), endString = fold(end), lastLine = lines[lines.length - 1] + if (endString.slice(0, lastLine.length) != lastLine) continue search + return {from: Pos(line, adjustPos(orig, string, cutFrom, fold) + ch), + to: Pos(line + lines.length - 1, adjustPos(end, endString, lastLine.length, fold))} + } + } + } + + function searchStringBackward(doc, query, start, caseFold) { + if (!query.length) return null + var fold = caseFold ? doFold : noFold + var lines = fold(query).split(/\r|\n\r?/) + + search: for (var line = start.line, ch = start.ch, first = doc.firstLine() - 1 + lines.length; line >= first; line--, ch = -1) { + var orig = doc.getLine(line) + if (ch > -1) orig = orig.slice(0, ch) + var string = fold(orig) + if (lines.length == 1) { + var found = string.lastIndexOf(lines[0]) + if (found == -1) continue search + return {from: Pos(line, adjustPos(orig, string, found, fold)), + to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold))} + } else { + var lastLine = lines[lines.length - 1] + if (string.slice(0, lastLine.length) != lastLine) continue search + for (var i = 1, start = line - lines.length + 1; i < lines.length - 1; i++) + if (fold(doc.getLine(start + i)) != lines[i]) continue search + var top = doc.getLine(line + 1 - lines.length), topString = fold(top) + if (topString.slice(topString.length - lines[0].length) != lines[0]) continue search + return {from: Pos(line + 1 - lines.length, adjustPos(top, topString, top.length - lines[0].length, fold)), + to: Pos(line, adjustPos(orig, string, lastLine.length, fold))} + } + } + } + + function SearchCursor(doc, query, pos, options) { + this.atOccurrence = false + this.doc = doc + pos = pos ? doc.clipPos(pos) : Pos(0, 0) + this.pos = {from: pos, to: pos} + + var caseFold + if (typeof options == "object") { + caseFold = options.caseFold + } else { // Backwards compat for when caseFold was the 4th argument + caseFold = options + options = null + } + + if (typeof query == "string") { + if (caseFold == null) caseFold = false + this.matches = function(reverse, pos) { + return (reverse ? searchStringBackward : searchStringForward)(doc, query, pos, caseFold) + } + } else { + query = ensureFlags(query, "gm") + if (!options || options.multiline !== false) + this.matches = function(reverse, pos) { + return (reverse ? searchRegexpBackwardMultiline : searchRegexpForwardMultiline)(doc, query, pos) + } + else + this.matches = function(reverse, pos) { + return (reverse ? searchRegexpBackward : searchRegexpForward)(doc, query, pos) + } + } + } + + SearchCursor.prototype = { + findNext: function() {return this.find(false)}, + findPrevious: function() {return this.find(true)}, + + find: function(reverse) { + var result = this.matches(reverse, this.doc.clipPos(reverse ? this.pos.from : this.pos.to)) + + // Implements weird auto-growing behavior on null-matches for + // backwards-compatibility with the vim code (unfortunately) + while (result && CodeMirror.cmpPos(result.from, result.to) == 0) { + if (reverse) { + if (result.from.ch) result.from = Pos(result.from.line, result.from.ch - 1) + else if (result.from.line == this.doc.firstLine()) result = null + else result = this.matches(reverse, this.doc.clipPos(Pos(result.from.line - 1))) + } else { + if (result.to.ch < this.doc.getLine(result.to.line).length) result.to = Pos(result.to.line, result.to.ch + 1) + else if (result.to.line == this.doc.lastLine()) result = null + else result = this.matches(reverse, Pos(result.to.line + 1, 0)) + } + } + + if (result) { + this.pos = result + this.atOccurrence = true + return this.pos.match || true + } else { + var end = Pos(reverse ? this.doc.firstLine() : this.doc.lastLine() + 1, 0) + this.pos = {from: end, to: end} + return this.atOccurrence = false + } + }, + + from: function() {if (this.atOccurrence) return this.pos.from}, + to: function() {if (this.atOccurrence) return this.pos.to}, + + replace: function(newText, origin) { + if (!this.atOccurrence) return + var lines = CodeMirror.splitLines(newText) + this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin) + this.pos.to = Pos(this.pos.from.line + lines.length - 1, + lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0)) + } + } + + CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) { + return new SearchCursor(this.doc, query, pos, caseFold) + }) + CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) { + return new SearchCursor(this, query, pos, caseFold) + }) + + CodeMirror.defineExtension("selectMatches", function(query, caseFold) { + var ranges = [] + var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold) + while (cur.findNext()) { + if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break + ranges.push({anchor: cur.from(), head: cur.to()}) + } + if (ranges.length) + this.setSelections(ranges, 0) + }) +}); +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +// Open simple dialogs on top of an editor. Relies on dialog.css. + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + function dialogDiv(cm, template, bottom) { + var wrap = cm.getWrapperElement(); + var dialog; + dialog = wrap.appendChild(document.createElement("div")); + if (bottom) + dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom"; + else + dialog.className = "CodeMirror-dialog CodeMirror-dialog-top"; + + if (typeof template == "string") { + dialog.innerHTML = template; + } else { // Assuming it's a detached DOM element. + dialog.appendChild(template); + } + CodeMirror.addClass(wrap, 'dialog-opened'); + return dialog; + } + + function closeNotification(cm, newVal) { + if (cm.state.currentNotificationClose) + cm.state.currentNotificationClose(); + cm.state.currentNotificationClose = newVal; + } + + CodeMirror.defineExtension("openDialog", function(template, callback, options) { + if (!options) options = {}; + + closeNotification(this, null); + + var dialog = dialogDiv(this, template, options.bottom); + var closed = false, me = this; + function close(newVal) { + if (typeof newVal == 'string') { + inp.value = newVal; + } else { + if (closed) return; + closed = true; + CodeMirror.rmClass(dialog.parentNode, 'dialog-opened'); + dialog.parentNode.removeChild(dialog); + me.focus(); + + if (options.onClose) options.onClose(dialog); + } + } + + var inp = dialog.getElementsByTagName("input")[0], button; + if (inp) { + inp.focus(); + + if (options.value) { + inp.value = options.value; + if (options.selectValueOnOpen !== false) { + inp.select(); + } + } + + if (options.onInput) + CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);}); + if (options.onKeyUp) + CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);}); + + CodeMirror.on(inp, "keydown", function(e) { + if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; } + if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) { + inp.blur(); + CodeMirror.e_stop(e); + close(); + } + if (e.keyCode == 13) callback(inp.value, e); + }); + + if (options.closeOnBlur !== false) CodeMirror.on(dialog, "focusout", function (evt) { + if (evt.relatedTarget !== null) close(); + }); + } else if (button = dialog.getElementsByTagName("button")[0]) { + CodeMirror.on(button, "click", function() { + close(); + me.focus(); + }); + + if (options.closeOnBlur !== false) CodeMirror.on(button, "blur", close); + + button.focus(); + } + return close; + }); + + CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) { + closeNotification(this, null); + var dialog = dialogDiv(this, template, options && options.bottom); + var buttons = dialog.getElementsByTagName("button"); + var closed = false, me = this, blurring = 1; + function close() { + if (closed) return; + closed = true; + CodeMirror.rmClass(dialog.parentNode, 'dialog-opened'); + dialog.parentNode.removeChild(dialog); + me.focus(); + } + buttons[0].focus(); + for (var i = 0; i < buttons.length; ++i) { + var b = buttons[i]; + (function(callback) { + CodeMirror.on(b, "click", function(e) { + CodeMirror.e_preventDefault(e); + close(); + if (callback) callback(me); + }); + })(callbacks[i]); + CodeMirror.on(b, "blur", function() { + --blurring; + setTimeout(function() { if (blurring <= 0) close(); }, 200); + }); + CodeMirror.on(b, "focus", function() { ++blurring; }); + } + }); + + /* + * openNotification + * Opens a notification, that can be closed with an optional timer + * (default 5000ms timer) and always closes on click. + * + * If a notification is opened while another is opened, it will close the + * currently opened one and open the new one immediately. + */ + CodeMirror.defineExtension("openNotification", function(template, options) { + closeNotification(this, close); + var dialog = dialogDiv(this, template, options && options.bottom); + var closed = false, doneTimer; + var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000; + + function close() { + if (closed) return; + closed = true; + clearTimeout(doneTimer); + CodeMirror.rmClass(dialog.parentNode, 'dialog-opened'); + dialog.parentNode.removeChild(dialog); + } + + CodeMirror.on(dialog, 'click', function(e) { + CodeMirror.e_preventDefault(e); + close(); + }); + + if (duration) + doneTimer = setTimeout(close, duration); + + return close; + }); +}); diff --git a/src/main/resources/static/js/codemirror.min.js b/src/main/resources/static/js/codemirror.min.js new file mode 100644 index 0000000..3fc5f80 --- /dev/null +++ b/src/main/resources/static/js/codemirror.min.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).CodeMirror=t()}(this,function(){"use strict";var e=navigator.userAgent,t=navigator.platform,d=/gecko\/\d/i.test(e),n=/MSIE \d/.test(e),r=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(e),i=/Edge\/(\d+)/.exec(e),w=n||r||i,v=w&&(n?document.documentMode||6:+(i||r)[1]),f=!i&&/WebKit\//.test(e),r=f&&/Qt\/\d+\.\d+/.test(e),o=!i&&/Chrome\//.test(e),p=/Opera\//.test(e),c=/Apple Computer/.test(navigator.vendor),l=/Mac OS X 1\d\D([8-9]|\d\d)\D/.test(e),u=/PhantomJS/.test(e),s=c&&(/Mobile\/\w+/.test(e)||2t)return i;o.to==t&&(o.from!=o.to&&"before"==n?r=i:oe=i),o.from==t&&(o.from!=o.to&&"before"!=n?r=i:oe=i)}return null!=r?r:oe}var se,ae,ue,ce,he,de,fe,pe=(se="bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN",ae="nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111",ue=/[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/,ce=/[stwN]/,he=/[LRr]/,de=/[Lb1n]/,fe=/[1n]/,function(e,t){var n="ltr"==t?"L":"R";if(0==e.length||"ltr"==t&&!ue.test(e))return!1;for(var r,i=e.length,o=[],l=0;l=e.size)throw new Error("There is no line "+(t+e.first)+" in the document.");for(var n=e;!n.lines;)for(var r=0;;++r){var i=n.children[r],o=i.chunkSize();if(t=e.first&&tn?rt(n,$e(e,n).text.length):(e=$e(e,(n=t).line).text.length,null==(t=n.ch)||e=this.string.length},_e.prototype.sol=function(){return this.pos==this.lineStart},_e.prototype.peek=function(){return this.string.charAt(this.pos)||void 0},_e.prototype.next=function(){if(this.post},_e.prototype.eatSpace=function(){for(var e=this.pos;/[\s\u00a0]/.test(this.string.charAt(this.pos));)++this.pos;return this.pos>e},_e.prototype.skipToEnd=function(){this.pos=this.string.length},_e.prototype.skipTo=function(e){e=this.string.indexOf(e,this.pos);if(-1e.options.maxHighlightLength&&je(e.doc.mode,r.state),o=pt(e,t,r),i&&(r.state=i),t.stateAfter=r.save(!i),t.styles=o.styles,o.classes?t.styleClasses=o.classes:t.styleClasses&&(t.styleClasses=null),n===e.doc.highlightFrontier&&(e.doc.modeFrontier=Math.max(e.doc.modeFrontier,++e.doc.highlightFrontier))),t.styles}function mt(n,r,e){var t=n.doc,i=n.display;if(!t.mode.startState)return new ft(t,!0,r);var o=function(e,t,n){for(var r,i,o=e.doc,l=n?-1:t-(e.doc.mode.innerMode?1e3:100),s=t;lt.first&&$e(t,o-1).stateAfter,s=l?ft.fromSaved(t,l,o):new ft(t,Ye(t.mode),o);return t.iter(o,r,function(e){vt(n,e.text,s);var t=s.line;e.stateAfter=t==r-1||t%5==0||t>=i.viewFrom&&tt.start)return o}throw new Error("Mode "+e.name+" failed to advance stream.")}ft.prototype.lookAhead=function(e){var t=this.doc.getLine(this.line+e);return null!=t&&e>this.maxLookAhead&&(this.maxLookAhead=e),t},ft.prototype.baseToken=function(e){if(!this.baseTokens)return null;for(;this.baseTokens[this.baseTokenPos]<=e;)this.baseTokenPos+=2;var t=this.baseTokens[this.baseTokenPos+1];return{type:t&&t.replace(/( |^)overlay .*/,""),size:this.baseTokens[this.baseTokenPos]-e}},ft.prototype.nextLine=function(){this.line++,0e.options.maxHighlightLength?(s=!1,l&&vt(e,t,r,c.pos),c.pos=t.length,null):Ct(bt(n,c,r.state,h),o);if(!h||(d=h[0].name)&&(f="m-"+(f?d+" "+f:d)),!s||u!=f){for(;a=t:l.to>t),(r=r||[]).push(new Tt(s,l.from,o?null:l.to)))}return r}(n,i,e),s=function(e,t,n){var r;if(e)for(var i=0;i=t:l.to>t))&&(l.from!=t||"bookmark"!=s.type||n&&!l.marker.insertLeft)||(o=null==l.from||(s.inclusiveLeft?l.from<=t:l.frome.lastLine())return t;var n,r=$e(e,t);if(!Ut(e,r))return t;for(;n=It(r);)r=n.find(1,!0).line;return Je(r)+1}function Ut(e,t){var n=kt&&t.markedSpans;if(n)for(var r,i=0;in.maxLineLength&&(n.maxLineLength=t,n.maxLine=e)})}var Xt=function(e,t,n){this.text=e,Dt(this,t),this.height=n?n(this):1};Xt.prototype.lineNo=function(){return Je(this)},ke(Xt);var Yt={},_t={};function $t(e,t){if(!e||/^\s*$/.test(e))return null;t=t.addModeClass?_t:Yt;return t[e]||(t[e]=e.replace(/\S+/g,"cm-$&"))}function qt(e,t){var n=N("span",null,null,f?"padding-right: .1px":null),r={pre:N("pre",[n],"CodeMirror-line"),content:n,col:0,pos:0,cm:e,trailingSpace:!1,splitSpaces:e.getOption("lineWrapping")};t.measure={};for(var i=0;i<=(t.rest?t.rest.length:0);i++){var o=i?t.rest[i-1]:t.line,l=void 0;r.pos=0,r.addToken=Qt,function(e){if(null!=He)return He;var t=T(e,document.createTextNode("AخA")),n=S(t,0,1).getBoundingClientRect(),t=S(t,1,2).getBoundingClientRect();return k(e),n&&n.left!=n.right&&(He=t.right-n.right<3)}(e.display.measure)&&(l=me(o,e.doc.direction))&&(r.addToken=function(h,d){return function(e,t,n,r,i,o,l){n=n?n+" cm-force-border":"cm-force-border";for(var s=e.pos,a=s+t.length;;){for(var u=void 0,c=0;cs&&u.from<=s);c++);if(u.to>=a)return h(e,t,n,r,i,o,l);h(e,t.slice(0,u.to-s),n,r,null,o,l),r=null,t=t.slice(u.to-s),s=u.to}}}(r.addToken,l)),r.map=[],function(e,t,n){var r=e.markedSpans,i=e.text,o=0;if(!r){for(var l=1;lg||S.collapsed&&C.to==g&&C.from==g)){if(null!=C.to&&C.to!=g&&y>C.to&&(y=C.to,c=""),S.className&&(u+=" "+S.className),S.css&&(a=(a?a+";":"")+S.css),S.startStyle&&C.from==g&&(h+=" "+S.startStyle),S.endStyle&&C.to==y&&(w=w||[]).push(S.endStyle,C.to),S.title&&((f=f||{}).title=S.title),S.attributes)for(var L in S.attributes)(f=f||{})[L]=S.attributes[L];S.collapsed&&(!d||Ft(d.marker,S)<0)&&(d=C)}else C.from>g&&y>C.from&&(y=C.from)}if(w)for(var k=0;kn)return{map:e.measure.maps[i],cache:e.measure.caches[i],before:!0}}function Ln(e,t,n,r){return Mn(e,Tn(e,t),n,r)}function kn(e,t){if(t>=e.display.viewFrom&&t=e.lineN&&tt)&&(i=(o=a-s)-1,a<=t&&(l="right")),null!=i){if(r=e[u+2],s==a&&n==(r.insertLeft?"left":"right")&&(l=n),"left"==n&&0==i)for(;u&&e[u-2]==e[u-3]&&e[u-1].insertLeft;)r=e[2+(u-=3)],l="left";if("right"==n&&i==a-s)for(;u=i.text.length?(t=i.text.length,u="before"):t<=0&&(t=0,u="after"),!a)return s("before"==u?t-1:t,"before"==u);function c(e,t,n){return s(n?e-1:e,1==a[t].level!=n)}var h=le(a,t,u),e=oe,h=c(t,h,"before"==u);return null!=e&&(h.other=c(t,e,"before"!=u)),h}function Gn(e,t){var n=0;t=ct(e.doc,t),e.options.lineWrapping||(n=_n(e.display)*t.ch);t=$e(e.doc,t.line),e=Vt(t)+vn(e.display);return{left:n,right:n,top:e,bottom:e+t.height}}function Un(e,t,n,r,i){n=rt(e,t,n);return n.xRel=i,r&&(n.outside=r),n}function Vn(e,t,n){var r=e.doc;if((n+=e.display.viewOffset)<0)return Un(r.first,0,null,-1,-1);var i=et(r,n),o=r.first+r.size-1;if(o=a||f.to<=s||(d=1!=f.level,d=Mn(e,r,d?Math.min(a,f.to)-1:Math.max(s,f.from)).right,d=da&&(u={from:u.from,to:a,level:u.level});return u}:function(n,r,i,o,l,s,a){var e=ie(function(e){var t=l[e],e=1!=t.level;return Xn(Bn(n,rt(i,e?t.to:t.from,e?"before":"after"),"line",r,o),s,a,!0)},0,l.length-1),t=l[e];{var u;0a&&(t=l[e-1]))}return t})(n,e,t,o,c,r,i),u=1!=f.level,s=u?f.from:f.to-1,a=u?f.to:f.from-1);var h=null,d=null,c=ie(function(e){var t=Mn(n,o,e);return t.top+=l,t.bottom+=l,Xn(t,r,i,!1)&&(t.top<=i&&t.left<=r&&(h=e,d=t),1)},s,a),f=!1;{var p,g;d?(p=r-d.left=u.bottom?1:0)}return c=re(e.text,c,1),Un(t,c,g,f,r-p)}(e,l,i,t,n),a=function(e,t){var n,r=kt&&e.markedSpans;if(r)for(var i=0;it)&&(!n||Ft(n,o.marker)<0)&&(n=o.marker)}return n}(l,s.ch+(0r},e,i)}}function jn(e,t,n,r){return Kn(e,t,n=n||Tn(e,t),In(e,t,Mn(e,n,r),"line").top)}function Xn(e,t,n,r){return!(e.bottom<=n)&&(e.top>n||(r?e.left:e.right)>t)}function Yn(e){if(null!=e.cachedTextHeight)return e.cachedTextHeight;if(null==Nn){Nn=M("pre",null,"CodeMirror-line-like");for(var t=0;t<49;++t)Nn.appendChild(document.createTextNode("x")),Nn.appendChild(M("br"));Nn.appendChild(document.createTextNode("x"))}T(e.measure,Nn);var n=Nn.offsetHeight/50;return 3=e.display.viewTo)return null;if((t-=e.display.viewFrom)<0)return null;for(var n=e.display.view,r=0;rt)&&(l.updateLineNumbers=t),e.curOp.viewChanged=!0,t>=l.viewTo?kt&&Bt(e.doc,t)l.viewFrom?rr(e):(l.viewFrom+=r,l.viewTo+=r):t<=l.viewFrom&&n>=l.viewTo?rr(e):t<=l.viewFrom?(i=ir(e,n,n+r,1))?(l.view=l.view.slice(i.index),l.viewFrom=i.lineN,l.viewTo+=r):rr(e):n>=l.viewTo?(o=ir(e,t,t,-1))?(l.view=l.view.slice(0,o.index),l.viewTo=o.lineN):rr(e):(i=ir(e,t,t,-1),o=ir(e,n,n+r,1),i&&o?(l.view=l.view.slice(0,i.index).concat(tn(e,i.lineN,o.lineN)).concat(l.view.slice(o.index)),l.viewTo+=r):rr(e));e=l.externalMeasured;e&&(n=i.lineN&&t=r.viewTo||(null==(t=r.view[er(e,t)]).node||-1==R(t=t.changes||(t.changes=[]),n)&&t.push(n))}function rr(e){e.display.viewFrom=e.display.viewTo=e.doc.first,e.display.view=[],e.display.viewOffset=0}function ir(e,t,n,r){var i,o=er(e,t),l=e.display.view;if(!kt||n==e.doc.first+e.doc.size)return{index:o,lineN:n};for(var s=e.display.viewFrom,a=0;a=e.display.viewTo||n.to().linet||t==n&&l.to==t)&&(r(Math.max(l.from,t),Math.min(l.to,n),1==l.level?"rtl":"ltr",o),i=!0)}i||r(t,n,"ltr")}(C,g||0,null==m?b:m,function(e,t,n,r){var i,o,l,s,a="ltr"==n,u=w(e,a?"left":"right"),c=w(t-1,a?"right":"left"),h=null==g&&0==e,d=null==m&&t==b,f=0==r,p=!C||r==C.length-1;c.top-u.top<=3?(i=(k?d:h)&&p,r=(k?h:d)&&f?S:(a?u:c).left,i=i?L:(a?c:u).right,T(r,u.top,i-r,u.bottom)):(n=a?(o=k&&h&&f?S:u.left,l=k?L:x(e,n,"before"),s=k?S:x(t,n,"after"),k&&d&&p?L:c.right):(o=k?x(e,n,"before"):S,l=!k&&h&&f?L:u.right,s=!k&&d&&p?S:c.left,k?x(t,n,"after"):L),T(o,u.top,l-o,u.bottom),u.bottome.display.sizerWidth&&((a=Math.ceil(a/_n(e.display)))>e.display.maxLineLength&&(e.display.maxLineLength=a,e.display.maxLine=o.line,e.display.maxLineChanged=!0))}}}function mr(e){if(e.widgets)for(var t=0;t=r&&(o=et(t,Vt($e(t,n))-e.wrapper.clientHeight),r=n)),{from:o,to:Math.max(r,o+1)}}function yr(e,t){var n=e.display,r=Yn(e.display);t.top<0&&(t.top=0);var i=(e.curOp&&null!=e.curOp.scrollTop?e.curOp:n.scroller).scrollTop,o=Cn(e),l={};t.bottom-t.top>o&&(t.bottom=t.top+o);var s=e.doc.height+yn(n),a=t.tops-r;t.topi+o&&((u=Math.min(t.top,(r?s:t.bottom)-o))!=i&&(l.scrollTop=u));var i=e.options.fixedGutter?0:n.gutters.offsetWidth,u=e.curOp&&null!=e.curOp.scrollLeft?e.curOp.scrollLeft:n.scroller.scrollLeft-i,e=xn(e)-n.gutters.offsetWidth,n=t.right-t.left>e;return n&&(t.right=t.left+e),t.left<10?l.scrollLeft=0:t.lefte+u-3&&(l.scrollLeft=t.right+(n?0:10)-e),l}function br(e,t){null!=t&&(Cr(e),e.curOp.scrollTop=(null==e.curOp.scrollTop?e.doc:e.curOp).scrollTop+t)}function wr(e){Cr(e);var t=e.getCursor();e.curOp.scrollToPos={from:t,to:t,margin:e.options.cursorScrollMargin}}function xr(e,t,n){null==t&&null==n||Cr(e),null!=t&&(e.curOp.scrollLeft=t),null!=n&&(e.curOp.scrollTop=n)}function Cr(e){var t=e.curOp.scrollToPos;t&&(e.curOp.scrollToPos=null,Sr(e,Gn(e,t.from),Gn(e,t.to),t.margin))}function Sr(e,t,n,r){r=yr(e,{left:Math.min(t.left,n.left),top:Math.min(t.top,n.top)-r,right:Math.max(t.right,n.right),bottom:Math.max(t.bottom,n.bottom)+r});xr(e,r.scrollLeft,r.scrollTop)}function Lr(e,t){Math.abs(e.doc.scrollTop-t)<2||(d||Kr(e,{top:t}),kr(e,t,!0),d&&Kr(e),zr(e,100))}function kr(e,t,n){t=Math.max(0,Math.min(e.display.scroller.scrollHeight-e.display.scroller.clientHeight,t)),e.display.scroller.scrollTop==t&&!n||(e.doc.scrollTop=t,e.display.scrollbars.setScrollTop(t),e.display.scroller.scrollTop!=t&&(e.display.scroller.scrollTop=t))}function Tr(e,t,n,r){t=Math.max(0,Math.min(t,e.display.scroller.scrollWidth-e.display.scroller.clientWidth)),(n?t==e.doc.scrollLeft:Math.abs(e.doc.scrollLeft-t)<2)&&!r||(e.doc.scrollLeft=t,Yr(e),e.display.scroller.scrollLeft!=t&&(e.display.scroller.scrollLeft=t),e.display.scrollbars.setScrollLeft(t))}function Mr(e){var t=e.display,n=t.gutters.offsetWidth,r=Math.round(e.doc.height+yn(e.display));return{clientHeight:t.scroller.clientHeight,viewHeight:t.wrapper.clientHeight,scrollWidth:t.scroller.scrollWidth,clientWidth:t.scroller.clientWidth,viewWidth:t.wrapper.clientWidth,barLeft:e.options.fixedGutter?n:0,docHeight:r,scrollHeight:r+wn(e)+t.barHeight,nativeBarWidth:t.nativeBarWidth,gutterWidth:n}}e=function(e,t,n){this.cm=n;var r=this.vert=M("div",[M("div",null,null,"min-width: 1px")],"CodeMirror-vscrollbar"),i=this.horiz=M("div",[M("div",null,null,"height: 100%; min-height: 1px")],"CodeMirror-hscrollbar");r.tabIndex=i.tabIndex=-1,e(r),e(i),ye(r,"scroll",function(){r.clientHeight&&t(r.scrollTop,"vertical")}),ye(i,"scroll",function(){i.clientWidth&&t(i.scrollLeft,"horizontal")}),this.checkedZeroWidth=!1,w&&v<8&&(this.horiz.style.minHeight=this.vert.style.minWidth="18px")};e.prototype.update=function(e){var t,n=e.scrollWidth>e.clientWidth+1,r=e.scrollHeight>e.clientHeight+1,i=e.nativeBarWidth;return r?(this.vert.style.display="block",this.vert.style.bottom=n?i+"px":"0",t=e.viewHeight-(n?i:0),this.vert.firstChild.style.height=Math.max(0,e.scrollHeight-e.clientHeight+t)+"px"):(this.vert.style.display="",this.vert.firstChild.style.height="0"),n?(this.horiz.style.display="block",this.horiz.style.right=r?i+"px":"0",this.horiz.style.left=e.barLeft+"px",t=e.viewWidth-e.barLeft-(r?i:0),this.horiz.firstChild.style.width=Math.max(0,e.scrollWidth-e.clientWidth+t)+"px"):(this.horiz.style.display="",this.horiz.firstChild.style.width="0"),!this.checkedZeroWidth&&0=n.viewTo)||n.maxLineChanged&&t.options.lineWrapping,e.update=e.mustUpdate&&new Gr(t,e.mustUpdate&&{top:e.scrollTop,ensure:e.scrollToPos},e.forceUpdate)}(t[n]);for(var r=0;r(window.innerHeight||document.documentElement.clientHeight)&&(i=!1),null==i||u||(t=M("div","​",null,"position: absolute;\n top: "+(t.top-n.viewOffset-vn(e.display))+"px;\n height: "+(t.bottom-t.top+wn(e)+n.barHeight)+"px;\n left: "+t.left+"px; width: "+Math.max(2,t.right-t.left)+"px;"),e.display.lineSpace.appendChild(t),t.scrollIntoView(i),e.display.lineSpace.removeChild(t)))}(t,i))}var o=e.maybeHiddenMarkers,l=e.maybeUnhiddenMarkers;if(o)for(var s=0;s=l.display.viewTo||(s=+new Date+l.options.workTime,a=mt(l,c.highlightFrontier),u=[],c.iter(a.line,Math.min(c.first+c.size,l.display.viewTo+500),function(e){if(a.line>=l.display.viewFrom){var t=e.styles,n=e.text.length>l.options.maxHighlightLength?je(c.mode,a.state):null,r=pt(l,e,a,!0);n&&(a.state=n),e.styles=r.styles;n=e.styleClasses,r=r.classes;r?e.styleClasses=r:n&&(e.styleClasses=null);for(var i=!t||t.length!=e.styles.length||n!=r&&(!n||!r||n.bgClass!=r.bgClass||n.textClass!=r.textClass),o=0;!i&&os)return zr(l,l.options.workDelay),!0}),c.highlightFrontier=a.line,c.modeFrontier=Math.max(c.modeFrontier,a.line),u.length&&Pr(l,function(){for(var e=0;e=n.viewFrom&&t.visible.to<=n.viewTo&&(null==n.updateLineNumbers||n.updateLineNumbers>=n.viewTo)&&n.renderedView==n.view&&0==or(e))return!1;_r(e)&&(rr(e),t.dims=$n(e));var i=r.first+r.size,o=Math.max(t.visible.from-e.options.viewportMargin,r.first),l=Math.min(i,t.visible.to+e.options.viewportMargin);n.viewFroml&&n.viewTo-l<20&&(l=Math.min(i,n.viewTo)),kt&&(o=Bt(e.doc,o),l=Gt(e.doc,l));var s=o!=n.viewFrom||l!=n.viewTo||n.lastWrapHeight!=t.wrapperHeight||n.lastWrapWidth!=t.wrapperWidth;r=o,i=l,0==(l=(o=e).display).view.length||r>=l.viewTo||i<=l.viewFrom?(l.view=tn(o,r,i),l.viewFrom=r):(l.viewFrom>r?l.view=tn(o,r,l.viewFrom).concat(l.view):l.viewFromi&&(l.view=l.view.slice(0,er(o,i)))),l.viewTo=i,n.viewOffset=Vt($e(e.doc,n.viewFrom)),e.display.mover.style.top=n.viewOffset+"px";o=or(e);if(!s&&0==o&&!t.force&&n.renderedView==n.view&&(null==n.updateLineNumbers||n.updateLineNumbers>=n.viewTo))return!1;l=function(e){if(e.hasFocus())return null;var t=A();if(!t||!O(e.display.lineDiv,t))return null;var n={activeElt:t};return!window.getSelection||(t=window.getSelection()).anchorNode&&t.extend&&O(e.display.lineDiv,t.anchorNode)&&(n.anchorNode=t.anchorNode,n.anchorOffset=t.anchorOffset,n.focusNode=t.focusNode,n.focusOffset=t.focusOffset),n}(e);return 4=e.display.viewFrom&&t.visible.to<=e.display.viewTo)break;if(!Ur(e,t))break;gr(e);var i=Mr(e);lr(e),Nr(e,i),Xr(e,i),t.force=!1}t.signal(e,"update",e),e.display.viewFrom==e.display.reportedViewFrom&&e.display.viewTo==e.display.reportedViewTo||(t.signal(e,"viewportChange",e,e.display.viewFrom,e.display.viewTo),e.display.reportedViewFrom=e.display.viewFrom,e.display.reportedViewTo=e.display.viewTo)}function Kr(e,t){var n=new Gr(e,t);Ur(e,n)&&(gr(e),Vr(e,n),t=Mr(e),lr(e),Nr(e,t),Xr(e,t),n.finish())}function jr(e){var t=e.gutters.offsetWidth;e.sizer.style.marginLeft=t+"px"}function Xr(e,t){e.display.sizer.style.minHeight=t.docHeight+"px",e.display.heightForcer.style.top=t.docHeight+"px",e.display.gutters.style.height=t.docHeight+e.display.barHeight+wn(e)+"px"}function Yr(e){var t=e.display,n=t.view;if(t.alignWidgets||t.gutters.firstChild&&e.options.fixedGutter){for(var r=qn(t)-t.scroller.scrollLeft+e.doc.scrollLeft,i=t.gutters.offsetWidth,o=r+"px",l=0;ll.clientWidth,a=l.scrollHeight>l.clientHeight;if(r&&s||i&&a){if(i&&g&&f)e:for(var u=t.target,c=o.view;u!=l;u=u.parentNode)for(var h=0;hs-(e.cm?e.cm.options.historyEventDelay:500)||"*"==t.origin.charAt(0)))&&(o=(a=i).lastOp==r?(xi(a.done),Y(a.done)):a.done.length&&!Y(a.done).ranges?Y(a.done):1i.undoDepth;)i.done.shift(),i.done[0].ranges||i.done.shift()}i.done.push(n),i.generation=++i.maxGeneration,i.lastModTime=i.lastSelTime=s,i.lastOp=i.lastSelOp=r,i.lastOrigin=i.lastSelOrigin=t.origin,l||xe(e,"historyAdded")}function Si(e,t,n,r){var i,o,l,s=e.history,a=r&&r.origin;n==s.lastSelOp||a&&s.lastSelOrigin==a&&(s.lastModTime==s.lastSelTime&&s.lastOrigin==a||(i=e,o=a,l=Y(s.done),e=t,"*"==(o=o.charAt(0))||"+"==o&&l.ranges.length==e.ranges.length&&l.somethingSelected()==e.somethingSelected()&&new Date-i.history.lastSelTime<=(i.cm?i.cm.options.historyEventDelay:500)))?s.done[s.done.length-1]=t:Li(t,s.done),s.lastSelTime=+new Date,s.lastSelOrigin=a,s.lastSelOp=n,r&&!1!==r.clearRedo&&xi(s.undone)}function Li(e,t){var n=Y(t);n&&n.ranges&&n.equals(e)||t.push(e)}function ki(t,n,e,r){var i=n["spans_"+t.id],o=0;t.iter(Math.max(t.first,e),Math.min(t.first+t.size,r),function(e){e.markedSpans&&((i=i||(n["spans_"+t.id]={}))[o]=e.markedSpans),++o})}function Ti(e,t){var n=t["spans_"+e.id];if(!n)return null;for(var r=[],i=0;i=t.ch:s.to>t.ch))){if(i&&(xe(a,"beforeCursorEnter"),a.explicitlyCleared)){if(o.markedSpans){--l;continue}break}if(a.atomic){if(n){var h=a.find(r<0?1:-1),s=void 0;if((r<0?c:u)&&(h=Ui(e,h,-r,h&&h.line==t.line?o:null)),h&&h.line==t.line&&(s=it(h,n))&&(r<0?s<0:0e.first?ct(e,rt(t.line-1)):null:0e.lastLine()||(t.from.linei&&(t={from:t.from,to:rt(i,$e(e,i).text.length),text:[t.text[0]],origin:t.origin}),t.removed=qe(e,t.from,t.to),n=n||ci(e,t),e.cm?function(e,t,n){var r=e.doc,i=e.display,o=t.from,l=t.to,s=!1,a=o.line;e.options.lineWrapping||(a=Je(zt($e(r,o.line))),r.iter(a,l.line+1,function(e){if(e==i.maxLine)return s=!0}));-1i.maxLineLength&&(i.maxLine=e,i.maxLineLength=t,i.maxLineChanged=!0,s=!1)}),s&&(e.curOp.updateMaxLine=!0));(function(e,t){if(e.modeFrontier=Math.min(e.modeFrontier,t),!(e.highlightFrontiert.display.maxLineLength&&(t.display.maxLine=u,t.display.maxLineLength=c,t.display.maxLineChanged=!0)}null!=r&&t&&this.collapsed&&tr(t,r,i+1),this.lines.length=0,this.explicitlyCleared=!0,this.atomic&&this.doc.cantEdit&&(this.doc.cantEdit=!1,t&&Ri(t.doc)),t&&ln(t,"markerCleared",t,this,r,i),n&&Fr(t),this.parent&&this.parent.clear()}},lo.prototype.find=function(e,t){var n,r;null==e&&"bookmark"==this.type&&(e=1);for(var i=0;i=e.ch)&&t.push(i.marker.parent||i.marker)}return t},findMarks:function(i,o,l){i=ct(this,i),o=ct(this,o);var s=[],a=i.line;return this.iter(i.line,o.line+1,function(e){var t=e.markedSpans;if(t)for(var n=0;n=r.to||null==r.from&&a!=i.line||null!=r.from&&a==o.line&&r.from>=o.ch||l&&!l(r.marker)||s.push(r.marker.parent||r.marker)}++a}),s},getAllMarks:function(){var r=[];return this.iter(function(e){var t=e.markedSpans;if(t)for(var n=0;nt&&(t=e.from),null!=e.to&&e.toe.text.length?null:n}function Fo(e,t,n){e=Ho(e,t.ch,n);return null==e?null:new rt(t.line,e,n<0?"after":"before")}function Po(e,t,n,r,i){if(e){"rtl"==t.doc.direction&&(i=-i);var o=me(n,t.doc.direction);if(o){var l,s,a,e=i<0?Y(o):o[0],o=i<0==(1==e.level)?"after":"before";return 0=n.text.length?(s.ch=n.text.length,s.sticky="before"):s.ch<=0&&(s.ch=0,s.sticky="after");var r=le(a,s.ch,s.sticky),i=a[r];if("ltr"==t.doc.direction&&i.level%2==0&&(0s.ch:i.from=i.from&&d>=c.begin)){var f=h?"before":"after";return new rt(s.line,d,f)}}f=function(e,t,n){for(var r=function(e,t){return t?new rt(s.line,u(e,1),"before"):new rt(s.line,e,"after")};0<=e&&el.doc.first&&((n=$e(l.doc,e.line-1).text)&&(e=new rt(e.line,1),l.replaceRange(t.charAt(0)+l.doc.lineSeparator()+n.charAt(n.length-1),rt(e.line-1,n.length-1),e,"+transpose")))),i.push(new oi(e,e)));l.setSelections(i)})},newlineAndIndent:function(r){return Pr(r,function(){for(var e=r.listSelections(),t=e.length-1;0<=t;t--)r.replaceRange(r.doc.lineSeparator(),e[t].anchor,e[t].head,"+input");e=r.listSelections();for(var n=0;nc&&t.push(new oi(rt(s,c),rt(s,K(u,l,n))))}t.length||t.push(new oi(f,f)),Pi(g,li(d,y.ranges.slice(0,v).concat(t),v),{origin:"*mouse",scroll:!1}),d.scrollIntoView(e)}else{var h,r=m,i=Jo(d,e,p.unit),e=r.anchor,e=0=n.to||o.linea.bottom?20:0)&&setTimeout(Er(d,function(){u==i&&(l.scroller.scrollTop+=r,e(t))}),50))}:n)(e)}),i=Er(d,n);d.state.selectingText=i,ye(l.wrapper.ownerDocument,"mousemove",r),ye(l.wrapper.ownerDocument,"mouseup",i)})(e,r,t,o)}(l,t,i,e):Ae(e)==s.scroller&&Te(e):2==n?(t&&Ai(l.doc,t),setTimeout(function(){return s.input.focus()},20)):3==n&&(x?l.display.input.onContextMenu(e):dr(l)))))}function Jo(e,t,n){if("char"==n)return new oi(t,t);if("word"==n)return e.findWordAt(t);if("line"==n)return new oi(rt(t.line,0),ct(e.doc,rt(t.line+1,0)));t=n(e,t);return new oi(t.from,t.to)}function el(e,t,n,r){var i,o;if(t.touches)i=t.touches[0].clientX,o=t.touches[0].clientY;else try{i=t.clientX,o=t.clientY}catch(e){return!1}if(i>=Math.floor(e.display.gutters.getBoundingClientRect().right))return!1;r&&Te(t);var l=e.display,r=l.lineDiv.getBoundingClientRect();if(o>r.bottom||!Le(e,n))return Ne(t);o-=r.top-l.viewOffset;for(var s=0;s=i)return xe(e,n,e,et(e.doc,o),e.display.gutterSpecs[s].className,t),Ne(t)}}function tl(e,t){return el(e,t,"gutterClick",!0)}function nl(e,t){var n,r;mn(e.display,t)||(r=t,Le(n=e,"gutterContextMenu")&&el(n,r,"gutterContextMenu",!1))||Ce(e,t,"contextmenu")||x||e.display.input.onContextMenu(t)}function rl(e){e.display.wrapper.className=e.display.wrapper.className.replace(/\s*cm-s-\S+/g,"")+e.options.theme.replace(/(^|\s)\s*/g," cm-s-"),Hn(e)}Zo.prototype.compare=function(e,t,n){return this.time+400>e&&0==it(t,this.pos)&&n==this.button};var il={toString:function(){return"CodeMirror.Init"}},ol={},ll={};function sl(e,t,n){!t!=!(n&&n!=il)&&(n=e.display.dragFunctions,(t=t?ye:we)(e.display.scroller,"dragstart",n.start),t(e.display.scroller,"dragenter",n.enter),t(e.display.scroller,"dragover",n.over),t(e.display.scroller,"dragleave",n.leave),t(e.display.scroller,"drop",n.drop))}function al(e){e.options.lineWrapping?(D(e.display.wrapper,"CodeMirror-wrap"),e.display.sizer.style.minWidth="",e.display.sizerWidth=null):(L(e.display.wrapper,"CodeMirror-wrap"),jt(e)),Qn(e),tr(e),Hn(e),setTimeout(function(){return Nr(e)},100)}function ul(e,t){var n=this;if(!(this instanceof ul))return new ul(e,t);this.options=t=t?P(t):{},P(ol,t,!1);var r=t.value;"string"==typeof r?r=new ho(r,t.mode,null,t.lineSeparator,t.direction):t.mode&&(r.modeOption=t.mode),this.doc=r;var i,o=new ul.inputStyles[t.inputStyle](this),o=this.display=new Qr(e,r,o,t);for(i in rl(o.wrapper.CodeMirror=this),t.lineWrapping&&(this.display.wrapper.className+=" CodeMirror-wrap"),Dr(this),this.state={keyMaps:[],overlays:[],modeGen:0,overwrite:!1,delayingBlurEvent:!1,focused:!1,suppressEdits:!1,pasteIncoming:-1,cutIncoming:-1,selectingText:!1,draggingText:!1,highlight:new I,keySeq:null,specialChars:null},t.autofocus&&!h&&o.input.focus(),w&&v<11&&setTimeout(function(){return n.display.input.reset(!0)},20),function(r){var i=r.display;ye(i.scroller,"mousedown",Er(r,Qo)),ye(i.scroller,"dblclick",w&&v<11?Er(r,function(e){var t;Ce(r,e)||(!(t=Jn(r,e))||tl(r,e)||mn(r.display,e)||(Te(e),t=r.findWordAt(t),Ai(r.doc,t.anchor,t.head)))}):function(e){return Ce(r,e)||Te(e)});ye(i.scroller,"contextmenu",function(e){return nl(r,e)}),ye(i.input.getField(),"contextmenu",function(e){i.scroller.contains(e.target)||nl(r,e)});var n,o={end:0};function l(){i.activeTouch&&(n=setTimeout(function(){return i.activeTouch=null},1e3),(o=i.activeTouch).end=+new Date)}function s(e,t){if(null==t.left)return 1;var n=t.left-e.left,e=t.top-e.top;return 400o.first?E($e(o,t-1).text,null,l):0:"add"==n?u=a+e.options.indentUnit:"subtract"==n?u=a-e.options.indentUnit:"number"==typeof n&&(u=a+n),u=Math.max(0,u);var h="",d=0;if(e.options.indentWithTabs)for(var f=Math.floor(u/l);f;--f)d+=l,h+="\t";if(dl,a=Pe(t),u=null;if(s&&1l?"cut":"+input")};ji(e.doc,g),ln(e,"inputRead",e,g)}t&&!s&&ml(e,t),wr(e),e.curOp.updateInput<2&&(e.curOp.updateInput=h),e.curOp.typing=!0,e.state.pasteIncoming=e.state.cutIncoming=-1}function gl(e,t){var n=e.clipboardData&&e.clipboardData.getData("Text");return n&&(e.preventDefault(),t.isReadOnly()||t.options.disableInput||Pr(t,function(){return pl(t,n,0,null,"paste")}),1)}function ml(e,t){if(e.options.electricChars&&e.options.smartIndent)for(var n=e.doc.sel,r=n.ranges.length-1;0<=r;r--){var i=n.ranges[r];if(!(100=i.first+i.size||(o=new rt(r,o.ch,o.sticky),!(u=$e(i,r))))return;o=Po(a,i.cm,u,o.line,c)}else o=n;return 1}if("char"==s||"codepoint"==s)n();else if("column"==s)n(!0);else if("word"==s||"group"==s)for(var r=null,h="group"==s,d=i.cm&&i.cm.getHelper(o,"wordChars"),f=!0;!(l<0)||n(!f);f=!1){var p=u.text.charAt(o.ch)||"\n",p=J(p,d)?"w":h&&"\n"==p?"n":!h||/\s/.test(p)?null:"p";if(!h||f||p||(p="s"),r&&r!=p){l<0&&(l=1,n(),o.sticky="after");break}if(p&&(r=p),0=s.height){l.hitSide=!0;break}o+=5*n}return l}e=function(e){this.cm=e,this.lastAnchorNode=this.lastAnchorOffset=this.lastFocusNode=this.lastFocusOffset=null,this.polling=new I,this.composing=null,this.gracePeriod=!1,this.readDOMTimeout=null};function Cl(e,t){var n=kn(e,t.line);if(!n||n.hidden)return null;var r=$e(e.doc,t.line),n=Sn(n,r,t.line),r=me(r,e.doc.direction),e="left";r&&(e=le(r,t.ch)%2?"right":"left");e=An(n.map,t.ch,e);return e.offset="right"==e.collapse?e.end:e.start,e}function Sl(e,t){return t&&(e.bad=!0),e}function Ll(e,t,n){var r;if(t==e.display.lineDiv){if(!(r=e.display.lineDiv.childNodes[n]))return Sl(e.clipPos(rt(e.display.viewTo-1)),!0);t=null,n=0}else for(r=t;;r=r.parentNode){if(!r||r==e.display.lineDiv)return null;if(r.parentNode&&r.parentNode==e.display.lineDiv)break}for(var i=0;i=t.display.viewTo||i.line=t.display.viewFrom&&Cl(t,r)||{node:n[0].measure.map[2],offset:0},s=i.linet.firstLine()&&(i=rt(i.line-1,$e(t.doc,i.line-1).length)),r.ch==$e(t.doc,r.line).text.length&&r.linen.viewTo-1)return!1;m=i.line==n.viewFrom||0==(m=er(t,i.line))?(e=Je(n.view[0].line),n.view[0].node):(e=Je(n.view[m].line),n.view[m-1].node.nextSibling);var o,r=er(t,r.line),r=r==n.view.length-1?(o=n.viewTo-1,n.lineDiv.lastChild):(o=Je(n.view[r+1].line)-1,n.view[r+1].node.previousSibling);if(!m)return!1;for(var l=t.doc.splitLines(function(l,e,t,s,a){var n="",u=!1,c=l.doc.lineSeparator(),h=!1;function d(){u&&(n+=c,h&&(n+=c),u=h=!1)}function f(e){e&&(d(),n+=e)}for(;!function e(t){if(1==t.nodeType){var n=t.getAttribute("cm-text");if(n)f(n);else if(n=t.getAttribute("cm-marker"))(n=l.findMarks(rt(s,0),rt(a+1,0),(o=+n,function(e){return e.id==o}))).length&&(r=n[0].find(0))&&f(qe(l.doc,r.from,r.to).join(c));else if("false"!=t.getAttribute("contenteditable")){var r=/^(pre|div|p|li|table|br)$/i.test(t.nodeName);if(/^br$/i.test(t.nodeName)||0!=t.textContent.length){r&&d();for(var i=0;ii.ch&&f.charCodeAt(f.length-u-1)==p.charCodeAt(p.length-u-1);)a--,u++;l[l.length-1]=f.slice(0,f.length-u).replace(/^\u200b+/,""),l[0]=l[0].slice(a).replace(/\u200b+$/,"");var m=rt(e,a),r=rt(o,s.length?Y(s).length-u:0);return 1n&&(hl(this,i.head.line,e,!0),n=i.head.line,r==this.doc.sel.primIndex&&wr(this));else{for(var o=i.from(),l=i.to(),i=Math.max(n,o.line),n=Math.min(this.lastLine(),l.line-(l.ch?0:1))+1,s=i;s>1;if((l?n[2*l-1]:0)>=o)i=l;else{if(!(n[2*l+1]o)&&e.top>t.offsetHeight?a=e.top-t.offsetHeight:e.bottom+t.offsetHeight<=o&&(a=e.bottom),u+t.offsetWidth>l&&(u=l-t.offsetWidth)),t.style.top=a+"px",t.style.left=t.style.right="","right"==i?(u=s.sizer.clientWidth-t.offsetWidth,t.style.right="0px"):("left"==i?u=0:"middle"==i&&(u=(s.sizer.clientWidth-t.offsetWidth)/2),t.style.left=u+"px"),n&&(n=this,t={left:u,top:a,right:u+t.offsetWidth,bottom:a+t.offsetHeight},null!=(t=yr(n,t)).scrollTop&&Lr(n,t.scrollTop),null!=t.scrollLeft&&Tr(n,t.scrollLeft))},triggerOnKeyDown:Ir(Xo),triggerOnKeyPress:Ir(_o),triggerOnKeyUp:Yo,triggerOnMouseDown:Ir(Qo),execCommand:function(e){if(Io.hasOwnProperty(e))return Io[e].call(null,this)},triggerElectric:Ir(function(e){ml(this,e)}),findPosH:function(e,t,n,r){var i=1;t<0&&(i=-1,t=-t);for(var o=ct(this.doc,e),l=0;l { + $("#step-b").hide(); + $("#step-c").hide(); +}); + +function download(logId) { + zip.configure({ + workerScripts: { + deflate: ["static/js/zip-worker.js"], + inflate: ["static/js/zip-worker.js"] + } + }) + + const httpReader = new zip.HttpReader("api/logs/" + logId, { + preventHeadRequest: true + }); + const zipReader = new zip.ZipReader(httpReader); + zipReader.getEntries() + .then(displayEntries) + .catch(displayError); + + $("#step-a").hide(); + $("#step-b").show(); +} + +function displayEntries(entries) { + $("#step-b").hide(); + $("#step-c").show(); + + entries + .filter(entry => !entry.directory) + .forEach(entry => { + const element = $("
  • "); + element.text(entry.filename); + element.click(() => showFile(entry, element)); + + $("#file-list").append(element); + }) +} + +async function showFile(file, statusElement) { + console.log(file); + + statusElement.text("Entpacke Datei..."); + + if (isImage(file.filename)) { // Show data as image + const base64 = await file.getData(new zip.Data64URIWriter("image/" + getFileEnding(file.filename)), { + onprogress: (val, max) => { + statusElement.text("Entpacke Datei... (" + Math.round((val / max * 100)) + "%)"); + } + }); + + const div = $("
    "); + const divNative = div.get(0); + div.height("100%"); + div.width("100%"); + div.css("background-image", "url('" + base64 + "')"); + div.css("background-size", "contain"); + div.css("background-repeat", "no-repeat"); + div.css("background-position", "center"); + + jsPanel.create({ + contentSize: "500 500", + position: "left-top 30 20", + content: divNative, + headerTitle: "" + file.filename + "", + theme: "#e20074", + }); + + } else { // Show data as text + const text = await file.getData(new zip.TextWriter(), { + onprogress: (val, max) => { + statusElement.text("Entpacke Datei... (" + Math.round((val / max * 100)) + "%)"); + } + }); + + const div = $("
    "); + const divNative = div.get(0); + div.height("100%"); + div.width("100%"); + + jsPanel.create({ + contentSize: "800 500", + position: "left-top 30 20", + content: divNative, + headerTitle: "" + file.filename + "", + theme: "#e20074", + callback: (panel) => { + console.log(panel); + CodeMirror(divNative, { + value: text, + lineNumbers: true, + readOnly: true + }); + } + }); + } + + statusElement.text(file.filename); +} + +function isImage(filename) { + return imageFileEndings.some(fileEnding => filename.endsWith(fileEnding)); +} + +function getFileEnding(filename) { + return filename.substring(filename.lastIndexOf(".") + 1) +} + +function displayError(e) { + console.error("Error: ", e); +} \ No newline at end of file diff --git a/src/main/resources/static/js/jquery.min.js b/src/main/resources/static/js/jquery.min.js new file mode 100644 index 0000000..c39d8fd --- /dev/null +++ b/src/main/resources/static/js/jquery.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.5.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector | (c) JS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(g,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,v=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,y=n.hasOwnProperty,a=y.toString,l=a.call(Object),m={},b=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},w=g.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function C(e,t,n){var r,i,o=(n=n||w).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function T(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector",E=function(e,t){return new E.fn.init(e,t)};function d(e){var t=!!e&&"length"in e&&e.length,n=T(e);return!b(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+R+")"+R+"*"),U=new RegExp(R+"|>"),V=new RegExp(W),X=new RegExp("^"+B+"$"),Q={ID:new RegExp("^#("+B+")"),CLASS:new RegExp("^\\.("+B+")"),TAG:new RegExp("^("+B+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+R+"*(even|odd|(([+-]|)(\\d*)n|)"+R+"*(?:([+-]|)"+R+"*(\\d+)|))"+R+"*\\)|)","i"),bool:new RegExp("^(?:"+I+")$","i"),needsContext:new RegExp("^"+R+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+R+"*((?:-\\d)?\\d*)"+R+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,G=/^(?:input|select|textarea|button)$/i,K=/^h\d$/i,J=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+R+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){C()},ae=xe(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{O.apply(t=P.call(d.childNodes),d.childNodes),t[d.childNodes.length].nodeType}catch(e){O={apply:t.length?function(e,t){q.apply(e,P.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,d=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==d&&9!==d&&11!==d)return n;if(!r&&(C(e),e=e||T,E)){if(11!==d&&(u=Z.exec(t)))if(i=u[1]){if(9===d){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return O.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&p.getElementsByClassName&&e.getElementsByClassName)return O.apply(n,e.getElementsByClassName(i)),n}if(p.qsa&&!k[t+" "]&&(!v||!v.test(t))&&(1!==d||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===d&&(U.test(t)||_.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&p.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=A)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+be(l[o]);c=l.join(",")}try{return O.apply(n,f.querySelectorAll(c)),n}catch(e){k(t,!0)}finally{s===A&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>x.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[A]=!0,e}function ce(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)x.attrHandle[n[r]]=t}function de(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function pe(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in p=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},C=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:d;return r!=T&&9===r.nodeType&&r.documentElement&&(a=(T=r).documentElement,E=!i(T),d!=T&&(n=T.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),p.scope=ce(function(e){return a.appendChild(e).appendChild(T.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),p.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),p.getElementsByTagName=ce(function(e){return e.appendChild(T.createComment("")),!e.getElementsByTagName("*").length}),p.getElementsByClassName=J.test(T.getElementsByClassName),p.getById=ce(function(e){return a.appendChild(e).id=A,!T.getElementsByName||!T.getElementsByName(A).length}),p.getById?(x.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},x.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(x.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},x.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),x.find.TAG=p.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):p.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},x.find.CLASS=p.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(p.qsa=J.test(T.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+R+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+R+"*(?:value|"+I+")"),e.querySelectorAll("[id~="+A+"-]").length||v.push("~="),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+R+"*name"+R+"*="+R+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+A+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=T.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+R+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(p.matchesSelector=J.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){p.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",W)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=J.test(a.compareDocumentPosition),y=t||J.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!p.sortDetached&&t.compareDocumentPosition(e)===n?e==T||e.ownerDocument==d&&y(d,e)?-1:t==T||t.ownerDocument==d&&y(d,t)?1:u?H(u,e)-H(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==T?-1:t==T?1:i?-1:o?1:u?H(u,e)-H(u,t):0;if(i===o)return de(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?de(a[r],s[r]):a[r]==d?-1:s[r]==d?1:0}),T},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(C(e),p.matchesSelector&&E&&!k[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||p.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){k(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&V.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+R+")"+e+"("+R+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return b(n)?E.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?E.grep(e,function(e){return e===n!==r}):"string"!=typeof n?E.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(E.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||L,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:j.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof E?t[0]:t,E.merge(this,E.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:w,!0)),k.test(r[1])&&E.isPlainObject(t))for(r in t)b(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=w.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):b(e)?void 0!==n.ready?n.ready(e):e(E):E.makeArray(e,this)}).prototype=E.fn,L=E(w);var q=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}E.fn.extend({has:function(e){var t=E(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,pe=/^$|^module$|\/(?:java|ecma)script/i;le=w.createDocumentFragment().appendChild(w.createElement("div")),(ce=w.createElement("input")).setAttribute("type","radio"),ce.setAttribute("checked","checked"),ce.setAttribute("name","t"),le.appendChild(ce),m.checkClone=le.cloneNode(!0).cloneNode(!0).lastChild.checked,le.innerHTML="",m.noCloneChecked=!!le.cloneNode(!0).lastChild.defaultValue,le.innerHTML="",m.option=!!le.lastChild;var he={thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};function ge(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&S(e,t)?E.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n",""]);var ye=/<|&#?\w+;/;function me(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),d=[],p=0,h=e.length;p\s*$/g;function Le(e,t){return S(e,"table")&&S(11!==t.nodeType?t:t.firstChild,"tr")&&E(e).children("tbody")[0]||e}function je(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n
    ",2===ft.childNodes.length),E.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(m.createHTMLDocument?((r=(t=w.implementation.createHTMLDocument("")).createElement("base")).href=w.location.href,t.head.appendChild(r)):t=w),o=!n&&[],(i=k.exec(e))?[t.createElement(i[1])]:(i=me([e],t,o),o&&o.length&&E(o).remove(),E.merge([],i.childNodes)));var r,i,o},E.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=E.css(e,"position"),c=E(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=E.css(e,"top"),u=E.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),b(t)&&(t=t.call(e,n,E.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},E.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){E.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===E.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===E.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=E(e).offset()).top+=E.css(e,"borderTopWidth",!0),i.left+=E.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-E.css(r,"marginTop",!0),left:t.left-i.left-E.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===E.css(e,"position"))e=e.offsetParent;return e||re})}}),E.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;E.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),E.each(["top","left"],function(e,n){E.cssHooks[n]=Fe(m.pixelPosition,function(e,t){if(t)return t=We(e,n),Ie.test(t)?E(e).position()[n]+"px":t})}),E.each({Height:"height",Width:"width"},function(a,s){E.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){E.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?E.css(e,t,i):E.style(e,t,n,i)},s,n?e:void 0,n)}})}),E.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),E.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){E.fn[n]=function(e,t){return 0e.length)&&(t=e.length);for(var n=0,o=new Array(t);n',maximize:'',normalize:'',minimize:'',smallify:''},idCounter:0,isIE:navigator.appVersion.match(/Trident/),pointerdown:"ontouchend"in window?["touchstart","mousedown"]:["mousedown"],pointermove:"ontouchend"in window?["touchmove","mousemove"]:["mousemove"],pointerup:"ontouchend"in window?["touchend","mouseup"]:["mouseup"],polyfills:(Object.assign||Object.defineProperty(Object,"assign",{enumerable:!1,configurable:!0,writable:!0,value:function(e){if(null==e)throw new TypeError("Cannot convert first argument to object");for(var t=Object(e),n=1;n=0&&n.item(t)!==o;);}while(t<0&&(o=o.parentElement));return o}),function(){if("function"==typeof window.CustomEvent)return!1;function e(e,t){t=t||{bubbles:!1,cancelable:!1,detail:void 0};var n=document.createEvent("CustomEvent");return n.initCustomEvent(e,t.bubbles,t.cancelable,t.detail),n}e.prototype=window.Event.prototype,window.CustomEvent=e}(),String.prototype.endsWith||(String.prototype.endsWith=function(e,t){return tthis.length)&&-1!==this.indexOf(e,t)}),Number.isInteger=Number.isInteger||function(e){return"number"==typeof e&&isFinite(e)&&Math.floor(e)===e},void(Array.prototype.includes||Object.defineProperty(Array.prototype,"includes",{value:function(e,t){if(null==this)throw new TypeError('"this" is null or not defined');var n=Object(this),o=n.length>>>0;if(0===o)return!1;for(var a,r,i=0|t,l=Math.max(i>=0?i:o-Math.abs(i),0);l1&&(n-=1),n<1/6?e+6*(t-e)*n:n<.5?t:n<2/3?e+(t-e)*(2/3-n)*6:e},l=n<.5?n*(1+t):n+t-n*t,s=2*n-l;o=i(s,l,e+1/3),a=i(s,l,e),r=i(s,l,e-1/3)}return[Math.round(255*o),Math.round(255*a),Math.round(255*r)]},rgbToHsl:function(e,t,n){e/=255,t/=255,n/=255;var o,a,r=Math.max(e,t,n),i=Math.min(e,t,n),l=(r+i)/2;if(r===i)o=a=0;else{var s=r-i;switch(a=l>.5?s/(2-r-i):s/(r+i),r){case e:o=(t-n)/s+(t=0&&(h=this.applyPositionAutopos(e,h,t)),(t.offsetX||t.offsetY)&&(h=this.applyPositionOffset(e,h,t)),(t.minLeft||t.minTop||t.maxLeft||t.maxTop)&&(h=this.applyPositionMinMax(e,h,t)),t.modify&&(h=this.applyPositionModify(e,h,t)),"number"==typeof e.options.opacity?e.style.opacity=e.options.opacity:e.style.opacity=1,e},applyPositionAutopos:function(e,t,n){var o="".concat(n.my,"-").concat(n.autoposition.toLowerCase());e.classList.add(o);var a=Array.prototype.slice.call(document.querySelectorAll(".".concat(o))),r=a.indexOf(e);if(a.length>1){switch(n.autoposition){case"down":a.forEach(function(e,n){n>0&&n<=r&&(t.top=parseFloat(t.top)+a[--n].getBoundingClientRect().height+jsPanel.autopositionSpacing+"px")});break;case"up":a.forEach(function(e,n){n>0&&n<=r&&(t.top=parseFloat(t.top)-a[--n].getBoundingClientRect().height-jsPanel.autopositionSpacing+"px")});break;case"right":a.forEach(function(e,n){n>0&&n<=r&&(t.left=parseFloat(t.left)+a[--n].getBoundingClientRect().width+jsPanel.autopositionSpacing+"px")});break;case"left":a.forEach(function(e,n){n>0&&n<=r&&(t.left=parseFloat(t.left)-a[--n].getBoundingClientRect().width-jsPanel.autopositionSpacing+"px")})}e.style.left=t.left,e.style.top=t.top}return{left:t.left,top:t.top}},applyPositionOffset:function(e,t,n){["offsetX","offsetY"].forEach(function(e){n[e]?("function"==typeof n[e]&&(n[e]=n[e].call(t,t,n)),!1===isNaN(n[e])&&(n[e]="".concat(n[e],"px"))):n[e]="0px"}),e.style.left="calc(".concat(e.style.left," + ").concat(n.offsetX,")"),e.style.top="calc(".concat(e.style.top," + ").concat(n.offsetY,")");var o=getComputedStyle(e);return{left:o.left,top:o.top}},applyPositionMinMax:function(e,t,n){if(["minLeft","minTop","maxLeft","maxTop"].forEach(function(e){n[e]&&("function"==typeof n[e]&&(n[e]=n[e].call(t,t,n)),(Number.isInteger(n[e])||n[e].match(/^\d+$/))&&(n[e]="".concat(n[e],"px")))}),n.minLeft){e.style.left=n.minLeft;var o=getComputedStyle(e).left;parseFloat(o)parseFloat(t.left)?e.style.left=t.left:t.left=r}if(n.maxTop){e.style.top=n.maxTop;var i=getComputedStyle(e).top;parseFloat(i)>parseFloat(t.top)?e.style.top=t.top:t.top=i}var l=getComputedStyle(e);return{left:l.left,top:l.top}},applyPositionModify:function(e,t,n){if(n.modify&&"function"==typeof n.modify){var o=n.modify.call(t,t,n);e.style.left=Number.isInteger(o.left)||o.left.match(/^\d+$/)?"".concat(o.left,"px"):o.left,e.style.top=Number.isInteger(o.top)||o.top.match(/^\d+$/)?"".concat(o.top,"px"):o.top}var a=getComputedStyle(e);return{left:a.left,top:a.top}},autopositionRemaining:function(e){var t,n=e.options.container;(["left-top-down","left-top-right","center-top-down","right-top-down","right-top-left","left-bottom-up","left-bottom-right","center-bottom-up","right-bottom-up","right-bottom-left"].forEach(function(n){e.classList.contains(n)&&(t=n)}),t)&&("window"===n?document.body:"string"==typeof n?document.querySelector(n):n).querySelectorAll(".".concat(t)).forEach(function(e){e.reposition()})},addScript:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"application/javascript",n=arguments.length>2?arguments[2]:void 0;if(!document.querySelector('script[src="'.concat(e,'"]'))){var o=document.createElement("script");n&&(o.onload=n),o.src=e,o.type=t,document.head.appendChild(o)}},ajax:function(e,t){var n,o,a=new XMLHttpRequest,r={method:"GET",async:!0,user:"",pwd:"",done:function(){if(t){var e=jsPanel.strToHtml(this.responseText);n.urlSelector&&(e=e.querySelector(n.urlSelector)),t.contentRemove(),t.content.append(e)}},autoresize:!0,autoreposition:!0};if(t&&"string"==typeof e)n=Object.assign({},r,{url:e});else{if("object"!==_typeof(e)||!e.url){if(this.errorReporting){jsPanel.errorpanel("XMLHttpRequest seems to miss the url parameter!")}return}(n=Object.assign({},r,e)).url=e.url,!1===n.async&&(n.timeout=0,n.withCredentials&&(n.withCredentials=void 0),n.responseType&&(n.responseType=void 0))}o=n.url.trim().split(/\s+/),n.url=encodeURI(o[0]),o.length>1&&(o.shift(),n.urlSelector=o.join(" ")),a.onreadystatechange=function(){4===a.readyState&&(200===a.status?t?n.done.call(a,a,t):n.done.call(a,a):n.fail&&(t?n.fail.call(a,a,t):n.fail.call(a,a)),n.always&&(t?n.always.call(a,a,t):n.always.call(a,a)),t&&(n.autoresize||n.autoreposition)&&jsPanel.ajaxAutoresizeAutoreposition(t,n),jsPanel.ajaxAlwaysCallbacks.length&&jsPanel.ajaxAlwaysCallbacks.forEach(function(e){t?e.call(a,a,t):e.call(a,a)}))},a.open(n.method,n.url,n.async,n.user,n.pwd),a.timeout=n.timeout||0,n.withCredentials&&(a.withCredentials=n.withCredentials),n.responseType&&(a.responseType=n.responseType),n.beforeSend&&(t?n.beforeSend.call(a,a,t):n.beforeSend.call(a,a)),n.data?a.send(n.data):a.send(null)},ajaxAutoresizeAutoreposition:function(e,t){var n=e.options.contentSize;if("string"==typeof n&&n.match(/auto/i)){var o=n.split(" "),a=Object.assign({},{width:o[0],height:o[1]});t.autoresize&&e.resize(a),e.classList.contains("jsPanel-contextmenu")||t.autoreposition&&e.reposition()}else if("object"===_typeof(n)&&("auto"===n.width||"auto"===n.height)){var r=Object.assign({},n);t.autoresize&&e.resize(r),e.classList.contains("jsPanel-contextmenu")||t.autoreposition&&e.reposition()}},createPanelTemplate:function(){var e=!(arguments.length>0&&void 0!==arguments[0])||arguments[0],t=document.createElement("div");return t.className="jsPanel",t.style.left="0",t.style.top="0",e&&["close","maximize","normalize","minimize","smallify"].forEach(function(e){t.setAttribute("data-btn".concat(e),"enabled")}),t.innerHTML='
    \n
    \n \n
    \n
    \n
    \n
    \n \n \n \n \n \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    '),t},createMinimizedTemplate:function(){var e=document.createElement("div");return e.className="jsPanel-replacement",e.innerHTML='
    \n
    \n \n
    \n
    \n
    \n
    \n \n \n \n
    \n
    \n
    "),e},createSnapArea:function(e,t,n){var o=document.createElement("div"),a=e.parentElement;o.className="jsPanel-snap-area jsPanel-snap-area-".concat(t),"lt"===t||"rt"===t||"rb"===t||"lb"===t?(o.style.width=n+"px",o.style.height=n+"px"):"ct"===t||"cb"===t?o.style.height=n+"px":"lc"!==t&&"rc"!==t||(o.style.width=n+"px"),a!==document.body&&(o.style.position="absolute"),document.querySelector(".jsPanel-snap-area.jsPanel-snap-area-".concat(t))||e.parentElement.appendChild(o)},emptyNode:function(e){for(;e.firstChild;)e.removeChild(e.firstChild);return e},extend:function(e){if("[object Object]"===Object.prototype.toString.call(e))for(var t in e)Object.prototype.hasOwnProperty.call(e,t)&&(this.extensions[t]=e[t])},fetch:function(e){function t(t,n){return e.apply(this,arguments)}return t.toString=function(){return e.toString()},t}(function(e,t){var n,o={bodyMethod:"text",autoresize:!0,autoreposition:!0,done:function(e,t){if(t){var n=jsPanel.strToHtml(e);t.contentRemove(),t.content.append(n)}}};if(t&&"string"==typeof e)n=Object.assign({},o,{resource:encodeURI(e)});else{if("object"!==_typeof(e)||!e.resource){if(this.errorReporting){jsPanel.errorpanel("Fetch Request seems to miss the resource parameter!")}return}(n=Object.assign({},o,e)).resource=encodeURI(e.resource)}var a=n.fetchInit||{};n.beforeSend&&(t?n.beforeSend.call(e,e,t):n.beforeSend.call(e,e)),fetch(n.resource,a).then(function(e){if(e.ok)return e[n.bodyMethod]()}).then(function(e){t?n.done.call(e,e,t):n.done.call(e,e),t&&(n.autoresize||n.autoreposition)&&jsPanel.ajaxAutoresizeAutoreposition(t,n)})}),getPanels:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:function(){return this.classList.contains("jsPanel-standard")};return Array.prototype.slice.call(document.querySelectorAll(".jsPanel")).filter(function(t){return e.call(t,t)}).sort(function(e,t){return t.style.zIndex-e.style.zIndex})},pOcontainer:function(e){if("window"===e)return document.body;if("string"==typeof e){var t=document.querySelectorAll(e);return!!(t.length&&t.length>0)&&t}return 1===e.nodeType?e:!!e.length&&e[0]},pOcontainment:function(e){var t=e;if("function"==typeof e&&(t=e()),"number"==typeof t)return[t,t,t,t];if(Array.isArray(t)){if(1===t.length)return[t[0],t[0],t[0],t[0]];if(2===t.length)return t.concat(t);3===t.length&&(t[3]=t[1])}return t},pOsize:function(e,t){var n=t||this.defaults.contentSize,o=e.parentElement;if("string"==typeof n){var a=n.trim().split(" ");(n={}).width=a[0],2===a.length?n.height=a[1]:n.height=a[0]}else n.width&&!n.height?n.height=n.width:n.height&&!n.width&&(n.width=n.height);if(String(n.width).match(/^[0-9.]+$/gi))n.width+="px";else if("string"==typeof n.width&&n.width.endsWith("%"))if(o===document.body)n.width=window.innerWidth*(parseFloat(n.width)/100)+"px";else{var r=window.getComputedStyle(o),i=parseFloat(r.borderLeftWidth)+parseFloat(r.borderRightWidth);n.width=(parseFloat(r.width)-i)*(parseFloat(n.width)/100)+"px"}else"function"==typeof n.width&&(n.width=n.width.call(e,e),"number"==typeof n.width?n.width+="px":"string"==typeof n.width&&n.width.match(/^[0-9.]+$/gi)&&(n.width+="px"));if(String(n.height).match(/^[0-9.]+$/gi))n.height+="px";else if("string"==typeof n.height&&n.height.endsWith("%"))if(o===document.body)n.height=window.innerHeight*(parseFloat(n.height)/100)+"px";else{var l=window.getComputedStyle(o),s=parseFloat(l.borderTopWidth)+parseFloat(l.borderBottomWidth);n.height=(parseFloat(l.height)-s)*(parseFloat(n.height)/100)+"px"}else"function"==typeof n.height&&(n.height=n.height.call(e,e),"number"==typeof n.height?n.height+="px":"string"==typeof n.height&&n.height.match(/^[0-9.]+$/gi)&&(n.height+="px"));return n},pOborder:function(e){e=e.trim();var t=new Array(3),n=e.match(/\s*(none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset)\s*/gi),o=e.match(/\s*(thin|medium|thick)|(\d*\.?\d+[a-zA-Z]{2,4})\s*/gi);return n?(t[1]=n[0].trim(),e=e.replace(t[1],"")):t[1]="solid",o?(t[0]=o[0].trim(),e=e.replace(t[0],"")):t[0]="medium",t[2]=e.trim(),t},pOheaderControls:function(e){if("string"==typeof e){var t={},n=e.toLowerCase(),o=n.match(/xl|lg|md|sm|xs/),a=n.match(/closeonly|none/);return o&&(t.size=o[0]),a&&(t=Object.assign({},t,{maximize:"remove",normalize:"remove",minimize:"remove",smallify:"remove"}),"none"===a[0]&&(t.close="remove")),Object.assign({},this.defaults.headerControls,t)}return Object.assign({},this.defaults.headerControls,e)},processCallbacks:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"some",o=arguments.length>3?arguments[3]:void 0,a=arguments.length>4?arguments[4]:void 0;if("function"==typeof t&&(t=[t]),n)return t[n](function(t){return t.call(e,e,o,a)});t.forEach(function(t){t.call(e,e,o,a)})},removeSnapAreas:function(){document.querySelectorAll(".jsPanel-snap-area").forEach(function(e){e.parentElement.removeChild(e)})},resetZi:function(){this.zi=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:jsPanel.ziBase;return{next:function(){return e++}}}(),Array.prototype.slice.call(document.querySelectorAll(".jsPanel-standard")).sort(function(e,t){return e.style.zIndex-t.style.zIndex}).forEach(function(e){e.style.zIndex=jsPanel.zi.next()})},setClass:function(e,t){return t.trim().split(/\s+/).forEach(function(t){return e.classList.add(t)}),e},remClass:function(e,t){return t.trim().split(/\s+/).forEach(function(t){return e.classList.remove(t)}),e},toggleClass:function(e,t){return t.trim().split(/\s+/).forEach(function(t){e.classList.contains(t)?e.classList.remove(t):e.classList.add(t)}),e},setStyles:function(e,t){for(var n in t)n in e.style?e.style[n]=t[n]:e.style.setProperty(n,t[n]);return e},setStyle:function(e,t){return this.setStyles.call(e,e,t)},strToHtml:function(e){return document.createRange().createContextualFragment(e)},errorpanel:function(e){this.create({paneltype:"error",dragit:!1,resizeit:!1,theme:{bgPanel:"white",bgContent:"white",colorHeader:"rebeccapurple",colorContent:"#333",border:"2px solid rebeccapurple"},borderRadius:".33rem",headerControls:"closeonly xs",headerTitle:"⚠ jsPanel Error",contentSize:{width:"50%",height:"auto"},position:"center-top 0 5 down",animateIn:"jsPanelFadeIn",content:'

    '.concat(e,"

    ")})},create:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=arguments.length>1?arguments[1]:void 0;jsPanel.zi||(jsPanel.zi=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:jsPanel.ziBase;return{next:function(){return e++}}}()),t.config?delete(t=Object.assign({},this.defaults,t.config,t)).config:t=Object.assign({},this.defaults,t),t.id?"function"==typeof t.id&&(t.id=t.id()):t.id="jsPanel-".concat(jsPanel.idCounter+=1);var o=document.getElementById(t.id);if(null!==o){if(o.classList.contains("jsPanel")&&o.front(),this.errorReporting){var a="◀ COULD NOT CREATE NEW JSPANEL ►
    An element with the ID ".concat(t.id," already exists in the document.");jsPanel.errorpanel(a)}return!1}var r=this.pOcontainer(t.container);if("object"===_typeof(r)&&r.length&&r.length>0&&(r=r[0]),!r){if(this.errorReporting){jsPanel.errorpanel("◀ COULD NOT CREATE NEW JSPANEL ►
    The container to append the panel to does not exist")}return!1}["onbeforeclose","onbeforemaximize","onbeforeminimize","onbeforenormalize","onbeforesmallify","onbeforeunsmallify","onclosed","onfronted","onmaximized","onminimized","onnormalized","onsmallified","onstatuschange","onunsmallified"].forEach(function(e){t[e]?"function"==typeof t[e]&&(t[e]=[t[e]]):t[e]=[]});var i=t.template?t.template:this.createPanelTemplate();i.options=t,i.closetimer=void 0,i.status="initialized",i.currentData={},i.header=i.querySelector(".jsPanel-hdr"),i.headerbar=i.header.querySelector(".jsPanel-headerbar"),i.titlebar=i.header.querySelector(".jsPanel-titlebar"),i.headerlogo=i.headerbar.querySelector(".jsPanel-headerlogo"),i.headertitle=i.headerbar.querySelector(".jsPanel-title"),i.controlbar=i.headerbar.querySelector(".jsPanel-controlbar"),i.headertoolbar=i.header.querySelector(".jsPanel-hdr-toolbar"),i.content=i.querySelector(".jsPanel-content"),i.footer=i.querySelector(".jsPanel-ftr"),i.snappableTo=!1,i.snapped=!1,i.droppableTo=!1,i.progressbar=i.autocloseProgressbar=i.querySelector(".jsPanel-progressbar");var l=new CustomEvent("jspanelloaded",{detail:t.id,cancelable:!0}),s=new CustomEvent("jspanelstatuschange",{detail:t.id,cancelable:!0}),c=new CustomEvent("jspanelbeforenormalize",{detail:t.id,cancelable:!0}),d=new CustomEvent("jspanelnormalized",{detail:t.id,cancelable:!0}),p=new CustomEvent("jspanelbeforemaximize",{detail:t.id,cancelable:!0}),h=new CustomEvent("jspanelmaximized",{detail:t.id,cancelable:!0}),f=new CustomEvent("jspanelbeforeminimize",{detail:t.id,cancelable:!0}),u=new CustomEvent("jspanelminimized",{detail:t.id,cancelable:!0}),m=new CustomEvent("jspanelbeforesmallify",{detail:t.id,cancelable:!0}),g=new CustomEvent("jspanelsmallified",{detail:t.id,cancelable:!0}),b=new CustomEvent("jspanelsmallifiedmax",{detail:t.id,cancelable:!0}),y=new CustomEvent("jspanelbeforeunsmallify",{detail:t.id,cancelable:!0}),v=new CustomEvent("jspanelfronted",{detail:t.id,cancelable:!0}),w=new CustomEvent("jspanelbeforeclose",{detail:t.id,cancelable:!0}),j=new CustomEvent("jspanelclosed",{detail:t.id,cancelable:!0}),P=new CustomEvent("jspanelcloseduser",{detail:t.id,cancelable:!0});[l,s,c,d,p,h,f,u,m,g,b,y,v,w].forEach(function(e){e.panel=i});var E=i.querySelector(".jsPanel-btn-close"),x=i.querySelector(".jsPanel-btn-maximize"),C=i.querySelector(".jsPanel-btn-normalize"),F=i.querySelector(".jsPanel-btn-smallify"),z=i.querySelector(".jsPanel-btn-minimize");E&&jsPanel.pointerup.forEach(function(e){E.addEventListener(e,function(e){if(e.preventDefault(),e.button&&e.button>0)return!1;i.close(null,!0)})}),x&&jsPanel.pointerup.forEach(function(e){x.addEventListener(e,function(e){if(e.preventDefault(),e.button&&e.button>0)return!1;i.maximize()})}),C&&jsPanel.pointerup.forEach(function(e){C.addEventListener(e,function(e){if(e.preventDefault(),e.button&&e.button>0)return!1;i.normalize()})}),F&&jsPanel.pointerup.forEach(function(e){F.addEventListener(e,function(e){if(e.preventDefault(),e.button&&e.button>0)return!1;"normalized"===i.status||"maximized"===i.status?i.smallify():"smallified"!==i.status&&"smallifiedmax"!==i.status||i.unsmallify()})}),z&&jsPanel.pointerup.forEach(function(e){z.addEventListener(e,function(e){if(e.preventDefault(),e.button&&e.button>0)return!1;i.minimize()})});var S=jsPanel.extensions;for(var A in S)Object.prototype.hasOwnProperty.call(S,A)&&(i[A]=S[A]);if(i.clearTheme=function(e){return jsPanel.themes.forEach(function(e){["panel","jsPanel-theme-".concat(e),"panel-".concat(e),"".concat(e,"-color")].forEach(function(e){i.classList.remove(e)}),i.header.classList.remove("jsPanel-theme-".concat(e))}),i.content.classList.remove("jsPanel-content-filled","jsPanel-content-filledlight"),i.header.classList.remove("jsPanel-hdr-light"),i.header.classList.remove("jsPanel-hdr-dark"),i.style.backgroundColor="",jsPanel.setStyle(i.headertoolbar,{boxShadow:"",width:"",marginLeft:"",borderTopColor:"transparent"}),jsPanel.setStyle(i.content,{background:"",borderTopColor:"transparent"}),i.header.style.background="",Array.prototype.slice.call(i.controlbar.querySelectorAll(".jsPanel-icon")).concat([i.headerlogo,i.headertitle,i.headertoolbar,i.content]).forEach(function(e){e.style.color=""}),e&&e.call(i,i),i},i.getThemeDetails=function(e){var t=e.toLowerCase(),n={color:!1,colors:!1,filling:!1},o=t.split("fill");if(n.color=o[0].trim().replace(/\s*/g,""),2===o.length)if(o[1].startsWith("edlight"))n.filling="filledlight";else if(o[1].startsWith("eddark"))n.filling="filleddark";else if(o[1].startsWith("ed"))n.filling="filled";else if(o[1].startsWith("color")){var a=o[1].split("color"),r=a[a.length-1].trim().replace(/\s*/g,"");jsPanel.colorNames[r]&&(r=jsPanel.colorNames[r]),r.match(/^([0-9a-f]{3}|[0-9a-f]{6})$/gi)&&(r="#"+r),n.filling=r}if(jsPanel.themes.some(function(e){return e===n.color.split(/\s/i)[0]})){var i=n.color.split(/\s/i)[0],l=document.createElement("button");l.className=i+"-bg",document.body.appendChild(l),n.color=getComputedStyle(l).backgroundColor.replace(/\s+/gi,""),document.body.removeChild(l),l=void 0}else if(n.color.startsWith("bootstrap-")){var s=n.color.indexOf("-"),c=document.createElement("button");c.className="btn btn"+n.color.slice(s),document.body.appendChild(c),n.color=getComputedStyle(c).backgroundColor.replace(/\s+/gi,""),document.body.removeChild(c),c=void 0}else if(n.color.startsWith("mdb-")){var d,p=n.color.indexOf("-")+1,h=document.createElement("span");d=n.color.endsWith("-dark")?(d=n.color.slice(p)).replace("-dark","-color-dark"):n.color.slice(p)+"-color",h.className=d,document.body.appendChild(h),n.color=getComputedStyle(h).backgroundColor.replace(/\s+/gi,""),document.body.removeChild(h),h=void 0}return n.colors=jsPanel.calcColors(n.color),n},i.applyColorTheme=function(e){if(i.style.backgroundColor=e.colors[0],i.header.style.backgroundColor=e.colors[0],i.header.style.color=e.colors[3],[".jsPanel-headerlogo",".jsPanel-title",".jsPanel-hdr-toolbar"].forEach(function(t){i.querySelector(t).style.color=e.colors[3]}),i.querySelectorAll(".jsPanel-controlbar .jsPanel-btn").forEach(function(t){t.style.color=e.colors[3]}),"string"==typeof i.options.theme&&"filled"===e.filling&&(i.content.style.borderTop="#000000"===e.colors[3]?"1px solid rgba(0,0,0,0.15)":"1px solid rgba(255,255,255,0.15)"),"#000000"===e.colors[3]?i.header.classList.add("jsPanel-hdr-light"):i.header.classList.add("jsPanel-hdr-dark"),e.filling)switch(e.filling){case"filled":jsPanel.setStyle(i.content,{backgroundColor:e.colors[2],color:e.colors[3]});break;case"filledlight":i.content.style.backgroundColor=e.colors[1];break;case"filleddark":jsPanel.setStyle(i.content,{backgroundColor:e.colors[6],color:e.colors[7]});break;default:i.content.style.backgroundColor=e.filling,i.content.style.color=jsPanel.perceivedBrightness(e.filling)<=jsPanel.colorBrightnessThreshold?"#fff":"#000"}return i},i.applyCustomTheme=function(e){var t,n={bgPanel:"#fff",bgContent:"#fff",colorHeader:"#000",colorContent:"#000"},o=(t="object"===_typeof(e)?Object.assign(n,e):n).bgPanel,a=t.bgContent,r=t.colorHeader,l=t.colorContent;if(jsPanel.colorNames[o]?i.style.background="#"+jsPanel.colorNames[o]:i.style.background=o,jsPanel.colorNames[r]&&(r="#"+jsPanel.colorNames[r]),[".jsPanel-headerlogo",".jsPanel-title",".jsPanel-hdr-toolbar"].forEach(function(e){i.querySelector(e).style.color=r}),i.querySelectorAll(".jsPanel-controlbar .jsPanel-btn").forEach(function(e){e.style.color=r}),jsPanel.colorNames[a]?i.content.style.background="#"+jsPanel.colorNames[a]:i.content.style.background=a,jsPanel.colorNames[l]?i.content.style.color="#"+jsPanel.colorNames[l]:i.content.style.color=l,jsPanel.perceivedBrightness(r)>jsPanel.colorBrightnessThreshold?i.header.classList.add("jsPanel-hdr-dark"):i.header.classList.add("jsPanel-hdr-light"),jsPanel.perceivedBrightness(l)>jsPanel.colorBrightnessThreshold?i.content.style.borderTop="1px solid rgba(255,255,255,0.15)":i.content.style.borderTop="1px solid rgba(0,0,0,0.15)",t.border){var s=t.border,c=s.lastIndexOf(" "),d=s.slice(++c);jsPanel.colorNames[d]&&(s=s.replace(d,"#"+jsPanel.colorNames[d])),i.style.border=s}return i},i.setBorder=function(e){var t=jsPanel.pOborder(e);return t[2].length?jsPanel.colorNames[t[2]]&&(t[2]="#"+jsPanel.colorNames[t[2]]):t[2]=i.style.backgroundColor,t=t.join(" "),i.style.border=t,i.options.border=t,i},i.setBorderRadius=function(e){"number"==typeof e&&(e+="px"),i.style.borderRadius=e;var t=getComputedStyle(i);return i.options.header?(i.header.style.borderTopLeftRadius=t.borderTopLeftRadius,i.header.style.borderTopRightRadius=t.borderTopRightRadius):(i.content.style.borderTopLeftRadius=t.borderTopLeftRadius,i.content.style.borderTopRightRadius=t.borderTopRightRadius),i.options.footerToolbar?(i.footer.style.borderBottomRightRadius=t.borderBottomRightRadius,i.footer.style.borderBottomLeftRadius=t.borderBottomLeftRadius):(i.content.style.borderBottomRightRadius=t.borderBottomRightRadius,i.content.style.borderBottomLeftRadius=t.borderBottomLeftRadius),i},i.setTheme=function(){var e,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:t.theme,o=arguments.length>1?arguments[1]:void 0;if("minimized"===i.status&&(e=!0,i.normalize()),i.clearTheme(),"object"===_typeof(n))t.border=void 0,i.applyCustomTheme(n);else{"none"===n&&(n="white");var a=i.getThemeDetails(n);i.applyColorTheme(a)}return e&&i.minimize(),o&&o.call(i,i),i},i.remove=function(e,t,n){i.parentElement.removeChild(i),document.getElementById(e)?n&&n.call(i,e,i):(i.removeMinimizedReplacement(),t&&document.dispatchEvent(P),document.dispatchEvent(j),i.options.onclosed&&jsPanel.processCallbacks(i,i.options.onclosed,"every",t),jsPanel.autopositionRemaining(i),n&&n.call(e,e)),window.removeEventListener("resize",i.windowResizeHandler),document.removeEventListener("jspanelresize",i.parentResizeHandler)},i.close=function(e,t){if(i.closetimer&&window.clearInterval(i.closetimer),document.dispatchEvent(w),i.options.onbeforeclose&&i.options.onbeforeclose.length>0&&!jsPanel.processCallbacks(i,i.options.onbeforeclose,"some",i.status,t))return i;i.options.animateOut?(i.options.animateIn&&jsPanel.remClass(i,i.options.animateIn),jsPanel.setClass(i,i.options.animateOut),i.addEventListener("animationend",function(n){n.stopPropagation(),i.remove(i.id,t,e)})):i.remove(i.id,t,e)},i.maximize=function(e,n){if(i.statusBefore=i.status,t.onbeforemaximize&&t.onbeforemaximize.length>0&&!jsPanel.processCallbacks(i,t.onbeforemaximize,"some",i.statusBefore))return i;document.dispatchEvent(p);var o=i.parentElement,a=jsPanel.pOcontainment(t.maximizedMargin);return o===document.body?(i.style.width=document.documentElement.clientWidth-a[1]-a[3]+"px",i.style.height=document.documentElement.clientHeight-a[0]-a[2]+"px",i.style.left=a[3]+"px",i.style.top=a[0]+"px"):(i.style.width=o.clientWidth-a[1]-a[3]+"px",i.style.height=o.clientHeight-a[0]-a[2]+"px",i.style.left=a[3]+"px",i.style.top=a[0]+"px"),F.style.transform="unset",i.removeMinimizedReplacement(),i.status="maximized",i.setControls([".jsPanel-btn-maximize"]),n||i.front(),document.dispatchEvent(h),document.dispatchEvent(s),t.onstatuschange&&jsPanel.processCallbacks(i,t.onstatuschange,"every",i.statusBefore),e&&e.call(i,i,i.statusBefore),t.onmaximized&&jsPanel.processCallbacks(i,t.onmaximized,"every",i.statusBefore),i},i.minimize=function(e){if("minimized"===i.status)return i;if(i.statusBefore=i.status,t.onbeforeminimize&&t.onbeforeminimize.length>0&&!jsPanel.processCallbacks(i,t.onbeforeminimize,"some",i.statusBefore))return i;if(document.dispatchEvent(f),!document.getElementById("jsPanel-replacement-container")){var n=document.createElement("div");n.id="jsPanel-replacement-container",document.body.append(n)}if(i.style.left="-9999px",i.status="minimized",document.dispatchEvent(u),document.dispatchEvent(s),t.onstatuschange&&jsPanel.processCallbacks(i,t.onstatuschange,"every",i.statusBefore),t.minimizeTo){var o,a,r,l=i.createMinimizedReplacement();switch(t.minimizeTo){case"default":document.getElementById("jsPanel-replacement-container").append(l);break;case"parentpanel":(o=(r=(a=i.closest(".jsPanel-content").parentElement).querySelectorAll(".jsPanel-minimized-box"))[r.length-1]).append(l);break;case"parent":(o=(a=i.parentElement).querySelector(".jsPanel-minimized-container"))||((o=document.createElement("div")).className="jsPanel-minimized-container",a.append(o)),o.append(l);break;default:document.querySelector(t.minimizeTo).append(l)}}return e&&e.call(i,i,i.statusBefore),t.onminimized&&jsPanel.processCallbacks(i,t.onminimized,"every",i.statusBefore),i},i.normalize=function(e){return"normalized"===i.status?i:(i.statusBefore=i.status,t.onbeforenormalize&&t.onbeforenormalize.length>0&&!jsPanel.processCallbacks(i,t.onbeforenormalize,"some",i.statusBefore)?i:(document.dispatchEvent(c),i.style.width=i.currentData.width,i.style.height=i.currentData.height,i.snapped?i.snap(i.snapped,!0):(i.style.left=i.currentData.left,i.style.top=i.currentData.top),F.style.transform="unset",i.removeMinimizedReplacement(),i.status="normalized",i.setControls([".jsPanel-btn-normalize"]),i.front(),document.dispatchEvent(d),document.dispatchEvent(s),t.onstatuschange&&jsPanel.processCallbacks(i,t.onstatuschange,"every",i.statusBefore),e&&e.call(i,i,i.statusBefore),t.onnormalized&&jsPanel.processCallbacks(i,t.onnormalized,"every",i.statusBefore),i))},i.smallify=function(e){if("smallified"===i.status||"smallifiedmax"===i.status)return i;if(i.statusBefore=i.status,t.onbeforesmallify&&t.onbeforesmallify.length>0&&!jsPanel.processCallbacks(i,t.onbeforesmallify,"some",i.statusBefore))return i;document.dispatchEvent(m),i.style.overflow="hidden";var n=window.getComputedStyle(i),o=parseFloat(window.getComputedStyle(i.headerbar).height);i.style.height=parseFloat(n.borderTopWidth)+parseFloat(n.borderBottomWidth)+o+"px",F.style.transform="rotate(180deg)","normalized"===i.status?(i.setControls([".jsPanel-btn-normalize"]),i.status="smallified",document.dispatchEvent(g),document.dispatchEvent(s),t.onstatuschange&&jsPanel.processCallbacks(i,t.onstatuschange,"every",i.statusBefore)):"maximized"===i.status&&(i.setControls([".jsPanel-btn-maximize"]),i.status="smallifiedmax",document.dispatchEvent(b),document.dispatchEvent(s),t.onstatuschange&&jsPanel.processCallbacks(i,t.onstatuschange,"every",i.statusBefore));var a=i.querySelectorAll(".jsPanel-minimized-box");return a[a.length-1].style.display="none",e&&e.call(i,i,i.statusBefore),t.onsmallified&&jsPanel.processCallbacks(i,t.onsmallified,"every",i.statusBefore),i},i.unsmallify=function(e){if(i.statusBefore=i.status,"smallified"===i.status||"smallifiedmax"===i.status){if(t.onbeforeunsmallify&&t.onbeforeunsmallify.length>0&&!jsPanel.processCallbacks(i,t.onbeforeunsmallify,"some",i.statusBefore))return i;document.dispatchEvent(y),i.style.overflow="visible",i.front(),"smallified"===i.status?(i.style.height=i.currentData.height,i.setControls([".jsPanel-btn-normalize"]),i.status="normalized",document.dispatchEvent(d),document.dispatchEvent(s),t.onstatuschange&&jsPanel.processCallbacks(i,t.onstatuschange,"every",i.statusBefore)):"smallifiedmax"===i.status?i.maximize():"minimized"===i.status&&i.normalize(),F.style.transform="rotate(0deg)";var n=i.querySelectorAll(".jsPanel-minimized-box");n[n.length-1].style.display="flex",e&&e.call(i,i,i.statusBefore),t.onunsmallified&&jsPanel.processCallbacks(i,t.onunsmallified,"every",i.statusBefore)}return i},i.front=function(e){var n=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];if("minimized"===i.status)"maximized"===i.statusBefore?i.maximize():i.normalize();else{var o=Array.prototype.slice.call(document.querySelectorAll(".jsPanel-standard")).map(function(e){return e.style.zIndex});Math.max.apply(Math,_toConsumableArray(o))>i.style.zIndex&&(i.style.zIndex=jsPanel.zi.next()),jsPanel.resetZi()}return document.dispatchEvent(v),e&&e.call(i,i),t.onfronted&&n&&jsPanel.processCallbacks(i,t.onfronted,"every",i.status),i},i.snap=function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];if(t||(i.currentData.beforeSnap={width:i.currentData.width,height:i.currentData.height}),e&&"function"==typeof e&&!t)e.call(i,i,i.snappableTo);else if(!1!==e){var n=[0,0];if(i.options.dragit.snap.containment&&i.options.dragit.containment){var o=jsPanel.pOcontainment(i.options.dragit.containment),a=i.snappableTo;a.startsWith("left")?n[0]=o[3]:a.startsWith("right")&&(n[0]=-o[1]),a.endsWith("top")?n[1]=o[0]:a.endsWith("bottom")&&(n[1]=-o[2])}i.reposition("".concat(i.snappableTo," ").concat(n[0]," ").concat(n[1]))}t||(i.snapped=i.snappableTo)},i.move=function(e,t){var n=i.overlaps(e,"paddingbox"),o=i.parentElement;return e.appendChild(i),i.options.container=e,i.style.left=n.left+"px",i.style.top=n.top+"px",i.saveCurrentDimensions(),i.saveCurrentPosition(),i.calcSizeFactors(),t&&t.call(i,i,e,o),i},i.closeChildpanels=function(e){return i.getChildpanels().forEach(function(e){return e.close()}),e&&e.call(i,i),i},i.getChildpanels=function(e){var t=i.content.querySelectorAll(".jsPanel");return e&&t.forEach(function(t,n,o){e.call(t,t,n,o)}),t},i.isChildpanel=function(e){var t=i.closest(".jsPanel-content"),n=t?t.parentElement:null;return e&&e.call(i,i,n),!!t&&n},i.contentRemove=function(e){return jsPanel.emptyNode(i.content),e&&e.call(i,i),i},i.createMinimizedReplacement=function(){var e=jsPanel.createMinimizedTemplate(),n=window.getComputedStyle(i.headertitle).color,o=window.getComputedStyle(i),a=t.iconfont,r=e.querySelector(".jsPanel-controlbar");return"auto-show-hide"!==i.options.header?jsPanel.setStyle(e,{backgroundColor:o.backgroundColor,backgroundPositionX:o.backgroundPositionX,backgroundPositionY:o.backgroundPositionY,backgroundRepeat:o.backgroundRepeat,backgroundAttachment:o.backgroundAttachment,backgroundImage:o.backgroundImage,backgroundSize:o.backgroundSize,backgroundOrigin:o.backgroundOrigin,backgroundClip:o.backgroundClip}):e.style.backgroundColor=window.getComputedStyle(i.header).backgroundColor,e.id=i.id+"-min",e.querySelector(".jsPanel-headerbar").replaceChild(i.headerlogo.cloneNode(!0),e.querySelector(".jsPanel-headerlogo")),e.querySelector(".jsPanel-titlebar").replaceChild(i.headertitle.cloneNode(!0),e.querySelector(".jsPanel-title")),e.querySelector(".jsPanel-titlebar").setAttribute("title",i.headertitle.textContent),e.querySelector(".jsPanel-title").style.color=n,r.style.color=n,r.querySelectorAll("button").forEach(function(e){e.style.color=n}),["jsPanel-hdr-dark","jsPanel-hdr-light"].forEach(function(t){i.header.classList.contains(t)&&e.querySelector(".jsPanel-hdr").classList.add(t)}),i.setIconfont(a,e),"enabled"===i.dataset.btnnormalize?jsPanel.pointerup.forEach(function(t){e.querySelector(".jsPanel-btn-normalize").addEventListener(t,function(e){if(e.preventDefault(),e.button&&e.button>0)return!1;i.normalize()})}):r.querySelector(".jsPanel-btn-normalize").style.display="none","enabled"===i.dataset.btnmaximize?jsPanel.pointerup.forEach(function(t){e.querySelector(".jsPanel-btn-maximize").addEventListener(t,function(e){if(e.preventDefault(),e.button&&e.button>0)return!1;i.maximize()})}):r.querySelector(".jsPanel-btn-maximize").style.display="none","enabled"===i.dataset.btnclose?jsPanel.pointerup.forEach(function(t){e.querySelector(".jsPanel-btn-close").addEventListener(t,function(e){if(e.preventDefault(),e.button&&e.button>0)return!1;i.close(null,!0)})}):r.querySelector(".jsPanel-btn-close").style.display="none",e},i.removeMinimizedReplacement=function(){var e=document.getElementById("".concat(i.id,"-min"));e&&e.parentElement.removeChild(e)},i.drag=function(){var e,t,n,o=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},a=new CustomEvent("jspaneldragstart",{detail:i.id}),r=new CustomEvent("jspaneldrag",{detail:i.id}),l=new CustomEvent("jspaneldragstop",{detail:i.id});[a,r,l].forEach(function(e){e.panel=i});var s=function(e){var t=e.split("-");return t.forEach(function(e,n){t[n]=e.charAt(0).toUpperCase()+e.slice(1)}),"snap"+t.join("")};function c(e){null===e.relatedTarget&&jsPanel.pointermove.forEach(function(e){document.removeEventListener(e,t,!1),i.style.opacity=1})}var d=o.handles||jsPanel.defaults.dragit.handles,p=o.cursor||jsPanel.defaults.dragit.cursor;function h(o){if(jsPanel.pointermove.forEach(function(e){document.removeEventListener(e,t)}),jsPanel.removeSnapAreas(),e){if(i.style.opacity=1,e=void 0,n.snap){switch(i.snappableTo){case"left-top":i.snap(n.snap.snapLeftTop);break;case"center-top":i.snap(n.snap.snapCenterTop);break;case"right-top":i.snap(n.snap.snapRightTop);break;case"right-center":i.snap(n.snap.snapRightCenter);break;case"right-bottom":i.snap(n.snap.snapRightBottom);break;case"center-bottom":i.snap(n.snap.snapCenterBottom);break;case"left-bottom":i.snap(n.snap.snapLeftBottom);break;case"left-center":i.snap(n.snap.snapLeftCenter)}n.snap.callback&&i.snappableTo&&"function"==typeof n.snap.callback&&(n.snap.callback.call(i,i),n.snap.repositionOnSnap&&!1!==n.snap[s(i.snappableTo)]&&i.repositionOnSnap(i.snappableTo)),i.snappableTo&&n.snap.repositionOnSnap&&n.snap[s(i.snappableTo)]&&i.repositionOnSnap(i.snappableTo)}if(i.droppableTo&&i.droppableTo){var a=i.parentElement;i.move(i.droppableTo),n.drop.callback&&n.drop.callback.call(i,i,i.droppableTo,a)}if(document.dispatchEvent(l),n.stop.length){var r=window.getComputedStyle(i),c={left:parseFloat(r.left),top:parseFloat(r.top),width:parseFloat(r.width),height:parseFloat(r.height)};jsPanel.processCallbacks(i,n.stop,!1,c,o)}i.saveCurrentPosition(),i.calcSizeFactors()}i.controlbar.style.pointerEvents="inherit",i.content.style.pointerEvents="inherit",document.querySelectorAll("iframe").forEach(function(e){e.style.pointerEvents="auto"}),document.removeEventListener(o,h)}return i.querySelectorAll(d).forEach(function(l){l.style.touchAction="none",l.style.cursor=p,jsPanel.pointerdown.forEach(function(s){l.addEventListener(s,function(l){if(l.button&&l.button>0)return!1;if((n=Object.assign({},jsPanel.defaults.dragit,o)).disableOnMaximized&&"maximized"===i.status)return!1;if((n.containment||0===n.containment)&&(n.containment=jsPanel.pOcontainment(n.containment)),n.grid&&Array.isArray(n.grid)&&1===n.grid.length&&(n.grid[1]=n.grid[0]),n.snap&&("object"===_typeof(n.snap)?n.snap=Object.assign({},jsPanel.defaultSnapConfig,n.snap):n.snap=jsPanel.defaultSnapConfig),!l.target.closest(".jsPanel-ftr-btn")){i.controlbar.style.pointerEvents="none",i.content.style.pointerEvents="none",document.querySelectorAll("iframe").forEach(function(e){e.style.pointerEvents="none"});var s=window.getComputedStyle(i),d=parseFloat(s.left),p=parseFloat(s.top),h=parseFloat(s.width),f=parseFloat(s.height),u=l.touches?l.touches[0].clientX:l.clientX,m=l.touches?l.touches[0].clientY:l.clientY,g=i.parentElement,b=g.getBoundingClientRect(),y=window.getComputedStyle(g),v=i.getScaleFactor(),w=0;t=function(t){if(t.preventDefault(),!e){if(document.dispatchEvent(a),i.style.opacity=n.opacity,i.snapped&&n.snap.resizeToPreSnap&&i.currentData.beforeSnap){i.resize(i.currentData.beforeSnap.width+" "+i.currentData.beforeSnap.height),i.setControls([".jsPanel-btn-normalize"]);var o=i.getBoundingClientRect(),l=u-(o.left+o.width),s=o.width/2;l>-s&&(w=l+s)}if(i.front(),i.snapped=!1,"maximized"===i.status&&(i.setControls([".jsPanel-btn-normalize"]),i.status="normalized"),n.drop&&n.drop.dropZones){var c=n.drop.dropZones.map(function(e){return jsPanel.pOcontainer(e)}),j=[];c.forEach(function(e){e.length?e.forEach(function(e){j.push(e)}):j.push(e)}),j=j.filter(function(e,t,n){return n.indexOf(e)===t}),n.drop.dropZones=j}n.start.length&&jsPanel.processCallbacks(i,n.start,!1,{left:d,top:p,width:h,height:f},t)}var P,E,x,C,F,z,S,A,k,B;e=1;var T,L=t.touches?t.touches[0].clientX:t.clientX,R=t.touches?t.touches[0].clientY:t.clientY,W=window.getComputedStyle(i);if(g===document.body){var D=i.getBoundingClientRect();k=window.innerWidth-parseInt(y.borderLeftWidth,10)-parseInt(y.borderRightWidth,10)-(D.left+D.width),B=window.innerHeight-parseInt(y.borderTopWidth,10)-parseInt(y.borderBottomWidth,10)-(D.top+D.height)}else k=parseInt(y.width,10)-parseInt(y.borderLeftWidth,10)-parseInt(y.borderRightWidth,10)-(parseInt(W.left,10)+parseInt(W.width,10)),B=parseInt(y.height,10)-parseInt(y.borderTopWidth,10)-parseInt(y.borderBottomWidth,10)-(parseInt(W.top,10)+parseInt(W.height,10));P=parseFloat(W.left),x=parseFloat(W.top),F=k,S=B,n.snap&&("panel"===n.snap.trigger?(E=Math.pow(P,2),C=Math.pow(x,2),z=Math.pow(F,2),A=Math.pow(S,2)):"pointer"===n.snap.trigger&&("window"===i.options.container?(P=L,x=R,F=window.innerWidth-L,S=window.innerHeight-R,E=Math.pow(L,2),C=Math.pow(x,2),z=Math.pow(F,2),A=Math.pow(S,2)):(P=(T=i.overlaps(g,"paddingbox",t)).pointer.left,x=T.pointer.top,F=T.pointer.right,S=T.pointer.bottom,E=Math.pow(T.pointer.left,2),C=Math.pow(T.pointer.top,2),z=Math.pow(T.pointer.right,2),A=Math.pow(T.pointer.bottom,2))));var q=Math.sqrt(E+C),O=Math.sqrt(E+A),M=Math.sqrt(z+C),I=Math.sqrt(z+A),H=Math.abs(P-F)/2,N=Math.abs(x-S)/2,_=Math.sqrt(E+Math.pow(N,2)),X=Math.sqrt(C+Math.pow(H,2)),Y=Math.sqrt(z+Math.pow(N,2)),$=Math.sqrt(A+Math.pow(H,2));if(window.getSelection().removeAllRanges(),document.dispatchEvent(r),n.axis&&"x"!==n.axis||(i.style.left=d+(L-u)/v.x+w+"px"),n.axis&&"y"!==n.axis||(i.style.top=p+(R-m)/v.y+"px"),n.grid){var V=n.grid,Z=n.axis,U=V[0]*Math.round((d+(L-u))/V[0]),K=V[1]*Math.round((p+(R-m))/V[1]);Z&&"x"!==Z||(i.style.left="".concat(U,"px")),Z&&"y"!==Z||(i.style.top="".concat(K,"px"))}if(n.containment||0===n.containment){var G,J,Q=n.containment;if(i.options.container===document.body)G=window.innerWidth-parseFloat(W.width)-Q[1],J=window.innerHeight-parseFloat(W.height)-Q[2];else{var ee=parseFloat(y.borderLeftWidth)+parseFloat(y.borderRightWidth),te=parseFloat(y.borderTopWidth)+parseFloat(y.borderBottomWidth);G=b.width/v.x-parseFloat(W.width)-Q[1]-ee,J=b.height/v.y-parseFloat(W.height)-Q[2]-te}parseFloat(i.style.left)<=Q[3]&&(i.style.left=Q[3]+"px"),parseFloat(i.style.top)<=Q[0]&&(i.style.top=Q[0]+"px"),parseFloat(i.style.left)>=G&&(i.style.left=G+"px"),parseFloat(i.style.top)>=J&&(i.style.top=J+"px")}if(n.drag.length){var ne={left:P,top:x,right:F,bottom:S,width:parseFloat(W.width),height:parseFloat(W.height)};jsPanel.processCallbacks(i,n.drag,!1,ne,t)}if(n.snap){var oe=n.snap.sensitivity,ae=g===document.body?window.innerWidth/8:b.width/8,re=g===document.body?window.innerHeight/8:b.height/8;i.snappableTo=!1,jsPanel.removeSnapAreas(),q0&&T.pointer.top>0?(i.snappableTo="left-top",jsPanel.createSnapArea(i,"lt",oe)):(i.snappableTo=!1,jsPanel.removeSnapAreas())):(i.snappableTo="left-top",jsPanel.createSnapArea(i,"lt",oe))):O0&&T.pointer.bottom>0?(i.snappableTo="left-bottom",jsPanel.createSnapArea(i,"lb",oe)):(i.snappableTo=!1,jsPanel.removeSnapAreas())):(i.snappableTo="left-bottom",jsPanel.createSnapArea(i,"lb",oe))):M0&&T.pointer.top>0?(i.snappableTo="right-top",jsPanel.createSnapArea(i,"rt",oe)):(i.snappableTo=!1,jsPanel.removeSnapAreas())):(i.snappableTo="right-top",jsPanel.createSnapArea(i,"rt",oe))):I0&&T.pointer.bottom>0?(i.snappableTo="right-bottom",jsPanel.createSnapArea(i,"rb",oe)):(i.snappableTo=!1,jsPanel.removeSnapAreas())):(i.snappableTo="right-bottom",jsPanel.createSnapArea(i,"rb",oe))):x0?(i.snappableTo="center-top",jsPanel.createSnapArea(i,"ct",oe)):(i.snappableTo=!1,jsPanel.removeSnapAreas())):(i.snappableTo="center-top",jsPanel.createSnapArea(i,"ct",oe))):P0?(i.snappableTo="left-center",jsPanel.createSnapArea(i,"lc",oe)):(i.snappableTo=!1,jsPanel.removeSnapAreas())):(i.snappableTo="left-center",jsPanel.createSnapArea(i,"lc",oe))):F0?(i.snappableTo="right-center",jsPanel.createSnapArea(i,"rc",oe)):(i.snappableTo=!1,jsPanel.removeSnapAreas())):(i.snappableTo="right-center",jsPanel.createSnapArea(i,"rc",oe))):S0?(i.snappableTo="center-bottom",jsPanel.createSnapArea(i,"cb",oe)):(i.snappableTo=!1,jsPanel.removeSnapAreas())):(i.snappableTo="center-bottom",jsPanel.createSnapArea(i,"cb",oe)))}if(n.drop&&n.drop.dropZones){var ie=jsPanel.isIE?"msElementsFromPoint":"elementsFromPoint",le=document[ie](t.clientX,t.clientY);Array.isArray(le)||(le=Array.prototype.slice.call(le)),n.drop.dropZones.forEach(function(e){le.includes(e)&&(i.droppableTo=e)}),le.includes(i.droppableTo)||(i.droppableTo=!1)}},jsPanel.pointermove.forEach(function(e){document.addEventListener(e,t)}),window.addEventListener("mouseout",c,!1)}})}),jsPanel.pointerup.forEach(function(e){document.addEventListener(e,h),window.removeEventListener("mouseout",c)}),o.disable&&(l.style.pointerEvents="none")}),i},i.dragit=function(e){var n=Object.assign({},jsPanel.defaults.dragit,t.dragit),o=i.querySelectorAll(n.handles);return"disable"===e?o.forEach(function(e){e.style.pointerEvents="none"}):o.forEach(function(e){e.style.pointerEvents="auto"}),i},i.sizeit=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=new CustomEvent("jspanelresizestart",{detail:i.id}),n=new CustomEvent("jspanelresize",{detail:i.id}),o=new CustomEvent("jspanelresizestop",{detail:i.id});[t,n,o].forEach(function(e){e.panel=i});var a,r,l,s,c,d,p={};p.handles=e.handles||jsPanel.defaults.resizeit.handles,p.handles.split(",").forEach(function(e){var t=document.createElement("DIV");t.className="jsPanel-resizeit-handle jsPanel-resizeit-".concat(e.trim()),i.append(t)});var h=!!e.aspectRatio&&e.aspectRatio;function f(e){null===e.relatedTarget&&jsPanel.pointermove.forEach(function(e){document.removeEventListener(e,a,!1)})}function u(e){if(jsPanel.pointermove.forEach(function(e){document.removeEventListener(e,a,!1)}),e.target.classList&&e.target.classList.contains("jsPanel-resizeit-handle")){var t,n,l=e.target.className;if(l.match(/jsPanel-resizeit-nw|jsPanel-resizeit-w|jsPanel-resizeit-sw/i)&&(t=!0),l.match(/jsPanel-resizeit-nw|jsPanel-resizeit-n|jsPanel-resizeit-ne/i)&&(n=!0),p.grid&&Array.isArray(p.grid)){1===p.grid.length&&(p.grid[1]=p.grid[0]);var s=parseFloat(i.style.width),c=parseFloat(i.style.height),f=s%p.grid[0],m=c%p.grid[1],g=parseFloat(i.style.left),b=parseFloat(i.style.top),y=g%p.grid[0],v=b%p.grid[1];fd+5&&(w.style.transform="rotate(0deg)"),document.dispatchEvent(o),p.stop.length){var P=window.getComputedStyle(i),E={left:parseFloat(P.left),top:parseFloat(P.top),width:parseFloat(P.width),height:parseFloat(P.height)};jsPanel.processCallbacks(i,p.stop,!1,E,e)}}i.content.style.pointerEvents="inherit",document.querySelectorAll("iframe").forEach(function(e){e.style.pointerEvents="auto"}),p.aspectRatio=h,document.removeEventListener(e,u)}return i.querySelectorAll(".jsPanel-resizeit-handle").forEach(function(o){o.style.touchAction="none",jsPanel.pointerdown.forEach(function(h){o.addEventListener(h,function(o){if(o.preventDefault(),o.stopPropagation(),o.button&&o.button>0)return!1;var h=1;if(((p=Object.assign({},jsPanel.defaults.resizeit,e)).containment||0===p.containment)&&(p.containment=jsPanel.pOcontainment(p.containment)),p.aspectRatio&&!0===p.aspectRatio&&(p.aspectRatio="panel"),jsPanel.modifier){var u=jsPanel.modifier;u.altKey?p.aspectRatio="content":u.ctrlKey?p.aspectRatio="panel":u.shiftKey&&(p.aspectRatio=!1,h=2)}var m="function"==typeof p.maxWidth?p.maxWidth():p.maxWidth||1e4,g="function"==typeof p.maxHeight?p.maxHeight():p.maxHeight||1e4,b="function"==typeof p.minWidth?p.minWidth():p.minWidth,y="function"==typeof p.minHeight?p.minHeight():p.minHeight;i.content.style.pointerEvents="none",document.querySelectorAll("iframe").forEach(function(e){e.style.pointerEvents="none"});var v=i.parentElement,w=v.tagName.toLowerCase(),j=i.getBoundingClientRect(),P=v.getBoundingClientRect(),E=window.getComputedStyle(v,null),x=parseInt(E.borderLeftWidth,10),C=parseInt(E.borderTopWidth,10),F=E.getPropertyValue("position"),z=o.clientX||o.touches[0].clientX,S=o.clientY||o.touches[0].clientY,A=z/S,k=o.target.classList,B=i.getScaleFactor(),T=j.width/j.height,L=i.content.getBoundingClientRect(),R=L.width/L.height,W=i.header.getBoundingClientRect().height,D=i.footer.getBoundingClientRect().height||0,q=j.left,O=j.top,M=1e4,I=1e4,H=1e4,N=1e4;c=j.width,d=j.height,"body"!==w&&(q=j.left-P.left+v.scrollLeft,O=j.top-P.top+v.scrollTop),"body"===w&&p.containment?(M=document.documentElement.clientWidth-j.left,H=document.documentElement.clientHeight-j.top,I=j.width+j.left,N=j.height+j.top):p.containment&&("static"===F?(M=P.width-j.left+x,H=P.height+P.top-j.top+C,I=j.width+(j.left-P.left)-x,N=j.height+(j.top-P.top)-C):(M=v.clientWidth-(j.left-P.left)/B.x+x,H=v.clientHeight-(j.top-P.top)/B.y+C,I=(j.width+j.left-P.left)/B.x-x,N=i.clientHeight+(j.top-P.top)/B.y-C)),p.containment&&(I-=p.containment[3],N-=p.containment[0],M-=p.containment[1],H-=p.containment[2]);var _=window.getComputedStyle(i),X=parseFloat(_.width)-j.width,Y=parseFloat(_.height)-j.height,$=parseFloat(_.left)-j.left,V=parseFloat(_.top)-j.top;v!==document.body&&($+=P.left,V+=P.top);var Z=parseInt(_.borderTopWidth,10),U=parseInt(_.borderRightWidth,10),K=parseInt(_.borderBottomWidth,10),G=parseInt(_.borderLeftWidth,10);a=function(e){e.preventDefault(),r||(document.dispatchEvent(t),p.start.length&&jsPanel.processCallbacks(i,p.start,!1,{width:c,height:d,left:q,top:O},e),i.front(),j.height>d+5&&(i.status="normalized",i.setControls([".jsPanel-btn-normalize"]))),r=1,document.dispatchEvent(n);var o=e.touches?e.touches[0].clientX:e.clientX,a=e.touches?e.touches[0].clientY:e.clientY;k.contains("jsPanel-resizeit-e")?((l=c+(o-z)*h/B.x+X)>=M&&(l=M),l>=m&&(l=m),l<=b&&(l=b),i.style.width=l+"px",2===h&&(i.style.left=q-(o-z)+"px"),"content"===p.aspectRatio?(i.style.height=(l-U-G)/R+W+D+Z+K+"px",p.containment&&i.overlaps(v).bottom<=p.containment[2]&&(i.style.height=H+"px",i.style.width=H*R+"px")):"panel"===p.aspectRatio&&(i.style.height=l/T+"px",p.containment&&i.overlaps(v).bottom<=p.containment[2]&&(i.style.height=H+"px",i.style.width=H*T+"px"))):k.contains("jsPanel-resizeit-s")?((s=d+(a-S)*h/B.y+Y)>=H&&(s=H),s>=g&&(s=g),s<=y&&(s=y),i.style.height=s+"px",2===h&&(i.style.top=O-(a-S)+"px"),"content"===p.aspectRatio?(i.style.width=(s-W-D-Z-K)*R+Z+K+"px",p.containment&&i.overlaps(v).right<=p.containment[1]&&(i.style.width=M+"px",i.style.height=M/R+"px")):"panel"===p.aspectRatio&&(i.style.width=s*T+"px",p.containment&&i.overlaps(v).right<=p.containment[1]&&(i.style.width=M+"px",i.style.height=M/T+"px"))):k.contains("jsPanel-resizeit-w")?((l=c+(z-o)*h/B.x+X)<=m&&l>=b&&l<=I&&(i.style.left=q+(o-z)/B.x+$+"px"),l>=I&&(l=I),l>=m&&(l=m),l<=b&&(l=b),i.style.width=l+"px","content"===p.aspectRatio?(i.style.height=(l-U-G)/R+W+D+Z+K+"px",p.containment&&i.overlaps(v).bottom<=p.containment[2]&&(i.style.height=H+"px",i.style.width=H*R+"px")):"panel"===p.aspectRatio&&(i.style.height=l/T+"px",p.containment&&i.overlaps(v).bottom<=p.containment[2]&&(i.style.height=H+"px",i.style.width=H*T+"px"))):k.contains("jsPanel-resizeit-n")?((s=d+(S-a)*h/B.y+Y)<=g&&s>=y&&s<=N&&(i.style.top=O+(a-S)/B.y+V+"px"),s>=N&&(s=N),s>=g&&(s=g),s<=y&&(s=y),i.style.height=s+"px","content"===p.aspectRatio?(i.style.width=(s-W-D-Z-K)*R+Z+K+"px",p.containment&&i.overlaps(v).right<=p.containment[1]&&(i.style.width=M+"px",i.style.height=M/R+"px")):"panel"===p.aspectRatio&&(i.style.width=s*T+"px",p.containment&&i.overlaps(v).right<=p.containment[1]&&(i.style.width=M+"px",i.style.height=M/T+"px"))):k.contains("jsPanel-resizeit-se")?((l=c+(o-z)*h/B.x+X)>=M&&(l=M),l>=m&&(l=m),l<=b&&(l=b),i.style.width=l+"px",2===h&&(i.style.left=q-(o-z)+"px"),p.aspectRatio&&(i.style.height=l/T+"px"),(s=d+(a-S)*h/B.y+Y)>=H&&(s=H),s>=g&&(s=g),s<=y&&(s=y),i.style.height=s+"px",2===h&&(i.style.top=O-(a-S)+"px"),"content"===p.aspectRatio?(i.style.width=(s-W-D-Z-K)*R+Z+K+"px",p.containment&&i.overlaps(v).right<=p.containment[1]&&(i.style.width=M+"px",i.style.height=M/R+"px")):"panel"===p.aspectRatio&&(i.style.width=s*T+"px",p.containment&&i.overlaps(v).right<=p.containment[1]&&(i.style.width=M+"px",i.style.height=M/T+"px"))):k.contains("jsPanel-resizeit-sw")?((s=d+(a-S)*h/B.y+Y)>=H&&(s=H),s>=g&&(s=g),s<=y&&(s=y),i.style.height=s+"px",2===h&&(i.style.top=O-(a-S)+"px"),p.aspectRatio&&(i.style.width=s*T+"px"),(l=c+(z-o)*h/B.x+X)<=m&&l>=b&&l<=I&&(i.style.left=q+(o-z)/B.x+$+"px"),l>=I&&(l=I),l>=m&&(l=m),l<=b&&(l=b),i.style.width=l+"px","content"===p.aspectRatio?(i.style.height=(l-U-G)/R+W+D+Z+K+"px",p.containment&&i.overlaps(v).bottom<=p.containment[2]&&(i.style.height=H+"px",i.style.width=H*R+"px")):"panel"===p.aspectRatio&&(i.style.height=l/T+"px",p.containment&&i.overlaps(v).bottom<=p.containment[2]&&(i.style.height=H+"px",i.style.width=H*T+"px"))):k.contains("jsPanel-resizeit-ne")?((l=c+(o-z)*h/B.x+X)>=M&&(l=M),l>=m&&(l=m),l<=b&&(l=b),i.style.width=l+"px",2===h&&(i.style.left=q-(o-z)+"px"),p.aspectRatio&&(i.style.height=l/T+"px"),(s=d+(S-a)*h/B.y+Y)<=g&&s>=y&&s<=N&&(i.style.top=O+(a-S)/B.y+V+"px"),s>=N&&(s=N),s>=g&&(s=g),s<=y&&(s=y),i.style.height=s+"px","content"===p.aspectRatio?(i.style.width=(s-W-D-Z-K)*R+Z+K+"px",p.containment&&i.overlaps(v).right<=p.containment[1]&&(i.style.width=M+"px",i.style.height=M/R+"px")):"panel"===p.aspectRatio&&(i.style.width=s*T+"px",p.containment&&i.overlaps(v).right<=p.containment[1]&&(i.style.width=M+"px",i.style.height=M/T+"px"))):k.contains("jsPanel-resizeit-nw")&&(p.aspectRatio&&k.contains("jsPanel-resizeit-nw")&&(a=(o=a*A)/A),(l=c+(z-o)*h/B.x+X)<=m&&l>=b&&l<=I&&(i.style.left=q+(o-z)/B.x+$+"px"),l>=I&&(l=I),l>=m&&(l=m),l<=b&&(l=b),i.style.width=l+"px",p.aspectRatio&&(i.style.height=l/T+"px"),(s=d+(S-a)*h/B.y+Y)<=g&&s>=y&&s<=N&&(i.style.top=O+(a-S)/B.y+V+"px"),s>=N&&(s=N),s>=g&&(s=g),s<=y&&(s=y),i.style.height=s+"px","content"===p.aspectRatio?i.style.width=(s-W-D-Z-K)*R+Z+K+"px":"panel"===p.aspectRatio&&(i.style.width=s*T+"px")),window.getSelection().removeAllRanges();var f=window.getComputedStyle(i),u={left:parseFloat(f.left),top:parseFloat(f.top),right:parseFloat(f.right),bottom:parseFloat(f.bottom),width:parseFloat(f.width),height:parseFloat(f.height)};p.resize.length&&jsPanel.processCallbacks(i,p.resize,!1,u,e)},jsPanel.pointermove.forEach(function(e){document.addEventListener(e,a,!1)}),window.addEventListener("mouseout",f,!1)})}),jsPanel.pointerup.forEach(function(e){document.addEventListener(e,u),window.removeEventListener("mouseout",f)}),e.disable&&(o.style.pointerEvents="none")}),i},i.resizeit=function(e){var t=i.querySelectorAll(".jsPanel-resizeit-handle");return"disable"===e?t.forEach(function(e){e.style.pointerEvents="none"}):t.forEach(function(e){e.style.pointerEvents="auto"}),i},i.getScaleFactor=function(){var e=i.getBoundingClientRect();return{x:e.width/i.offsetWidth,y:e.height/i.offsetHeight}},i.calcSizeFactors=function(){var e=window.getComputedStyle(i);if("window"===t.container)i.hf=parseFloat(e.left)/(window.innerWidth-parseFloat(e.width)),i.vf=parseFloat(e.top)/(window.innerHeight-parseFloat(e.height));else if(i.parentElement){var n=i.parentElement.getBoundingClientRect();i.hf=parseFloat(e.left)/(n.width-parseFloat(e.width)),i.vf=parseFloat(e.top)/(n.height-parseFloat(e.height))}},i.saveCurrentDimensions=function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0],t=window.getComputedStyle(i);i.currentData.width=t.width,"normalized"===i.status&&(i.currentData.height=t.height),e&&(i.style.height=t.height)},i.saveCurrentPosition=function(){var e=window.getComputedStyle(i);i.currentData.left=e.left,i.currentData.top=e.top},i.reposition=function(){for(var e,n=t.position,o=!0,a=arguments.length,r=new Array(a),l=0;l0&&i.slaves.forEach(function(e){e.reposition()}),o&&i.saveCurrentPosition(),e&&e.call(i,i),i},i.repositionOnSnap=function(e){var n="0",o="0",a=jsPanel.pOcontainment(t.dragit.containment);if(t.dragit.snap.containment)switch(e){case"left-top":n=a[3],o=a[0];break;case"right-top":n=-a[1],o=a[0];break;case"right-bottom":n=-a[1],o=-a[2];break;case"left-bottom":n=a[3],o=-a[2];break;case"center-top":n=a[3]/2-a[1]/2,o=a[0];break;case"center-bottom":n=a[3]/2-a[1]/2,o=-a[2];break;case"left-center":n=a[3],o=a[0]/2-a[2]/2;break;case"right-center":n=-a[1],o=a[0]/2-a[2]/2}jsPanel.position(i,e),jsPanel.setStyle(i,{left:"calc(".concat(i.style.left," + ").concat(n,"px)"),top:"calc(".concat(i.style.top," + ").concat(o,"px)")})},i.overlaps=function(e,t,n){var o,a=i.getBoundingClientRect(),r=getComputedStyle(i.parentElement),l=i.getScaleFactor(),s={top:0,right:0,bottom:0,left:0},c=0,d=0,p=0,h=0;"window"!==i.options.container&&"paddingbox"===t&&(s.top=parseInt(r.borderTopWidth,10)*l.y,s.right=parseInt(r.borderRightWidth,10)*l.x,s.bottom=parseInt(r.borderBottomWidth,10)*l.y,s.left=parseInt(r.borderLeftWidth,10)*l.x),o="string"==typeof e?"window"===e?{left:0,top:0,right:window.innerWidth,bottom:window.innerHeight}:"parent"===e?i.parentElement.getBoundingClientRect():document.querySelector(e).getBoundingClientRect():e.getBoundingClientRect(),n&&(c=n.touches?n.touches[0].clientX:n.clientX,d=n.touches?n.touches[0].clientY:n.clientY,p=c-o.left,h=d-o.top);var f=a.lefto.left,u=a.topo.top;return{overlaps:f&&u,top:a.top-o.top-s.top,right:o.right-a.right-s.right,bottom:o.bottom-a.bottom-s.bottom,left:a.left-o.left-s.left,parentBorderWidth:s,panelRect:a,referenceRect:o,pointer:{clientX:c,clientY:d,left:p-s.left,top:h-s.top,right:o.width-p-s.right,bottom:o.height-h-s.bottom}}},i.setSize=function(){if(t.panelSize){var e=jsPanel.pOsize(i,t.panelSize);i.style.width=e.width,i.style.height=e.height}else if(t.contentSize){var n=jsPanel.pOsize(i,t.contentSize);i.content.style.width=n.width,i.content.style.height=n.height,i.style.width=n.width,i.content.style.width="100%"}return i},i.resize=function(){for(var e,t=window.getComputedStyle(i),n={width:t.width,height:t.height},o=!0,a=arguments.length,r=new Array(a),l=0;l0&&i.slaves.forEach(function(e){e.reposition()}),o&&i.saveCurrentDimensions(),i.status="normalized";var c=i.controlbar.querySelector(".jsPanel-btn-smallify");return c&&(c.style.transform="rotate(0deg)"),e&&e.call(i,i),i.calcSizeFactors(),i},i.windowResizeHandler=function(e){if(e.target===window){var n,o,a=i.status,r=t.onwindowresize;"maximized"===a&&r?i.maximize(!1,!0):i.snapped&&"minimized"!==a?i.snap(i.snapped,!0):"normalized"===a||"smallified"===a||"maximized"===a?"function"==typeof r?r.call(i,e,i):(n=(window.innerWidth-i.offsetWidth)*i.hf,i.style.left=n<=0?0:n+"px",o=(window.innerHeight-i.offsetHeight)*i.vf,i.style.top=o<=0?0:o+"px"):"smallifiedmax"===a&&r&&i.maximize(!1,!0).smallify(),i.slaves&&i.slaves.size>0&&i.slaves.forEach(function(e){e.reposition()})}},i.setControls=function(e,t){return i.header.querySelectorAll(".jsPanel-btn").forEach(function(e){var t=e.className.split("-"),n=t[t.length-1];"hidden"!==i.getAttribute("data-btn".concat(n))&&(e.style.display="block")}),e.forEach(function(e){var t=i.controlbar.querySelector(e);t&&(t.style.display="none")}),t&&t.call(i,i),i},i.setControlStatus=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"enable",n=arguments.length>2?arguments[2]:void 0,o=i.controlbar.querySelector(".jsPanel-btn-".concat(e));switch(t){case"disable":"removed"!==i.getAttribute("data-btn".concat(e))&&(i.setAttribute("data-btn".concat(e),"disabled"),o.style.pointerEvents="none",o.style.opacity=.4,o.style.cursor="default");break;case"hide":"removed"!==i.getAttribute("data-btn".concat(e))&&(i.setAttribute("data-btn".concat(e),"hidden"),o.style.display="none");break;case"show":"removed"!==i.getAttribute("data-btn".concat(e))&&(i.setAttribute("data-btn".concat(e),"enabled"),o.style.display="block",o.style.pointerEvents="auto",o.style.opacity=1,o.style.cursor="pointer");break;case"enable":"removed"!==i.getAttribute("data-btn".concat(e))&&("hidden"===i.getAttribute("data-btn".concat(e))&&(o.style.display="block"),i.setAttribute("data-btn".concat(e),"enabled"),o.style.pointerEvents="auto",o.style.opacity=1,o.style.cursor="pointer");break;case"remove":i.controlbar.removeChild(o),i.setAttribute("data-btn".concat(e),"removed")}return n&&n.call(i,i),i},i.setControlSize=function(e){var t=e.toLowerCase();i.controlbar.querySelectorAll(".jsPanel-btn").forEach(function(e){["jsPanel-btn-xl","jsPanel-btn-lg","jsPanel-btn-md","jsPanel-btn-sm","jsPanel-btn-xs"].forEach(function(t){e.classList.remove(t)}),e.classList.add("jsPanel-btn-".concat(t))}),"xl"===t?i.titlebar.style.fontSize="1.5rem":"lg"===t?i.titlebar.style.fontSize="1.25rem":"md"===t?i.titlebar.style.fontSize="1.05rem":"sm"===t?i.titlebar.style.fontSize=".9rem":"xs"===t&&(i.titlebar.style.fontSize=".8rem")},i.setHeaderControls=function(e){if(i.options.headerControls.add){var n=i.options.headerControls.add;Array.isArray(n)||(n=[n]),n.forEach(function(e){i.addControl(e)})}var o=[];i.controlbar.querySelectorAll(".jsPanel-btn").forEach(function(e){var t=e.className.match(/jsPanel-btn-[a-z0-9]{3,}/i)[0].substring(12);o.push(t)});var a=jsPanel.pOheaderControls(t.headerControls);return t.headerControls=a,o.forEach(function(e){a[e]&&i.setControlStatus(e,a[e])}),i.setControlSize(a.size),e&&e.call(i,i),i},i.setHeaderLogo=function(e,t){var n=[i.headerlogo],o=document.querySelector("#"+i.id+"-min");return o&&n.push(o.querySelector(".jsPanel-headerlogo")),"string"==typeof e?"<"!==e.substr(0,1)?n.forEach(function(t){jsPanel.emptyNode(t);var n=document.createElement("img");n.src=e,t.append(n)}):n.forEach(function(t){t.innerHTML=e}):n.forEach(function(t){jsPanel.emptyNode(t),t.append(e)}),i.headerlogo.childNodes.forEach(function(e){e.nodeName&&"IMG"===e.nodeName&&e.setAttribute("draggable","false")}),t&&t.call(i,i),i},i.setHeaderRemove=function(e){return i.removeChild(i.header),i.content.classList.add("jsPanel-content-noheader"),["close","maximize","normalize","minimize","smallify"].forEach(function(e){i.setAttribute("data-btn".concat(e),"removed")}),e&&e.call(i,i),i},i.setHeaderTitle=function(e,t){var n=[i.headertitle],o=document.querySelector("#"+i.id+"-min");return o&&n.push(o.querySelector(".jsPanel-title")),"string"==typeof e?n.forEach(function(t){t.innerHTML=e}):"function"==typeof e?n.forEach(function(t){jsPanel.emptyNode(t),t.innerHTML=e()}):n.forEach(function(t){jsPanel.emptyNode(t),t.append(e)}),t&&t.call(i,i),i},i.setIconfont=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:i,n=arguments.length>2?arguments[2]:void 0;if(e){var o,a;if("fa"===e||"far"===e||"fal"===e||"fas"===e||"fad"===e)o=["".concat(e," fa-window-close"),"".concat(e," fa-window-maximize"),"".concat(e," fa-window-restore"),"".concat(e," fa-window-minimize"),"".concat(e," fa-chevron-up")];else if("material-icons"===e)o=[e,e,e,e,e,e],a=["close","fullscreen","fullscreen_exit","call_received","expand_less"];else if(Array.isArray(e))o=["custom-control-icon ".concat(e[4]),"custom-control-icon ".concat(e[3]),"custom-control-icon ".concat(e[2]),"custom-control-icon ".concat(e[1]),"custom-control-icon ".concat(e[0])];else{if("bootstrap"!==e&&"glyphicon"!==e)return t;o=["glyphicon glyphicon-remove","glyphicon glyphicon-fullscreen","glyphicon glyphicon-resize-full","glyphicon glyphicon-minus","glyphicon glyphicon-chevron-up"]}t.querySelectorAll(".jsPanel-controlbar .jsPanel-btn").forEach(function(e){jsPanel.emptyNode(e).innerHTML=""}),Array.prototype.slice.call(t.querySelectorAll(".jsPanel-controlbar .jsPanel-btn > span")).reverse().forEach(function(t,n){t.className=o[n],"material-icons"===e&&(t.textContent=a[n])})}return n&&n.call(t,t),t},i.addToolbar=function(e,t,n){if("header"===e?e=i.headertoolbar:"footer"===e&&(e=i.footer),"string"==typeof t)e.innerHTML=t;else if(Array.isArray(t))t.forEach(function(t){"string"==typeof t?e.innerHTML+=t:e.append(t)});else if("function"==typeof t){var o=t.call(i,i);"string"==typeof o?e.innerHTML=o:e.append(o)}else e.append(t);return e.classList.add("active"),n&&n.call(i,i),i},i.addCloseControl=function(){var e=document.createElement("button"),t=i.content.style.color;return e.classList.add("jsPanel-addCloseCtrl"),e.innerHTML=jsPanel.icons.close,e.style.color=t,i.options.rtl&&e.classList.add("rtl"),i.appendChild(e),jsPanel.pointerup.forEach(function(t){e.addEventListener(t,function(e){if(e.preventDefault(),e.button&&e.button>0)return!1;i.close(null,!0)})}),jsPanel.pointerdown.forEach(function(t){e.addEventListener(t,function(e){e.preventDefault()})}),i},i.addControl=function(e){if(!e.html)return i;e.position||(e.position=1);var n=i.controlbar.querySelectorAll(".jsPanel-btn").length,o=document.createElement("button");o.innerHTML=e.html,o.className="jsPanel-btn jsPanel-btn-".concat(e.name," jsPanel-btn-").concat(t.headerControls.size),o.style.color=i.header.style.color,e.position>n?i.controlbar.append(o):i.controlbar.insertBefore(o,i.querySelector(".jsPanel-controlbar .jsPanel-btn:nth-child(".concat(e.position,")")));var a=e.ariaLabel||e.name;return a&&o.setAttribute("aria-label",a),jsPanel.pointerup.forEach(function(t){o.addEventListener(t,function(t){if(t.preventDefault(),t.button&&t.button>0)return!1;e.handler.call(i,i,o)})}),e.afterInsert&&e.afterInsert.call(o,o),i},i.setRtl=function(){[i.header,i.content,i.footer].forEach(function(e){e.dir="rtl",t.rtl.lang&&(e.lang=t.rtl.lang)})},i.id=t.id,i.classList.add("jsPanel-"+t.paneltype),"standard"===t.paneltype&&(i.style.zIndex=this.zi.next()),r.append(i),i.front(!1,!1),i.setTheme(t.theme),t.boxShadow&&i.classList.add("jsPanel-depth-".concat(t.boxShadow)),t.header){if(t.headerLogo&&i.setHeaderLogo(t.headerLogo),i.setIconfont(t.iconfont),i.setHeaderTitle(t.headerTitle),i.setHeaderControls(),jsPanel.isIE){var k=[i.headerbar,i.controlbar];switch(i.options.headerControls.size){case"md":k.forEach(function(e){e.style.height="34px"});break;case"xs":k.forEach(function(e){e.style.height="26px"});break;case"sm":k.forEach(function(e){e.style.height="30px"});break;case"lg":k.forEach(function(e){e.style.height="38px"});break;case"xl":k.forEach(function(e){e.style.height="42px"})}}if("auto-show-hide"===t.header){var B="jsPanel-depth-"+t.boxShadow;i.header.style.opacity=0,i.style.backgroundColor="transparent",this.remClass(i,B),this.setClass(i.content,B),i.header.addEventListener("mouseenter",function(){i.header.style.opacity=1,jsPanel.setClass(i,B),jsPanel.remClass(i.content,B)}),i.header.addEventListener("mouseleave",function(){i.header.style.opacity=0,jsPanel.remClass(i,B),jsPanel.setClass(i.content,B)})}}else i.setHeaderRemove(),t.addCloseControl&&i.addCloseControl();if(t.headerToolbar&&i.addToolbar(i.headertoolbar,t.headerToolbar),t.footerToolbar&&i.addToolbar(i.footer,t.footerToolbar),t.border&&i.setBorder(t.border),t.borderRadius&&i.setBorderRadius(t.borderRadius),t.content&&("function"==typeof t.content?t.content.call(i,i):"string"==typeof t.content?i.content.innerHTML=t.content:i.content.append(t.content)),t.contentAjax&&this.ajax(t.contentAjax,i),t.contentFetch&&this.fetch(t.contentFetch,i),t.contentOverflow){var T=t.contentOverflow.split(" ");1===T.length?i.content.style.overflow=T[0]:2===T.length&&(i.content.style.overflowX=T[0],i.content.style.overflowY=T[1])}if(t.autoclose){"number"==typeof t.autoclose?t.autoclose={time:t.autoclose+"ms"}:"string"==typeof t.autoclose&&(t.autoclose={time:t.autoclose});var L=Object.assign({},jsPanel.defaultAutocloseConfig,t.autoclose);L.time&&"number"==typeof L.time&&(L.time+="ms");var R=i.progressbar.querySelector("div");R.addEventListener("animationend",function(e){e.stopPropagation(),i.progressbar.classList.remove("active"),i.close()}),L.progressbar&&(i.progressbar.classList.add("active"),L.background?jsPanel.themes.indexOf(L.background)>-1?i.progressbar.classList.add(L.background+"-bg"):jsPanel.colorNames[L.background]?i.progressbar.style.background="#"+jsPanel.colorNames[L.background]:i.progressbar.style.background=L.background:i.progressbar.classList.add("success-bg")),R.style.animation="".concat(L.time," progressbar")}if(t.rtl&&i.setRtl(),i.setSize(),i.status="normalized",t.position?this.position(i,t.position):i.style.opacity=1,document.dispatchEvent(d),i.calcSizeFactors(),t.animateIn&&(i.addEventListener("animationend",function(){e.remClass(i,t.animateIn)}),this.setClass(i,t.animateIn)),t.syncMargins){var W=this.pOcontainment(t.maximizedMargin);t.dragit&&(t.dragit.containment=W,!0===t.dragit.snap?(t.dragit.snap=jsPanel.defaultSnapConfig,t.dragit.snap.containment=!0):t.dragit.snap&&(t.dragit.snap.containment=!0)),t.resizeit&&(t.resizeit.containment=W)}if(t.dragit?(["start","drag","stop"].forEach(function(e){t.dragit[e]?"function"==typeof t.dragit[e]&&(t.dragit[e]=[t.dragit[e]]):t.dragit[e]=[]}),i.drag(t.dragit),i.addEventListener("jspaneldragstop",function(e){e.panel===i&&i.calcSizeFactors()},!1)):i.titlebar.style.cursor="default",t.resizeit){["start","resize","stop"].forEach(function(e){t.resizeit[e]?"function"==typeof t.resizeit[e]&&(t.resizeit[e]=[t.resizeit[e]]):t.resizeit[e]=[]}),i.sizeit(t.resizeit);var D=void 0;i.addEventListener("jspanelresizestart",function(e){e.panel===i&&(D=i.status)},!1),i.addEventListener("jspanelresizestop",function(e){e.panel===i&&("smallified"===D||"smallifiedmax"===D||"maximized"===D)&&parseFloat(i.style.height)>parseFloat(window.getComputedStyle(i.header).height)&&(i.setControls([".jsPanel-btn-normalize"]),i.status="normalized",document.dispatchEvent(d),document.dispatchEvent(s),t.onstatuschange&&jsPanel.processCallbacks(i,t.onstatuschange,"every"),i.calcSizeFactors())},!1)}if(i.saveCurrentDimensions(!0),i.saveCurrentPosition(),t.setStatus&&("smallifiedmax"===t.setStatus?i.maximize().smallify():"smallified"===t.setStatus?i.smallify():i[t.setStatus.substr(0,t.setStatus.length-1)]()),this.pointerdown.forEach(function(e){i.addEventListener(e,function(e){e.target.closest(".jsPanel-btn-close")||e.target.closest(".jsPanel-btn-minimize")||"standard"!==t.paneltype||i.front()},!1)}),t.onwindowresize&&"window"===i.options.container&&window.addEventListener("resize",i.windowResizeHandler,!1),t.onparentresize){var q=t.onparentresize,O=i.isChildpanel();if(O){var M=O.content,I=[];i.parentResizeHandler=function(e){if(e.panel===O){I[0]=M.offsetWidth,I[1]=M.offsetHeight;var t,n,o=i.status;"maximized"===o&&q?i.maximize():i.snapped&&"minimized"!==o?i.snap(i.snapped,!0):"normalized"===o||"smallified"===o||"maximized"===o?"function"==typeof q?q.call(i,i,{width:I[0],height:I[1]}):(t=(I[0]-i.offsetWidth)*i.hf,i.style.left=t<=0?0:t+"px",n=(I[1]-i.offsetHeight)*i.vf,i.style.top=n<=0?0:n+"px"):"smallifiedmax"===o&&q&&i.maximize().smallify()}},document.addEventListener("jspanelresize",i.parentResizeHandler,!1)}}return this.globalCallbacks&&(Array.isArray(this.globalCallbacks)?this.globalCallbacks.forEach(function(e){e.call(i,i)}):this.globalCallbacks.call(i,i)),t.callback&&(Array.isArray(t.callback)?t.callback.forEach(function(e){e.call(i,i)}):t.callback.call(i,i)),n&&n.call(i,i),document.dispatchEvent(l),i}};"undefined"!=typeof module&&(module.exports=jsPanel); \ No newline at end of file diff --git a/src/main/resources/static/js/logout.js b/src/main/resources/static/js/logout.js new file mode 100644 index 0000000..e17748c --- /dev/null +++ b/src/main/resources/static/js/logout.js @@ -0,0 +1,6 @@ +function showUserPopup() { + var popup_logout = document.getElementById("logout"); + var popup_password_reset = document.getElementById("password_reset"); + popup_logout.classList.toggle("show"); + popup_password_reset.classList.toggle("show"); +} diff --git a/src/main/resources/static/js/zip-worker.js b/src/main/resources/static/js/zip-worker.js new file mode 100644 index 0000000..bf86f98 --- /dev/null +++ b/src/main/resources/static/js/zip-worker.js @@ -0,0 +1 @@ +!function(){"use strict";const t=[];for(let e=0;e<256;e++){let n=e;for(let t=0;t<8;t++)1&n?n=n>>>1^3988292384:n>>>=1;t[e]=n}class e{constructor(){this.crc=-1}append(e){let n=0|this.crc;for(let i=0,a=0|e.length;i>>8^t[255&(n^e[i])];this.crc=n}get(){return~this.crc}}const n="Invalid pasword",i=16,a="raw",r={name:"PBKDF2"},s={name:"HMAC"},o="SHA-1",l={name:"AES-CTR"},_=Object.assign({hash:s},r),d=Object.assign({iterations:1e3,hash:{name:o}},r),u=Object.assign({hash:o},s),f=Object.assign({length:i},l),c=["deriveBits"],h=["sign"],b=[8,12,16],x=[16,24,32],w=10,p=[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],g=crypto.subtle;class y{constructor(t,e,n){this.password=t,this.signed=e,this.strength=n-1,this.input=e&&new Uint8Array(0),this.pendingInput=new Uint8Array(0)}async append(t){const e=async(n=0)=>{if(n+i<=r.length-w){const t=r.subarray(n,n+i),s=await g.decrypt(Object.assign({counter:this.counter},f),this.keys.key,t);return k(this.counter),a.set(new Uint8Array(s),n),e(n+i)}return this.pendingInput=r.subarray(n),this.signed&&(this.input=A(this.input,t)),a};if(this.password){const e=t.subarray(0,b[this.strength]+2);await async function(t,e,i){await m(t,i,e.subarray(0,b[t.strength]),["decrypt"]);const a=e.subarray(b[t.strength]),r=t.keys.passwordVerification;if(r[0]!=a[0]||r[1]!=a[1])throw new Error(n)}(this,e,this.password),this.password=null,t=t.subarray(b[this.strength]+2)}let a=new Uint8Array(t.length-w-(t.length-w)%i),r=t;return this.pendingInput.length&&(r=A(this.pendingInput,t),a=U(a,r.length-w-(r.length-w)%i)),e()}async flush(){const t=this.pendingInput,e=this.keys,n=t.subarray(0,t.length-w),i=t.subarray(t.length-w);let a=new Uint8Array(0);if(n.length){const t=await g.decrypt(Object.assign({counter:this.counter},f),e.key,n);a=new Uint8Array(t)}let r=!0;if(this.signed){const t=await g.sign(s,e.authentication,this.input.subarray(0,this.input.length-w)),n=new Uint8Array(t);this.input=null;for(let t=0;t{if(r+i<=t.length){const s=t.subarray(r,r+i),o=await g.encrypt(Object.assign({counter:this.counter},f),this.keys.key,s);return k(this.counter),a.set(new Uint8Array(o),r+n.length),e(r+i)}return this.pendingInput=t.subarray(r),this.output=A(this.output,a),a};let n=new Uint8Array(0);this.password&&(n=await async function(t,e){const n=crypto.getRandomValues(new Uint8Array(b[t.strength]));return await m(t,e,n,["encrypt"]),A(n,t.keys.passwordVerification)}(this,this.password),this.password=null);let a=new Uint8Array(n.length+t.length-t.length%i);return a.set(n,0),this.pendingInput.length&&(t=A(this.pendingInput,t),a=U(a,t.length-t.length%i)),e()}async flush(){let t=new Uint8Array(0);if(this.pendingInput.length){const e=await g.encrypt(Object.assign({counter:this.counter},f),this.keys.key,this.pendingInput);t=new Uint8Array(e),this.output=A(this.output,t)}const e=await g.sign(s,this.keys.authentication,this.output.subarray(b[this.strength]+2));this.output=null;const n=new Uint8Array(e).subarray(0,w);return{data:A(t,n),signature:n}}}async function m(t,e,n,i){t.counter=new Uint8Array(p);const r=(new TextEncoder).encode(e),s=await g.importKey(a,r,_,!1,c),o=await g.deriveBits(Object.assign({salt:n},d),s,8*(2*x[t.strength]+2)),f=new Uint8Array(o);t.keys={key:await g.importKey(a,f.subarray(0,x[t.strength]),l,!0,i),authentication:await g.importKey(a,f.subarray(x[t.strength],2*x[t.strength]),u,!1,h),passwordVerification:f.subarray(2*x[t.strength])}}function k(t){for(let e=0;e<16;e++){if(255!=t[e]){t[e]++;break}t[e]=0}}function A(t,e){let n=t;return t.length+e.length&&(n=new Uint8Array(t.length+e.length),n.set(t,0),n.set(e,t.length)),n}function U(t,e){if(e&&e>t.length){const n=t;(t=new Uint8Array(e)).set(n,0)}return t}const I="deflate",E="inflate",S="Invalid signature";class C{constructor(t){this.signature=t.inputSignature,this.encrypted=Boolean(t.inputPassword),this.signed=t.inputSigned,this.compressed=t.inputCompressed,this.inflate=t.inputCompressed&&new t.codecConstructor,this.crc32=t.inputSigned&&new e,this.decrypt=this.encrypted&&new y(t.inputPassword,t.inputSigned,t.inputEncryptionStrength)}async append(t){return this.encrypted&&(t=await this.decrypt.append(t)),this.compressed&&t.length&&(t=await this.inflate.append(t)),!this.encrypted&&this.signed&&this.crc32.append(t),t}async flush(){let t,e=new Uint8Array(0);if(this.encrypted){const t=await this.decrypt.flush();if(!t.valid)throw new Error(S);e=t.data}else if(this.signed){const e=new DataView(new Uint8Array(4).buffer);if(t=this.crc32.get(),e.setUint32(0,t),this.signature!=e.getUint32(0,!1))throw new Error(S)}return this.compressed&&(e=await this.inflate.append(e)||new Uint8Array(0),await this.inflate.flush()),{data:e,signature:t}}}class M{constructor(t){this.encrypted=t.outputEncrypted,this.signed=t.outputSigned,this.compressed=t.outputCompressed,this.deflate=t.outputCompressed&&new t.codecConstructor({level:t.level||5}),this.crc32=t.outputSigned&&new e,this.encrypt=this.encrypted&&new v(t.outputPassword,t.outputEncryptionStrength)}async append(t){let e=t;return this.compressed&&t.length&&(e=await this.deflate.append(t)),this.encrypted?e=await this.encrypt.append(e):this.signed&&this.crc32.append(t),e}async flush(){let t,e=new Uint8Array(0);if(this.compressed&&(e=await this.deflate.flush()||new Uint8Array(0)),this.encrypted){e=await this.encrypt.append(e);const n=await this.encrypt.flush();t=n.signature;const i=new Uint8Array(e.length+n.data.length);i.set(e,0),i.set(n.data,e.length),e=i}else this.signed&&(t=this.crc32.get());return{data:e,signature:t}}}const D={init(t){t.scripts&&t.scripts.length&&importScripts.apply(void 0,t.scripts);const e=t.options;self.initCodec&&self.initCodec(),e.codecType.startsWith(I)?e.codecConstructor=self.Deflate:e.codecType.startsWith(E)&&(e.codecConstructor=self.Inflate),j=function(t){return t.codecType.startsWith(I)?new M(t):t.codecType.startsWith(E)?new C(t):void 0}(e)},append:async t=>({data:await j.append(t.data)}),flush:()=>j.flush()};let j;addEventListener("message",(async t=>{const e=t.data,n=e.type,i=D[n];if(i)try{const t=await i(e)||{};if(t.type=n,t.data)try{postMessage(t,[t.data.buffer])}catch(e){postMessage(t)}else postMessage(t)}catch(t){postMessage({type:n,error:{message:t.message,stack:t.stack}})}}));const O=256,P=256,T=-2,V=-5;function q(t){return t.map((([t,e])=>new Array(t).fill(e,0,t))).flat()}const z=[0,1,2,3].concat(...q([[2,4],[2,5],[4,6],[4,7],[8,8],[8,9],[16,10],[16,11],[32,12],[32,13],[64,14],[64,15],[2,0],[1,16],[1,17],[2,18],[2,19],[4,20],[4,21],[8,22],[8,23],[16,24],[16,25],[32,26],[32,27],[64,28],[64,29]]));function B(){const t=this;function e(t,e){let n=0;do{n|=1&t,t>>>=1,n<<=1}while(--e>0);return n>>>1}t.build_tree=function(n){const i=t.dyn_tree,a=t.stat_desc.static_tree,r=t.stat_desc.elems;let s,o,l,_=-1;for(n.heap_len=0,n.heap_max=573,s=0;s=1;s--)n.pqdownheap(i,s);l=r;do{s=n.heap[1],n.heap[1]=n.heap[n.heap_len--],n.pqdownheap(i,1),o=n.heap[1],n.heap[--n.heap_max]=s,n.heap[--n.heap_max]=o,i[2*l]=i[2*s]+i[2*o],n.depth[l]=Math.max(n.depth[s],n.depth[o])+1,i[2*s+1]=i[2*o+1]=l,n.heap[1]=l++,n.pqdownheap(i,1)}while(n.heap_len>=2);n.heap[--n.heap_max]=n.heap[1],function(e){const n=t.dyn_tree,i=t.stat_desc.static_tree,a=t.stat_desc.extra_bits,r=t.stat_desc.extra_base,s=t.stat_desc.max_length;let o,l,_,d,u,f,c=0;for(d=0;d<=15;d++)e.bl_count[d]=0;for(n[2*e.heap[e.heap_max]+1]=0,o=e.heap_max+1;o<573;o++)l=e.heap[o],d=n[2*n[2*l+1]+1]+1,d>s&&(d=s,c++),n[2*l+1]=d,l>t.max_code||(e.bl_count[d]++,u=0,l>=r&&(u=a[l-r]),f=n[2*l],e.opt_len+=f*(d+u),i&&(e.static_len+=f*(i[2*l+1]+u)));if(0!==c){do{for(d=s-1;0===e.bl_count[d];)d--;e.bl_count[d]--,e.bl_count[d+1]+=2,e.bl_count[s]--,c-=2}while(c>0);for(d=s;0!==d;d--)for(l=e.bl_count[d];0!==l;)_=e.heap[--o],_>t.max_code||(n[2*_+1]!=d&&(e.opt_len+=(d-n[2*_+1])*n[2*_],n[2*_+1]=d),l--)}}(n),function(t,n,i){const a=[];let r,s,o,l=0;for(r=1;r<=15;r++)a[r]=l=l+i[r-1]<<1;for(s=0;s<=n;s++)o=t[2*s+1],0!==o&&(t[2*s]=e(a[o]++,o))}(i,t.max_code,n.bl_count)}}function K(t,e,n,i,a){const r=this;r.static_tree=t,r.extra_bits=e,r.extra_base=n,r.elems=i,r.max_length=a}B._length_code=[0,1,2,3,4,5,6,7].concat(...q([[2,8],[2,9],[2,10],[2,11],[4,12],[4,13],[4,14],[4,15],[8,16],[8,17],[8,18],[8,19],[16,20],[16,21],[16,22],[16,23],[32,24],[32,25],[32,26],[31,27],[1,28]])),B.base_length=[0,1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,40,48,56,64,80,96,112,128,160,192,224,0],B.base_dist=[0,1,2,3,4,6,8,12,16,24,32,48,64,96,128,192,256,384,512,768,1024,1536,2048,3072,4096,6144,8192,12288,16384,24576],B.d_code=function(t){return t<256?z[t]:z[256+(t>>>7)]},B.extra_lbits=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0],B.extra_dbits=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],B.extra_blbits=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7],B.bl_order=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],K.static_ltree=[12,8,140,8,76,8,204,8,44,8,172,8,108,8,236,8,28,8,156,8,92,8,220,8,60,8,188,8,124,8,252,8,2,8,130,8,66,8,194,8,34,8,162,8,98,8,226,8,18,8,146,8,82,8,210,8,50,8,178,8,114,8,242,8,10,8,138,8,74,8,202,8,42,8,170,8,106,8,234,8,26,8,154,8,90,8,218,8,58,8,186,8,122,8,250,8,6,8,134,8,70,8,198,8,38,8,166,8,102,8,230,8,22,8,150,8,86,8,214,8,54,8,182,8,118,8,246,8,14,8,142,8,78,8,206,8,46,8,174,8,110,8,238,8,30,8,158,8,94,8,222,8,62,8,190,8,126,8,254,8,1,8,129,8,65,8,193,8,33,8,161,8,97,8,225,8,17,8,145,8,81,8,209,8,49,8,177,8,113,8,241,8,9,8,137,8,73,8,201,8,41,8,169,8,105,8,233,8,25,8,153,8,89,8,217,8,57,8,185,8,121,8,249,8,5,8,133,8,69,8,197,8,37,8,165,8,101,8,229,8,21,8,149,8,85,8,213,8,53,8,181,8,117,8,245,8,13,8,141,8,77,8,205,8,45,8,173,8,109,8,237,8,29,8,157,8,93,8,221,8,61,8,189,8,125,8,253,8,19,9,275,9,147,9,403,9,83,9,339,9,211,9,467,9,51,9,307,9,179,9,435,9,115,9,371,9,243,9,499,9,11,9,267,9,139,9,395,9,75,9,331,9,203,9,459,9,43,9,299,9,171,9,427,9,107,9,363,9,235,9,491,9,27,9,283,9,155,9,411,9,91,9,347,9,219,9,475,9,59,9,315,9,187,9,443,9,123,9,379,9,251,9,507,9,7,9,263,9,135,9,391,9,71,9,327,9,199,9,455,9,39,9,295,9,167,9,423,9,103,9,359,9,231,9,487,9,23,9,279,9,151,9,407,9,87,9,343,9,215,9,471,9,55,9,311,9,183,9,439,9,119,9,375,9,247,9,503,9,15,9,271,9,143,9,399,9,79,9,335,9,207,9,463,9,47,9,303,9,175,9,431,9,111,9,367,9,239,9,495,9,31,9,287,9,159,9,415,9,95,9,351,9,223,9,479,9,63,9,319,9,191,9,447,9,127,9,383,9,255,9,511,9,0,7,64,7,32,7,96,7,16,7,80,7,48,7,112,7,8,7,72,7,40,7,104,7,24,7,88,7,56,7,120,7,4,7,68,7,36,7,100,7,20,7,84,7,52,7,116,7,3,8,131,8,67,8,195,8,35,8,163,8,99,8,227,8],K.static_dtree=[0,5,16,5,8,5,24,5,4,5,20,5,12,5,28,5,2,5,18,5,10,5,26,5,6,5,22,5,14,5,30,5,1,5,17,5,9,5,25,5,5,5,21,5,13,5,29,5,3,5,19,5,11,5,27,5,7,5,23,5],K.static_l_desc=new K(K.static_ltree,B.extra_lbits,257,286,15),K.static_d_desc=new K(K.static_dtree,B.extra_dbits,0,30,15),K.static_bl_desc=new K(null,B.extra_blbits,0,19,7);function W(t,e,n,i,a){const r=this;r.good_length=t,r.max_lazy=e,r.nice_length=n,r.max_chain=i,r.func=a}const H=[new W(0,0,0,0,0),new W(4,4,8,4,1),new W(4,5,16,8,1),new W(4,6,32,32,1),new W(4,4,16,16,2),new W(8,16,32,32,2),new W(8,16,128,128,2),new W(8,32,128,256,2),new W(32,128,258,1024,2),new W(32,258,258,4096,2)],R=["need dictionary","stream end","","","stream error","data error","","buffer error","",""],F=113,L=666,G=258,J=262;function N(t,e,n,i){const a=t[2*e],r=t[2*n];return a>>8&255)}function lt(t,e){let n;const i=e;it>16-i?(n=t,nt|=n<>>16-it,it+=i-16):(nt|=t<=8&&(st(255&nt),nt>>>=8,it-=8)}function ft(e,n){let i,a,r;if(t.pending_buf[$+2*Z]=e>>>8&255,t.pending_buf[$+2*Z+1]=255&e,t.pending_buf[X+Z]=255&n,Z++,0===e?D[2*n]++:(tt++,e--,D[2*(B._length_code[n]+O+1)]++,j[2*B.d_code(e)]++),0==(8191&Z)&&E>2){for(i=8*Z,a=v-w,r=0;r<30;r++)i+=j[2*r]*(5+B.extra_dbits[r]);if(i>>>=3,tt8?ot(nt):it>0&&st(255&nt),nt=0,it=0}function bt(e,n,i){lt(0+(i?1:0),3),function(e,n,i){ht(),et=8,i&&(ot(n),ot(~n)),t.pending_buf.set(l.subarray(e,e+n),t.pending),t.pending+=n}(e,n,!0)}function xt(e,n,i){let a,r,s=0;E>0?(z.build_tree(t),W.build_tree(t),s=function(){let e;for(rt(D,z.max_code),rt(j,W.max_code),Q.build_tree(t),e=18;e>=3&&0===q[2*B.bl_order[e]+1];e--);return t.opt_len+=3*(e+1)+5+5+4,e}(),a=t.opt_len+3+7>>>3,r=t.static_len+3+7>>>3,r<=a&&(a=r)):a=r=n+5,n+4<=a&&-1!=e?bt(e,n,i):r==a?(lt(2+(i?1:0),3),ct(K.static_ltree,K.static_dtree)):(lt(4+(i?1:0),3),function(t,e,n){let i;for(lt(t-257,5),lt(e-1,5),lt(n-4,4),i=0;i=0?w:-1,v-w,t),w=v,e.flush_pending()}function pt(){let t,n,i,a;do{if(a=_-k-v,0===a&&0===v&&0===k)a=r;else if(-1==a)a--;else if(v>=r+r-J){l.set(l.subarray(r,r+r),0),m-=r,v-=r,w-=r,t=c,i=t;do{n=65535&u[--i],u[i]=n>=r?n-r:0}while(0!=--t);t=r,i=t;do{n=65535&d[--i],d[i]=n>=r?n-r:0}while(0!=--t);a+=r}if(0===e.avail_in)return;t=e.read_buf(l,v+k,a),k+=t,k>=3&&(f=255&l[v],f=(f<r-J?v-(r-J):0;let u=M;const f=o,c=v+G;let h=l[a+s-1],b=l[a+s];A>=C&&(i>>=2),u>k&&(u=k);do{if(e=t,l[e+s]==b&&l[e+s-1]==h&&l[e]==l[a]&&l[++e]==l[a+1]){a+=2,e++;do{}while(l[++a]==l[++e]&&l[++a]==l[++e]&&l[++a]==l[++e]&&l[++a]==l[++e]&&l[++a]==l[++e]&&l[++a]==l[++e]&&l[++a]==l[++e]&&l[++a]==l[++e]&&as){if(m=t,s=n,n>=u)break;h=l[a+s-1],b=l[a+s]}}}while((t=65535&d[t&f])>_&&0!=--i);return s<=k?s:k}function yt(e){return e.total_in=e.total_out=0,e.msg=null,t.pending=0,t.pending_out=0,n=F,a=0,z.dyn_tree=D,z.stat_desc=K.static_l_desc,W.dyn_tree=j,W.stat_desc=K.static_d_desc,Q.dyn_tree=q,Q.stat_desc=K.static_bl_desc,nt=0,it=0,et=8,at(),function(){_=2*r,u[c-1]=0;for(let t=0;t9||8!=_||a<9||a>15||n<0||n>9||w<0||w>2?T:(e.dstate=t,s=a,r=1<9||n<0||n>2?T:(H[E].func!=H[e].func&&0!==t.total_in&&(i=t.deflate(1)),E!=e&&(E=e,I=H[E].max_lazy,C=H[E].good_length,M=H[E].nice_length,U=H[E].max_chain),S=n,i)},t.deflateSetDictionary=function(t,e,i){let a,s=i,_=0;if(!e||42!=n)return T;if(s<3)return 0;for(s>r-J&&(s=r-J,_=i-s),l.set(e.subarray(_,_+s),0),v=s,w=s,f=255&l[0],f=(f<4||h<0)return T;if(!_.next_out||!_.next_in&&0!==_.avail_in||n==L&&4!=h)return _.msg=R[4],T;if(0===_.avail_out)return _.msg=R[7],V;var O;if(e=_,D=a,a=h,42==n&&(C=8+(s-8<<4)<<8,M=(E-1&255)>>1,M>3&&(M=3),C|=M<<6,0!==v&&(C|=32),C+=31-C%31,n=F,st((O=C)>>8&255),st(255&O)),0!==t.pending){if(e.flush_pending(),0===e.avail_out)return a=-1,0}else if(0===e.avail_in&&h<=D&&4!=h)return e.msg=R[7],V;if(n==L&&0!==e.avail_in)return _.msg=R[7],V;if(0!==e.avail_in||0!==k||0!=h&&n!=L){switch(j=-1,H[E].func){case 0:j=function(t){let n,a=65535;for(a>i-5&&(a=i-5);;){if(k<=1){if(pt(),0===k&&0==t)return 0;if(0===k)break}if(v+=k,k=0,n=w+a,(0===v||v>=n)&&(k=v-n,v=n,wt(!1),0===e.avail_out))return 0;if(v-w>=r-J&&(wt(!1),0===e.avail_out))return 0}return wt(4==t),0===e.avail_out?4==t?2:0:4==t?3:1}(h);break;case 1:j=function(t){let n,i=0;for(;;){if(k=3&&(f=(f<=3)if(n=ft(v-m,p-3),k-=p,p<=I&&k>=3){p--;do{v++,f=(f<=3&&(f=(f<4096)&&(p=2)),A>=3&&p<=A){i=v+k-3,n=ft(v-1-g,A-3),k-=A-1,A-=2;do{++v<=i&&(f=(f<0&&e.next_in_index!=o&&(a(e.next_in_index),o=e.next_in_index)}while(e.avail_in>0||0===e.avail_out);return s=new Uint8Array(_),d.forEach((function(t){s.set(t,l),l+=t.length})),s}},this.flush=function(){let t,a,r=0,s=0;const o=[];do{if(e.next_out_index=0,e.avail_out=n,t=e.deflate(4),1!=t&&0!=t)throw new Error("deflating: "+e.msg);n-e.avail_out>0&&o.push(new Uint8Array(i.subarray(0,e.next_out_index))),s+=e.next_out_index}while(e.avail_in>0||0===e.avail_out);return e.deflateEnd(),a=new Uint8Array(s),o.forEach((function(t){a.set(t,r),r+=t.length})),a}}X.prototype={deflateInit:function(t,e){const n=this;return n.dstate=new Q,e||(e=15),n.dstate.deflateInit(n,t,e)},deflate:function(t){const e=this;return e.dstate?e.dstate.deflate(e,t):T},deflateEnd:function(){const t=this;if(!t.dstate)return T;const e=t.dstate.deflateEnd();return t.dstate=null,e},deflateParams:function(t,e){const n=this;return n.dstate?n.dstate.deflateParams(n,t,e):T},deflateSetDictionary:function(t,e){const n=this;return n.dstate?n.dstate.deflateSetDictionary(n,t,e):T},read_buf:function(t,e,n){const i=this;let a=i.avail_in;return a>n&&(a=n),0===a?0:(i.avail_in-=a,t.set(i.next_in.subarray(i.next_in_index,i.next_in_index+a),e),i.next_in_index+=a,i.total_in+=a,a)},flush_pending:function(){const t=this;let e=t.dstate.pending;e>t.avail_out&&(e=t.avail_out),0!==e&&(t.next_out.set(t.dstate.pending_buf.subarray(t.dstate.pending_out,t.dstate.pending_out+e),t.next_out_index),t.next_out_index+=e,t.dstate.pending_out+=e,t.total_out+=e,t.avail_out-=e,t.dstate.pending-=e,0===t.dstate.pending&&(t.dstate.pending_out=0))}};const Z=-2,$=-3,tt=-5,et=[0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535],nt=[96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,192,80,7,10,0,8,96,0,8,32,0,9,160,0,8,0,0,8,128,0,8,64,0,9,224,80,7,6,0,8,88,0,8,24,0,9,144,83,7,59,0,8,120,0,8,56,0,9,208,81,7,17,0,8,104,0,8,40,0,9,176,0,8,8,0,8,136,0,8,72,0,9,240,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,200,81,7,13,0,8,100,0,8,36,0,9,168,0,8,4,0,8,132,0,8,68,0,9,232,80,7,8,0,8,92,0,8,28,0,9,152,84,7,83,0,8,124,0,8,60,0,9,216,82,7,23,0,8,108,0,8,44,0,9,184,0,8,12,0,8,140,0,8,76,0,9,248,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,196,81,7,11,0,8,98,0,8,34,0,9,164,0,8,2,0,8,130,0,8,66,0,9,228,80,7,7,0,8,90,0,8,26,0,9,148,84,7,67,0,8,122,0,8,58,0,9,212,82,7,19,0,8,106,0,8,42,0,9,180,0,8,10,0,8,138,0,8,74,0,9,244,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,204,81,7,15,0,8,102,0,8,38,0,9,172,0,8,6,0,8,134,0,8,70,0,9,236,80,7,9,0,8,94,0,8,30,0,9,156,84,7,99,0,8,126,0,8,62,0,9,220,82,7,27,0,8,110,0,8,46,0,9,188,0,8,14,0,8,142,0,8,78,0,9,252,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,194,80,7,10,0,8,97,0,8,33,0,9,162,0,8,1,0,8,129,0,8,65,0,9,226,80,7,6,0,8,89,0,8,25,0,9,146,83,7,59,0,8,121,0,8,57,0,9,210,81,7,17,0,8,105,0,8,41,0,9,178,0,8,9,0,8,137,0,8,73,0,9,242,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,202,81,7,13,0,8,101,0,8,37,0,9,170,0,8,5,0,8,133,0,8,69,0,9,234,80,7,8,0,8,93,0,8,29,0,9,154,84,7,83,0,8,125,0,8,61,0,9,218,82,7,23,0,8,109,0,8,45,0,9,186,0,8,13,0,8,141,0,8,77,0,9,250,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,198,81,7,11,0,8,99,0,8,35,0,9,166,0,8,3,0,8,131,0,8,67,0,9,230,80,7,7,0,8,91,0,8,27,0,9,150,84,7,67,0,8,123,0,8,59,0,9,214,82,7,19,0,8,107,0,8,43,0,9,182,0,8,11,0,8,139,0,8,75,0,9,246,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,206,81,7,15,0,8,103,0,8,39,0,9,174,0,8,7,0,8,135,0,8,71,0,9,238,80,7,9,0,8,95,0,8,31,0,9,158,84,7,99,0,8,127,0,8,63,0,9,222,82,7,27,0,8,111,0,8,47,0,9,190,0,8,15,0,8,143,0,8,79,0,9,254,96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,193,80,7,10,0,8,96,0,8,32,0,9,161,0,8,0,0,8,128,0,8,64,0,9,225,80,7,6,0,8,88,0,8,24,0,9,145,83,7,59,0,8,120,0,8,56,0,9,209,81,7,17,0,8,104,0,8,40,0,9,177,0,8,8,0,8,136,0,8,72,0,9,241,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,201,81,7,13,0,8,100,0,8,36,0,9,169,0,8,4,0,8,132,0,8,68,0,9,233,80,7,8,0,8,92,0,8,28,0,9,153,84,7,83,0,8,124,0,8,60,0,9,217,82,7,23,0,8,108,0,8,44,0,9,185,0,8,12,0,8,140,0,8,76,0,9,249,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,197,81,7,11,0,8,98,0,8,34,0,9,165,0,8,2,0,8,130,0,8,66,0,9,229,80,7,7,0,8,90,0,8,26,0,9,149,84,7,67,0,8,122,0,8,58,0,9,213,82,7,19,0,8,106,0,8,42,0,9,181,0,8,10,0,8,138,0,8,74,0,9,245,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,205,81,7,15,0,8,102,0,8,38,0,9,173,0,8,6,0,8,134,0,8,70,0,9,237,80,7,9,0,8,94,0,8,30,0,9,157,84,7,99,0,8,126,0,8,62,0,9,221,82,7,27,0,8,110,0,8,46,0,9,189,0,8,14,0,8,142,0,8,78,0,9,253,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,195,80,7,10,0,8,97,0,8,33,0,9,163,0,8,1,0,8,129,0,8,65,0,9,227,80,7,6,0,8,89,0,8,25,0,9,147,83,7,59,0,8,121,0,8,57,0,9,211,81,7,17,0,8,105,0,8,41,0,9,179,0,8,9,0,8,137,0,8,73,0,9,243,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,203,81,7,13,0,8,101,0,8,37,0,9,171,0,8,5,0,8,133,0,8,69,0,9,235,80,7,8,0,8,93,0,8,29,0,9,155,84,7,83,0,8,125,0,8,61,0,9,219,82,7,23,0,8,109,0,8,45,0,9,187,0,8,13,0,8,141,0,8,77,0,9,251,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,199,81,7,11,0,8,99,0,8,35,0,9,167,0,8,3,0,8,131,0,8,67,0,9,231,80,7,7,0,8,91,0,8,27,0,9,151,84,7,67,0,8,123,0,8,59,0,9,215,82,7,19,0,8,107,0,8,43,0,9,183,0,8,11,0,8,139,0,8,75,0,9,247,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,207,81,7,15,0,8,103,0,8,39,0,9,175,0,8,7,0,8,135,0,8,71,0,9,239,80,7,9,0,8,95,0,8,31,0,9,159,84,7,99,0,8,127,0,8,63,0,9,223,82,7,27,0,8,111,0,8,47,0,9,191,0,8,15,0,8,143,0,8,79,0,9,255],it=[80,5,1,87,5,257,83,5,17,91,5,4097,81,5,5,89,5,1025,85,5,65,93,5,16385,80,5,3,88,5,513,84,5,33,92,5,8193,82,5,9,90,5,2049,86,5,129,192,5,24577,80,5,2,87,5,385,83,5,25,91,5,6145,81,5,7,89,5,1537,85,5,97,93,5,24577,80,5,4,88,5,769,84,5,49,92,5,12289,82,5,13,90,5,3073,86,5,193,192,5,24577],at=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,0,0],rt=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,112,112],st=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],ot=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],lt=15;function _t(){let t,e,n,i,a,r;function s(t,e,s,o,l,_,d,u,f,c,h){let b,x,w,p,g,y,v,m,k,A,U,I,E,S,C;A=0,g=s;do{n[t[e+A]]++,A++,g--}while(0!==g);if(n[0]==s)return d[0]=-1,u[0]=0,0;for(m=u[0],y=1;y<=lt&&0===n[y];y++);for(v=y,mg&&(m=g),u[0]=m,S=1<I+m;){if(p++,I+=m,C=w-I,C=C>m?m:C,(x=1<<(y=v-I))>b+1&&(x-=b+1,E=v,y1440)return $;a[p]=U=c[0],c[0]+=C,0!==p?(r[p]=g,i[0]=y,i[1]=m,y=g>>>I-m,i[2]=U-a[p-1]-y,f.set(i,3*(a[p-1]+y))):d[0]=U}for(i[1]=v-I,A>=s?i[0]=192:h[A]>>I;y>>=1)g^=y;for(g^=y,k=(1<257?(c==$?f.msg="oversubscribed distance tree":c==tt?(f.msg="incomplete distance tree",c=$):-4!=c&&(f.msg="empty distance tree with lengths",c=$),c):0)}}_t.inflate_trees_fixed=function(t,e,n,i){return t[0]=9,e[0]=5,n[0]=nt,i[0]=it,0};function dt(){const t=this;let e,n,i,a,r=0,s=0,o=0,l=0,_=0,d=0,u=0,f=0,c=0,h=0;function b(t,e,n,i,a,r,s,o){let l,_,d,u,f,c,h,b,x,w,p,g,y,v,m,k;h=o.next_in_index,b=o.avail_in,f=s.bitb,c=s.bitk,x=s.write,w=x>=_[k+1],c-=_[k+1],0!=(16&u)){for(u&=15,y=_[k+2]+(f&et[u]),f>>=u,c-=u;c<15;)b--,f|=(255&o.read_byte(h++))<>=_[k+1],c-=_[k+1],0!=(16&u)){for(u&=15;c>=u,c-=u,w-=y,x>=v)m=x-v,x-m>0&&2>x-m?(s.window[x++]=s.window[m++],s.window[x++]=s.window[m++],y-=2):(s.window.set(s.window.subarray(m,m+2),x),x+=2,m+=2,y-=2);else{m=x-v;do{m+=s.end}while(m<0);if(u=s.end-m,y>u){if(y-=u,x-m>0&&u>x-m)do{s.window[x++]=s.window[m++]}while(0!=--u);else s.window.set(s.window.subarray(m,m+u),x),x+=u,m+=u,u=0;m=0}}if(x-m>0&&y>x-m)do{s.window[x++]=s.window[m++]}while(0!=--y);else s.window.set(s.window.subarray(m,m+y),x),x+=y,m+=y,y=0;break}if(0!=(64&u))return o.msg="invalid distance code",y=o.avail_in-b,y=c>>3>3:y,b+=y,h-=y,c-=y<<3,s.bitb=f,s.bitk=c,o.avail_in=b,o.total_in+=h-o.next_in_index,o.next_in_index=h,s.write=x,$;l+=_[k+2],l+=f&et[u],k=3*(d+l),u=_[k]}break}if(0!=(64&u))return 0!=(32&u)?(y=o.avail_in-b,y=c>>3>3:y,b+=y,h-=y,c-=y<<3,s.bitb=f,s.bitk=c,o.avail_in=b,o.total_in+=h-o.next_in_index,o.next_in_index=h,s.write=x,1):(o.msg="invalid literal/length code",y=o.avail_in-b,y=c>>3>3:y,b+=y,h-=y,c-=y<<3,s.bitb=f,s.bitk=c,o.avail_in=b,o.total_in+=h-o.next_in_index,o.next_in_index=h,s.write=x,$);if(l+=_[k+2],l+=f&et[u],k=3*(d+l),0===(u=_[k])){f>>=_[k+1],c-=_[k+1],s.window[x++]=_[k+2],w--;break}}else f>>=_[k+1],c-=_[k+1],s.window[x++]=_[k+2],w--}while(w>=258&&b>=10);return y=o.avail_in-b,y=c>>3>3:y,b+=y,h-=y,c-=y<<3,s.bitb=f,s.bitk=c,o.avail_in=b,o.total_in+=h-o.next_in_index,o.next_in_index=h,s.write=x,0}t.init=function(t,r,s,o,l,_){e=0,u=t,f=r,i=s,c=o,a=l,h=_,n=null},t.proc=function(t,x,w){let p,g,y,v,m,k,A,U=0,I=0,E=0;for(E=x.next_in_index,v=x.avail_in,U=t.bitb,I=t.bitk,m=t.write,k=m=258&&v>=10&&(t.bitb=U,t.bitk=I,x.avail_in=v,x.total_in+=E-x.next_in_index,x.next_in_index=E,t.write=m,w=b(u,f,i,c,a,h,t,x),E=x.next_in_index,v=x.avail_in,U=t.bitb,I=t.bitk,m=t.write,k=m>>=n[g+1],I-=n[g+1],y=n[g],0===y){l=n[g+2],e=6;break}if(0!=(16&y)){_=15&y,r=n[g+2],e=2;break}if(0==(64&y)){o=y,s=g/3+n[g+2];break}if(0!=(32&y)){e=7;break}return e=9,x.msg="invalid literal/length code",w=$,t.bitb=U,t.bitk=I,x.avail_in=v,x.total_in+=E-x.next_in_index,x.next_in_index=E,t.write=m,t.inflate_flush(x,w);case 2:for(p=_;I>=p,I-=p,o=f,n=a,s=h,e=3;case 3:for(p=o;I>=n[g+1],I-=n[g+1],y=n[g],0!=(16&y)){_=15&y,d=n[g+2],e=4;break}if(0==(64&y)){o=y,s=g/3+n[g+2];break}return e=9,x.msg="invalid distance code",w=$,t.bitb=U,t.bitk=I,x.avail_in=v,x.total_in+=E-x.next_in_index,x.next_in_index=E,t.write=m,t.inflate_flush(x,w);case 4:for(p=_;I>=p,I-=p,e=5;case 5:for(A=m-d;A<0;)A+=t.end;for(;0!==r;){if(0===k&&(m==t.end&&0!==t.read&&(m=0,k=m7&&(I-=8,v++,E--),t.write=m,w=t.inflate_flush(x,w),m=t.write,k=mt.avail_out&&(i=t.avail_out),0!==i&&e==tt&&(e=0),t.avail_out-=i,t.total_out+=i,t.next_out.set(n.window.subarray(r,r+i),a),a+=i,r+=i,r==n.end&&(r=0,n.write==n.end&&(n.write=0),i=n.write-r,i>t.avail_out&&(i=t.avail_out),0!==i&&e==tt&&(e=0),t.avail_out-=i,t.total_out+=i,t.next_out.set(n.window.subarray(r,r+i),a),a+=i,r+=i),t.next_out_index=a,n.read=r,e},n.proc=function(t,e){let h,b,x,w,p,g,y,v;for(w=t.next_in_index,p=t.avail_in,b=n.bitb,x=n.bitk,g=n.write,y=g>>1){case 0:b>>>=3,x-=3,h=7&x,b>>>=h,x-=h,a=1;break;case 1:m=[],k=[],A=[[]],U=[[]],_t.inflate_trees_fixed(m,k,A,U),d.init(m[0],k[0],A[0],0,U[0],0),b>>>=3,x-=3,a=6;break;case 2:b>>>=3,x-=3,a=3;break;case 3:return b>>>=3,x-=3,a=9,t.msg="invalid block type",e=$,n.bitb=b,n.bitk=x,t.avail_in=p,t.total_in+=w-t.next_in_index,t.next_in_index=w,n.write=g,n.inflate_flush(t,e)}break;case 1:for(;x<32;){if(0===p)return n.bitb=b,n.bitk=x,t.avail_in=p,t.total_in+=w-t.next_in_index,t.next_in_index=w,n.write=g,n.inflate_flush(t,e);e=0,p--,b|=(255&t.read_byte(w++))<>>16&65535)!=(65535&b))return a=9,t.msg="invalid stored block lengths",e=$,n.bitb=b,n.bitk=x,t.avail_in=p,t.total_in+=w-t.next_in_index,t.next_in_index=w,n.write=g,n.inflate_flush(t,e);r=65535&b,b=x=0,a=0!==r?2:0!==u?7:0;break;case 2:if(0===p)return n.bitb=b,n.bitk=x,t.avail_in=p,t.total_in+=w-t.next_in_index,t.next_in_index=w,n.write=g,n.inflate_flush(t,e);if(0===y&&(g==n.end&&0!==n.read&&(g=0,y=gp&&(h=p),h>y&&(h=y),n.window.set(t.read_buf(w,h),g),w+=h,p-=h,g+=h,y-=h,0!=(r-=h))break;a=0!==u?7:0;break;case 3:for(;x<14;){if(0===p)return n.bitb=b,n.bitk=x,t.avail_in=p,t.total_in+=w-t.next_in_index,t.next_in_index=w,n.write=g,n.inflate_flush(t,e);e=0,p--,b|=(255&t.read_byte(w++))<29||(h>>5&31)>29)return a=9,t.msg="too many length or distance symbols",e=$,n.bitb=b,n.bitk=x,t.avail_in=p,t.total_in+=w-t.next_in_index,t.next_in_index=w,n.write=g,n.inflate_flush(t,e);if(h=258+(31&h)+(h>>5&31),!i||i.length>>=14,x-=14,o=0,a=4;case 4:for(;o<4+(s>>>10);){for(;x<3;){if(0===p)return n.bitb=b,n.bitk=x,t.avail_in=p,t.total_in+=w-t.next_in_index,t.next_in_index=w,n.write=g,n.inflate_flush(t,e);e=0,p--,b|=(255&t.read_byte(w++))<>>=3,x-=3}for(;o<19;)i[ut[o++]]=0;if(l[0]=7,h=c.inflate_trees_bits(i,l,_,f,t),0!=h)return(e=h)==$&&(i=null,a=9),n.bitb=b,n.bitk=x,t.avail_in=p,t.total_in+=w-t.next_in_index,t.next_in_index=w,n.write=g,n.inflate_flush(t,e);o=0,a=5;case 5:for(;h=s,!(o>=258+(31&h)+(h>>5&31));){let r,d;for(h=l[0];x>>=h,x-=h,i[o++]=d;else{for(v=18==d?7:d-14,r=18==d?11:3;x>>=h,x-=h,r+=b&et[v],b>>>=v,x-=v,v=o,h=s,v+r>258+(31&h)+(h>>5&31)||16==d&&v<1)return i=null,a=9,t.msg="invalid bit length repeat",e=$,n.bitb=b,n.bitk=x,t.avail_in=p,t.total_in+=w-t.next_in_index,t.next_in_index=w,n.write=g,n.inflate_flush(t,e);d=16==d?i[v-1]:0;do{i[v++]=d}while(0!=--r);o=v}}if(_[0]=-1,I=[],E=[],S=[],C=[],I[0]=9,E[0]=6,h=s,h=c.inflate_trees_dynamic(257+(31&h),1+(h>>5&31),i,I,E,S,C,f,t),0!=h)return h==$&&(i=null,a=9),e=h,n.bitb=b,n.bitk=x,t.avail_in=p,t.total_in+=w-t.next_in_index,t.next_in_index=w,n.write=g,n.inflate_flush(t,e);d.init(I[0],E[0],f,S[0],f,C[0]),a=6;case 6:if(n.bitb=b,n.bitk=x,t.avail_in=p,t.total_in+=w-t.next_in_index,t.next_in_index=w,n.write=g,1!=(e=d.proc(n,t,e)))return n.inflate_flush(t,e);if(e=0,d.free(t),w=t.next_in_index,p=t.avail_in,b=n.bitb,x=n.bitk,g=n.write,y=g15?(t.inflateEnd(n),Z):(t.wbits=i,n.istate.blocks=new ft(n,1<>4)>a.wbits){a.mode=ct,t.msg="invalid window size",a.marker=5;break}a.mode=1;case 1:if(0===t.avail_in)return n;if(n=e,t.avail_in--,t.total_in++,i=255&t.read_byte(t.next_in_index++),((a.method<<8)+i)%31!=0){a.mode=ct,t.msg="incorrect header check",a.marker=5;break}if(0==(32&i)){a.mode=7;break}a.mode=2;case 2:if(0===t.avail_in)return n;n=e,t.avail_in--,t.total_in++,a.need=(255&t.read_byte(t.next_in_index++))<<24&4278190080,a.mode=3;case 3:if(0===t.avail_in)return n;n=e,t.avail_in--,t.total_in++,a.need+=(255&t.read_byte(t.next_in_index++))<<16&16711680,a.mode=4;case 4:if(0===t.avail_in)return n;n=e,t.avail_in--,t.total_in++,a.need+=(255&t.read_byte(t.next_in_index++))<<8&65280,a.mode=5;case 5:return 0===t.avail_in?n:(n=e,t.avail_in--,t.total_in++,a.need+=255&t.read_byte(t.next_in_index++),a.mode=6,2);case 6:return a.mode=ct,t.msg="need dictionary",a.marker=0,Z;case 7:if(n=a.blocks.proc(t,n),n==$){a.mode=ct,a.marker=0;break}if(0==n&&(n=e),1!=n)return n;n=e,a.blocks.reset(t,a.was),a.mode=12;case 12:return 1;case ct:return $;default:return Z}},t.inflateSetDictionary=function(t,e,n){let i=0,a=n;if(!t||!t.istate||6!=t.istate.mode)return Z;const r=t.istate;return a>=1<0&&t.next_in_index!=l&&(a(t.next_in_index),l=t.next_in_index)}while(t.avail_in>0||0===t.avail_out);return o=new Uint8Array(d),r.forEach((function(t){o.set(t,_),_+=t.length})),o}},this.flush=function(){t.inflateEnd()}}xt.prototype={inflateInit:function(t){const e=this;return e.istate=new bt,t||(t=15),e.istate.inflateInit(e,t)},inflate:function(t){const e=this;return e.istate?e.istate.inflate(e,t):Z},inflateEnd:function(){const t=this;if(!t.istate)return Z;const e=t.istate.inflateEnd(t);return t.istate=null,e},inflateSync:function(){const t=this;return t.istate?t.istate.inflateSync(t):Z},inflateSetDictionary:function(t,e){const n=this;return n.istate?n.istate.inflateSetDictionary(n,t,e):Z},read_byte:function(t){return this.next_in.subarray(t,t+1)[0]},read_buf:function(t,e){return this.next_in.subarray(t,t+e)}},self.initCodec=()=>{self.Deflate=Y,self.Inflate=wt}}(); \ No newline at end of file diff --git a/src/main/resources/static/js/zip.min.js b/src/main/resources/static/js/zip.min.js new file mode 100644 index 0000000..099c5ab --- /dev/null +++ b/src/main/resources/static/js/zip.min.js @@ -0,0 +1 @@ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).zip={})}(this,(function(t){"use strict";const e={chunkSize:524288,maxWorkers:"undefined"!=typeof navigator&&navigator.hardwareConcurrency||2,useWebWorkers:!0,workerScripts:void 0};let n=Object.assign({},e);function i(){return n}function r(t){if(void 0!==t.chunkSize&&(n.chunkSize=t.chunkSize),void 0!==t.maxWorkers&&(n.maxWorkers=t.maxWorkers),void 0!==t.useWebWorkers&&(n.useWebWorkers=t.useWebWorkers),void 0!==t.Deflate&&(n.Deflate=t.Deflate),void 0!==t.Inflate&&(n.Inflate=t.Inflate),void 0!==t.workerScripts){if(t.workerScripts.deflate){if(!Array.isArray(t.workerScripts.deflate))throw new Error("workerScripts.deflate must be an array");n.workerScripts||(n.workerScripts={}),n.workerScripts.deflate=t.workerScripts.deflate}if(t.workerScripts.inflate){if(!Array.isArray(t.workerScripts.inflate))throw new Error("workerScripts.inflate must be an array");n.workerScripts||(n.workerScripts={}),n.workerScripts.inflate=t.workerScripts.inflate}}}const a="function";function s(t,e){return class{constructor(n){const i=t=>{if(this.pendingData){const e=this.pendingData;this.pendingData=new Uint8Array(e.length+t.length),this.pendingData.set(e,0),this.pendingData.set(t,e.length)}else this.pendingData=new Uint8Array(t)};if(this.codec=new t(Object.assign({},e,n)),typeof this.codec.onData==a)this.codec.onData=i;else{if(typeof this.codec.on!=a)throw new Error("Cannot register the callback function");this.codec.on("data",i)}}async append(t){return this.codec.push(t),n(this)}async flush(){return this.codec.push(new Uint8Array(0),!0),n(this)}};function n(t){if(t.pendingData){const e=t.pendingData;return t.pendingData=null,e}return new Uint8Array(0)}}const o="HTTP error ",l="HTTP Range not supported",d="text/plain",c="Content-Length",u="Accept-Ranges",h="HEAD",f="GET",p="bytes";class w{constructor(){this.size=0}init(){this.initialized=!0}}class _ extends w{}class g extends w{writeUint8Array(t){this.size+=t.length}}class y extends _{constructor(t){super(),this.blob=t,this.size=t.size}async readUint8Array(t,e){const n=new FileReader;return new Promise(((i,r)=>{n.onload=t=>i(new Uint8Array(t.target.result)),n.onerror=r,n.readAsArrayBuffer(this.blob.slice(t,t+e))}))}}class b extends _{constructor(t,e){super(),this.url=t,this.preventHeadRequest=e.preventHeadRequest,this.useRangeHeader=e.useRangeHeader,this.forceRangeRequests=e.forceRangeRequests,this.options=Object.assign({},e),delete this.options.preventHeadRequest,delete this.options.useRangeHeader,delete this.options.forceRangeRequests,delete this.options.useXHR}async init(){if(super.init(),R(this.url)&&!this.preventHeadRequest){const t=await m(h,this.url,this.options);if(this.size=Number(t.headers.get(c)),!this.forceRangeRequests&&this.useRangeHeader&&t.headers.get(u)!=p)throw new Error(l);void 0===this.size&&await x(this,this.options)}else await x(this,this.options)}async readUint8Array(t,e){if(this.useRangeHeader){const n=await m(f,this.url,this.options,Object.assign({},this.options.headers,{HEADER_RANGE:"bytes="+t+"-"+(t+e-1)}));if(206!=n.status)throw new Error(l);return new Uint8Array(await n.arrayBuffer())}return this.data||await x(this,this.options),new Uint8Array(this.data.subarray(t,t+e))}}async function x(t,e){const n=await m(f,t.url,e);t.data=new Uint8Array(await n.arrayBuffer()),t.size||(t.size=t.data.length)}async function m(t,e,n,i){i=Object.assign({},n.headers,i);const r=await fetch(e,Object.assign({},n,{method:t,headers:i}));if(r.status<400)return r;throw new Error(o+(r.statusText||r.status))}class v extends _{constructor(t,e){super(),this.url=t,this.preventHeadRequest=e.preventHeadRequest,this.useRangeHeader=e.useRangeHeader,this.forceRangeRequests=e.forceRangeRequests}async init(){if(super.init(),R(this.url)&&!this.preventHeadRequest)return new Promise(((t,e)=>A(h,this.url,(n=>{this.size=Number(n.getResponseHeader(c)),this.useRangeHeader?this.forceRangeRequests||n.getResponseHeader(u)==p?t():e(new Error(l)):void 0===this.size?k(this,this.url).then((()=>t())).catch(e):t()}),e)));await k(this,this.url)}async readUint8Array(t,e){if(!this.useRangeHeader)return this.data||await k(this,this.url),new Uint8Array(this.data.subarray(t,t+e));if(206!=(await new Promise(((n,i)=>A(f,this.url,(t=>n(new Uint8Array(t.response))),i,[["Range","bytes="+t+"-"+(t+e-1)]])))).status)throw new Error(l)}}function k(t,e){return new Promise(((n,i)=>A(f,e,(e=>{t.data=new Uint8Array(e.response),t.size||(t.size=t.data.length),n()}),i)))}function A(t,e,n,i,r=[]){const a=new XMLHttpRequest;return a.addEventListener("load",(()=>{a.status<400?n(a):i(o+(a.statusText||a.status))}),!1),a.addEventListener("error",i,!1),a.open(t,e),r.forEach((t=>a.setRequestHeader(t[0],t[1]))),a.responseType="arraybuffer",a.send(),a}class E extends _{constructor(t,e={}){super(),this.url=t,e.useXHR?this.reader=new v(t,e):this.reader=new b(t,e)}set size(t){}get size(){return this.reader.size}async init(){super.init(),await this.reader.init()}async readUint8Array(t,e){return this.reader.readUint8Array(t,e)}}class U extends g{constructor(){super(),this.array=new Uint8Array(0)}async writeUint8Array(t){super.writeUint8Array(t);const e=this.array;this.array=new Uint8Array(e.length+t.length),this.array.set(e),this.array.set(t,e.length)}getData(){return this.array}}function R(t){if("undefined"!=typeof document){const e=document.createElement("a");return e.href=t,"http:"==e.protocol||"https:"==e.protocol}return/^https?:\/\//i.test(t)}const S=4294967295,D=65535,F=67324752,I=134695760,T=33639248,C=101010256,z=101075792,O=117853008,M=39169,W=2048,L="/",N=new Date(2107,11,31),P=new Date(1980,0,1),V=["\0","☺","☻","♥","♦","♣","♠","•","◘","○","◙","♂","♀","♪","♫","☼","►","◄","↕","‼","¶","§","▬","↨","↑","↓","→","←","∟","↔","▲","▼"," ","!",'"',"#","$","%","&","'","(",")","*","+",",","-",".","/","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","{","|","}","~","⌂","Ç","ü","é","â","ä","à","å","ç","ê","ë","è","ï","î","ì","Ä","Å","É","æ","Æ","ô","ö","ò","û","ù","ÿ","Ö","Ü","¢","£","¥","₧","ƒ","á","í","ó","ú","ñ","Ñ","ª","º","¿","⌐","¬","½","¼","¡","«","»","░","▒","▓","│","┤","╡","╢","╖","╕","╣","║","╗","╝","╜","╛","┐","└","┴","┬","├","─","┼","╞","╟","╚","╔","╩","╦","╠","═","╬","╧","╨","╤","╥","╙","╘","╒","╓","╫","╪","┘","┌","█","▄","▌","▐","▀","α","ß","Γ","π","Σ","σ","µ","τ","Φ","Θ","Ω","δ","∞","φ","ε","∩","≡","±","≥","≤","⌠","⌡","÷","≈","°","∙","·","√","ⁿ","²","■"," "];const B=[];for(let t=0;t<256;t++){let e=t;for(let t=0;t<8;t++)1&e?e=e>>>1^3988292384:e>>>=1;B[t]=e}class H{constructor(){this.crc=-1}append(t){let e=0|this.crc;for(let n=0,i=0|t.length;n>>8^B[255&(e^t[n])];this.crc=e}get(){return~this.crc}}const j="Invalid pasword",q=16,Z="raw",K={name:"PBKDF2"},Y={name:"HMAC"},X="SHA-1",G={name:"AES-CTR"},J=Object.assign({hash:Y},K),Q=Object.assign({iterations:1e3,hash:{name:X}},K),$=Object.assign({hash:X},Y),tt=Object.assign({length:q},G),et=["deriveBits"],nt=["sign"],it=[8,12,16],rt=[16,24,32],at=10,st=[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],ot=crypto.subtle;class lt{constructor(t,e,n){this.password=t,this.signed=e,this.strength=n-1,this.input=e&&new Uint8Array(0),this.pendingInput=new Uint8Array(0)}async append(t){const e=async(r=0)=>{if(r+q<=i.length-at){const t=i.subarray(r,r+q),a=await ot.decrypt(Object.assign({counter:this.counter},tt),this.keys.key,t);return ut(this.counter),n.set(new Uint8Array(a),r),e(r+q)}return this.pendingInput=i.subarray(r),this.signed&&(this.input=ht(this.input,t)),n};if(this.password){const e=t.subarray(0,it[this.strength]+2);await async function(t,e,n){await ct(t,n,e.subarray(0,it[t.strength]),["decrypt"]);const i=e.subarray(it[t.strength]),r=t.keys.passwordVerification;if(r[0]!=i[0]||r[1]!=i[1])throw new Error(j)}(this,e,this.password),this.password=null,t=t.subarray(it[this.strength]+2)}let n=new Uint8Array(t.length-at-(t.length-at)%q),i=t;return this.pendingInput.length&&(i=ht(this.pendingInput,t),n=ft(n,i.length-at-(i.length-at)%q)),e()}async flush(){const t=this.pendingInput,e=this.keys,n=t.subarray(0,t.length-at),i=t.subarray(t.length-at);let r=new Uint8Array(0);if(n.length){const t=await ot.decrypt(Object.assign({counter:this.counter},tt),e.key,n);r=new Uint8Array(t)}let a=!0;if(this.signed){const t=await ot.sign(Y,e.authentication,this.input.subarray(0,this.input.length-at)),n=new Uint8Array(t);this.input=null;for(let t=0;t{if(r+q<=t.length){const a=t.subarray(r,r+q),s=await ot.encrypt(Object.assign({counter:this.counter},tt),this.keys.key,a);return ut(this.counter),i.set(new Uint8Array(s),r+n.length),e(r+q)}return this.pendingInput=t.subarray(r),this.output=ht(this.output,i),i};let n=new Uint8Array(0);this.password&&(n=await async function(t,e){const n=crypto.getRandomValues(new Uint8Array(it[t.strength]));return await ct(t,e,n,["encrypt"]),ht(n,t.keys.passwordVerification)}(this,this.password),this.password=null);let i=new Uint8Array(n.length+t.length-t.length%q);return i.set(n,0),this.pendingInput.length&&(t=ht(this.pendingInput,t),i=ft(i,t.length-t.length%q)),e()}async flush(){let t=new Uint8Array(0);if(this.pendingInput.length){const e=await ot.encrypt(Object.assign({counter:this.counter},tt),this.keys.key,this.pendingInput);t=new Uint8Array(e),this.output=ht(this.output,t)}const e=await ot.sign(Y,this.keys.authentication,this.output.subarray(it[this.strength]+2));this.output=null;const n=new Uint8Array(e).subarray(0,at);return{data:ht(t,n),signature:n}}}async function ct(t,e,n,i){t.counter=new Uint8Array(st);const r=(new TextEncoder).encode(e),a=await ot.importKey(Z,r,J,!1,et),s=await ot.deriveBits(Object.assign({salt:n},Q),a,8*(2*rt[t.strength]+2)),o=new Uint8Array(s);t.keys={key:await ot.importKey(Z,o.subarray(0,rt[t.strength]),G,!0,i),authentication:await ot.importKey(Z,o.subarray(rt[t.strength],2*rt[t.strength]),$,!1,nt),passwordVerification:o.subarray(2*rt[t.strength])}}function ut(t){for(let e=0;e<16;e++){if(255!=t[e]){t[e]++;break}t[e]=0}}function ht(t,e){let n=t;return t.length+e.length&&(n=new Uint8Array(t.length+e.length),n.set(t,0),n.set(e,t.length)),n}function ft(t,e){if(e&&e>t.length){const n=t;(t=new Uint8Array(e)).set(n,0)}return t}const pt="deflate",wt="inflate",_t="Invalid signature";class gt{constructor(t){this.signature=t.inputSignature,this.encrypted=Boolean(t.inputPassword),this.signed=t.inputSigned,this.compressed=t.inputCompressed,this.inflate=t.inputCompressed&&new t.codecConstructor,this.crc32=t.inputSigned&&new H,this.decrypt=this.encrypted&&new lt(t.inputPassword,t.inputSigned,t.inputEncryptionStrength)}async append(t){return this.encrypted&&(t=await this.decrypt.append(t)),this.compressed&&t.length&&(t=await this.inflate.append(t)),!this.encrypted&&this.signed&&this.crc32.append(t),t}async flush(){let t,e=new Uint8Array(0);if(this.encrypted){const t=await this.decrypt.flush();if(!t.valid)throw new Error(_t);e=t.data}else if(this.signed){const e=new DataView(new Uint8Array(4).buffer);if(t=this.crc32.get(),e.setUint32(0,t),this.signature!=e.getUint32(0,!1))throw new Error(_t)}return this.compressed&&(e=await this.inflate.append(e)||new Uint8Array(0),await this.inflate.flush()),{data:e,signature:t}}}class yt{constructor(t){this.encrypted=t.outputEncrypted,this.signed=t.outputSigned,this.compressed=t.outputCompressed,this.deflate=t.outputCompressed&&new t.codecConstructor({level:t.level||5}),this.crc32=t.outputSigned&&new H,this.encrypt=this.encrypted&&new dt(t.outputPassword,t.outputEncryptionStrength)}async append(t){let e=t;return this.compressed&&t.length&&(e=await this.deflate.append(t)),this.encrypted?e=await this.encrypt.append(e):this.signed&&this.crc32.append(t),e}async flush(){let t,e=new Uint8Array(0);if(this.compressed&&(e=await this.deflate.flush()||new Uint8Array(0)),this.encrypted){e=await this.encrypt.append(e);const n=await this.encrypt.flush();t=n.signature;const i=new Uint8Array(e.length+n.data.length);i.set(e,0),i.set(n.data,e.length),e=i}else this.signed&&(t=this.crc32.get());return{data:e,signature:t}}}const bt="init",xt="append",mt="flush",vt="message";var kt=(t,e,n,i,r)=>(t.busy=!0,t.options=e,t.scripts=r,t.webWorker=i,t.onTaskFinished=()=>{t.busy=!1;n(t)&&t.worker&&t.worker.terminate()},i?function(t){let e;t.interface||(t.worker=new Worker(new URL(t.scripts[0],"undefined"==typeof document?new(require("url").URL)("file:"+__filename).href:document.currentScript&&document.currentScript.src||new URL("zip.min.js",document.baseURI).href)),t.worker.addEventListener(vt,r,!1),t.interface={append:t=>n({type:xt,data:t}),flush:()=>n({type:mt})});return t.interface;async function n(n){if(!e){const e=t.options,n=t.scripts.slice(1);await i(Object.assign({scripts:n,type:bt,options:{codecType:e.codecType,inputPassword:e.inputPassword,inputEncryptionStrength:e.inputEncryptionStrength,inputSigned:e.inputSigned,inputSignature:e.signature,inputCompressed:e.inputCompressed,inputEncrypted:e.inputEncrypted,level:e.level,outputPassword:e.outputPassword,outputEncryptionStrength:e.outputEncryptionStrength,outputSigned:e.outputSigned,outputCompressed:e.outputCompressed,outputEncrypted:e.outputEncrypted}}))}return i(n)}function i(n){const i=t.worker,r=new Promise(((t,n)=>e={resolve:t,reject:n}));try{if(n.data)try{i.postMessage(n,[n.data.buffer])}catch(t){i.postMessage(n)}else i.postMessage(n)}catch(n){e.reject(n),e=null,t.onTaskFinished()}return r}function r(n){const i=n.data;if(e){const n=i.error,r=i.type;if(n){const i=new Error(n.message);i.stack=n.stack,e.reject(i),e=null,t.onTaskFinished()}else if(r==bt||r==mt||r==xt){const n=i.data;r==mt?(e.resolve({data:new Uint8Array(n),signature:i.signature}),e=null,t.onTaskFinished()):e.resolve(n&&new Uint8Array(n))}}}}(t):function(t){const e=function(t){return t.codecType.startsWith(pt)?new yt(t):t.codecType.startsWith(wt)?new gt(t):void 0}(t.options);return{async append(n){try{return await e.append(n)}catch(e){throw t.onTaskFinished(),e}},async flush(){try{return await e.flush()}finally{t.onTaskFinished()}}}}(t));let At=[],Et=[];function Ut(t,e){const n=!!(t.inputCompressed||t.inputSigned||t.inputEncrypted||t.outputCompressed||t.outputSigned||t.outputEncrypted)&&(t.useWebWorkers||void 0===t.useWebWorkers&&e.useWebWorkers),i=n&&e.workerScripts?e.workerScripts[t.codecType]:[];if(At.length!t.busy));return e?kt(e,t,Rt,n,i):new Promise((e=>Et.push({resolve:e,options:t,webWorker:n,scripts:i})))}}function Rt(t){const e=!Et.length;if(e)At=At.filter((e=>e!=t));else{const[{resolve:e,options:n,webWorker:i,scripts:r}]=Et.splice(0,1);e(kt(t,n,Rt,i,r))}return e}async function St(t,e,n,i,r,a,s){const o=Math.max(a.chunkSize,64);return async function a(l=0,d=0){if(lthis[e]=t[e]))}}const Tt="File format is not recognized",Ct="End of central directory not found",zt="End of Zip64 central directory not found",Ot="End of Zip64 central directory locator not found",Mt="Central directory header not found",Wt="Local file header not found",Lt="Zip64 extra field not found",Nt="File contains encrypted entry",Pt="Encryption method not supported",Vt="Compression method not supported",Bt="utf-8",Ht=["uncompressedSize","compressedSize","offset"];class jt{constructor(t,e,n){this.reader=t,this.config=e,this.options=n}async getData(t,e={}){const n=this.reader;n.initialized||await n.init();const i=await n.readUint8Array(this.offset,30),r=new DataView(i.buffer),a=Yt(this,e,"password"),s=a&&a.length&&a;if(this.extraFieldAES&&99!=this.extraFieldAES.originalCompressionMethod)throw new Error(Vt);if(0!=this.compressionMethod&&8!=this.compressionMethod)throw new Error(Vt);if(Qt(r,0)!=F)throw new Error(Wt);const o=this.localDirectory={};qt(o,r,4),o.rawExtraField=i.subarray(this.offset+30+o.filenameLength,this.offset+30+o.filenameLength+o.extraFieldLength),Zt(this,o,r,4);let l=this.offset+30+o.filenameLength+o.extraFieldLength;const d=this.bitFlag.encrypted&&o.bitFlag.encrypted,c=this.extraFieldAES&&this.extraFieldAES.strength;if(d){if(void 0===c)throw new Error(Pt);if(!s)throw new Error(Nt)}const u=await Ut({codecType:wt,codecConstructor:this.config.Inflate,inputPassword:s,inputEncryptionStrength:c,inputSigned:Yt(this,e,"checkSignature"),inputSignature:this.signature,inputCompressed:0!=this.compressionMethod,inputEncrypted:d,useWebWorkers:Yt(this,e,"useWebWorkers")},this.config);return t.initialized||await t.init(),await St(u,n,t,l,this.compressedSize,this.config,{onprogress:e.onprogress}),t.getData()}}function qt(t,e,n){t.version=Jt(e,n);const i=t.rawBitFlag=Jt(e,n+2);t.bitFlag={encrypted:1==(1&i),level:(6&i)>>1,dataDescriptor:8==(8&i),languageEncodingFlag:(i&W)==W},t.encrypted=t.bitFlag.encrypted,t.rawLastModDate=Qt(e,n+6),t.lastModDate=function(t){const e=(4294901760&t)>>16,n=65535&t;try{return new Date(1980+((65024&e)>>9),((480&e)>>5)-1,31&e,(63488&n)>>11,(2016&n)>>5,2*(31&n),0)}catch(t){}}(t.rawLastModDate),t.filenameLength=Jt(e,n+22),t.extraFieldLength=Jt(e,n+24)}function Zt(t,e,n,i){const r=e.rawExtraField,a=e.extraField=new Map,s=new DataView(new Uint8Array(r).buffer);let o=0;try{for(;oe[t]==S));for(let e=0;e{if(e[n]==S){if(!t||void 0===t[n])throw new Error(Lt);e[n]=t[n]}}))}(d,e);const c=e.extraFieldUnicodePath=a.get(28789);c&&Kt(c,"filename","rawFilename",e,t);let u=e.extraFieldUnicodeComment=a.get(25461);u&&Kt(u,"comment","rawComment",e,t);const h=e.extraFieldAES=a.get(39169);h?function(t,e,n){if(t){const i=new DataView(t.data.buffer);t.vendorVersion=Gt(i,0),t.vendorId=Gt(i,2);const r=Gt(i,4);t.strength=r,t.originalCompressionMethod=n,e.compressionMethod=t.compressionMethod=Jt(i,5)}else e.compressionMethod=n}(h,e,l):e.compressionMethod=l,8==e.compressionMethod&&(e.bitFlag.enhancedDeflating=16!=(16&e.rawBitFlag))}function Kt(t,e,n,i,r){const a=new DataView(t.data.buffer);t.version=Gt(a,0),t.signature=Qt(a,1);const s=new H;s.append(r[n]);const o=new DataView(new Uint8Array(4).buffer);o.setUint32(0,s.get(),!0),t[e]=(new TextDecoder).decode(t.data.subarray(5)),t.valid=!r.bitFlag.languageEncodingFlag&&t.signature==Qt(o,0),t.valid&&(i[e]=t[e],i[e+"UTF8"]=!0)}function Yt(t,e,n){return void 0===e[n]?t.options[n]:e[n]}function Xt(t,e){return e&&"cp437"!=e.trim().toLowerCase()?new TextDecoder(e).decode(t):(t=>{let e="";for(let n=0;n{if("function"==typeof URL.createObjectURL){const t=(()=>{const t=[];for(let e=0;e<256;e++){let n=e;for(let t=0;t<8;t++)1&n?n=n>>>1^3988292384:n>>>=1;t[e]=n}class e{constructor(){this.crc=-1}append(e){let n=0|this.crc;for(let i=0,r=0|e.length;i>>8^t[255&(n^e[i])];this.crc=n}get(){return~this.crc}}const n={name:"PBKDF2"},i={name:"HMAC"},r={name:"AES-CTR"},a=Object.assign({hash:i},n),s=Object.assign({iterations:1e3,hash:{name:"SHA-1"}},n),o=Object.assign({hash:"SHA-1"},i),l=Object.assign({length:16},r),d=["deriveBits"],c=["sign"],u=[8,12,16],h=[16,24,32],f=[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],p=crypto.subtle;class w{constructor(t,e,n){this.password=t,this.signed=e,this.strength=n-1,this.input=e&&new Uint8Array(0),this.pendingInput=new Uint8Array(0)}async append(t){const e=async(r=0)=>{if(r+16<=i.length-10){const t=i.subarray(r,r+16),a=await p.decrypt(Object.assign({counter:this.counter},l),this.keys.key,t);return y(this.counter),n.set(new Uint8Array(a),r),e(r+16)}return this.pendingInput=i.subarray(r),this.signed&&(this.input=b(this.input,t)),n};if(this.password){const e=t.subarray(0,u[this.strength]+2);await async function(t,e,n){await g(t,n,e.subarray(0,u[t.strength]),["decrypt"]);const i=e.subarray(u[t.strength]),r=t.keys.passwordVerification;if(r[0]!=i[0]||r[1]!=i[1])throw new Error("Invalid pasword")}(this,e,this.password),this.password=null,t=t.subarray(u[this.strength]+2)}let n=new Uint8Array(t.length-10-(t.length-10)%16),i=t;return this.pendingInput.length&&(i=b(this.pendingInput,t),n=x(n,i.length-10-(i.length-10)%16)),e()}async flush(){const t=this.pendingInput,e=this.keys,n=t.subarray(0,t.length-10),r=t.subarray(t.length-10);let a=new Uint8Array(0);if(n.length){const t=await p.decrypt(Object.assign({counter:this.counter},l),e.key,n);a=new Uint8Array(t)}let s=!0;if(this.signed){const t=await p.sign(i,e.authentication,this.input.subarray(0,this.input.length-10)),n=new Uint8Array(t);this.input=null;for(let t=0;t<10;t++)n[t]!=r[t]&&(s=!1)}return{valid:s,data:a}}}class _{constructor(t,e){this.password=t,this.strength=e-1,this.output=new Uint8Array(0),this.pendingInput=new Uint8Array(0)}async append(t){const e=async(r=0)=>{if(r+16<=t.length){const a=t.subarray(r,r+16),s=await p.encrypt(Object.assign({counter:this.counter},l),this.keys.key,a);return y(this.counter),i.set(new Uint8Array(s),r+n.length),e(r+16)}return this.pendingInput=t.subarray(r),this.output=b(this.output,i),i};let n=new Uint8Array(0);this.password&&(n=await async function(t,e){const n=crypto.getRandomValues(new Uint8Array(u[t.strength]));return await g(t,e,n,["encrypt"]),b(n,t.keys.passwordVerification)}(this,this.password),this.password=null);let i=new Uint8Array(n.length+t.length-t.length%16);return i.set(n,0),this.pendingInput.length&&(t=b(this.pendingInput,t),i=x(i,t.length-t.length%16)),e()}async flush(){let t=new Uint8Array(0);if(this.pendingInput.length){const e=await p.encrypt(Object.assign({counter:this.counter},l),this.keys.key,this.pendingInput);t=new Uint8Array(e),this.output=b(this.output,t)}const e=await p.sign(i,this.keys.authentication,this.output.subarray(u[this.strength]+2));this.output=null;const n=new Uint8Array(e).subarray(0,10);return{data:b(t,n),signature:n}}}async function g(t,e,n,i){t.counter=new Uint8Array(f);const l=(new TextEncoder).encode(e),u=await p.importKey("raw",l,a,!1,d),w=await p.deriveBits(Object.assign({salt:n},s),u,8*(2*h[t.strength]+2)),_=new Uint8Array(w);t.keys={key:await p.importKey("raw",_.subarray(0,h[t.strength]),r,!0,i),authentication:await p.importKey("raw",_.subarray(h[t.strength],2*h[t.strength]),o,!1,c),passwordVerification:_.subarray(2*h[t.strength])}}function y(t){for(let e=0;e<16;e++){if(255!=t[e]){t[e]++;break}t[e]=0}}function b(t,e){let n=t;return t.length+e.length&&(n=new Uint8Array(t.length+e.length),n.set(t,0),n.set(e,t.length)),n}function x(t,e){if(e&&e>t.length){const n=t;(t=new Uint8Array(e)).set(n,0)}return t}class m{constructor(t){this.signature=t.inputSignature,this.encrypted=Boolean(t.inputPassword),this.signed=t.inputSigned,this.compressed=t.inputCompressed,this.inflate=t.inputCompressed&&new t.codecConstructor,this.crc32=t.inputSigned&&new e,this.decrypt=this.encrypted&&new w(t.inputPassword,t.inputSigned,t.inputEncryptionStrength)}async append(t){return this.encrypted&&(t=await this.decrypt.append(t)),this.compressed&&t.length&&(t=await this.inflate.append(t)),!this.encrypted&&this.signed&&this.crc32.append(t),t}async flush(){let t,e=new Uint8Array(0);if(this.encrypted){const t=await this.decrypt.flush();if(!t.valid)throw new Error("Invalid signature");e=t.data}else if(this.signed){const e=new DataView(new Uint8Array(4).buffer);if(t=this.crc32.get(),e.setUint32(0,t),this.signature!=e.getUint32(0,!1))throw new Error("Invalid signature")}return this.compressed&&(e=await this.inflate.append(e)||new Uint8Array(0),await this.inflate.flush()),{data:e,signature:t}}}class v{constructor(t){this.encrypted=t.outputEncrypted,this.signed=t.outputSigned,this.compressed=t.outputCompressed,this.deflate=t.outputCompressed&&new t.codecConstructor({level:t.level||5}),this.crc32=t.outputSigned&&new e,this.encrypt=this.encrypted&&new _(t.outputPassword,t.outputEncryptionStrength)}async append(t){let e=t;return this.compressed&&t.length&&(e=await this.deflate.append(t)),this.encrypted?e=await this.encrypt.append(e):this.signed&&this.crc32.append(t),e}async flush(){let t,e=new Uint8Array(0);if(this.compressed&&(e=await this.deflate.flush()||new Uint8Array(0)),this.encrypted){e=await this.encrypt.append(e);const n=await this.encrypt.flush();t=n.signature;const i=new Uint8Array(e.length+n.data.length);i.set(e,0),i.set(n.data,e.length),e=i}else this.signed&&(t=this.crc32.get());return{data:e,signature:t}}}const k={init(t){t.scripts&&t.scripts.length&&importScripts.apply(void 0,t.scripts);const e=t.options;self.initCodec&&self.initCodec(),e.codecType.startsWith("deflate")?e.codecConstructor=self.Deflate:e.codecType.startsWith("inflate")&&(e.codecConstructor=self.Inflate),A=function(t){return t.codecType.startsWith("deflate")?new v(t):t.codecType.startsWith("inflate")?new m(t):void 0}(e)},append:async t=>({data:await A.append(t.data)}),flush:()=>A.flush()};let A;function E(t){return t.map((([t,e])=>new Array(t).fill(e,0,t))).flat()}addEventListener("message",(async t=>{const e=t.data,n=e.type,i=k[n];if(i)try{const t=await i(e)||{};if(t.type=n,t.data)try{postMessage(t,[t.data.buffer])}catch(e){postMessage(t)}else postMessage(t)}catch(t){postMessage({type:n,error:{message:t.message,stack:t.stack}})}}));const U=[0,1,2,3].concat(...E([[2,4],[2,5],[4,6],[4,7],[8,8],[8,9],[16,10],[16,11],[32,12],[32,13],[64,14],[64,15],[2,0],[1,16],[1,17],[2,18],[2,19],[4,20],[4,21],[8,22],[8,23],[16,24],[16,25],[32,26],[32,27],[64,28],[64,29]]));function R(){const t=this;function e(t,e){let n=0;do{n|=1&t,t>>>=1,n<<=1}while(--e>0);return n>>>1}t.build_tree=function(n){const i=t.dyn_tree,r=t.stat_desc.static_tree,a=t.stat_desc.elems;let s,o,l,d=-1;for(n.heap_len=0,n.heap_max=573,s=0;s=1;s--)n.pqdownheap(i,s);l=a;do{s=n.heap[1],n.heap[1]=n.heap[n.heap_len--],n.pqdownheap(i,1),o=n.heap[1],n.heap[--n.heap_max]=s,n.heap[--n.heap_max]=o,i[2*l]=i[2*s]+i[2*o],n.depth[l]=Math.max(n.depth[s],n.depth[o])+1,i[2*s+1]=i[2*o+1]=l,n.heap[1]=l++,n.pqdownheap(i,1)}while(n.heap_len>=2);n.heap[--n.heap_max]=n.heap[1],function(e){const n=t.dyn_tree,i=t.stat_desc.static_tree,r=t.stat_desc.extra_bits,a=t.stat_desc.extra_base,s=t.stat_desc.max_length;let o,l,d,c,u,h,f=0;for(c=0;c<=15;c++)e.bl_count[c]=0;for(n[2*e.heap[e.heap_max]+1]=0,o=e.heap_max+1;o<573;o++)l=e.heap[o],c=n[2*n[2*l+1]+1]+1,c>s&&(c=s,f++),n[2*l+1]=c,l>t.max_code||(e.bl_count[c]++,u=0,l>=a&&(u=r[l-a]),h=n[2*l],e.opt_len+=h*(c+u),i&&(e.static_len+=h*(i[2*l+1]+u)));if(0!==f){do{for(c=s-1;0===e.bl_count[c];)c--;e.bl_count[c]--,e.bl_count[c+1]+=2,e.bl_count[s]--,f-=2}while(f>0);for(c=s;0!==c;c--)for(l=e.bl_count[c];0!==l;)d=e.heap[--o],d>t.max_code||(n[2*d+1]!=c&&(e.opt_len+=(c-n[2*d+1])*n[2*d],n[2*d+1]=c),l--)}}(n),function(t,n,i){const r=[];let a,s,o,l=0;for(a=1;a<=15;a++)r[a]=l=l+i[a-1]<<1;for(s=0;s<=n;s++)o=t[2*s+1],0!==o&&(t[2*s]=e(r[o]++,o))}(i,t.max_code,n.bl_count)}}function S(t,e,n,i,r){const a=this;a.static_tree=t,a.extra_bits=e,a.extra_base=n,a.elems=i,a.max_length=r}function D(t,e,n,i,r){const a=this;a.good_length=t,a.max_lazy=e,a.nice_length=n,a.max_chain=i,a.func=r}R._length_code=[0,1,2,3,4,5,6,7].concat(...E([[2,8],[2,9],[2,10],[2,11],[4,12],[4,13],[4,14],[4,15],[8,16],[8,17],[8,18],[8,19],[16,20],[16,21],[16,22],[16,23],[32,24],[32,25],[32,26],[31,27],[1,28]])),R.base_length=[0,1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,40,48,56,64,80,96,112,128,160,192,224,0],R.base_dist=[0,1,2,3,4,6,8,12,16,24,32,48,64,96,128,192,256,384,512,768,1024,1536,2048,3072,4096,6144,8192,12288,16384,24576],R.d_code=function(t){return t<256?U[t]:U[256+(t>>>7)]},R.extra_lbits=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0],R.extra_dbits=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],R.extra_blbits=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7],R.bl_order=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],S.static_ltree=[12,8,140,8,76,8,204,8,44,8,172,8,108,8,236,8,28,8,156,8,92,8,220,8,60,8,188,8,124,8,252,8,2,8,130,8,66,8,194,8,34,8,162,8,98,8,226,8,18,8,146,8,82,8,210,8,50,8,178,8,114,8,242,8,10,8,138,8,74,8,202,8,42,8,170,8,106,8,234,8,26,8,154,8,90,8,218,8,58,8,186,8,122,8,250,8,6,8,134,8,70,8,198,8,38,8,166,8,102,8,230,8,22,8,150,8,86,8,214,8,54,8,182,8,118,8,246,8,14,8,142,8,78,8,206,8,46,8,174,8,110,8,238,8,30,8,158,8,94,8,222,8,62,8,190,8,126,8,254,8,1,8,129,8,65,8,193,8,33,8,161,8,97,8,225,8,17,8,145,8,81,8,209,8,49,8,177,8,113,8,241,8,9,8,137,8,73,8,201,8,41,8,169,8,105,8,233,8,25,8,153,8,89,8,217,8,57,8,185,8,121,8,249,8,5,8,133,8,69,8,197,8,37,8,165,8,101,8,229,8,21,8,149,8,85,8,213,8,53,8,181,8,117,8,245,8,13,8,141,8,77,8,205,8,45,8,173,8,109,8,237,8,29,8,157,8,93,8,221,8,61,8,189,8,125,8,253,8,19,9,275,9,147,9,403,9,83,9,339,9,211,9,467,9,51,9,307,9,179,9,435,9,115,9,371,9,243,9,499,9,11,9,267,9,139,9,395,9,75,9,331,9,203,9,459,9,43,9,299,9,171,9,427,9,107,9,363,9,235,9,491,9,27,9,283,9,155,9,411,9,91,9,347,9,219,9,475,9,59,9,315,9,187,9,443,9,123,9,379,9,251,9,507,9,7,9,263,9,135,9,391,9,71,9,327,9,199,9,455,9,39,9,295,9,167,9,423,9,103,9,359,9,231,9,487,9,23,9,279,9,151,9,407,9,87,9,343,9,215,9,471,9,55,9,311,9,183,9,439,9,119,9,375,9,247,9,503,9,15,9,271,9,143,9,399,9,79,9,335,9,207,9,463,9,47,9,303,9,175,9,431,9,111,9,367,9,239,9,495,9,31,9,287,9,159,9,415,9,95,9,351,9,223,9,479,9,63,9,319,9,191,9,447,9,127,9,383,9,255,9,511,9,0,7,64,7,32,7,96,7,16,7,80,7,48,7,112,7,8,7,72,7,40,7,104,7,24,7,88,7,56,7,120,7,4,7,68,7,36,7,100,7,20,7,84,7,52,7,116,7,3,8,131,8,67,8,195,8,35,8,163,8,99,8,227,8],S.static_dtree=[0,5,16,5,8,5,24,5,4,5,20,5,12,5,28,5,2,5,18,5,10,5,26,5,6,5,22,5,14,5,30,5,1,5,17,5,9,5,25,5,5,5,21,5,13,5,29,5,3,5,19,5,11,5,27,5,7,5,23,5],S.static_l_desc=new S(S.static_ltree,R.extra_lbits,257,286,15),S.static_d_desc=new S(S.static_dtree,R.extra_dbits,0,30,15),S.static_bl_desc=new S(null,R.extra_blbits,0,19,7);const F=[new D(0,0,0,0,0),new D(4,4,8,4,1),new D(4,5,16,8,1),new D(4,6,32,32,1),new D(4,4,16,16,2),new D(8,16,32,32,2),new D(8,16,128,128,2),new D(8,32,128,256,2),new D(32,128,258,1024,2),new D(32,258,258,4096,2)],I=["need dictionary","stream end","","","stream error","data error","","buffer error","",""];function T(t,e,n,i){const r=t[2*e],a=t[2*n];return r>>8&255)}function tt(t,e){let n;const i=e;X>16-i?(n=t,Y|=n<>>16-X,X+=i-16):(Y|=t<=8&&(Q(255&Y),Y>>>=8,X-=8)}function rt(e,n){let i,r,a;if(t.pending_buf[q+2*j]=e>>>8&255,t.pending_buf[q+2*j+1]=255&e,t.pending_buf[B+j]=255&n,j++,0===e?M[2*n]++:(Z++,e--,M[2*(R._length_code[n]+256+1)]++,W[2*R.d_code(e)]++),0==(8191&j)&&D>2){for(i=8*j,r=m-g,a=0;a<30;a++)i+=W[2*a]*(5+R.extra_dbits[a]);if(i>>>=3,Z8?$(Y):X>0&&Q(255&Y),Y=0,X=0}function ot(e,n,i){tt(0+(i?1:0),3),function(e,n,i){st(),K=8,$(n),$(~n),t.pending_buf.set(l.subarray(e,e+n),t.pending),t.pending+=n}(e,n)}function lt(e,n,i){let r,a,s=0;D>0?(N.build_tree(t),P.build_tree(t),s=function(){let e;for(J(M,N.max_code),J(W,P.max_code),V.build_tree(t),e=18;e>=3&&0===L[2*R.bl_order[e]+1];e--);return t.opt_len+=3*(e+1)+5+5+4,e}(),r=t.opt_len+3+7>>>3,a=t.static_len+3+7>>>3,a<=r&&(r=a)):r=a=n+5,n+4<=r&&-1!=e?ot(e,n,i):a==r?(tt(2+(i?1:0),3),at(S.static_ltree,S.static_dtree)):(tt(4+(i?1:0),3),function(t,e,n){let i;for(tt(t-257,5),tt(e-1,5),tt(n-4,4),i=0;i=0?g:-1,m-g,t),g=m,e.flush_pending()}function ct(){let t,n,i,r;do{if(r=d-k-m,0===r&&0===m&&0===k)r=a;else if(-1==r)r--;else if(m>=a+a-262){l.set(l.subarray(a,a+a),0),v-=a,m-=a,g-=a,t=f,i=t;do{n=65535&u[--i],u[i]=n>=a?n-a:0}while(0!=--t);t=a,i=t;do{n=65535&c[--i],c[i]=n>=a?n-a:0}while(0!=--t);r+=a}if(0===e.avail_in)return;t=e.read_buf(l,m+k,r),k+=t,k>=3&&(h=255&l[m],h=(h<<_^255&l[m+1])&w)}while(k<262&&0!==e.avail_in)}function ut(t){let e,n,i=E,r=m,s=A;const d=m>a-262?m-(a-262):0;let u=O;const h=o,f=m+258;let p=l[r+s-1],w=l[r+s];A>=z&&(i>>=2),u>k&&(u=k);do{if(e=t,l[e+s]==w&&l[e+s-1]==p&&l[e]==l[r]&&l[++e]==l[r+1]){r+=2,e++;do{}while(l[++r]==l[++e]&&l[++r]==l[++e]&&l[++r]==l[++e]&&l[++r]==l[++e]&&l[++r]==l[++e]&&l[++r]==l[++e]&&l[++r]==l[++e]&&l[++r]==l[++e]&&rs){if(v=t,s=n,n>=u)break;p=l[r+s-1],w=l[r+s]}}}while((t=65535&c[t&h])>d&&0!=--i);return s<=k?s:k}function ht(e){return e.total_in=e.total_out=0,e.msg=null,t.pending=0,t.pending_out=0,n=113,r=0,N.dyn_tree=M,N.stat_desc=S.static_l_desc,P.dyn_tree=W,P.stat_desc=S.static_d_desc,V.dyn_tree=L,V.stat_desc=S.static_bl_desc,Y=0,X=0,K=8,G(),function(){d=2*a,u[f-1]=0;for(let t=0;t9||8!=d||r<9||r>15||n<0||n>9||g<0||g>2?-2:(e.dstate=t,s=r,a=1<9||n<0||n>2?-2:(F[D].func!=F[e].func&&0!==t.total_in&&(i=t.deflate(1)),D!=e&&(D=e,U=F[D].max_lazy,z=F[D].good_length,O=F[D].nice_length,E=F[D].max_chain),C=n,i)},t.deflateSetDictionary=function(t,e,i){let r,s=i,d=0;if(!e||42!=n)return-2;if(s<3)return 0;for(s>a-262&&(s=a-262,d=i-s),l.set(e.subarray(d,d+s),0),m=s,g=s,h=255&l[0],h=(h<<_^255&l[1])&w,r=0;r<=s-3;r++)h=(h<<_^255&l[r+2])&w,c[r&o]=u[h],u[h]=r;return 0},t.deflate=function(d,p){let E,R,T,z,O;if(p>4||p<0)return-2;if(!d.next_out||!d.next_in&&0!==d.avail_in||666==n&&4!=p)return d.msg=I[4],-2;if(0===d.avail_out)return d.msg=I[7],-5;var M;if(e=d,z=r,r=p,42==n&&(R=8+(s-8<<4)<<8,T=(D-1&255)>>1,T>3&&(T=3),R|=T<<6,0!==m&&(R|=32),R+=31-R%31,n=113,Q((M=R)>>8&255),Q(255&M)),0!==t.pending){if(e.flush_pending(),0===e.avail_out)return r=-1,0}else if(0===e.avail_in&&p<=z&&4!=p)return e.msg=I[7],-5;if(666==n&&0!==e.avail_in)return d.msg=I[7],-5;if(0!==e.avail_in||0!==k||0!=p&&666!=n){switch(O=-1,F[D].func){case 0:O=function(t){let n,r=65535;for(r>i-5&&(r=i-5);;){if(k<=1){if(ct(),0===k&&0==t)return 0;if(0===k)break}if(m+=k,k=0,n=g+r,(0===m||m>=n)&&(k=m-n,m=n,dt(!1),0===e.avail_out))return 0;if(m-g>=a-262&&(dt(!1),0===e.avail_out))return 0}return dt(4==t),0===e.avail_out?4==t?2:0:4==t?3:1}(p);break;case 1:O=function(t){let n,i=0;for(;;){if(k<262){if(ct(),k<262&&0==t)return 0;if(0===k)break}if(k>=3&&(h=(h<<_^255&l[m+2])&w,i=65535&u[h],c[m&o]=u[h],u[h]=m),0!==i&&(m-i&65535)<=a-262&&2!=C&&(y=ut(i)),y>=3)if(n=rt(m-v,y-3),k-=y,y<=U&&k>=3){y--;do{m++,h=(h<<_^255&l[m+2])&w,i=65535&u[h],c[m&o]=u[h],u[h]=m}while(0!=--y);m++}else m+=y,y=0,h=255&l[m],h=(h<<_^255&l[m+1])&w;else n=rt(0,255&l[m]),k--,m++;if(n&&(dt(!1),0===e.avail_out))return 0}return dt(4==t),0===e.avail_out?4==t?2:0:4==t?3:1}(p);break;case 2:O=function(t){let n,i,r=0;for(;;){if(k<262){if(ct(),k<262&&0==t)return 0;if(0===k)break}if(k>=3&&(h=(h<<_^255&l[m+2])&w,r=65535&u[h],c[m&o]=u[h],u[h]=m),A=y,b=v,y=2,0!==r&&A4096)&&(y=2)),A>=3&&y<=A){i=m+k-3,n=rt(m-1-b,A-3),k-=A-1,A-=2;do{++m<=i&&(h=(h<<_^255&l[m+2])&w,r=65535&u[h],c[m&o]=u[h],u[h]=m)}while(0!=--A);if(x=0,y=2,m++,n&&(dt(!1),0===e.avail_out))return 0}else if(0!==x){if(n=rt(0,255&l[m-1]),n&&dt(!1),m++,k--,0===e.avail_out)return 0}else x=1,m++,k--}return 0!==x&&(n=rt(0,255&l[m-1]),x=0),dt(4==t),0===e.avail_out?4==t?2:0:4==t?3:1}(p)}if(2!=O&&3!=O||(n=666),0==O||2==O)return 0===e.avail_out&&(r=-1),0;if(1==O){if(1==p)tt(2,3),et(256,S.static_ltree),it(),1+K+10-X<9&&(tt(2,3),et(256,S.static_ltree),it()),K=7;else if(ot(0,0,!1),3==p)for(E=0;E0&&e.next_in_index!=o&&(r(e.next_in_index),o=e.next_in_index)}while(e.avail_in>0||0===e.avail_out);return s=new Uint8Array(d),c.forEach((function(t){s.set(t,l),l+=t.length})),s}},this.flush=function(){let t,r,a=0,s=0;const o=[];do{if(e.next_out_index=0,e.avail_out=n,t=e.deflate(4),1!=t&&0!=t)throw new Error("deflating: "+e.msg);n-e.avail_out>0&&o.push(new Uint8Array(i.subarray(0,e.next_out_index))),s+=e.next_out_index}while(e.avail_in>0||0===e.avail_out);return e.deflateEnd(),r=new Uint8Array(s),o.forEach((function(t){r.set(t,a),a+=t.length})),r}}z.prototype={deflateInit:function(t,e){const n=this;return n.dstate=new C,e||(e=15),n.dstate.deflateInit(n,t,e)},deflate:function(t){const e=this;return e.dstate?e.dstate.deflate(e,t):-2},deflateEnd:function(){const t=this;if(!t.dstate)return-2;const e=t.dstate.deflateEnd();return t.dstate=null,e},deflateParams:function(t,e){const n=this;return n.dstate?n.dstate.deflateParams(n,t,e):-2},deflateSetDictionary:function(t,e){const n=this;return n.dstate?n.dstate.deflateSetDictionary(n,t,e):-2},read_buf:function(t,e,n){const i=this;let r=i.avail_in;return r>n&&(r=n),0===r?0:(i.avail_in-=r,t.set(i.next_in.subarray(i.next_in_index,i.next_in_index+r),e),i.next_in_index+=r,i.total_in+=r,r)},flush_pending:function(){const t=this;let e=t.dstate.pending;e>t.avail_out&&(e=t.avail_out),0!==e&&(t.next_out.set(t.dstate.pending_buf.subarray(t.dstate.pending_out,t.dstate.pending_out+e),t.next_out_index),t.next_out_index+=e,t.dstate.pending_out+=e,t.total_out+=e,t.avail_out-=e,t.dstate.pending-=e,0===t.dstate.pending&&(t.dstate.pending_out=0))}};const M=[0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535],W=[96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,192,80,7,10,0,8,96,0,8,32,0,9,160,0,8,0,0,8,128,0,8,64,0,9,224,80,7,6,0,8,88,0,8,24,0,9,144,83,7,59,0,8,120,0,8,56,0,9,208,81,7,17,0,8,104,0,8,40,0,9,176,0,8,8,0,8,136,0,8,72,0,9,240,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,200,81,7,13,0,8,100,0,8,36,0,9,168,0,8,4,0,8,132,0,8,68,0,9,232,80,7,8,0,8,92,0,8,28,0,9,152,84,7,83,0,8,124,0,8,60,0,9,216,82,7,23,0,8,108,0,8,44,0,9,184,0,8,12,0,8,140,0,8,76,0,9,248,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,196,81,7,11,0,8,98,0,8,34,0,9,164,0,8,2,0,8,130,0,8,66,0,9,228,80,7,7,0,8,90,0,8,26,0,9,148,84,7,67,0,8,122,0,8,58,0,9,212,82,7,19,0,8,106,0,8,42,0,9,180,0,8,10,0,8,138,0,8,74,0,9,244,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,204,81,7,15,0,8,102,0,8,38,0,9,172,0,8,6,0,8,134,0,8,70,0,9,236,80,7,9,0,8,94,0,8,30,0,9,156,84,7,99,0,8,126,0,8,62,0,9,220,82,7,27,0,8,110,0,8,46,0,9,188,0,8,14,0,8,142,0,8,78,0,9,252,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,194,80,7,10,0,8,97,0,8,33,0,9,162,0,8,1,0,8,129,0,8,65,0,9,226,80,7,6,0,8,89,0,8,25,0,9,146,83,7,59,0,8,121,0,8,57,0,9,210,81,7,17,0,8,105,0,8,41,0,9,178,0,8,9,0,8,137,0,8,73,0,9,242,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,202,81,7,13,0,8,101,0,8,37,0,9,170,0,8,5,0,8,133,0,8,69,0,9,234,80,7,8,0,8,93,0,8,29,0,9,154,84,7,83,0,8,125,0,8,61,0,9,218,82,7,23,0,8,109,0,8,45,0,9,186,0,8,13,0,8,141,0,8,77,0,9,250,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,198,81,7,11,0,8,99,0,8,35,0,9,166,0,8,3,0,8,131,0,8,67,0,9,230,80,7,7,0,8,91,0,8,27,0,9,150,84,7,67,0,8,123,0,8,59,0,9,214,82,7,19,0,8,107,0,8,43,0,9,182,0,8,11,0,8,139,0,8,75,0,9,246,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,206,81,7,15,0,8,103,0,8,39,0,9,174,0,8,7,0,8,135,0,8,71,0,9,238,80,7,9,0,8,95,0,8,31,0,9,158,84,7,99,0,8,127,0,8,63,0,9,222,82,7,27,0,8,111,0,8,47,0,9,190,0,8,15,0,8,143,0,8,79,0,9,254,96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,193,80,7,10,0,8,96,0,8,32,0,9,161,0,8,0,0,8,128,0,8,64,0,9,225,80,7,6,0,8,88,0,8,24,0,9,145,83,7,59,0,8,120,0,8,56,0,9,209,81,7,17,0,8,104,0,8,40,0,9,177,0,8,8,0,8,136,0,8,72,0,9,241,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,201,81,7,13,0,8,100,0,8,36,0,9,169,0,8,4,0,8,132,0,8,68,0,9,233,80,7,8,0,8,92,0,8,28,0,9,153,84,7,83,0,8,124,0,8,60,0,9,217,82,7,23,0,8,108,0,8,44,0,9,185,0,8,12,0,8,140,0,8,76,0,9,249,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,197,81,7,11,0,8,98,0,8,34,0,9,165,0,8,2,0,8,130,0,8,66,0,9,229,80,7,7,0,8,90,0,8,26,0,9,149,84,7,67,0,8,122,0,8,58,0,9,213,82,7,19,0,8,106,0,8,42,0,9,181,0,8,10,0,8,138,0,8,74,0,9,245,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,205,81,7,15,0,8,102,0,8,38,0,9,173,0,8,6,0,8,134,0,8,70,0,9,237,80,7,9,0,8,94,0,8,30,0,9,157,84,7,99,0,8,126,0,8,62,0,9,221,82,7,27,0,8,110,0,8,46,0,9,189,0,8,14,0,8,142,0,8,78,0,9,253,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,195,80,7,10,0,8,97,0,8,33,0,9,163,0,8,1,0,8,129,0,8,65,0,9,227,80,7,6,0,8,89,0,8,25,0,9,147,83,7,59,0,8,121,0,8,57,0,9,211,81,7,17,0,8,105,0,8,41,0,9,179,0,8,9,0,8,137,0,8,73,0,9,243,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,203,81,7,13,0,8,101,0,8,37,0,9,171,0,8,5,0,8,133,0,8,69,0,9,235,80,7,8,0,8,93,0,8,29,0,9,155,84,7,83,0,8,125,0,8,61,0,9,219,82,7,23,0,8,109,0,8,45,0,9,187,0,8,13,0,8,141,0,8,77,0,9,251,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,199,81,7,11,0,8,99,0,8,35,0,9,167,0,8,3,0,8,131,0,8,67,0,9,231,80,7,7,0,8,91,0,8,27,0,9,151,84,7,67,0,8,123,0,8,59,0,9,215,82,7,19,0,8,107,0,8,43,0,9,183,0,8,11,0,8,139,0,8,75,0,9,247,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,207,81,7,15,0,8,103,0,8,39,0,9,175,0,8,7,0,8,135,0,8,71,0,9,239,80,7,9,0,8,95,0,8,31,0,9,159,84,7,99,0,8,127,0,8,63,0,9,223,82,7,27,0,8,111,0,8,47,0,9,191,0,8,15,0,8,143,0,8,79,0,9,255],L=[80,5,1,87,5,257,83,5,17,91,5,4097,81,5,5,89,5,1025,85,5,65,93,5,16385,80,5,3,88,5,513,84,5,33,92,5,8193,82,5,9,90,5,2049,86,5,129,192,5,24577,80,5,2,87,5,385,83,5,25,91,5,6145,81,5,7,89,5,1537,85,5,97,93,5,24577,80,5,4,88,5,769,84,5,49,92,5,12289,82,5,13,90,5,3073,86,5,193,192,5,24577],N=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,0,0],P=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,112,112],V=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],B=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13];function H(){let t,e,n,i,r,a;function s(t,e,s,o,l,d,c,u,h,f,p){let w,_,g,y,b,x,m,v,k,A,E,U,R,S,D;A=0,b=s;do{n[t[e+A]]++,A++,b--}while(0!==b);if(n[0]==s)return c[0]=-1,u[0]=0,0;for(v=u[0],x=1;x<=15&&0===n[x];x++);for(m=x,vb&&(v=b),u[0]=v,S=1<U+v;){if(y++,U+=v,D=g-U,D=D>v?v:D,(_=1<<(x=m-U))>w+1&&(_-=w+1,R=m,x1440)return-3;r[y]=E=f[0],f[0]+=D,0!==y?(a[y]=b,i[0]=x,i[1]=v,x=b>>>U-v,i[2]=E-r[y-1]-x,h.set(i,3*(r[y-1]+x))):c[0]=E}for(i[1]=m-U,A>=s?i[0]=192:p[A]>>U;x>>=1)b^=x;for(b^=x,k=(1<257?(-3==f?h.msg="oversubscribed distance tree":-5==f?(h.msg="incomplete distance tree",f=-3):-4!=f&&(h.msg="empty distance tree with lengths",f=-3),f):0)}}function j(){const t=this;let e,n,i,r,a=0,s=0,o=0,l=0,d=0,c=0,u=0,h=0,f=0,p=0;function w(t,e,n,i,r,a,s,o){let l,d,c,u,h,f,p,w,_,g,y,b,x,m,v,k;p=o.next_in_index,w=o.avail_in,h=s.bitb,f=s.bitk,_=s.write,g=_>=d[k+1],f-=d[k+1],0!=(16&u)){for(u&=15,x=d[k+2]+(h&M[u]),h>>=u,f-=u;f<15;)w--,h|=(255&o.read_byte(p++))<>=d[k+1],f-=d[k+1],0!=(16&u)){for(u&=15;f>=u,f-=u,g-=x,_>=m)v=_-m,_-v>0&&2>_-v?(s.window[_++]=s.window[v++],s.window[_++]=s.window[v++],x-=2):(s.window.set(s.window.subarray(v,v+2),_),_+=2,v+=2,x-=2);else{v=_-m;do{v+=s.end}while(v<0);if(u=s.end-v,x>u){if(x-=u,_-v>0&&u>_-v)do{s.window[_++]=s.window[v++]}while(0!=--u);else s.window.set(s.window.subarray(v,v+u),_),_+=u,v+=u,u=0;v=0}}if(_-v>0&&x>_-v)do{s.window[_++]=s.window[v++]}while(0!=--x);else s.window.set(s.window.subarray(v,v+x),_),_+=x,v+=x,x=0;break}if(0!=(64&u))return o.msg="invalid distance code",x=o.avail_in-w,x=f>>3>3:x,w+=x,p-=x,f-=x<<3,s.bitb=h,s.bitk=f,o.avail_in=w,o.total_in+=p-o.next_in_index,o.next_in_index=p,s.write=_,-3;l+=d[k+2],l+=h&M[u],k=3*(c+l),u=d[k]}break}if(0!=(64&u))return 0!=(32&u)?(x=o.avail_in-w,x=f>>3>3:x,w+=x,p-=x,f-=x<<3,s.bitb=h,s.bitk=f,o.avail_in=w,o.total_in+=p-o.next_in_index,o.next_in_index=p,s.write=_,1):(o.msg="invalid literal/length code",x=o.avail_in-w,x=f>>3>3:x,w+=x,p-=x,f-=x<<3,s.bitb=h,s.bitk=f,o.avail_in=w,o.total_in+=p-o.next_in_index,o.next_in_index=p,s.write=_,-3);if(l+=d[k+2],l+=h&M[u],k=3*(c+l),0===(u=d[k])){h>>=d[k+1],f-=d[k+1],s.window[_++]=d[k+2],g--;break}}else h>>=d[k+1],f-=d[k+1],s.window[_++]=d[k+2],g--}while(g>=258&&w>=10);return x=o.avail_in-w,x=f>>3>3:x,w+=x,p-=x,f-=x<<3,s.bitb=h,s.bitk=f,o.avail_in=w,o.total_in+=p-o.next_in_index,o.next_in_index=p,s.write=_,0}t.init=function(t,a,s,o,l,d){e=0,u=t,h=a,i=s,f=o,r=l,p=d,n=null},t.proc=function(t,_,g){let y,b,x,m,v,k,A,E=0,U=0,R=0;for(R=_.next_in_index,m=_.avail_in,E=t.bitb,U=t.bitk,v=t.write,k=v=258&&m>=10&&(t.bitb=E,t.bitk=U,_.avail_in=m,_.total_in+=R-_.next_in_index,_.next_in_index=R,t.write=v,g=w(u,h,i,f,r,p,t,_),R=_.next_in_index,m=_.avail_in,E=t.bitb,U=t.bitk,v=t.write,k=v>>=n[b+1],U-=n[b+1],x=n[b],0===x){l=n[b+2],e=6;break}if(0!=(16&x)){d=15&x,a=n[b+2],e=2;break}if(0==(64&x)){o=x,s=b/3+n[b+2];break}if(0!=(32&x)){e=7;break}return e=9,_.msg="invalid literal/length code",g=-3,t.bitb=E,t.bitk=U,_.avail_in=m,_.total_in+=R-_.next_in_index,_.next_in_index=R,t.write=v,t.inflate_flush(_,g);case 2:for(y=d;U>=y,U-=y,o=h,n=r,s=p,e=3;case 3:for(y=o;U>=n[b+1],U-=n[b+1],x=n[b],0!=(16&x)){d=15&x,c=n[b+2],e=4;break}if(0==(64&x)){o=x,s=b/3+n[b+2];break}return e=9,_.msg="invalid distance code",g=-3,t.bitb=E,t.bitk=U,_.avail_in=m,_.total_in+=R-_.next_in_index,_.next_in_index=R,t.write=v,t.inflate_flush(_,g);case 4:for(y=d;U>=y,U-=y,e=5;case 5:for(A=v-c;A<0;)A+=t.end;for(;0!==a;){if(0===k&&(v==t.end&&0!==t.read&&(v=0,k=v7&&(U-=8,m++,R--),t.write=v,g=t.inflate_flush(_,g),v=t.write,k=vt.avail_out&&(i=t.avail_out),0!==i&&-5==e&&(e=0),t.avail_out-=i,t.total_out+=i,t.next_out.set(n.window.subarray(a,a+i),r),r+=i,a+=i,a==n.end&&(a=0,n.write==n.end&&(n.write=0),i=n.write-a,i>t.avail_out&&(i=t.avail_out),0!==i&&-5==e&&(e=0),t.avail_out-=i,t.total_out+=i,t.next_out.set(n.window.subarray(a,a+i),r),r+=i,a+=i),t.next_out_index=r,n.read=a,e},n.proc=function(t,e){let p,w,_,g,y,b,x,m;for(g=t.next_in_index,y=t.avail_in,w=n.bitb,_=n.bitk,b=n.write,x=b>>1){case 0:w>>>=3,_-=3,p=7&_,w>>>=p,_-=p,r=1;break;case 1:v=[],k=[],A=[[]],E=[[]],H.inflate_trees_fixed(v,k,A,E),c.init(v[0],k[0],A[0],0,E[0],0),w>>>=3,_-=3,r=6;break;case 2:w>>>=3,_-=3,r=3;break;case 3:return w>>>=3,_-=3,r=9,t.msg="invalid block type",e=-3,n.bitb=w,n.bitk=_,t.avail_in=y,t.total_in+=g-t.next_in_index,t.next_in_index=g,n.write=b,n.inflate_flush(t,e)}break;case 1:for(;_<32;){if(0===y)return n.bitb=w,n.bitk=_,t.avail_in=y,t.total_in+=g-t.next_in_index,t.next_in_index=g,n.write=b,n.inflate_flush(t,e);e=0,y--,w|=(255&t.read_byte(g++))<<_,_+=8}if((~w>>>16&65535)!=(65535&w))return r=9,t.msg="invalid stored block lengths",e=-3,n.bitb=w,n.bitk=_,t.avail_in=y,t.total_in+=g-t.next_in_index,t.next_in_index=g,n.write=b,n.inflate_flush(t,e);a=65535&w,w=_=0,r=0!==a?2:0!==u?7:0;break;case 2:if(0===y)return n.bitb=w,n.bitk=_,t.avail_in=y,t.total_in+=g-t.next_in_index,t.next_in_index=g,n.write=b,n.inflate_flush(t,e);if(0===x&&(b==n.end&&0!==n.read&&(b=0,x=by&&(p=y),p>x&&(p=x),n.window.set(t.read_buf(g,p),b),g+=p,y-=p,b+=p,x-=p,0!=(a-=p))break;r=0!==u?7:0;break;case 3:for(;_<14;){if(0===y)return n.bitb=w,n.bitk=_,t.avail_in=y,t.total_in+=g-t.next_in_index,t.next_in_index=g,n.write=b,n.inflate_flush(t,e);e=0,y--,w|=(255&t.read_byte(g++))<<_,_+=8}if(s=p=16383&w,(31&p)>29||(p>>5&31)>29)return r=9,t.msg="too many length or distance symbols",e=-3,n.bitb=w,n.bitk=_,t.avail_in=y,t.total_in+=g-t.next_in_index,t.next_in_index=g,n.write=b,n.inflate_flush(t,e);if(p=258+(31&p)+(p>>5&31),!i||i.length>>=14,_-=14,o=0,r=4;case 4:for(;o<4+(s>>>10);){for(;_<3;){if(0===y)return n.bitb=w,n.bitk=_,t.avail_in=y,t.total_in+=g-t.next_in_index,t.next_in_index=g,n.write=b,n.inflate_flush(t,e);e=0,y--,w|=(255&t.read_byte(g++))<<_,_+=8}i[q[o++]]=7&w,w>>>=3,_-=3}for(;o<19;)i[q[o++]]=0;if(l[0]=7,p=f.inflate_trees_bits(i,l,d,h,t),0!=p)return-3==(e=p)&&(i=null,r=9),n.bitb=w,n.bitk=_,t.avail_in=y,t.total_in+=g-t.next_in_index,t.next_in_index=g,n.write=b,n.inflate_flush(t,e);o=0,r=5;case 5:for(;p=s,!(o>=258+(31&p)+(p>>5&31));){let a,c;for(p=l[0];_>>=p,_-=p,i[o++]=c;else{for(m=18==c?7:c-14,a=18==c?11:3;_>>=p,_-=p,a+=w&M[m],w>>>=m,_-=m,m=o,p=s,m+a>258+(31&p)+(p>>5&31)||16==c&&m<1)return i=null,r=9,t.msg="invalid bit length repeat",e=-3,n.bitb=w,n.bitk=_,t.avail_in=y,t.total_in+=g-t.next_in_index,t.next_in_index=g,n.write=b,n.inflate_flush(t,e);c=16==c?i[m-1]:0;do{i[m++]=c}while(0!=--a);o=m}}if(d[0]=-1,U=[],R=[],S=[],D=[],U[0]=9,R[0]=6,p=s,p=f.inflate_trees_dynamic(257+(31&p),1+(p>>5&31),i,U,R,S,D,h,t),0!=p)return-3==p&&(i=null,r=9),e=p,n.bitb=w,n.bitk=_,t.avail_in=y,t.total_in+=g-t.next_in_index,t.next_in_index=g,n.write=b,n.inflate_flush(t,e);c.init(U[0],R[0],h,S[0],h,D[0]),r=6;case 6:if(n.bitb=w,n.bitk=_,t.avail_in=y,t.total_in+=g-t.next_in_index,t.next_in_index=g,n.write=b,1!=(e=c.proc(n,t,e)))return n.inflate_flush(t,e);if(e=0,c.free(t),g=t.next_in_index,y=t.avail_in,w=n.bitb,_=n.bitk,b=n.write,x=b15?(t.inflateEnd(n),-2):(t.wbits=i,n.istate.blocks=new Z(n,1<>4)>r.wbits){r.mode=13,t.msg="invalid window size",r.marker=5;break}r.mode=1;case 1:if(0===t.avail_in)return n;if(n=e,t.avail_in--,t.total_in++,i=255&t.read_byte(t.next_in_index++),((r.method<<8)+i)%31!=0){r.mode=13,t.msg="incorrect header check",r.marker=5;break}if(0==(32&i)){r.mode=7;break}r.mode=2;case 2:if(0===t.avail_in)return n;n=e,t.avail_in--,t.total_in++,r.need=(255&t.read_byte(t.next_in_index++))<<24&4278190080,r.mode=3;case 3:if(0===t.avail_in)return n;n=e,t.avail_in--,t.total_in++,r.need+=(255&t.read_byte(t.next_in_index++))<<16&16711680,r.mode=4;case 4:if(0===t.avail_in)return n;n=e,t.avail_in--,t.total_in++,r.need+=(255&t.read_byte(t.next_in_index++))<<8&65280,r.mode=5;case 5:return 0===t.avail_in?n:(n=e,t.avail_in--,t.total_in++,r.need+=255&t.read_byte(t.next_in_index++),r.mode=6,2);case 6:return r.mode=13,t.msg="need dictionary",r.marker=0,-2;case 7:if(n=r.blocks.proc(t,n),-3==n){r.mode=13,r.marker=0;break}if(0==n&&(n=e),1!=n)return n;n=e,r.blocks.reset(t,r.was),r.mode=12;case 12:return 1;case 13:return-3;default:return-2}},t.inflateSetDictionary=function(t,e,n){let i=0,r=n;if(!t||!t.istate||6!=t.istate.mode)return-2;const a=t.istate;return r>=1<0&&t.next_in_index!=l&&(r(t.next_in_index),l=t.next_in_index)}while(t.avail_in>0||0===t.avail_out);return o=new Uint8Array(c),a.forEach((function(t){o.set(t,d),d+=t.length})),o}},this.flush=function(){t.inflateEnd()}}X.prototype={inflateInit:function(t){const e=this;return e.istate=new Y,t||(t=15),e.istate.inflateInit(e,t)},inflate:function(t){const e=this;return e.istate?e.istate.inflate(e,t):-2},inflateEnd:function(){const t=this;if(!t.istate)return-2;const e=t.istate.inflateEnd(t);return t.istate=null,e},inflateSync:function(){const t=this;return t.istate?t.istate.inflateSync(t):-2},inflateSetDictionary:function(t,e){const n=this;return n.istate?n.istate.inflateSetDictionary(n,t,e):-2},read_byte:function(t){return this.next_in.subarray(t,t+1)[0]},read_buf:function(t,e){return this.next_in.subarray(t,t+e)}},self.initCodec=()=>{self.Deflate=O,self.Inflate=G}}).toString(),e=URL.createObjectURL(new Blob(["("+t+")()"],{type:"text/javascript"}));r({workerScripts:{inflate:[e],deflate:[e]}})}})(),t.BlobReader=y,t.BlobWriter=class extends g{constructor(t){super(),this.offset=0,this.contentType=t,this.blob=new Blob([],{type:t})}async writeUint8Array(t){super.writeUint8Array(t),this.blob=new Blob([this.blob,t.buffer],{type:this.contentType}),this.offset=this.blob.size}getData(){return this.blob}},t.Data64URIReader=class extends _{constructor(t){super(),this.dataURI=t;let e=t.length;for(;"="==t.charAt(e-1);)e--;this.dataStart=t.indexOf(",")+1,this.size=Math.floor(.75*(e-this.dataStart))}async readUint8Array(t,e){const n=new Uint8Array(e),i=4*Math.floor(t/3),r=atob(this.dataURI.substring(i+this.dataStart,4*Math.ceil((t+e)/3)+this.dataStart)),a=t-3*Math.floor(i/4);for(let t=a;t2?this.data+=btoa(n):this.pending=n}getData(){return this.data+btoa(this.pending)}},t.ERR_BAD_FORMAT=Tt,t.ERR_CENTRAL_DIRECTORY_NOT_FOUND=Mt,t.ERR_DUPLICATED_NAME=te,t.ERR_ENCRYPTED=Nt,t.ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND=Ot,t.ERR_EOCDR_NOT_FOUND=Ct,t.ERR_EOCDR_ZIP64_NOT_FOUND=zt,t.ERR_EXTRAFIELD_ZIP64_NOT_FOUND=Lt,t.ERR_HTTP_RANGE=l,t.ERR_INVALID_COMMENT=ee,t.ERR_INVALID_DATE=ae,t.ERR_INVALID_ENCRYPTION_STRENGTH=se,t.ERR_INVALID_ENTRY_COMMENT=ne,t.ERR_INVALID_ENTRY_NAME=ie,t.ERR_INVALID_EXTRAFIELD_DATA=le,t.ERR_INVALID_EXTRAFIELD_TYPE=oe,t.ERR_INVALID_PASSWORD=j,t.ERR_INVALID_SIGNATURE=_t,t.ERR_INVALID_VERSION=re,t.ERR_LOCAL_FILE_HEADER_NOT_FOUND=Wt,t.ERR_UNSUPPORTED_COMPRESSION=Vt,t.ERR_UNSUPPORTED_ENCRYPTION=Pt,t.HttpRangeReader=class extends E{constructor(t,e={}){e.useRangeHeader=!0,super(t,e)}},t.HttpReader=E,t.Reader=_,t.TextReader=class extends _{constructor(t){super(),this.blobReader=new y(new Blob([t],{type:d}))}async init(){super.init(),this.blobReader.init(),this.size=this.blobReader.size}async readUint8Array(t,e){return this.blobReader.readUint8Array(t,e)}},t.TextWriter=class extends g{constructor(t){super(),this.encoding=t,this.blob=new Blob([],{type:d})}async writeUint8Array(t){super.writeUint8Array(t),this.blob=new Blob([this.blob,t.buffer],{type:d})}getData(){const t=new FileReader;return new Promise(((e,n)=>{t.onload=t=>e(t.target.result),t.onerror=n,t.readAsText(this.blob,this.encoding)}))}},t.Uint8ArrayReader=class extends _{constructor(t){super(),this.array=t,this.size=t.length}async readUint8Array(t,e){return this.array.slice(t,t+e)}},t.Uint8ArrayWriter=U,t.Writer=g,t.ZipReader=class extends class{constructor(t,e={},n={}){this.reader=t,this.options=e,this.config=n}async getEntries(t={}){const e=this.reader;if(e.initialized||await e.init(),e.size<22)throw new Error(Tt);const n=await async function(t,e,n,i){const r=new Uint8Array(4);!function(t,e,n){t.setUint32(e,n,!0)}(new DataView(r.buffer),0,e);const a=n+i;let s=n,o=await l(s);o||(o=await l(Math.min(a,t.size)));return o;async function l(e){const i=t.size-e,a=await t.readUint8Array(i,e);for(let t=a.length-n;t>=0;t--)if(a[t]==r[0]&&a[t+1]==r[1]&&a[t+2]==r[2]&&a[t+3]==r[3])return{offset:i,buffer:a.slice(t,t+n).buffer}}}(e,C,22,1048560);if(!n)throw new Error(Ct);const i=new DataView(n.buffer);let r,a=Qt(i,12),s=Qt(i,16),o=Jt(i,8),l=0;if(s==S||o==D){r=!0;const t=await e.readUint8Array(n.offset-20,20),i=new DataView(t.buffer);if(Number(Qt(i,0))!=O)throw new Error(zt);s=Number($t(i,8));let d=await e.readUint8Array(s,56),c=new DataView(d.buffer);if(Number(Qt(c,0))!=z&&s!=n.offset-20-56){const t=s;s=n.offset-20-56,l=s-t,d=await e.readUint8Array(s,56),c=new DataView(d.buffer)}if(Number(Qt(c,0))!=z)throw new Error(Ot);o=Number($t(c,24)),a=Number($t(i,4)),s-=Number($t(c,40))}if(s<0||!r&&(s>=e.size||o>=D))throw new Error(Tt);let d=0,c=await e.readUint8Array(s,e.size-s),u=new DataView(c.buffer);if(Qt(u,d)!=T&&s!=n.offset-a){const t=s;s=n.offset-a,l=s-t,c=await e.readUint8Array(s,e.size-s),u=new DataView(c.buffer)}if(s<0)throw new Error(Tt);const h=[];for(let e=0;ee.getData(t,n),h.push(r),d+=46+e.filenameLength+e.extraFieldLength+e.commentLength}return h}async close(){}}{constructor(t,e){super(t,e,i())}},t.ZipWriter=class extends class{constructor(t,e={},n={}){this.writer=t,this.options=e,this.config=n,this.files=new Map,this.offset=t.size}async add(t="",e,n={}){if(t=t.trim(),n.directory&&!t.endsWith(L)?t+=L:n.directory=t.endsWith(L),this.files.has(t))throw new Error(te);const i=(new TextEncoder).encode(t);if(i.length>D)throw new Error(ie);const r=n.comment||"",a=(new TextEncoder).encode(r);if(a.length>D)throw new Error(ne);const s=this.options.version||n.version||0;if(s>D)throw new Error(re);const o=n.lastModDate||new Date;if(oN)throw new Error(ae);const l=ce(this,n,"password"),d=ce(this,n,"encryptionStrength")||3;if(void 0!==l&&void 0!==d&&(d<1||d>3))throw new Error(se);e&&!e.initialized&&await e.init();let c=new Uint8Array(0);const u=n.extraField;if(u){let t=0,e=0;u.forEach((e=>t+=4+e.length)),c=new Uint8Array(t),u.forEach(((t,n)=>{if(n>D)throw new Error(oe);if(t.length>D)throw new Error(le);c.set(new Uint16Array([n]),e),c.set(new Uint16Array([t.length]),e+2),c.set(t,e+4),e+=4+t.length}))}const h=n.zip64||this.options.zip64||this.offset>=S||e&&(e.size>=S||this.offset+e.size>=S),f=ce(this,n,"level"),p=ce(this,n,"useWebWorkers"),w=ce(this,n,"bufferedWrite"),_=ce(this,n,"keepOrder"),g=await async function(t,e,n,i){const r=t.files,a=t.writer;let s,o,l;r.set(e,null);try{let d,c;try{i.keepOrder&&(o=t.lockPreviousFile,t.lockPreviousFile=new Promise((t=>l=t))),i.bufferedWrite||t.lockWrite?(d=new U,await d.init()):(t.lockWrite=new Promise((t=>s=t)),a.initialized||await a.init(),d=a),c=await async function(t,e,n,i){const r=i.rawFilename,a=i.lastModDate,s=i.password,o=Boolean(s&&s.length),l=i.level,d=0!==l&&!i.directory,c=i.zip64;let u,h;if(o){u=new Uint8Array(de.length+2);const t=new DataView(u.buffer);he(t,0,M),u.set(de,2),h=i.encryptionStrength,ue(t,8,h)}else u=new Uint8Array(0);const f={version:i.version||20,zip64:c,directory:Boolean(i.directory),filenameUTF8:!0,rawFilename:r,commentUTF8:!0,rawComment:i.rawComment,rawExtraFieldZip64:c?new Uint8Array(28):new Uint8Array(0),rawExtraFieldAES:u,rawExtraField:i.rawExtraField};let p=2056,w=0;d&&(w=8);c&&(f.version=f.version>45?f.version:45);o&&(f.version=f.version>51?f.version:51,p|=1,w=99,d&&(f.rawExtraFieldAES[9]=8));const _=f.headerArray=new Uint8Array(26),g=new DataView(_.buffer);he(g,0,f.version),he(g,2,p),he(g,4,w),he(g,6,(a.getHours()<<6|a.getMinutes())<<5|a.getSeconds()/2),he(g,8,(a.getFullYear()-1980<<4|a.getMonth()+1)<<5|a.getDate()),he(g,22,r.length),he(g,24,0);const y=g.getUint32(6,!0),b=new Uint8Array(30+r.length);fe(new DataView(b.buffer),0,F),b.set(_,4),b.set(r,30);let x,m=0,v=0;if(t){m=t.size;const r=await Ut({codecType:pt,codecConstructor:n.Deflate,level:l,outputPassword:s,outputEncryptionStrength:h,outputSigned:!0,outputCompressed:d,outputEncrypted:o,useWebWorkers:i.useWebWorkers},n);await e.writeUint8Array(b),x=await St(r,t,e,0,m,n,{onprogress:i.onprogress}),v=x.length}else await e.writeUint8Array(b);const k=new Uint8Array(c?24:16),A=new DataView(k.buffer);if(fe(A,0,I),t)if(o||void 0===x.signature||(fe(g,10,x.signature),fe(A,4,x.signature),f.signature=x.signature),c){const t=new DataView(f.rawExtraFieldZip64.buffer);he(t,0,1),he(t,2,24),fe(g,14,S),pe(A,8,BigInt(v)),pe(t,12,BigInt(v)),fe(g,18,S),pe(A,16,BigInt(m)),pe(t,4,BigInt(m))}else fe(g,14,v),fe(A,8,v),fe(g,18,m),fe(A,12,m);return await e.writeUint8Array(k),f.compressedSize=v,f.uncompressedSize=m,f.lastModDate=a,f.rawLastModDate=y,f.encrypted=o,f.length=b.length+(x?x.length:0)+k.length,f}(n,d,t.config,i)}catch(t){throw r.delete(e),t}if(r.set(e,c),d!=a&&(t.lockWrite&&await t.lockWrite,o&&await o,await a.writeUint8Array(d.getData())),c.offset=t.offset,c.zip64){pe(new DataView(c.rawExtraFieldZip64.buffer),20,BigInt(c.offset))}return t.offset+=c.length,c}finally{l&&l(),s&&(t.lockWrite=null,s())}}(this,t,e,Object.assign({},n,{rawFilename:i,rawComment:a,version:s,lastModDate:o,rawExtraField:c,zip64:h,password:l,level:f,useWebWorkers:p,encryptionStrength:d,bufferedWrite:w,keepOrder:_}));return g.filename=t,g.comment=r,g.extraField=u,new It(g)}async close(t=new Uint8Array(0)){const e=this.writer,n=this.files;let i=0,r=0,a=this.offset,s=n.size;for(const[,t]of n)r+=46+t.rawFilename.length+t.rawComment.length+t.rawExtraFieldZip64.length+t.rawExtraFieldAES.length+t.rawExtraField.length;const o=this.options.zip64||a+r>=S||s>=D,l=new Uint8Array(r+(o?98:22)),d=new DataView(l.buffer);if(t.length){if(!(t.length<=D))throw new Error(ee);he(d,i+20,t.length)}for(const[,t]of n){const e=t.rawFilename,n=t.rawExtraFieldZip64,r=t.rawExtraFieldAES,a=n.length+r.length+t.rawExtraField.length;fe(d,i,T),he(d,i+4,t.version),l.set(t.headerArray,i+6),he(d,i+30,a),he(d,i+32,t.rawComment.length),t.directory&&ue(d,i+38,16),t.zip64?fe(d,i+42,S):fe(d,i+42,t.offset),l.set(e,i+46),l.set(n,i+46+e.length),l.set(r,i+46+e.length+n.length),l.set(t.rawExtraField,46+e.length+n.length+r.length),l.set(t.rawComment,i+46+e.length+a),i+=46+e.length+a+t.rawComment.length}return o&&(fe(d,i,z),pe(d,i+4,BigInt(44)),he(d,i+12,45),he(d,i+14,45),pe(d,i+24,BigInt(s)),pe(d,i+32,BigInt(s)),pe(d,i+40,BigInt(r)),pe(d,i+48,BigInt(a)),fe(d,i+56,O),pe(d,i+64,BigInt(a+r)),fe(d,i+72,1),s=D,a=S,i+=76),fe(d,i,C),he(d,i+8,s),he(d,i+10,s),fe(d,i+12,r),fe(d,i+16,a),await e.writeUint8Array(l),t.length&&await e.writeUint8Array(t),e.getData()}}{constructor(t,e){super(t,e,i())}},t.configure=r,t.getMimeType=function(){return"application/octet-stream"},t.initShimAsyncCodec=(t,e={})=>({Deflate:s(t.Deflate,e.deflate),Inflate:s(t.Inflate,e.inflate)}),Object.defineProperty(t,"__esModule",{value:!0})})); diff --git a/src/main/resources/templates/error.html b/src/main/resources/templates/error.html new file mode 100644 index 0000000..db3e00d --- /dev/null +++ b/src/main/resources/templates/error.html @@ -0,0 +1,40 @@ + + + + + + + + Corona Warn App - Log Upload + + + + +
    + + +
    + + + + + + + + + +
    + +
    +
    + +
    + + + + diff --git a/src/main/resources/templates/not_found.html b/src/main/resources/templates/not_found.html new file mode 100644 index 0000000..f269f7d --- /dev/null +++ b/src/main/resources/templates/not_found.html @@ -0,0 +1,40 @@ + + + + + + + + Corona Warn App - Log Upload + + + + +
    + + +
    + + + + + + + + + +
    + Es konnte keine Log-Datei mit der ID gefunden werden. +
    +
    + +
    + + + + diff --git a/src/main/resources/templates/search.html b/src/main/resources/templates/search.html new file mode 100644 index 0000000..589c10f --- /dev/null +++ b/src/main/resources/templates/search.html @@ -0,0 +1,92 @@ + + + + + + + + + + + Corona Warn App - Log Upload + + + + + + + + + + +
    + + +
    + + + + + + + + + +
    +
    + Dateigröße:
    + Log-ID:
    +
    + +
    +
    + Die Datei steht zum Download bereit. Klicken Sie auf den Download Button um die Datei herunterzuladen und den + Inhalt im Browser zu betrachten. +
    + +
    + +
    +
    + +
    +
    + Bitte warten Sie. Die Datei wird heruntergeladen... +
    +
    + +
    +
    + Klicken Sie eine Datei an, um Sie anzuzeigen. +
    +
      + + +
    +
    + + + + + + + + +
    +
    + + +
    + + + + + + diff --git a/src/main/resources/templates/start.html b/src/main/resources/templates/start.html new file mode 100644 index 0000000..18e2a37 --- /dev/null +++ b/src/main/resources/templates/start.html @@ -0,0 +1,55 @@ + + + + + + + + Corona Warn App - Log Upload + + + + +
    + + +
    + + + + + + + + + +
    Log-Datei suchen
    +
    Geben Sie eine Log-ID ein, um die entsprechende Datei anzuzeigen.
    +
    +
    + + +
    + + + + + + +
    +
    +
    + + +
    + + + + + + diff --git a/src/test/java/app/coronawarn/logupload/controller/LogDownloadApiControllerTest.java b/src/test/java/app/coronawarn/logupload/controller/LogDownloadApiControllerTest.java new file mode 100644 index 0000000..5aa1c08 --- /dev/null +++ b/src/test/java/app/coronawarn/logupload/controller/LogDownloadApiControllerTest.java @@ -0,0 +1,91 @@ +package app.coronawarn.logupload.controller; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import app.coronawarn.logupload.model.LogEntity; +import app.coronawarn.logupload.service.FileStorageService; +import com.c4_soft.springaddons.security.oauth2.test.annotations.keycloak.WithMockKeycloakAuth; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.time.ZonedDateTime; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpHeaders; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultMatcher; + +@WebMvcTest(controllers = LogDownloadApiController.class) +@ContextConfiguration(classes = LogDownloadApiController.class) +@ActiveProfiles("portal") +public class LogDownloadApiControllerTest { + + @Autowired + MockMvc mockMvc; + + @MockBean + FileStorageService fileStorageServiceMock; + + private final static byte[] dummyBytes = new byte[]{0xD, 0xE, 0xA, 0xD, 0xB, 0xE, 0xE, 0xF}; + private final static String dummyFileName = "dummy.zip"; + private final static String dummyHash = "hash123456789"; + private final static String dummyLogId = "ABCDEFG"; + private final static LogEntity dummyLogEntity = new LogEntity(dummyLogId, ZonedDateTime.now(), dummyFileName, dummyBytes.length, dummyHash, ""); + + @Test + @WithMockKeycloakAuth + public void testLogDownload() throws Exception { + InputStream stream = new ByteArrayInputStream(dummyBytes); + + given(fileStorageServiceMock.downloadFile(eq(dummyLogId))) + .willReturn(new FileStorageService.LogDownloadResponse(dummyLogEntity, stream)); + + mockMvc.perform(get("/portal/api/logs/" + dummyLogId).header("Host", "localhost:8085")) + .andExpect(ResultMatcher.matchAll( + status().isOk(), + header().string(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + dummyFileName + "\""), + header().longValue(HttpHeaders.CONTENT_LENGTH, dummyBytes.length), + content().bytes(dummyBytes) + )); + } + + @Test + public void testLogDownloadShouldFailWithInvalidRole() throws Exception { + InputStream stream = new ByteArrayInputStream(dummyBytes); + + given(fileStorageServiceMock.downloadFile(eq(dummyLogId))) + .willReturn(new FileStorageService.LogDownloadResponse(dummyLogEntity, stream)); + + mockMvc.perform(get("/portal/api/logs/" + dummyLogId).header("Host", "localhost:8085")) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockKeycloakAuth + public void testLogDownloadShouldReturn404IfFileNotFound() throws Exception { + given(fileStorageServiceMock.downloadFile(eq(dummyLogId))) + .willThrow(new FileStorageService.FileStoreException(FileStorageService.FileStoreException.Reason.FILE_NOT_FOUND)); + + mockMvc.perform(get("/portal/api/logs/" + dummyLogId).header("Host", "localhost:8085")) + .andExpect(status().isNotFound()); + } + + @Test + @WithMockKeycloakAuth + public void testLogDownloadShouldReturn500IfDownloadFails() throws Exception { + given(fileStorageServiceMock.downloadFile(eq(dummyLogId))) + .willThrow(new FileStorageService.FileStoreException(FileStorageService.FileStoreException.Reason.S3_DOWNLOAD_FAILED)); + + mockMvc.perform(get("/portal/api/logs/" + dummyLogId).header("Host", "localhost:8085")) + .andExpect(status().isInternalServerError()); + + } +} diff --git a/src/test/java/app/coronawarn/logupload/controller/LogUploadApiControllerTest.java b/src/test/java/app/coronawarn/logupload/controller/LogUploadApiControllerTest.java new file mode 100644 index 0000000..e79079e --- /dev/null +++ b/src/test/java/app/coronawarn/logupload/controller/LogUploadApiControllerTest.java @@ -0,0 +1,163 @@ +package app.coronawarn.logupload.controller; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import app.coronawarn.logupload.model.LogEntity; +import app.coronawarn.logupload.service.FileStorageService; +import app.coronawarn.logupload.service.OtpService; +import com.c4_soft.springaddons.security.oauth2.test.annotations.keycloak.WithMockKeycloakAuth; +import java.io.InputStream; +import java.time.ZonedDateTime; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultMatcher; + +@WebMvcTest(controllers = LogUploadApiController.class) +@ContextConfiguration(classes = {LogUploadApiController.class}) +@ActiveProfiles("api") +public class LogUploadApiControllerTest { + + + @Autowired + MockMvc mockMvc; + + @MockBean + FileStorageService fileStorageServiceMock; + + @MockBean + OtpService otpServiceMock; + + private final static byte[] dummyBytes = new byte[]{0xD, 0xE, 0xA, 0xD, 0xB, 0xE, 0xE, 0xF}; + private final static String dummyFileName = "dummy.zip"; + private final static String dummyHash = "hash123456789"; + private final static String dummyLogId = "ABCDEFG"; + private final static String OTP_HEADER = "cwa-otp"; + private final static LogEntity dummyLogEntity = new LogEntity(dummyLogId, ZonedDateTime.now(), dummyFileName, dummyBytes.length, dummyHash, ""); + private final static String testOtp = "ea8166fa-6a42-426a-8b14-a4c53ff710b5"; + + @Test + @WithMockKeycloakAuth + public void testLogUpload() throws Exception { + ArgumentCaptor streamArgumentCaptor = ArgumentCaptor.forClass(InputStream.class); + + given(fileStorageServiceMock.storeFileStream(eq(dummyFileName), eq(Long.valueOf(dummyBytes.length)), streamArgumentCaptor.capture())) + .willReturn(dummyLogEntity); + + given(otpServiceMock.verifyOtp(eq(testOtp))).willReturn(true); + + MockMultipartFile mockMultipartFile = new MockMultipartFile("file", dummyFileName, "", dummyBytes); + mockMvc.perform(multipart("/api/logs") + .file(mockMultipartFile) + .with(csrf().asHeader()) + .header(OTP_HEADER, testOtp) + .header(HttpHeaders.HOST, "localhost:8085") + .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON) + ) + .andExpect(ResultMatcher.matchAll( + status().isCreated(), + jsonPath("hash").value(dummyHash), + jsonPath("id").value(dummyLogId) + )); + + assertArrayEquals(dummyBytes, streamArgumentCaptor.getValue().readAllBytes()); + } + + @Test + @WithMockKeycloakAuth + public void testLogUploadShouldFailIfFileNotSent() throws Exception { + + mockMvc.perform(multipart("/api/logs") + .with(csrf().asHeader()) + .header(OTP_HEADER, testOtp) + .header(HttpHeaders.HOST, "localhost:8085") + .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON) + ) + .andExpect(status().isBadRequest()); + } + + @Test + @WithMockKeycloakAuth + public void testLogUploadShouldReturn500IfUploadFailed() throws Exception { + + given(otpServiceMock.verifyOtp(eq(testOtp))).willReturn(true); + + given(fileStorageServiceMock.storeFileStream(eq(dummyFileName), eq(Long.valueOf(dummyBytes.length)), any(InputStream.class))) + .willThrow(new FileStorageService.FileStoreException(FileStorageService.FileStoreException.Reason.S3_UPLOAD_FAILED)); + + MockMultipartFile mockMultipartFile = new MockMultipartFile("file", dummyFileName, "", dummyBytes); + mockMvc.perform(multipart("/api/logs") + .file(mockMultipartFile) + .with(csrf().asHeader()) + .header(OTP_HEADER, testOtp) + .header(HttpHeaders.HOST, "localhost:8085") + .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON) + ) + .andExpect(status().isInternalServerError()); + } + + @Test + @WithMockKeycloakAuth + public void testLogUploadShouldFailWithWrongOTP() throws Exception { + given(otpServiceMock.verifyOtp(eq(testOtp))).willReturn(false); + + MockMultipartFile mockMultipartFile = new MockMultipartFile("file", dummyFileName, "", dummyBytes); + mockMvc.perform(multipart("/api/logs") + .file(mockMultipartFile) + .with(csrf().asHeader()) + .header(OTP_HEADER, testOtp) + .header(HttpHeaders.HOST, "localhost:8085") + .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON) + ).andExpect(status().isUnauthorized()); + + verifyNoInteractions(fileStorageServiceMock); + } + + @Test + @WithMockKeycloakAuth + public void testLogUploadShouldFailWithoutOTP() throws Exception { + MockMultipartFile mockMultipartFile = new MockMultipartFile("file", dummyFileName, "", dummyBytes); + mockMvc.perform(multipart("/api/logs") + .file(mockMultipartFile) + .with(csrf().asHeader()) + .header(HttpHeaders.HOST, "localhost:8085") + .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON) + ).andExpect(status().isBadRequest()); + + verifyNoInteractions(fileStorageServiceMock); + verifyNoInteractions(otpServiceMock); + } + + @Test + @WithMockKeycloakAuth + public void testLogUploadShouldFailWithInvalidOTP() throws Exception { + MockMultipartFile mockMultipartFile = new MockMultipartFile("file", dummyFileName, "", dummyBytes); + mockMvc.perform(multipart("/api/logs") + .file(mockMultipartFile) + .with(csrf().asHeader()) + .header(OTP_HEADER, "no-uuid") + .header(HttpHeaders.HOST, "localhost:8085") + .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON) + ).andExpect(status().isBadRequest()); + + verifyNoInteractions(fileStorageServiceMock); + verifyNoInteractions(otpServiceMock); + } + +} diff --git a/src/test/java/app/coronawarn/logupload/controller/LogUploadPortalControllerTest.java b/src/test/java/app/coronawarn/logupload/controller/LogUploadPortalControllerTest.java new file mode 100644 index 0000000..516f0ad --- /dev/null +++ b/src/test/java/app/coronawarn/logupload/controller/LogUploadPortalControllerTest.java @@ -0,0 +1,120 @@ +package app.coronawarn.logupload.controller; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.given; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; + +import app.coronawarn.logupload.config.LogUploadConfig; +import app.coronawarn.logupload.model.LogEntity; +import app.coronawarn.logupload.service.FileStorageService; +import app.coronawarn.logupload.service.LogService; +import com.c4_soft.springaddons.security.oauth2.test.annotations.OidcStandardClaims; +import com.c4_soft.springaddons.security.oauth2.test.annotations.keycloak.WithMockKeycloakAuth; +import java.time.ZonedDateTime; +import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatchers; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpHeaders; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultMatcher; + +@WebMvcTest(controllers = LogUploadPortalController.class) +@ContextConfiguration(classes = LogUploadPortalController.class) +@ActiveProfiles("portal") +public class LogUploadPortalControllerTest { + + @Autowired + MockMvc mockMvc; + + @MockBean + FileStorageService fileStorageServiceMock; + + @MockBean + LogUploadConfig logUploadConfigMock; + + @MockBean + LogService logServiceMock; + + private final static String dummyFileName = "dummy.zip"; + private final static String dummyHash = "hash123456789"; + private final static String dummyLogId = "ABCDEFG"; + private final static LogEntity dummyLogEntity = new LogEntity(dummyLogId, ZonedDateTime.now(), dummyFileName, 8, dummyHash, ""); + private final static String dummyPwResetUrl = "https://dummy.de"; + private final static String dummyUsername = "dummy"; + + @BeforeEach + public void setup() { + given(logUploadConfigMock.getKeycloakPwResetUrl()).willReturn(dummyPwResetUrl); + } + + @Test + @WithMockKeycloakAuth(oidc = @OidcStandardClaims(preferredUsername = dummyUsername)) + public void testPortalStartPage() throws Exception { + mockMvc.perform(get("/portal/start").header("Host", "localhost:8085")) + .andExpect(ResultMatcher.matchAll( + status().isOk(), + view().name("start"), + model().attribute("logId", ""), + model().attribute("pwResetUrl", dummyPwResetUrl), + model().attribute("userName", dummyUsername) + )); + } + + @Test + @WithMockKeycloakAuth(oidc = @OidcStandardClaims(preferredUsername = dummyUsername)) + public void testPortalIndexRedirect() throws Exception { + mockMvc.perform(get("/").header("Host", "localhost:8085")) + .andExpect(ResultMatcher.matchAll( + status().isFound(), + header().string(HttpHeaders.LOCATION, "/portal/start") + )); + } + + @Test + @WithMockKeycloakAuth(oidc = @OidcStandardClaims(preferredUsername = dummyUsername)) + public void testPortalSearchPage() throws Exception { + given(logServiceMock.getLogEntity(eq(dummyLogId))) + .willReturn(dummyLogEntity); + + mockMvc.perform(post("/portal/search") + .param("logId", dummyLogId) + .with(csrf().asHeader()) + .header("Host", "localhost:8085")) + .andExpect(ResultMatcher.matchAll( + status().isOk(), + view().name("search"), + model().attribute("logEntity", dummyLogEntity), + model().attribute("fileSizeHr", FileUtils.byteCountToDisplaySize(dummyLogEntity.getSize())), + model().attribute("pwResetUrl", dummyPwResetUrl), + model().attribute("userName", dummyUsername) + )); + } + + @Test + @WithMockKeycloakAuth(oidc = @OidcStandardClaims(preferredUsername = dummyUsername)) + public void testPortalSearchPageNotFound() throws Exception { + given(logServiceMock.getLogEntity(eq(dummyLogId))) + .willReturn(null); + + mockMvc.perform(post("/portal/search") + .param("logId", dummyLogId) + .with(csrf().asHeader()) + .header("Host", "localhost:8085")) + .andExpect(ResultMatcher.matchAll( + status().isOk(), + view().name("not_found") + )); + } +} diff --git a/src/test/java/app/coronawarn/logupload/service/FileStorageServiceTest.java b/src/test/java/app/coronawarn/logupload/service/FileStorageServiceTest.java new file mode 100644 index 0000000..c7c1736 --- /dev/null +++ b/src/test/java/app/coronawarn/logupload/service/FileStorageServiceTest.java @@ -0,0 +1,230 @@ +package app.coronawarn.logupload.service; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import app.coronawarn.logupload.config.LogUploadConfig; +import app.coronawarn.logupload.config.LogUploadS3Config; +import app.coronawarn.logupload.model.LogEntity; +import app.coronawarn.logupload.repository.LogRepository; +import com.amazonaws.SdkClientException; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectResult; +import com.amazonaws.services.s3.model.S3Object; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +@SpringBootTest(properties = {"els-verify.tls.enabled=false"}) +public class FileStorageServiceTest { + + @Autowired + FileStorageService service; + + @MockBean + AmazonS3 amazonS3Mock; + + @MockBean + LogRepository logRepositoryMock; + + @MockBean + LogUploadConfig logUploadConfigMock; + + @MockBean + LogUploadS3Config logUploadS3ConfigMock; + + private final static byte[] dummyBytes = new byte[]{0xD, 0xE, 0xA, 0xD, 0xB, 0xE, 0xE, 0xF}; + private final static String dummyFileName = "dummy.zip"; + private final static String dummyHash = "hash123456789"; + private final static String dummyBucket = "niceBucket"; + private final static String dummyLogId = "ABCDEFG"; + + @BeforeEach + void setup() { + when(logUploadS3ConfigMock.getBucketName()).thenReturn(dummyBucket); + } + + @Test + public void testFileUploadToS3Bucket() throws FileStorageService.FileStoreException, IOException { + InputStream stream = new ByteArrayInputStream(dummyBytes); + + ArgumentCaptor logIdDbCaptor = ArgumentCaptor.forClass(String.class); + + given(logRepositoryMock.findById(logIdDbCaptor.capture())) + .willAnswer(new Answer>() { + private byte counter = 0; + + @Override + public Optional answer(InvocationOnMock invocationOnMock) { + counter++; + + if (counter <= 2) { + return Optional.of(new LogEntity(invocationOnMock.getArgument(0), null, null, 0, null, null)); + } else { + return Optional.empty(); + } + } + }); + + ArgumentCaptor bucketIdCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor logIdCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(ObjectMetadata.class); + ArgumentCaptor inputStreamArgumentCaptor = ArgumentCaptor.forClass(InputStream.class); + + PutObjectResult putObjectResult = new PutObjectResult(); + putObjectResult.setContentMd5(dummyHash); + + given(amazonS3Mock.putObject( + bucketIdCaptor.capture(), + logIdCaptor.capture(), + inputStreamArgumentCaptor.capture(), + metadataCaptor.capture())) + .willReturn(putObjectResult); + + // Mock behaviour of repository + given(logRepositoryMock.save(any())).will(invocationOnMock -> invocationOnMock.getArgument(0)); + + LogEntity returnValue = service.storeFileStream(dummyFileName, dummyBytes.length, stream); + String expectedLogId = logIdDbCaptor.getAllValues().get(2); + + // check that uploaded file has unused id (the third from the beginning) + assertEquals(expectedLogId, logIdCaptor.getValue()); + + // check that correct stream was uploaded + assertArrayEquals(dummyBytes, inputStreamArgumentCaptor.getValue().readAllBytes()); + + // check that correct content length was set + assertEquals(dummyBytes.length, metadataCaptor.getValue().getContentLength()); + + // check that file is uploaded to correct bucket + assertEquals(dummyBucket, bucketIdCaptor.getValue()); + + assertEquals(expectedLogId, returnValue.getId()); + assertEquals(dummyFileName, returnValue.getFilename()); + assertEquals(dummyHash, returnValue.getHash()); + assertEquals(dummyBytes.length, returnValue.getSize()); + } + + @Test + public void testFileUploadToS3BucketFailed() { + InputStream stream = new ByteArrayInputStream(dummyBytes); + + ArgumentCaptor logIdDbCaptor = ArgumentCaptor.forClass(String.class); + + given(logRepositoryMock.findById(logIdDbCaptor.capture())).willReturn(Optional.empty()); + given(logUploadS3ConfigMock.getBucketName()).willReturn(dummyBucket); + + + given(amazonS3Mock.putObject(anyString(), anyString(), any(InputStream.class), any(ObjectMetadata.class))) + .willThrow(new SdkClientException("")); + + // Nothing should be saved to database + verify(logRepositoryMock, times(0)).save(any()); + + FileStorageService.FileStoreException e = + assertThrows( + FileStorageService.FileStoreException.class, + () -> service.storeFileStream(dummyFileName, dummyBytes.length, stream)); + + assertEquals(FileStorageService.FileStoreException.Reason.S3_UPLOAD_FAILED, e.getReason()); + } + + @Test + public void testFileDownload() throws FileStorageService.FileStoreException, IOException { + InputStream stream = new ByteArrayInputStream(dummyBytes); + + given(logRepositoryMock.findById(eq(dummyLogId))) + .willReturn(Optional.of(new LogEntity(dummyLogId, null, dummyFileName, dummyBytes.length, dummyHash, null))); + + S3Object s3Object = new S3Object(); + s3Object.setObjectContent(stream); + + given(amazonS3Mock.getObject(eq(dummyBucket), eq(dummyLogId))) + .willReturn(s3Object); + + FileStorageService.LogDownloadResponse returnValue = service.downloadFile(dummyLogId); + + // check that correct stream was downloaded + assertArrayEquals(dummyBytes, returnValue.getInputStream().readAllBytes()); + + assertEquals(dummyLogId, returnValue.getLogEntity().getId()); + assertEquals(dummyFileName, returnValue.getLogEntity().getFilename()); + assertEquals(dummyHash, returnValue.getLogEntity().getHash()); + assertEquals(dummyBytes.length, returnValue.getLogEntity().getSize()); + } + + @Test + public void testFileDownloadFailsBecauseFileNotFoundInDb() { + given(logRepositoryMock.findById(eq(dummyLogId))) + .willReturn(Optional.empty()); + + FileStorageService.FileStoreException e = + assertThrows(FileStorageService.FileStoreException.class, () -> service.downloadFile(dummyLogId)); + + assertEquals(FileStorageService.FileStoreException.Reason.FILE_NOT_FOUND, e.getReason()); + } + + @Test + public void testFileDownloadFailsBecauseFileNotFoundInBucket() { + given(logRepositoryMock.findById(eq(dummyLogId))) + .willReturn(Optional.of(new LogEntity(dummyLogId, null, dummyFileName, dummyBytes.length, dummyHash, null))); + + given(amazonS3Mock.getObject(eq(dummyBucket), eq(dummyLogId))) + .willThrow(new SdkClientException("")); + + FileStorageService.FileStoreException e = + assertThrows(FileStorageService.FileStoreException.class, () -> service.downloadFile(dummyLogId)); + + assertEquals(FileStorageService.FileStoreException.Reason.S3_DOWNLOAD_FAILED, e.getReason()); + } + + @Test + public void testDeleteFile() { + LogEntity logEntity = new LogEntity(); + logEntity.setId(dummyLogId); + logEntity.setFilename(dummyFileName); + logEntity.setHash(dummyHash); + logEntity.setSize(dummyBytes.length); + + service.deleteFileSafe(logEntity); + + verify(amazonS3Mock).deleteObject(eq(dummyBucket), eq(dummyLogId)); + verify(logRepositoryMock).delete(eq(logEntity)); + } + + @Test + public void testDeleteFileShouldNotThrowAnException() { + LogEntity logEntity = new LogEntity(); + logEntity.setId(dummyLogId); + logEntity.setFilename(dummyFileName); + logEntity.setHash(dummyHash); + logEntity.setSize(dummyBytes.length); + + service.deleteFileSafe(logEntity); + + doThrow(new SdkClientException("")) + .when(amazonS3Mock).deleteObject(eq(dummyBucket), eq(dummyLogId)); + + // It should delete database entity if s3 deleting fails. + verify(logRepositoryMock).delete(eq(logEntity)); + } +} diff --git a/src/test/java/app/coronawarn/logupload/service/LogCleanupServiceTest.java b/src/test/java/app/coronawarn/logupload/service/LogCleanupServiceTest.java new file mode 100644 index 0000000..099bd41 --- /dev/null +++ b/src/test/java/app/coronawarn/logupload/service/LogCleanupServiceTest.java @@ -0,0 +1,66 @@ +package app.coronawarn.logupload.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.BDDMockito.given; + +import app.coronawarn.logupload.config.LogUploadConfig; +import app.coronawarn.logupload.model.LogEntity; +import app.coronawarn.logupload.repository.LogRepository; +import com.amazonaws.services.s3.AmazonS3; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest(properties = {"els-verify.tls.enabled=false"}) +@ActiveProfiles("api") +public class LogCleanupServiceTest { + + @Autowired + LogCleanupService service; + + @Autowired + LogRepository logRepository; + + @MockBean + AmazonS3 amazonS3Mock; + + @MockBean + LogUploadConfig logUploadConfigMock; + + @BeforeEach + public void setup() { + logRepository.deleteAll(); + } + + + @Test + public void testLogFileDeleteJob() { + given(logUploadConfigMock.getLogEntityLifetime()).willReturn(7); + + // should be deleted + LogEntity le1 = new LogEntity("A", ZonedDateTime.now().minus(8, ChronoUnit.DAYS), "", 0, "", null); + LogEntity le2 = new LogEntity("B", ZonedDateTime.now().minus(7, ChronoUnit.DAYS).minus(1, ChronoUnit.HOURS), "", 0, "", null); + + // should remain in database + LogEntity le3 = new LogEntity("C", ZonedDateTime.now().minus(7, ChronoUnit.DAYS).plus(1, ChronoUnit.HOURS), "", 0, "", null); + LogEntity le4 = new LogEntity("D", ZonedDateTime.now().minus(5, ChronoUnit.DAYS), "", 0, "", null); + logRepository.saveAll(Arrays.asList(le1, le2, le3, le4)); + + service.cleanup(); + + List remainingEntities = logRepository.findAll(); + + assertEquals(2, remainingEntities.size()); + assertEquals(le3.getId(), remainingEntities.get(0).getId()); + assertEquals(le4.getId(), remainingEntities.get(1).getId()); + } +} diff --git a/src/test/java/app/coronawarn/logupload/service/LogServiceTest.java b/src/test/java/app/coronawarn/logupload/service/LogServiceTest.java new file mode 100644 index 0000000..30a7168 --- /dev/null +++ b/src/test/java/app/coronawarn/logupload/service/LogServiceTest.java @@ -0,0 +1,33 @@ +package app.coronawarn.logupload.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; + +import app.coronawarn.logupload.model.LogEntity; +import app.coronawarn.logupload.repository.LogRepository; +import java.util.Optional; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +@SpringBootTest(properties = {"els-verify.tls.enabled=false"}) +public class LogServiceTest { + + @Autowired + LogService service; + + @MockBean + LogRepository logRepositoryMock; + + @Test + public void testGetLogEntity() { + String id = "XXX"; + LogEntity entity = new LogEntity(null, null, null, 0, null, null); + + given(logRepositoryMock.findById(eq(id))).willReturn(Optional.of(entity)); + + assertEquals(entity, service.getLogEntity(id)); + } +} diff --git a/src/test/java/app/coronawarn/logupload/service/OtpServiceTest.java b/src/test/java/app/coronawarn/logupload/service/OtpServiceTest.java new file mode 100644 index 0000000..3193456 --- /dev/null +++ b/src/test/java/app/coronawarn/logupload/service/OtpServiceTest.java @@ -0,0 +1,72 @@ +package app.coronawarn.logupload.service; + +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.when; + +import app.coronawarn.logupload.client.ElsVerifyClient; +import app.coronawarn.logupload.client.ElsVerifyClientRequest; +import app.coronawarn.logupload.client.ElsVerifyClientResponse; +import feign.FeignException; +import feign.Request; +import feign.RequestTemplate; +import java.util.HashMap; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +@SpringBootTest(properties = {"els-verify.tls.enabled=false"}) +public class OtpServiceTest { + + @Autowired + OtpService otpService; + + @MockBean + ElsVerifyClient elsVerifyClientMock; + + private static final String testOtp = "ea8166fa-6a42-426a-8b14-a4c53ff710b5"; + + @Test + public void shouldReturnTrueIfOtpIsValid() { + ArgumentCaptor captor = + ArgumentCaptor.forClass(ElsVerifyClientRequest.class); + + ElsVerifyClientResponse response = + new ElsVerifyClientResponse(testOtp, true, "valid"); + + when(elsVerifyClientMock.verifyOtp(captor.capture())).thenReturn(response); + + Assertions.assertTrue(otpService.verifyOtp(testOtp)); + Assertions.assertEquals(testOtp, captor.getValue().getOtp()); + } + + @Test + public void shouldReturnFalseIfOtpIsAlreadyUsed() { + ArgumentCaptor captor = + ArgumentCaptor.forClass(ElsVerifyClientRequest.class); + + doThrow(new FeignException.BadRequest("", getDummyFeignRequest(), null)) + .when(elsVerifyClientMock).verifyOtp(captor.capture()); + + Assertions.assertFalse(otpService.verifyOtp(testOtp)); + Assertions.assertEquals(testOtp, captor.getValue().getOtp()); + } + + @Test + public void shouldReturnFalseIfOtpRequestFailed() { + ArgumentCaptor captor = + ArgumentCaptor.forClass(ElsVerifyClientRequest.class); + + doThrow(new FeignException.InternalServerError("", getDummyFeignRequest(), null)) + .when(elsVerifyClientMock).verifyOtp(captor.capture()); + + Assertions.assertFalse(otpService.verifyOtp(testOtp)); + Assertions.assertEquals(testOtp, captor.getValue().getOtp()); + } + + private Request getDummyFeignRequest() { + return Request.create(Request.HttpMethod.GET, "url", new HashMap<>(), null, new RequestTemplate()); + } +} diff --git a/templates/file-header.txt b/templates/file-header.txt new file mode 100644 index 0000000..6d9f755 --- /dev/null +++ b/templates/file-header.txt @@ -0,0 +1,22 @@ +/* + * ---license-start + * Corona-Warn-App / cwa-log-upload + * + * (C) 2021, YOUR_NAME, YOUR_COMPANY + * + * Deutsche Telekom AG and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.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. + * ---license-end + */ diff --git a/trusted.key.gpg b/trusted.key.gpg new file mode 100644 index 0000000..ffdf9b9 Binary files /dev/null and b/trusted.key.gpg differ