pax_global_header00006660000000000000000000000064140752714640014524gustar00rootroot0000000000000052 comment=129fa68a0b0341cd7fb2924188c3c3fb5a294c84 configurable-http-proxy-4.5.0/000077500000000000000000000000001407527146400163265ustar00rootroot00000000000000configurable-http-proxy-4.5.0/.dockerignore000066400000000000000000000000641407527146400210020ustar00rootroot00000000000000# ignore local assets from development node_modules configurable-http-proxy-4.5.0/.eslintrc.js000066400000000000000000000003401407527146400205620ustar00rootroot00000000000000module.exports = { env: { commonjs: true, es6: true, node: true, }, extends: "eslint:recommended", parserOptions: { ecmaVersion: 12, }, root: true, rules: { "no-unused-vars": "off", }, }; configurable-http-proxy-4.5.0/.github/000077500000000000000000000000001407527146400176665ustar00rootroot00000000000000configurable-http-proxy-4.5.0/.github/dependabot.yml000066400000000000000000000004651407527146400225230ustar00rootroot00000000000000# Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "npm" directory: "/" # Location of package[-lock].json schedule: interval: "weekly" configurable-http-proxy-4.5.0/.github/workflows/000077500000000000000000000000001407527146400217235ustar00rootroot00000000000000configurable-http-proxy-4.5.0/.github/workflows/publish.yml000066400000000000000000000065771407527146400241330ustar00rootroot00000000000000# Publish NPM package and Docker image name: Release on: push: branches-ignore: # don't double-build dependabot PRs - dependabot/** tags: ["**"] pull_request: workflow_dispatch: jobs: # Run tests using node, publish a package when tagged # https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages publish-npm: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: node-version: 14 registry-url: https://registry.npmjs.org/ - run: npm ci - run: npm publish if: startsWith(github.ref, 'refs/tags/') env: NODE_AUTH_TOKEN: ${{ secrets.npm_token }} publish-docker: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 # Setup docker to build for multiple platforms, see: # https://github.com/docker/build-push-action/tree/v2.4.0#usage # https://github.com/docker/build-push-action/blob/v2.4.0/docs/advanced/multi-platform.md - name: Set up QEMU (for docker buildx) uses: docker/setup-qemu-action@25f0500ff22e406f7191a2a8ba8cda16901ca018 # associated tag: v1.0.2 - name: Set up Docker Buildx (for multi-arch builds) uses: docker/setup-buildx-action@2a4b53665e15ce7d7049afb11ff1f70ff1610609 # associated tag: v1.1.2 - name: Setup push rights to Docker Hub # This was setup by... # 1. Creating a Docker Hub service account "jupyterhubbot" # 2. Creating a access token for the service account specific to this # repository: https://hub.docker.com/settings/security # 3. Making the account part of the "bots" team, and granting that team # permissions to push to the relevant images: # https://hub.docker.com/orgs/jupyterhub/teams/bots/permissions # 4. Registering the username and token as a secret for this repo: # https://github.com/jupyterhub/configurable-http-proxy/settings/secrets/actions if: startsWith(github.ref, 'refs/tags/') run: | docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" -p "${{ secrets.DOCKERHUB_TOKEN }}" # https://github.com/jupyterhub/action-major-minor-tag-calculator # If this is a tagged build this will return additional parent tags. # E.g. 1.2.3 is expanded to Docker tags # [{prefix}:1.2.3, {prefix}:1.2, {prefix}:1, {prefix}:latest] unless # this is a backported tag in which case the newer tags aren't updated. # For branches this will return the branch name. # If GITHUB_TOKEN isn't available (e.g. in PRs) returns no tags []. - name: Get list of tags id: gettags uses: jupyterhub/action-major-minor-tag-calculator@v1 with: githubToken: ${{ secrets.GITHUB_TOKEN }} prefix: "jupyterhub/configurable-http-proxy:" - name: Display tags run: echo "Docker tags ${{ steps.gettags.outputs.tags }}" - name: Build and push uses: docker/build-push-action@e1b7f96249f2e4c8e4ac1519b9608c0d48944a1f # associated tag: v2.4.0 with: platforms: linux/amd64,linux/arm64 push: ${{ startsWith(github.ref, 'refs/tags/') }} # tags parameter must be a string input so convert `gettags` JSON # array into a comma separated list of tags tags: ${{ join(fromJson(steps.gettags.outputs.tags)) }} configurable-http-proxy-4.5.0/.github/workflows/test.yml000066400000000000000000000057761407527146400234440ustar00rootroot00000000000000# Useful GitHub Actions docs: # # - https://help.github.com/en/actions # - https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions # - https://help.github.com/en/actions/configuring-and-managing-workflows/configuring-a-workflow # - https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions name: Test on: push: branches-ignore: # don't double-build dependabot PRs - dependabot/** tags: ["**"] pull_request: workflow_dispatch: jobs: # Job to run linter / autoformat lint: runs-on: ubuntu-20.04 steps: # Action Repo: https://github.com/actions/checkout - name: "Checkout repo" uses: actions/checkout@v2 # Action Repo: https://github.com/actions/setup-node - name: "Setup Node" uses: actions/setup-node@v1 with: node-version: "14" # Action Repo: https://github.com/actions/cache - name: "Cache node_modules" uses: actions/cache@v2 with: path: node_modules key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }} restore-keys: | ${{ runner.os }}-npm- - name: "Install" run: | npm ci # Run the pre-commit action # Repo: https://github.com/pre-commit/action - uses: actions/setup-python@v2 - uses: pre-commit/action@v2.0.0 - name: npm audit run: | # If this fails, run `npm audit fix` npm audit --production --audit-level=moderate test: runs-on: ubuntu-20.04 # - https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategy strategy: fail-fast: false # Do not cancel all jobs if one fails matrix: # IMPORTANT: Make sure to update package.json's enginges.node field to # always require at least the oldest version, as well as our # README.md file under the install section. node_version: - "12" # Remove 2022-04-30, its end-of-life. - "14" - "15" # Remove about when 17 is available? - "16" # - "17" # Add 2021-10-19, its initial release date. steps: - name: "Checkout repo" uses: actions/checkout@v2 # Action Repo: https://github.com/actions/setup-node - name: "Setup Node" uses: actions/setup-node@v1 with: node-version: ${{ matrix.node_version }} # Action Repo: https://github.com/actions/cache - name: "Cache node_modules" uses: actions/cache@v2 with: path: node_modules key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }} restore-keys: | ${{ runner.os }}-npm- - name: "Install dependencies" run: | npm ci - name: "Run tests" run: | npm test # Action Repo: https://github.com/codecov/codecov-action - name: "Upload coverage to codecov" uses: codecov/codecov-action@v1 configurable-http-proxy-4.5.0/.gitignore000066400000000000000000000002071407527146400203150ustar00rootroot00000000000000npm-debug.log node_modules *.py[co] *~ .DS_Store /configurable-http-proxy bench/env bench/results bench/html coverage dist .nyc_output configurable-http-proxy-4.5.0/.jshintrc000066400000000000000000000002171407527146400201530ustar00rootroot00000000000000{ "eqeqeq": true, "esversion": 6, "devel": true, "forin": true, "latedef": true, "node": true, "strict": true, "undef": true } configurable-http-proxy-4.5.0/.npmrc000066400000000000000000000000241407527146400174420ustar00rootroot00000000000000tag-version-prefix= configurable-http-proxy-4.5.0/.pre-commit-config.yaml000066400000000000000000000007261407527146400226140ustar00rootroot00000000000000repos: # Autoformat: Bash scripts - repo: https://github.com/lovesegfault/beautysh rev: 6.0.1 hooks: - id: beautysh # Autoformat: markdown, javacsript - repo: https://github.com/pre-commit/mirrors-prettier rev: v2.2.1 hooks: - id: prettier args: - "--trailing-comma=es5" - "--print-width=100" # Lint - repo: https://github.com/pre-commit/mirrors-eslint rev: v7.21.0 hooks: - id: eslint configurable-http-proxy-4.5.0/CHANGELOG.md000066400000000000000000000715051407527146400201470ustar00rootroot00000000000000# Changes in configurable-http-proxy For detailed changes from the prior release, click on the version number, and its link will bring up a GitHub listing of changes. Use `git log` on the command line for details. ## [Unreleased] ## 4.5 ### [4.5.0] - 2021-07-19 This minor release will only be available for you if you have node version 12 and higher as we have explicitly dropped support for lower versions. #### Bugs fixed - Fix to flags configuring ip addresses for ipv4+ipv6 compatebility [#333](https://github.com/jupyterhub/configurable-http-proxy/pull/333) ([@consideRatio](https://github.com/consideRatio)) - Handle store backend errors [#325](https://github.com/jupyterhub/configurable-http-proxy/pull/325) ([@dtaniwaki](https://github.com/dtaniwaki)) - Require node 12+ explicitly [#323](https://github.com/jupyterhub/configurable-http-proxy/pull/323) ([@consideRatio](https://github.com/consideRatio)) - Set client SSL on only CA case [#319](https://github.com/jupyterhub/configurable-http-proxy/pull/319) ([@dtaniwaki](https://github.com/dtaniwaki)) #### Documentation improvements - docs: update outdated requirement of node [#335](https://github.com/jupyterhub/configurable-http-proxy/pull/335) ([@consideRatio](https://github.com/consideRatio)) - Amend changelog about dropping statsd [#317](https://github.com/jupyterhub/configurable-http-proxy/pull/317) ([@consideRatio](https://github.com/consideRatio)) #### Continuous integration improvements - ci: add tests of node 16 [#334](https://github.com/jupyterhub/configurable-http-proxy/pull/334) ([@consideRatio](https://github.com/consideRatio)) - ci: fix details in workflows causing duplicated builds and a failure [#326](https://github.com/jupyterhub/configurable-http-proxy/pull/326) ([@consideRatio](https://github.com/consideRatio)) #### Dependency updates - Bump ws from 7.4.5 to 7.5.3 [#324](https://github.com/jupyterhub/configurable-http-proxy/pull/324) [#328](https://github.com/jupyterhub/configurable-http-proxy/pull/328) [#332](https://github.com/jupyterhub/configurable-http-proxy/pull/332), [#331](https://github.com/jupyterhub/configurable-http-proxy/pull/331) ([@dependabot](https://github.com/dependabot)) - Bump commander from 7.2.0 to 8.0.0 [#329](https://github.com/jupyterhub/configurable-http-proxy/pull/329) ([@dependabot](https://github.com/dependabot)) #### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterhub/configurable-http-proxy/graphs/contributors?from=2021-05-26&to=2021-07-19&type=c)) [@consideRatio](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3AconsideRatio+updated%3A2021-05-26..2021-07-19&type=Issues) | [@dtaniwaki](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Adtaniwaki+updated%3A2021-05-26..2021-07-19&type=Issues) | [@Icare2000](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3AIcare2000+updated%3A2021-05-26..2021-07-19&type=Issues) | [@manics](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Amanics+updated%3A2021-05-26..2021-07-19&type=Issues) | [@minrk](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Aminrk+updated%3A2021-05-26..2021-07-19&type=Issues) ## 4.4 ### [4.4.0] - 2021-05-26 #### Breaking change - By mistake we released this as version 4.4.0 instead of 5.0.0 even though we introduced a breaking change in [#314](https://github.com/jupyterhub/configurable-http-proxy/pull/314) by dropping support for using [statsd](https://github.com/statsd/statsd#readme) metrics. #### New features added - Support prometheus metrics [#314](https://github.com/jupyterhub/configurable-http-proxy/pull/314) ([@dtaniwaki](https://github.com/dtaniwaki)) #### Documentation improvements - readme: update --help output [#315](https://github.com/jupyterhub/configurable-http-proxy/pull/315) ([@consideRatio](https://github.com/consideRatio)) #### Dependency bumps - Bump lodash from 4.17.20 to 4.17.21 [#312](https://github.com/jupyterhub/configurable-http-proxy/pull/312) ([@dependabot](https://github.com/dependabot)) - Bump ws from 7.4.4 to 7.4.5 [#306](https://github.com/jupyterhub/configurable-http-proxy/pull/306) ([@dependabot](https://github.com/dependabot)) #### Continuous integration - ci: github actions security [#307](https://github.com/jupyterhub/configurable-http-proxy/pull/307) ([@consideRatio](https://github.com/consideRatio)) #### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterhub/configurable-http-proxy/graphs/contributors?from=2021-04-12&to=2021-05-26&type=c)) [@consideRatio](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3AconsideRatio+updated%3A2021-04-12..2021-05-26&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Adependabot+updated%3A2021-04-12..2021-05-26&type=Issues) | [@dtaniwaki](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Adtaniwaki+updated%3A2021-04-12..2021-05-26&type=Issues) | [@minrk](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Aminrk+updated%3A2021-04-12..2021-05-26&type=Issues) ## 4.3 ### [4.3.2] - 2021-04-12 #### Enhancements made - Build and publish Docker Hub image for amd64 and arm64 [#304](https://github.com/jupyterhub/configurable-http-proxy/pull/304) ([@manics](https://github.com/manics)) #### Maintenance and upkeep improvements - Bump jasmine from 3.6.4 to 3.7.0 [#302](https://github.com/jupyterhub/configurable-http-proxy/pull/302) ([@dependabot](https://github.com/dependabot)) - Bump commander from 7.1.0 to 7.2.0 [#301](https://github.com/jupyterhub/configurable-http-proxy/pull/301) ([@dependabot](https://github.com/dependabot)) - Bump ws from 7.4.3 to 7.4.4 [#299](https://github.com/jupyterhub/configurable-http-proxy/pull/299) ([@dependabot](https://github.com/dependabot)) #### Documentation improvements - Simplify RELEASE.md with npm version [#298](https://github.com/jupyterhub/configurable-http-proxy/pull/298) ([@minrk](https://github.com/minrk)) #### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterhub/configurable-http-proxy/graphs/contributors?from=2021-03-05&to=2021-04-12&type=c)) [@consideRatio](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3AconsideRatio+updated%3A2021-03-05..2021-04-12&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Adependabot+updated%3A2021-03-05..2021-04-12&type=Issues) | [@manics](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Amanics+updated%3A2021-03-05..2021-04-12&type=Issues) | [@minrk](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Aminrk+updated%3A2021-03-05..2021-04-12&type=Issues) ### [4.3.1] - 2021-03-05 #### Bugs fixed - informative error when host cannot be determined for http->https redirect [#295](https://github.com/jupyterhub/configurable-http-proxy/pull/295) ([@minrk](https://github.com/minrk)) #### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterhub/configurable-http-proxy/graphs/contributors?from=2021-03-05&to=2021-03-05&type=c)) [@consideRatio](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3AconsideRatio+updated%3A2021-03-05..2021-03-05&type=Issues) | [@manics](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Amanics+updated%3A2021-03-05..2021-03-05&type=Issues) | [@minrk](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Aminrk+updated%3A2021-03-05..2021-03-05&type=Issues) ### [4.3.0] - 2021-03-05 4.3 is a small release that should mostly improve behavior when things are going wrong, especially when endpoints are unavailable and clients are still trying to talk to them. In particular: - requests to unavailable endpoints no longer register as activity - improved error handling and quieter logging in these cases, especially when running on node >= 12.9. #### Enhancements made - Do not update activity on failed requests [#292](https://github.com/jupyterhub/configurable-http-proxy/pull/292) ([@minrk](https://github.com/minrk)) #### Bugs fixed - Improvements when things go wrong [#290](https://github.com/jupyterhub/configurable-http-proxy/pull/290) ([@minrk](https://github.com/minrk)) #### Documentation improvements - changelog for 4.3 [#294](https://github.com/jupyterhub/configurable-http-proxy/pull/294) ([@minrk](https://github.com/minrk)) #### Maintenance and upkeep improvements - Add tests for last-activity updates [#293](https://github.com/jupyterhub/configurable-http-proxy/pull/293) ([@minrk](https://github.com/minrk)) - adopt pre-commit [#291](https://github.com/jupyterhub/configurable-http-proxy/pull/291) ([@minrk](https://github.com/minrk)) - Bump commander from 6.2.1 to 7.1.0 [#289](https://github.com/jupyterhub/configurable-http-proxy/pull/289) ([@dependabot](https://github.com/dependabot)) #### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterhub/configurable-http-proxy/graphs/contributors?from=2021-02-20&to=2021-03-05&type=c)) [@dependabot](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Adependabot+updated%3A2021-02-20..2021-03-05&type=Issues) | [@minrk](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Aminrk+updated%3A2021-02-20..2021-03-05&type=Issues) ## 4.2 ### [4.2.3] - 2021-02-19 #### Bugs fixed - lib: Use client ssl config to access error target [#254](https://github.com/jupyterhub/configurable-http-proxy/pull/254) ([@chancez](https://github.com/chancez)) #### Documentation improvements - docs: update release instructions and readme badges [#285](https://github.com/jupyterhub/configurable-http-proxy/pull/285) ([@consideRatio](https://github.com/consideRatio)) #### Continuous integration - travis -> github actions [#275](https://github.com/jupyterhub/configurable-http-proxy/pull/275) ([@minrk](https://github.com/minrk)) #### Dependency bumps - Bump ws from 7.4.2 to 7.4.3 [#288](https://github.com/jupyterhub/configurable-http-proxy/pull/288) ([@dependabot](https://github.com/dependabot)) - Bump ws from 7.4.1 to 7.4.2 [#282](https://github.com/jupyterhub/configurable-http-proxy/pull/282) ([@dependabot](https://github.com/dependabot)) - Bump ws from 7.4.0 to 7.4.1 [#280](https://github.com/jupyterhub/configurable-http-proxy/pull/280) ([@dependabot](https://github.com/dependabot)) - Bump ws from 7.3.1 to 7.4.0 [#273](https://github.com/jupyterhub/configurable-http-proxy/pull/273) ([@dependabot](https://github.com/dependabot)) - Bump commander from 6.2.0 to 6.2.1 [#281](https://github.com/jupyterhub/configurable-http-proxy/pull/281) ([@dependabot](https://github.com/dependabot)) - Bump commander from 6.1.0 to 6.2.0 [#271](https://github.com/jupyterhub/configurable-http-proxy/pull/271) ([@dependabot](https://github.com/dependabot)) #### Contributors to this release [@chancez](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Achancez+updated%3A2020-10-24..2021-02-19&type=Issues) | [@consideRatio](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3AconsideRatio+updated%3A2020-10-24..2021-02-19&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Adependabot+updated%3A2020-10-24..2021-02-19&type=Issues) | [@minrk](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Aminrk+updated%3A2020-10-24..2021-02-19&type=Issues) ### [4.2.2] - 2020-10-25 This release contains bugfixes, notably the `--custom-header` implementation. #### Bugs fixed - Emit proxyRequestWs events correctly, and some inline docs [#248](https://github.com/jupyterhub/configurable-http-proxy/pull/248) ([@consideRatio](https://github.com/consideRatio)) - fix: --custom-header flag implementation [#242](https://github.com/jupyterhub/configurable-http-proxy/pull/242) ([@consideRatio](https://github.com/consideRatio)) - Fix incorrect this/that on logging statement [#234](https://github.com/jupyterhub/configurable-http-proxy/pull/234) ([@jmartell7](https://github.com/jmartell7)) #### Maintenance and upkeep improvements - Security patches of known vulnerabilities in docker image [#270](https://github.com/jupyterhub/configurable-http-proxy/pull/270) ([@wongannaw](https://github.com/wongannaw)) - try dependabot for updates [#256](https://github.com/jupyterhub/configurable-http-proxy/pull/256) ([@minrk](https://github.com/minrk)) - simplify dockerignore to exclude node_modules [#255](https://github.com/jupyterhub/configurable-http-proxy/pull/255) ([@minrk](https://github.com/minrk)) - CI: npm audit cronjob details [#246](https://github.com/jupyterhub/configurable-http-proxy/pull/246) ([@consideRatio](https://github.com/consideRatio)) - Docker image: use package-lock.json and only include relevant parts [#241](https://github.com/jupyterhub/configurable-http-proxy/pull/241) ([@consideRatio](https://github.com/consideRatio)) - CI: fix .travis.yml syntax for cronjob [#240](https://github.com/jupyterhub/configurable-http-proxy/pull/240) ([@consideRatio](https://github.com/consideRatio)) - CI: npm-audit cronjob in travis [#239](https://github.com/jupyterhub/configurable-http-proxy/pull/239) ([@consideRatio](https://github.com/consideRatio)) - CI: test against node 14 [#237](https://github.com/jupyterhub/configurable-http-proxy/pull/237) ([@consideRatio](https://github.com/consideRatio)) - Stop installing development dependencies in Docker image [#227](https://github.com/jupyterhub/configurable-http-proxy/pull/227) ([@consideRatio](https://github.com/consideRatio)) #### Documentation improvements - Documentation changes #235 [#236](https://github.com/jupyterhub/configurable-http-proxy/pull/236) ([@suryag10](https://github.com/suryag10)) #### Dependency bumps - Bump jasmine from 3.6.1 to 3.6.2 [#268](https://github.com/jupyterhub/configurable-http-proxy/pull/268) ([@dependabot](https://github.com/dependabot)) - Bump prettier from 2.1.1 to 2.1.2 [#266](https://github.com/jupyterhub/configurable-http-proxy/pull/266) ([@dependabot](https://github.com/dependabot)) - Bump request from 2.88.0 to 2.88.2 [#265](https://github.com/jupyterhub/configurable-http-proxy/pull/265) ([@dependabot](https://github.com/dependabot)) - Bump ws from 7.3.0 to 7.3.1 [#264](https://github.com/jupyterhub/configurable-http-proxy/pull/264) ([@dependabot](https://github.com/dependabot)) - Bump request-promise-native from 1.0.5 to 1.0.9 [#263](https://github.com/jupyterhub/configurable-http-proxy/pull/263) ([@dependabot](https://github.com/dependabot)) - Bump prettier from 2.0.0 to 2.1.1 [#262](https://github.com/jupyterhub/configurable-http-proxy/pull/262) ([@dependabot](https://github.com/dependabot)) - Bump nyc from 15.0.0 to 15.1.0 [#261](https://github.com/jupyterhub/configurable-http-proxy/pull/261) ([@dependabot](https://github.com/dependabot)) - Bump jasmine from 3.5.0 to 3.6.1 [#260](https://github.com/jupyterhub/configurable-http-proxy/pull/260) ([@dependabot](https://github.com/dependabot)) - Bump commander from 5.1.0 to 6.1.0 [#259](https://github.com/jupyterhub/configurable-http-proxy/pull/259) ([@dependabot](https://github.com/dependabot)) - Bump jshint from 2.10.2 to 2.12.0 [#258](https://github.com/jupyterhub/configurable-http-proxy/pull/258) ([@dependabot](https://github.com/dependabot)) - Bump winston from 3.3.0 to 3.3.3 [#257](https://github.com/jupyterhub/configurable-http-proxy/pull/257) ([@dependabot](https://github.com/dependabot)) - Update node Docker tag to v12.18.3 [#253](https://github.com/jupyterhub/configurable-http-proxy/pull/253) ([@renovate](https://github.com/renovate)) - Bump lodash from 4.17.15 to 4.17.19 [#250](https://github.com/jupyterhub/configurable-http-proxy/pull/250) ([@dependabot](https://github.com/dependabot)) - chore(deps): update dependency ws to v7 [#247](https://github.com/jupyterhub/configurable-http-proxy/pull/247) ([@renovate](https://github.com/renovate)) - Update dependency winston to ~3.3.0 [#245](https://github.com/jupyterhub/configurable-http-proxy/pull/245) ([@renovate](https://github.com/renovate)) - Update node Docker tag to v12.18.2 [#243](https://github.com/jupyterhub/configurable-http-proxy/pull/243) ([@renovate](https://github.com/renovate)) - Deps: npm audit fix to bump patch versions [#238](https://github.com/jupyterhub/configurable-http-proxy/pull/238) ([@consideRatio](https://github.com/consideRatio)) - Update node Docker tag to v12.17.0 [#233](https://github.com/jupyterhub/configurable-http-proxy/pull/233) ([@renovate](https://github.com/renovate)) - Update node Docker tag to v12.16.3 [#231](https://github.com/jupyterhub/configurable-http-proxy/pull/231) ([@renovate](https://github.com/renovate)) - Update dependency prettier to v2 [#230](https://github.com/jupyterhub/configurable-http-proxy/pull/230) ([@renovate](https://github.com/renovate)) - Update dependency commander to v5 [#229](https://github.com/jupyterhub/configurable-http-proxy/pull/229) ([@renovate](https://github.com/renovate)) #### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterhub/configurable-http-proxy/graphs/contributors?from=2020-03-11&to=2020-10-24&type=c)) [@consideRatio](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3AconsideRatio+updated%3A2020-03-11..2020-10-24&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Adependabot+updated%3A2020-03-11..2020-10-24&type=Issues) | [@jmartell7](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Ajmartell7+updated%3A2020-03-11..2020-10-24&type=Issues) | [@manics](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Amanics+updated%3A2020-03-11..2020-10-24&type=Issues) | [@minrk](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Aminrk+updated%3A2020-03-11..2020-10-24&type=Issues) | [@renovate](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Arenovate+updated%3A2020-03-11..2020-10-24&type=Issues) | [@rgbkrk](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Argbkrk+updated%3A2020-03-11..2020-10-24&type=Issues) | [@suryag10](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Asuryag10+updated%3A2020-03-11..2020-10-24&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Awelcome+updated%3A2020-03-11..2020-10-24&type=Issues) | [@wongannaw](https://github.com/search?q=repo%3Ajupyterhub%2Fconfigurable-http-proxy+involves%3Awongannaw+updated%3A2020-03-11..2020-10-24&type=Issues) ### [4.2.1] - 2020-03-11 #### Summary This is a security release, fixing node package dependencies to configurable-http-proxy, which itself was left untouched. #### Merged PRs - Security Fixes [#226](https://github.com/jupyterhub/configurable-http-proxy/pull/226) ([@rafael-ladislau](https://github.com/rafael-ladislau)) - Update dependency commander to ~4.1.0 [#225](https://github.com/jupyterhub/configurable-http-proxy/pull/225) ([@renovate](https://github.com/renovate)) ### [4.2.0] - 2019-11-14 #### Summary - Now terminates on `SIGTERM` as can be caused by `docker stop` or `kubectl delete` - Add `--timeout` option to configure when to drop a request - Add `--custom-header` option that enables proxied requests to get additional headers attached - Support setting of the environment variable `CONFIGPROXY_AUTH_TOKEN` using a mounted file on the Docker image's container - Node version bumped from 10 to 12.13.0 in the Docker image - Various dependencies updated, including addressing security advisories from `npm audit` which do not affect CHP security itself. #### Merged PRs - RELEASE.md documentation and small fixes [#220](https://github.com/jupyterhub/configurable-http-proxy/pull/220) ([@consideRatio](https://github.com/consideRatio)) - Terminate on SIGTERM [#217](https://github.com/jupyterhub/configurable-http-proxy/pull/217) ([@consideRatio](https://github.com/consideRatio)) - Fix Vulnerabilities [#216](https://github.com/jupyterhub/configurable-http-proxy/pull/216) ([@rafael-ladislau](https://github.com/rafael-ladislau)) - Update dependency commander to v4 [#214](https://github.com/jupyterhub/configurable-http-proxy/pull/214) ([@renovate](https://github.com/renovate)) - chore: Udpate node, replace add with copy [#213](https://github.com/jupyterhub/configurable-http-proxy/pull/213) ([@jgwerner](https://github.com/jgwerner)) - Update dependency http-proxy to ~1.18.0 [#212](https://github.com/jupyterhub/configurable-http-proxy/pull/212) ([@renovate](https://github.com/renovate)) - Change user to numeric value for k8s compatibility [#211](https://github.com/jupyterhub/configurable-http-proxy/pull/211) ([@m2hofi94](https://github.com/m2hofi94)) - Add file_env function to set the token env var [#209](https://github.com/jupyterhub/configurable-http-proxy/pull/209) ([@rcthomas](https://github.com/rcthomas)) - Allow setting request timeout [#208](https://github.com/jupyterhub/configurable-http-proxy/pull/208) ([@archite](https://github.com/archite)) - Command line option for custom headers [#206](https://github.com/jupyterhub/configurable-http-proxy/pull/206) ([@ivan-gomes](https://github.com/ivan-gomes)) - chore(deps): update dependency nyc to v14 [#202](https://github.com/jupyterhub/configurable-http-proxy/pull/202) ([@renovate](https://github.com/renovate)) - Update dependency commander to ~2.20.0 [#201](https://github.com/jupyterhub/configurable-http-proxy/pull/201) ([@renovate](https://github.com/renovate)) ### [4.1] ### [4.1.0] - 2019-04-01 - Add `--redirect-to` option to specify destination port when redirecting http to https with `--redirect-from`. - Add health check endpoint at `/_chp_healthz`. - Docker base image is updated to `node/10-alpine` from `node/6-alpine` - Dependencies are updated via Renovate ## 4.0 ### [4.0.0] - 2018-10-12 - Add support for client SSL certificates for encrypting proxied requests. - Update all nodejs dependencies. Most significant is updating winston (logging) from 2 to 3. There is no longer a global logger, instead use `this.log`. - Drop support for node 4. Minimum node version is 6. - Support CONFIGPROXY_SSL_KEY_PASSPHRASE env for setting the passphrase of ssl keys (API_SSL for api ssl key). ## [3.1] ### [3.1.1] - 2018-01-15 - Fix a bug when using the new custom storage backend support where the body of requests could be lost. ### [3.1.0] - 2017-11-03 3.1 adds two new features: - Add `--change-origin` passthrough for node-http-proxy's changeOrigin option. - Add support via `--storage-backend ` for custom storage classes. See [configurable-http-proxy-redis-backend](https://github.com/globocom/configurable-http-proxy-redis-backend) for an example using redis. ## [3.0] ### [3.0.0] - 2017-09-19 3.0 is a major release because much of the code has been reorganized to adopt some javascript standards: - Use ES6 and Promises instead of ES5 and callbacks, which we can do without a compiler because CHP 2.0 required nodejs < 4. - auto-format code with prettify (run `npm run fmt` to auto-format your code after making changes). There shouldn't be any major changes in 3.0, but marking it as a major upgrade because there could be regressions introduced by the restructuring. Fixes: - Fix routing of `/prefix?query` where a query parameter was passed exactly on the routing prefix with no trailing slash. Improvements: - Quieter messages for ECONNREFUSED and ECONNRESET, which are generally not indicative of problems, but rather common events of peers disconnecting during a request. - The docker image for `jupyterhub/configurable-http-proxy` is now based on `node:6-alpine`. ## [2.0] ### 2.0.4 - 2017-06-21 - Add logging of all API requests ### 2.0.3 - 2017-06-12 - Fix docker image entrypoint, broken in 2.0.2 ### 2.0.2 - 2017-06-07 - Fix error raised trying to `setHeader` on an undefined response (e.g. when encountering socket-level error) ### 2.0.1 **Important** CHP 2.0.0 drops support for node.js ≤ 4.0. **Added:** - Add configuration option for proxy timeout `--proxy-timeout , Timeout (in millis) when proxy receives no response from target.` [\#86](https://github.com/jupyterhub/configurable-http-proxy/pull/86) - Add configuration options for auto rewrite and protocol rewrite [\#73](https://github.com/jupyterhub/configurable-http-proxy/pull/73): - `--auto-rewrite, Rewrite the Location header host/port in redirect responses` - `--protocol-rewrite ', Rewrite the Location header protocol in redirect responses to the specified protocol` - Add low-level code for separate stores of routes to enable future support of other data stores such as Redis [\#81](https://github.com/jupyterhub/configurable-http-proxy/pull/81) **Changed:** - Support only LTS releases and above for NodeJS [\#82](https://github.com/jupyterhub/configurable-http-proxy/pull/82). This means only ≥ 4.0 are supported. **Fixed:** - Fix behavior to correctly handle children when a parent node is deleted [\#93](https://github.com/jupyterhub/configurable-http-proxy/pull/93) - Fix closure reference when serving custom error pages [\#91](https://github.com/jupyterhub/configurable-http-proxy/pull/91) - Improved all-interfaces warning message when `ip='*'` [\#94](https://github.com/jupyterhub/configurable-http-proxy/pull/94) ## [1.3] ### [1.3.1] - 2016-10-12 - small fixes for node 6 support - fix `--no-x-forward` again (for real, this time) ### [1.3.0] - 2016-08-01 - add `--ssl-protocol`, so that one can restrict to TLS, e.g. `--ssl-protocol=TLSv1` - fix handling of ``--no-x-forward` ## [1.2] - 2016-04-19 - add statsd support ## [1.1] - 2016-01-04 - add `--ssl-request-cert` args for certificate-based client authentication - fix some SSL parameters that were ignored for API requests ## [1.0] - 2016-01-04 - add `ConfigProxy.proxy_request` event, for customizing requests as the pass through the proxy. - add more ssl-related options for specifying options on the CLI. - fix regression in 0.5 where deleting a top-level route would also delete the default route. ## [0.5] - 2015-10-05 - add `--error-target` for letting another http server render error pages. Server must handle `/404` and `/503` URLs. - add `--error-path` for custom static HTML error pages. `[CODE].html` will be used if it exists, otherwise `error.html`. - fix bug preventing root route from being deleted ## [0.4] - 2015-10-02 - add `--redirect-port` for automatically redirecting a common port to the correct one (e.g. redirecting http to https) ## [0.3] - 2015-04-29 - fixes for URL escaping - add host-based routing ## [0.2.1] - 2014-11-21 ## [0.2.0] - 2014-11-14 ## [0.1.1] - 2014-10-01 [unreleased]: https://github.com/jupyterhub/configurable-http-proxy/compare/4.5.0...HEAD [4.5.0]: https://github.com/jupyterhub/configurable-http-proxy/compare/4.4.4...4.5.0 [4.4.4]: https://github.com/jupyterhub/configurable-http-proxy/compare/4.3.2...4.4.4 [4.3.2]: https://github.com/jupyterhub/configurable-http-proxy/compare/4.3.1...4.3.2 [4.3.1]: https://github.com/jupyterhub/configurable-http-proxy/compare/4.3.0...4.3.1 [4.3.0]: https://github.com/jupyterhub/configurable-http-proxy/compare/4.2.3...4.3.0 [4.2.3]: https://github.com/jupyterhub/configurable-http-proxy/compare/4.2.2...4.2.3 [4.2.2]: https://github.com/jupyterhub/configurable-http-proxy/compare/4.2.1...4.2.2 [4.2.1]: https://github.com/jupyterhub/configurable-http-proxy/compare/4.2.0...4.2.1 [4.2.0]: https://github.com/jupyterhub/configurable-http-proxy/compare/4.1.0...4.2.0 [4.1.0]: https://github.com/jupyterhub/configurable-http-proxy/compare/4.0.1...4.1.0 [4.0.0]: https://github.com/jupyterhub/configurable-http-proxy/compare/3.1.1...4.0.1 [3.1.1]: https://github.com/jupyterhub/configurable-http-proxy/compare/3.1.0...3.1.1 [3.1]: https://github.com/jupyterhub/configurable-http-proxy/compare/3.0.0...3.1.0 [3.0]: https://github.com/jupyterhub/configurable-http-proxy/compare/2.0.4...3.0.0 [2.0]: https://github.com/jupyterhub/configurable-http-proxy/compare/1.3.1...2.0.4 [1.3]: https://github.com/jupyterhub/configurable-http-proxy/compare/1.2.0...1.3.0 [1.2]: https://github.com/jupyterhub/configurable-http-proxy/compare/1.1.0...1.2.0 [1.1]: https://github.com/jupyterhub/configurable-http-proxy/compare/1.0.0...1.1.0 [1.0]: https://github.com/jupyterhub/configurable-http-proxy/compare/0.5.0...1.0.0 [0.5]: https://github.com/jupyterhub/configurable-http-proxy/compare/0.4.0...0.5.0 [0.4]: https://github.com/jupyterhub/configurable-http-proxy/compare/0.3.0...0.4.0 [0.3]: https://github.com/jupyterhub/configurable-http-proxy/compare/0.2.1...0.3.0 [0.2.1]: https://github.com/jupyterhub/configurable-http-proxy/compare/0.2.0...0.2.1 [0.2.0]: https://github.com/jupyterhub/configurable-http-proxy/compare/0.1.1...0.2.0 configurable-http-proxy-4.5.0/CONTRIBUTING.md000066400000000000000000000003001407527146400205500ustar00rootroot00000000000000# Contributing Welcome! As a [Jupyter](https://jupyter.org) project, we follow the [Jupyter contributor guide](https://jupyter.readthedocs.io/en/latest/contributor/content-contributor.html). configurable-http-proxy-4.5.0/Dockerfile000066400000000000000000000016401407527146400203210ustar00rootroot00000000000000FROM node:lts-alpine3.12 # ref: https://hub.docker.com/_/node?tab=tags&name=12 LABEL maintainer="Jupyter Project " # Useful tools for debugging RUN apk add --no-cache jq curl # Copy relevant (see .dockerignore) RUN mkdir -p /srv/configurable-http-proxy COPY . /srv/configurable-http-proxy/ WORKDIR /srv/configurable-http-proxy # Install configurable-http-proxy according to package-lock.json (ci) without # devDepdendencies (--production), then uninstall npm which isn't needed. RUN npm ci --production \ && npm uninstall -g npm # Switch from the root user to the nobody user USER 65534 # Expose the proxy for traffic to be proxied (8000) and the # REST API where it can be configured (8001) EXPOSE 8000 EXPOSE 8001 # Put configurable-http-proxy on path for chp-docker-entrypoint ENV PATH=/srv/configurable-http-proxy/bin:$PATH ENTRYPOINT ["/srv/configurable-http-proxy/chp-docker-entrypoint"] configurable-http-proxy-4.5.0/LICENSE000066400000000000000000000027701407527146400173410ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2017, Project Jupyter Contributors 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 the copyright holder 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 HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. configurable-http-proxy-4.5.0/README.md000066400000000000000000000341331407527146400176110ustar00rootroot00000000000000# [configurable-http-proxy](https://github.com/jupyterhub/configurable-http-proxy) [![npm](https://img.shields.io/npm/v/configurable-http-proxy.svg?logo=npm)](https://www.npmjs.com/package/configurable-http-proxy) [![Docker Build status](https://img.shields.io/docker/cloud/build/jupyterhub/configurable-http-proxy?logo=docker&label=build)](https://hub.docker.com/r/jupyterhub/configurable-http-proxy/tags) [![GitHub Workflow Status - Test](https://img.shields.io/github/workflow/status/jupyterhub/configurable-http-proxy/Test?logo=github&label=tests)](https://github.com/jupyterhub/configurable-http-proxy/actions) [![GitHub](https://img.shields.io/badge/issue_tracking-github-blue?logo=github)](https://github.com/jupyterhub/configurable-http-proxy/issues) [![Discourse](https://img.shields.io/badge/help_forum-discourse-blue?logo=discourse)](https://discourse.jupyter.org/c/jupyterhub/z2jh-k8s) [![Gitter](https://img.shields.io/badge/social_chat-gitter-blue?logo=gitter)](https://gitter.im/jupyterhub/jupyterhub) **configurable-http-proxy** (CHP) provides you with a way to update and manage a proxy table using a command line interface or REST API. It is a simple wrapper around [node-http-proxy][]. node-http-proxy is an HTTP programmable proxying library that supports websockets and is suitable for implementing components such as reverse proxies and load balancers. By wrapping node-http-proxy, **configurable-http-proxy** extends this functionality to [JupyterHub] deployments. ## Table of Contents - [Install](#install) - [Usage](#usage) - [Starting the proxy](#starting-the-proxy) - [Setting a default target](#setting-a-default-target) - [Command-line options](#command-line-options) - [Using the REST API](#using-the-rest-api) - [REST API Basics](#REST-api-basics) - [Authenticating via passing a token](#authenticating-via-passing-a-token) - [Getting the routing table](#getting-the-routing-table) - [Adding new routes](#adding-new-routes) - [Deleting routes](#deleting-routes) - [Custom error pages](#custom-error-pages) - [Host-based routing](#host-based-routing) - [Troubleshooting](#troubleshooting) ## Install Prerequisite: [Node.js](https://nodejs.org/en/download/) ≥ 12 If you're installing `configurable-http-proxy` in Linux, you can follow [the instruction of nodesource](https://github.com/nodesource/distributions#installation-instructions) to install arbitrary version of Node.js. To install the `configurable-http-proxy` package globally using npm: ```bash npm install -g configurable-http-proxy ``` To install from the source code found in this GitHub repo: ```bash git clone https://github.com/jupyterhub/configurable-http-proxy cd configurable-http-proxy npm install # Use 'npm install -g' for global install ``` [**Return to top**][] ## Usage The configurable proxy runs two HTTP(S) servers: - The **public-facing interface** to your application (controlled by `--ip`, `--port`) - listens on **all interfaces** by default. - The **inward-facing REST API** (`--api-ip`, `--api-port`) - listens on localhost by default - The REST API uses token authorization, where the token is set in the `CONFIGPROXY_AUTH_TOKEN` environment variable. ![](./doc/_static/chp.png) [**Return to top**][] ### Starting the proxy ```bash configurable-http-proxy [options] ``` where `[options]` are the command-line options described below. [**Return to top**][] ### Setting a default target The **default target** is used when a client has requested a URL for which there is no routing target found in the proxy table. To set a **default target**, pass the command line option, `--default-target`, when starting the configurable proxy: ```bash configurable-http-proxy --default-target=proto://host[:port] ``` For example: ```bash configurable-http-proxy --default-target=http://localhost:8888 ``` [**Return to top**][] ### Command-line options ``` Usage: configurable-http-proxy [options] Options: -V, --version output the version number --ip Public-facing IP of the proxy --port (defaults to 8000) Public-facing port of the proxy --ssl-key SSL key to use, if any --ssl-cert SSL certificate to use, if any --ssl-ca SSL certificate authority, if any --ssl-request-cert Request SSL certs to authenticate clients --ssl-reject-unauthorized Reject unauthorized SSL connections (only meaningful if --ssl-request-cert is given) --ssl-protocol Set specific SSL protocol, e.g. TLSv1_2, SSLv3 --ssl-ciphers `:`-separated ssl cipher list. Default excludes RC4 --ssl-allow-rc4 Allow RC4 cipher for SSL (disabled by default) --ssl-dhparam SSL Diffie-Helman Parameters pem file, if any --api-ip Inward-facing IP for API requests (default: "localhost") --api-port Inward-facing port for API requests (defaults to --port=value+1) --api-ssl-key SSL key to use, if any, for API requests --api-ssl-cert SSL certificate to use, if any, for API requests --api-ssl-ca SSL certificate authority, if any, for API requests --api-ssl-request-cert Request SSL certs to authenticate clients for API requests --api-ssl-reject-unauthorized Reject unauthorized SSL connections (only meaningful if --api-ssl-request-cert is given) --client-ssl-key SSL key to use, if any, for proxy to client requests --client-ssl-cert SSL certificate to use, if any, for proxy to client requests --client-ssl-ca SSL certificate authority, if any, for proxy to client requests --client-ssl-request-cert Request SSL certs to authenticate clients for API requests --client-ssl-reject-unauthorized Reject unauthorized SSL connections (only meaningful if --client-ssl-request-cert is given) --default-target Default proxy target (proto://host[:port]) --error-target Alternate server for handling proxy errors (proto://host[:port]) --error-path Alternate server for handling proxy errors (proto://host[:port]) --redirect-port Redirect HTTP requests on this port to the server on HTTPS --redirect-to Redirect HTTP requests from --redirect-port to this port --pid-file Write our PID to a file --no-x-forward Don't add 'X-forward-' headers to proxied requests --no-prepend-path Avoid prepending target paths to proxied requests --no-include-prefix Don't include the routing prefix in proxied requests --auto-rewrite Rewrite the Location header host/port in redirect responses --change-origin Changes the origin of the host header to the target URL --protocol-rewrite Rewrite the Location header protocol in redirect responses to the specified protocol --custom-header
Custom header to add to proxied requests. Use same option for multiple headers (--custom-header k1:v1 --custom-header k2:v2) (default: {}) --insecure Disable SSL cert verification --host-routing Use host routing (host as first level of path) --metrics-ip IP for metrics server (default: "0.0.0.0") --metrics-port Port of metrics server. Defaults to no metrics server --log-level Log level (debug, info, warn, error) (default: "info") --timeout Timeout (in millis) when proxy drops connection for a request. --proxy-timeout Timeout (in millis) when proxy receives no response from target. --storage-backend Define an external storage class. Defaults to in-MemoryStore. -h, --help display help for command ``` [**Return to top**][] ## Using the REST API The configurable-http-proxy REST API is documented and available as: - a nicely rendered, interactive version at the [petstore swagger site][] - a [swagger specification file][] in this repo [**Return to top**][] ### REST API Basics **API Root** | HTTP method | Endpoint | Function | | ----------- | -------- | -------- | | GET | /api/ | API Root | **Routes** | HTTP method | Endpoint | Function | | ----------- | ------------------------ | ----------------------------------- | | GET | /api/routes | [Get all routes in routing table][] | | POST | /api/routes/{route_spec} | [Add a new route][] | | DELETE | /api/routes/{route_spec} | [Remove the given route][] | [**Return to top**][] ### Authenticating via passing a token The REST API is authenticated via passing a token in the `Authorization` header. The API is served under the `/api/routes` base URL. For example, this `curl` command entered in the terminal passes this header `"Authorization: token $CONFIGPROXY_AUTH_TOKEN"` for authentication and retrieves the current routing table from this endpoint, `http://localhost:8001/api/routes`: ```bash curl -H "Authorization: token $CONFIGPROXY_AUTH_TOKEN" http://localhost:8001/api/routes ``` [**Return to top**][] ### Getting the routing table **Request:** GET /api/routes[?inactive_since=ISO8601-timestamp] **Parameters:** `inactive_since`: If the `inactive_since` URL parameter is given as an [ISO8601](http://en.wikipedia.org/wiki/ISO_8601) timestamp, only routes whose `last_activity` is earlier than the timestamp will be returned. The `last_activity` timestamp is updated whenever the proxy passes data to or from the proxy target. **Response:** _Status code_ status: 200 OK _Response body_ A JSON dictionary of the current routing table. This JSON dictionary _excludes_ the default route. **Behavior:** The current routing table is returned to the user if the request is successful. [**Return to top**][] ### Adding new routes POST requests create new routes. The body of the request should be a JSON dictionary with at least one key: `target`, the target host to be proxied. **Request:** POST /api/routes/[:path] **Required input:** `target`: The host URL Example request body: ```json { "target": "http://localhost:8002" } ``` **Response:** status: 201 Created **Behavior:** After adding the new route, any request to `/path/prefix` on the proxy's public interface will be proxied to `target`. [**Return to top**][] ### Deleting routes **Request:** DELETE /api/routes/[:path] **Response:** status: 204 No Content **Behavior:** Removes a route from the proxy's routing table. [**Return to top**][] ## Custom error pages Custom error pages can be provided when the proxy encounters an error and has no proxy target to handle a request. There are two typical errors that CHP may hit, along with their status code: - 404 error: Returned when a client has requested a URL for which there is no routing target. This error **can be prevented** by setting a [`default target`][] before starting the configurable-http-proxy. - 503 error: Returned when a route exists, but the upstream server isn't responding. This is more common, and can be due to any number of reasons, including the target service having died, not finished starting, or network instability. [**Return to top**][] ### Setting the path for custom error pages When starting the CHP, specify an error path `--error-path /usr/share/chp-errors` to the location of the error page: ```bash configurable-http-proxy --error-path /usr/share/chp-errors ``` When a proxy error occurs, CHP will look in the following location for a custom html error page to serve: /usr/share/chp-errors/{CODE}.html where `{CODE}` is a status code number for an html page to serve. If there is a 503 error, CHP will look for a custom error page in this location `/usr/share/chp-errors/503.html`. If no custom error html file exists for the error code, CHP will use the default `error.html`. If you specify an error path, **make sure** you also create a default `error.html` file. [**Return to top**][] ### Setting a target for custom error handling You can specify a target URL to use when errors occur by setting `--error-target {URL}` when starting the CHP. If, for example, CHP starts with `--error-target http://localhost:1234`, and the proxy encounters an error, the proxy will make a GET request to the `error-target` server. The GET request will be sent to the `error-target` server URL, `http://localhost:1234`, appending the status code `/{CODE}`, and passing the failing request's URL escaped in a URL parameter: GET /404?url=%2Fescaped%2Fpath [**Return to top**][] ## Host-based routing If the CHP is started with the `--host-routing` option, the proxy will use the hostname of the incoming request to select a target. When using host-based routes, the API uses the target in the same way as if the hostname were the first part of the URL path, e.g.: ```python { "/example.com": "https://localhost:1234", "/otherdomain.biz": "http://10.0.1.4:5555", } ``` [**Return to top**][] ## Troubleshooting Q: My proxy is not starting. What could be happening? - If this occurs on Ubuntu/Debian, check that the you are using a recent version of node. Some versions of Ubuntu/Debian come with a version of node that is very old, and it is necessary to update node to a recent or `LTS` version. [**Return to top**][] [node-http-proxy]: https://github.com/nodejitsu/node-http-proxy [jupyterhub]: https://github.com/jupyterhub/jupyterhub [petstore swagger site]: http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyterhub/configurable-http-proxy/HEAD/doc/rest-api.yml#/default [swagger specification file]: https://github.com/jupyterhub/configurable-http-proxy/blob/HEAD/doc/rest-api.yml [get all routes in routing table]: #getting-the-routing-table [add a new route]: #adding-new-routes [remove the given route]: #deleting-routes [`default target`]: #setting-a-default-target [**return to top**]: #table-of-contents configurable-http-proxy-4.5.0/RELEASE.md000066400000000000000000000050641407527146400177350ustar00rootroot00000000000000# How to make a release `configurable-http-proxy` is available as a _npm package_ [available on npmjs](https://www.npmjs.com/package/configurable-http-proxy) and a _Docker image_ available on [DockerHub](https://hub.docker.com/r/jupyterhub/configurable-http-proxy). The Docker image is automatically built and published on changes to the git repository as is configured [here](https://hub.docker.com/repository/docker/jupyterhub/configurable-http-proxy/builds). To make a tagged release follow the instructions below, but first make sure you meet the prerequisites: - To have push rights to the [configurable-http-proxy GitHub repository](https://github.com/jupyterhub/configurable-http-proxy). - To have [Node.js](https://nodejs.org) installed. ## Steps to make a release 1. Update [CHANGELOG.md](CHANGELOG.md) if it is not up to date, and verify [README.md](README.md) has an updated output of running `--help`. Make a PR to review the CHANGELOG notes. 1. Once the changelog is up to date, checkout the main branch and make sure it is up to date and clean. ```bash ORIGIN=${ORIGIN:-origin} # set to the canonical remote, e.g. 'upstream' if 'origin' is not the official repo git checkout main git branch --set-upstream-to=$ORIGIN/main main git pull ``` 1. Update the version with [`npm version`](https://docs.npmjs.com/cli/v6/commands/npm-version) and let it make a git commit and tag as well for us. ```bash # specify the new version here, # which should already have been chosen when updating the changelog. npm version 1.2.3 # verify changes git diff HEAD~1 ``` 1. Reset the version to the next development version with `npm version`, without creating a tag: ```bash npm version --no-git-tag-version prerelease --preid=dev git commit -am "back to dev" # verify changes git diff HEAD~1 ``` 1. Push your new tag and commits to GitHub. ```bash git push --follow-tags $ORIGIN main ``` 1. Verify [the automated workflow](https://github.com/jupyterhub/configurable-http-proxy/actions?query=workflow%3A%22Publish+to+npm%22) succeeded. ## Manual release to npm A manual release should not generally be required, but might be needed in the event of a problem in the automated publishing workflow. 1. Verify you are a collaborator of the [npmjs project](https://www.npmjs.com/package/configurable-http-proxy). 1. Checkout the git tag. ``` git checkout ``` 1. Cleanup old node_modules etc. ``` git clean -xfd ``` 1. Publish to NPM. ```bash npm login npm publish ``` configurable-http-proxy-4.5.0/bin/000077500000000000000000000000001407527146400170765ustar00rootroot00000000000000configurable-http-proxy-4.5.0/bin/configurable-http-proxy000077500000000000000000000327711407527146400236320ustar00rootroot00000000000000#!/usr/bin/env node // // cli entrypoint for starting a Configurable Proxy // // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. // "use strict"; var fs = require("fs"), pkg = require("../package.json"), cli = require("commander"), tls = require("tls"), winston = require("winston"); cli .version(pkg.version) .option("--ip ", "Public-facing IP of the proxy") .option("--port (defaults to 8000)", "Public-facing port of the proxy", parseInt) .option("--ssl-key ", "SSL key to use, if any") .option("--ssl-cert ", "SSL certificate to use, if any") .option("--ssl-ca ", "SSL certificate authority, if any") .option("--ssl-request-cert", "Request SSL certs to authenticate clients") .option( "--ssl-reject-unauthorized", "Reject unauthorized SSL connections (only meaningful if --ssl-request-cert is given)" ) .option("--ssl-protocol ", "Set specific SSL protocol, e.g. TLSv1_2, SSLv3") .option("--ssl-ciphers ", "`:`-separated ssl cipher list. Default excludes RC4") .option("--ssl-allow-rc4", "Allow RC4 cipher for SSL (disabled by default)") .option("--ssl-dhparam ", "SSL Diffie-Helman Parameters pem file, if any") .option("--api-ip ", "Inward-facing IP for API requests", "localhost") .option( "--api-port ", "Inward-facing port for API requests (defaults to --port=value+1)", parseInt ) .option("--api-ssl-key ", "SSL key to use, if any, for API requests") .option("--api-ssl-cert ", "SSL certificate to use, if any, for API requests") .option("--api-ssl-ca ", "SSL certificate authority, if any, for API requests") .option("--api-ssl-request-cert", "Request SSL certs to authenticate clients for API requests") .option( "--api-ssl-reject-unauthorized", "Reject unauthorized SSL connections (only meaningful if --api-ssl-request-cert is given)" ) .option("--client-ssl-key ", "SSL key to use, if any, for proxy to client requests") .option( "--client-ssl-cert ", "SSL certificate to use, if any, for proxy to client requests" ) .option( "--client-ssl-ca ", "SSL certificate authority, if any, for proxy to client requests" ) .option("--client-ssl-request-cert", "Request SSL certs to authenticate clients for API requests") .option( "--client-ssl-reject-unauthorized", "Reject unauthorized SSL connections (only meaningful if --client-ssl-request-cert is given)" ) .option("--default-target ", "Default proxy target (proto://host[:port])") .option( "--error-target ", "Alternate server for handling proxy errors (proto://host[:port])" ) .option("--error-path ", "Alternate server for handling proxy errors (proto://host[:port])") .option( "--redirect-port ", "Redirect HTTP requests on this port to the server on HTTPS" ) .option("--redirect-to ", "Redirect HTTP requests from --redirect-port to this port") .option("--pid-file ", "Write our PID to a file") // passthrough http-proxy options .option("--no-x-forward", "Don't add 'X-forward-' headers to proxied requests") .option("--no-prepend-path", "Avoid prepending target paths to proxied requests") .option("--no-include-prefix", "Don't include the routing prefix in proxied requests") .option("--auto-rewrite", "Rewrite the Location header host/port in redirect responses") .option("--change-origin", "Changes the origin of the host header to the target URL") .option( "--protocol-rewrite ", "Rewrite the Location header protocol in redirect responses to the specified protocol" ) .option( "--custom-header
", "Custom header to add to proxied requests. Use same option for multiple headers (--custom-header k1:v1 --custom-header k2:v2)", collectHeadersIntoObject, {} ) .option("--insecure", "Disable SSL cert verification") .option("--host-routing", "Use host routing (host as first level of path)") .option("--metrics-ip ", "IP for metrics server", "") .option("--metrics-port ", "Port of metrics server. Defaults to no metrics server") .option("--log-level ", "Log level (debug, info, warn, error)", "info") .option( "--timeout ", "Timeout (in millis) when proxy drops connection for a request.", parseInt ) .option( "--proxy-timeout ", "Timeout (in millis) when proxy receives no response from target.", parseInt ) .option( "--storage-backend ", "Define an external storage class. Defaults to in-MemoryStore." ); // collects multiple flags to an object // --custom-header "k1:v1" --custom-header " k2 : v2 " --> {"k1":"v1","k2":"v2"} function collectHeadersIntoObject(value, previous) { var headerParts = value.split(":").map((p) => p.trim()); if (headerParts.length != 2) { log.error("A single colon was expected in custom header: " + value); process.exit(1); } previous[headerParts[0]] = headerParts[1]; } cli.parse(process.argv); var args = cli.opts(); var ConfigurableProxy = require("../lib/configproxy.js").ConfigurableProxy; var log = require("../lib/log.js").defaultLogger({ level: args.logLevel.toLowerCase() }); var options = { log: log }; var sslCiphers; if (args.sslCiphers) { sslCiphers = args.sslCiphers; } else { var rc4 = "!RC4"; // disable RC4 by default if (args.sslAllowRc4) { // autoCamelCase is duMb rc4 = "RC4"; } // ref: https://iojs.org/api/tls.html#tls_modifying_the_default_tls_cipher_suite sslCiphers = [ "ECDHE-RSA-AES128-GCM-SHA256", "ECDHE-ECDSA-AES128-GCM-SHA256", "ECDHE-RSA-AES256-GCM-SHA384", "ECDHE-ECDSA-AES256-GCM-SHA384", "DHE-RSA-AES128-GCM-SHA256", "ECDHE-RSA-AES128-SHA256", "DHE-RSA-AES128-SHA256", "ECDHE-RSA-AES256-SHA384", "DHE-RSA-AES256-SHA384", "ECDHE-RSA-AES256-SHA256", "DHE-RSA-AES256-SHA256", "HIGH", rc4, "!aNULL", "!eNULL", "!EXPORT", "!DES", "!RC4", "!MD5", "!PSK", "!SRP", "!CAMELLIA", ].join(":"); } function _loadSslCa(caFile) { // When multiple CAs need to be specified, they must be broken into // an array of certs unfortunately. var chain = fs.readFileSync(caFile, "utf8"); var ca = []; var cert = []; chain.split("\n").forEach(function (line) { cert.push(line); if (line.match(/-END CERTIFICATE-/)) { ca.push(new Buffer(cert.join("\n"))); cert = []; } }); return ca; } // ssl options if (args.sslKey || args.sslCert) { options.ssl = {}; if (args.sslKey) { options.ssl.key = fs.readFileSync(args.sslKey); if (process.env.CONFIGPROXY_SSL_KEY_PASSPHRASE) { options.ssl.passphrase = process.env.CONFIGPROXY_SSL_KEY_PASSPHRASE; } } if (args.sslCert) { options.ssl.cert = fs.readFileSync(args.sslCert); } if (args.sslCa) { options.ssl.ca = fs.readFileSync(args.sslCa); } if (args.sslDhparam) { options.ssl.dhparam = fs.readFileSync(args.sslDhparam); } if (args.sslProtocol) { options.ssl.secureProtocol = args.sslProtocol + "_method"; } options.ssl.ciphers = sslCiphers; options.ssl.honorCipherOrder = true; options.ssl.requestCert = args.sslRequestCert; options.ssl.rejectUnauthorized = args.sslRejectUnauthorized; } // ssl options for the API interface if (args.apiSslKey || args.apiSslCert) { options.apiSsl = {}; if (args.apiSslKey) { options.apiSsl.key = fs.readFileSync(args.apiSslKey); if (process.env.CONFIGPROXY_API_SSL_KEY_PASSPHRASE) { options.apiSsl.passphrase = process.env.CONFIGPROXY_API_SSL_KEY_PASSPHRASE; } } if (args.apiSslCert) { options.apiSsl.cert = fs.readFileSync(args.apiSslCert); } if (args.apiSslCa) { options.apiSsl.ca = _loadSslCa(args.apiSslCa); } if (args.sslDhparam) { options.apiSsl.dhparam = fs.readFileSync(args.sslDhparam); } if (args.sslProtocol) { options.apiSsl.secureProtocol = args.sslProtocol + "_method"; } options.apiSsl.ciphers = sslCiphers; options.apiSsl.honorCipherOrder = true; options.apiSsl.requestCert = args.apiSslRequestCert; options.apiSsl.rejectUnauthorized = args.apiSslRejectUnauthorized; } if (args.clientSslKey || args.clientSslCert || args.clientSslCa) { options.clientSsl = {}; if (args.clientSslKey) { options.clientSsl.key = fs.readFileSync(args.clientSslKey); } if (args.clientSslCert) { options.clientSsl.cert = fs.readFileSync(args.clientSslCert); } if (args.clientSslCa) { options.clientSsl.ca = _loadSslCa(args.clientSslCa); } if (args.sslDhparam) { options.clientSsl.dhparam = fs.readFileSync(args.sslDhparam); } if (args.sslProtocol) { options.clientSsl.secureProtocol = args.sslProtocol + "_method"; } options.clientSsl.ciphers = sslCiphers; options.clientSsl.honorCipherOrder = true; options.clientSsl.requestCert = args.clientSslRequestCert; options.clientSsl.rejectUnauthorized = args.clientSslRejectUnauthorized; } // because camelCase is the js way! options.defaultTarget = args.defaultTarget; options.errorTarget = args.errorTarget; options.errorPath = args.errorPath; options.hostRouting = args.hostRouting; options.authToken = process.env.CONFIGPROXY_AUTH_TOKEN; options.redirectPort = args.redirectPort; options.redirectTo = args.redirectTo; options.headers = args.customHeader; options.timeout = args.timeout; options.proxyTimeout = args.proxyTimeout; // metrics options options.enableMetrics = !!args.metricsPort; // certs need to be provided for https redirection if (!options.ssl && options.redirectPort) { log.error("HTTPS redirection specified but certificates not provided."); process.exit(1); } if (options.errorTarget && options.errorPath) { log.error("Cannot specify both error-target and error-path. Pick one."); process.exit(1); } // passthrough for http-proxy options if (args.insecure) options.secure = false; options.xfwd = args.xForward; options.prependPath = args.prependPath; options.includePrefix = args.includePrefix; if (args.autoRewrite) { options.autoRewrite = true; log.info("AutoRewrite of Location headers enabled."); } if (args.changeOrigin) { options.changeOrigin = true; log.info("Change Origin of host headers enabled."); } if (args.protocolRewrite) { options.protocolRewrite = args.protocolRewrite; log.info("ProtocolRewrite enabled. Rewriting to " + options.protocolRewrite); } if (!options.authToken) { log.warn("REST API is not authenticated."); } // external backend class options.storageBackend = args.storageBackend; var proxy = new ConfigurableProxy(options); var listen = {}; listen.port = parseInt(args.port) || 8000; if (args.ip === "*") { // handle ip=* alias for all interfaces log.warn( "Interpreting ip='*' as all-interfaces. Preferred usage is 0.0.0.0 for all IPv4 or '' for all-interfaces." ); args.ip = ""; } listen.ip = args.ip; listen.apiIp = args.apiIp; listen.apiPort = args.apiPort || listen.port + 1; listen.metricsIp = args.metricsIp; listen.metricsPort = args.metricsPort; proxy.proxyServer.listen(listen.port, listen.ip); proxy.apiServer.listen(listen.apiPort, listen.apiIp); if (listen.metricsPort) { proxy.metricsServer.listen(listen.metricsPort, listen.metricsIp); } log.info( "Proxying %s://%s:%s to %s", options.ssl ? "https" : "http", listen.ip || "*", listen.port, options.defaultTarget || "(no default)" ); log.info( "Proxy API at %s://%s:%s/api/routes", options.apiSsl ? "https" : "http", listen.apiIp || "*", listen.apiPort ); if (listen.metricsPort) { log.info("Serve metrics at %s://%s:%s/metrics", "http", listen.metricsIp, listen.metricsPort); } if (args.pidFile) { log.info("Writing pid %s to %s", process.pid, args.pidFile); var fd = fs.openSync(args.pidFile, "w"); fs.writeSync(fd, process.pid.toString()); fs.closeSync(fd); process.on("exit", function () { log.debug("Removing %s", args.pidFile); fs.unlinkSync(args.pidFile); }); } // Redirect HTTP to HTTPS on the proxy's port if (options.redirectPort && listen.port !== 80) { var http = require("http"); var redirectPort = options.redirectTo ? options.redirectTo : listen.port; var server = http .createServer(function (req, res) { if (typeof req.headers.host === "undefined") { res.statusCode = 400; res.write( "This server is HTTPS-only on port " + redirectPort + ", but an HTTP request was made and the host could not be determined from the request." ); res.end(); return; } var host = req.headers.host.split(":")[0]; // Make sure that when we redirect, it's to the port the proxy is running on // or the port to which we have been instructed to forward. if (redirectPort !== 443) { host = host + ":" + redirectPort; } res.writeHead(301, { Location: "https://" + host + req.url }); res.end(); }) .listen(options.redirectPort, () => { log.info( "Added HTTP to HTTPS redirection from " + server.address().port + " to " + redirectPort ); }); } // trigger normal exit on SIGINT // without this, PID cleanup won't fire on SIGINT process.on("SIGINT", function () { log.warn("Interrupted"); process.exit(2); }); // trigger normal exit on SIGTERM // fired on `docker stop` and during Kubernetes pod container evictions process.on("SIGTERM", function () { log.warn("Terminated"); process.exit(2); }); // log uncaught exceptions, don't exit now that setup is complete process.on("uncaughtException", function (e) { log.error("Uncaught Exception: " + e.message); if (e.stack) { log.error(e.stack); } }); configurable-http-proxy-4.5.0/chp-docker-entrypoint000077500000000000000000000023141407527146400225040ustar00rootroot00000000000000#!/bin/sh # Wrapper around CHP entrypoint that changes defaults slightly # to be more appropriate when run in a container. # usage: file_env VAR [DEFAULT] # ie: file_env 'XYZ_DB_PASSWORD' 'example' # (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of # "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) file_env() { var=$1 file_var="${var}_FILE" var_value=$(printenv $var || true) file_var_value=$(printenv $file_var || true) default_value=$2 if [ -n "$var_value" -a -n "$file_var_value" ]; then echo >&2 "error: both $var and $file_var are set (but are exclusive)" exit 1 fi if [ -z "${var_value}" ]; then if [ -z "${file_var_value}" ]; then export "${var}"="${default_value}" else export "${var}"="$(cat $file_var_value)" fi fi unset "$file_var" } file_env 'CONFIGPROXY_AUTH_TOKEN' case "$@" in *"--api-ip"*) break ;; *) # Default api-ip to all interfaces in docker, # so that it is accessible to other containers # and when port-forwarding is requested. ARGS="--api-ip=0.0.0.0" ;; esac exec configurable-http-proxy $ARGS "$@" configurable-http-proxy-4.5.0/doc/000077500000000000000000000000001407527146400170735ustar00rootroot00000000000000configurable-http-proxy-4.5.0/doc/_static/000077500000000000000000000000001407527146400205215ustar00rootroot00000000000000configurable-http-proxy-4.5.0/doc/_static/chp.png000066400000000000000000000476211407527146400220130ustar00rootroot00000000000000PNG  IHDR eMsRGB@IDATxxgfwR @D V` ElW^ `] -خzAA^D$ +XQABͦ&Mv<3̜wNYU8Sϔ[nEY0 n>}jxN$@$PW P3v @-IHg9;Qn"F$`bj$@$\y=j윤b\_q[rG19f(Ɲ 6ݕ7PUŘ2蜭ſ4MbR+V1!vYk5FJ3 h4{z{*j qVG (jϮ-yBZ.KK @&~]X5G~&HK(y\NI)FSSVO(mPHkkѶ/X3E}CW_tۥ$GUM9g<⾣꣈c4kެXCU> 9IWKX? @|WaٗJ=ܒ +ѹ_Hr_+H,qnR.l]W1⟿hjHM%o(UÞ,~P(P?&m~PjoYƪ,?$@K &~]x5Wg97"F[1V{&Ba*ahhbhmxP1rl(zWQbZF&@\o]_ev 5::x.(LZXemߏn %@.o;:pCm'#ݺ[EQ b֒ F&c0 e3qamʥ(H;j6p?5=t^lf3"vr&փ-c:M$`U\{CutY9Lm,p`p^q0r `YacCPpb?܏iZBj|_g"ɣkNn6C4tAyPNKW3eѯtb&oT0}*=2bb:}vmDDɩ}1lYQPA|pW5.u>PJݦ 󦠌W C R5 5߈P'3fe6;Y ! @,~ 40~򘔞LT8N ΣR{Ln (&b\$(VHfS5Q eMPfueHH P `$@$PW S1Q:ZXfK$@$@$@$@$@$@$@$@$@$@$@$@$@$@@,$@nSH& (*df$@$@*m  (NfF$@$`H& (*df$@$@*m  (NfF$@$`H& (*df$@$@*m  (NfF$@$`H& (*df$@$@*m  (NfF$@$`H& (*df$@$@*m  (NfF$@$`H& (*df$@$@*m  (NfF$@$`H& (*df$@$@*m  (NfF$@$`H& (*df$@$@*m  (NfF$@$`H& (*df$@$@*m  (NfF$@$`H& (*df$@$@*m  (NfF$@$`H& (*df$@$@*m  (NfF$@$`H& (*df$@$@*m  (NfF$@$`H& (*df$@$@*m  (NfF$@$`H& (*df$@$@*h@C*WHwN(I΃%_u1ȠCu3azGT0xQݏ Y+~K!դs< ;;H`$2=! FX 3$$1_CV0 DAVk6 ,>J4 ( AM1O#?E?SN>y3 UNyfNl<A$*ȃ_!AZCȓw2D$L0,\i_ <̋p\;r/H]\i9BB-~Aƅ$d4$y ^s˱Z_s)U&$ $ 2 "Ie.Kf0n #iނrD Ls8yI@r100if!(TY)$H[bQAN3t !ҙ/s(~#̄4HCPi2!+S R?h,Q/BL492"Jof17@DMb(u]<|pQ\·t04r-H0R N:v1W@S)Bf@H.?sD%%PF(8StLt1 As2N8$!̕%#n% ?eބc%D} T#-x!!V~!P$7r{(DYW!b\Dy"JHv@Daujf̟ O41L=(Py w LQX:(6EH>U5)rL-S6bZC  ?C,i&eDiH"ߓq,u 2bpHD!΄T6i" @HDH#F!bDH{@f@hmcͨyX{"'7|Qltrm+C>)k].܎- Eʵ:p H-+Pic2R#Oҩx*0DPle%{83pLUX ?[eI! &q p &=˻P,N_U/N2ڋ d@a+@&CR.pT\$tؕU.Nw#&۽ʩLo%Jnr!4 N#80YEf=F/IRMYJF2UE@s9ð~&Ead(Y.*`BS0̷IVG&P>UIоu zF@Wi (_5ߍ겊$@$pȎa'SF4$@$@$@$@$@$@$@$@$@$@$@M 6ӧwx}=**g-ͩ| 'u#iTCkFbømko)P\9+:|p\q^;+]]邢qZ{QQ.ٓ $JwU^1$mױ񵳋q8O0)ɛ\z)z!:l#XW F[1B)0 ~ȧ|oל;0 -*nQrj=KJa7n{wI;.I~ tЅ;e4OfCEM*yV6:2]7g=C1DMSRNULA8 giyvPp @u|\){zjl0mCi ntmb |(UUNŁck+)NJ7!to@_ -)<~5HUuhkÝ34|j\Q#maYV6ZrODc^A'O\lذ3@Q+ڡNFZ՚MA3\ 8?UK雟suU13jb,H . ԈAG{ϧpߍQ.f{"lbrO^nBsCuT =JtweM*}aE&oV͟j&)d;<1כr0*{ ?Q^iH{lrJI;{i֊[i[bCh> ]=?n @@Ei**rXӌzs+2B[BлVv]<65C^)7xO-1ߊ\VX({WkFJiU2OL"TMm5j$yj3o$ ;s_(̓ {~Y "ttL%wl(sqf ! $P# ڣO~k xTN3"ۅwYb nkxۛ?m*#{Wh#x;jaq<%:b9UIu`AeW1\< eCPoȥ(5rg:}q$kɕ߇8"#(,[Yl!#0uT'gdZvYAogOc=Z&iR# .(XԪizmJzۘzSwaet|a#'|`/ J 9!2 )FF4+ Aav2bD{tI? g0,#;–M&@Y22-Jr~eM2`-1@K q]SKt[C)2<)(mU`dewį*yZyez/Vu6)ܗ=5ۣk<`8VڇC6"q1s#Y!O/#W9_C1@$kc=o!gCB8턿\iH4M2ohTRQ>hiHFĄ@!Hck1=b+-W*dNiW+&VF&49ߜYiɀtлyb'zɶ"Tddb(?Q26 !3|H H1D**=c;EnWl,~"YZYLg! őY?"J' ETQd1d;2Ȃ,HKȇ)Ls5aHs9bd}MDL/mv}̆ͅHL#8x |/ ̼iQ)jjs 㽗ϰ[OMp.m|3<{Scn|!B79 dD`m)?q/:шtF1515xPA Za_/ssuIM4tiJ0>l!y"ޓ5*iJ$y:?~j*F֤r}'7=V( 3+x\(V\#qas c9$ `u0M! l>`=jcmx}EE{ky >`9tL' @(?O`v$ne ьj%cF4 3 ߼\2ye ^}_чуH q p4qϽe JdLe+d1/ I)=\װb1t!ˇ% @ISx'vY 6T!3  D & TL2  @`Q @ PT!3  D & TL2  @`Q @ PT!3  DM~{A9G)TfM>reqN@rI$@Q&%ʀc8{NaHH PcIH PaHH PcIH PaHH PcIH PaHH PcIH PaHH PcIH PaHH PcIH PaHH P٫4{q;kTr<&HLT0yޫLN߭|&MGoaYy $&?K s)b(J UU<5'i< ^4ېn6د{K^Vzoæ٣ $61rbt }sV *ɥWߠb4ּ^PxCS 26רF-GԴ+UUt:y$P pSGohӊX;]u>`)~+)tߑ4(5"Birq۴ԏF,cByaV4Q?ʞQIW'5"E$P P+4o~Fqyǥ3o*>̿U:JS5{^]~OQ\bbB:;XDzoL\ӤcQ+smS!5 @(:mE&ծc7=;Uy&?}]zLnQ6~Žǝ}FAG'>NXꝘ=* bc(''rStո#~M]N Cm/PL8IHF}g?(AՒQ`i;K/4^^VWa/\ϥ-ݹ`wJvCQUt؜]M]G%ZfNJ9_Z^ 2 2'pM_D "i{%jw+a[r7Tp!҅|P#`' U5/t,jLGB̥cUSQ }6闦5rӒ/|۹'ޱXa(Jrdio{qJgv6<F DŽ˪tP$ #6OOmrZaѮnBJ\{9ӎDX6jx~)-\K_tm M \P<X(;a_J7t hP2;h#!G e]r`5Tm(cS>ԩ6UQg!)^*OJ׸wFMP'O6=EcȨdĒw+S'LnI"} !oA^r!i12 s r͟ 6+HW!tC@Cs[v~0]Dֹ/ְ߁,Y  @ėv7E}D\o TF!' FCU" 7[ Ml_cWqXmո埡@gmdEPwEqQcWJmfW;4Iɘ%{[K6b M]{ziOx`t𰔇y¶V8S.=+#y-uR=oY#1-e싏to-hL#krևА@ث:m{JOx_5l_Tnx -ժ˔Mv'a7(vBu~";X9 k#:wSuK <&_aegE=[y/VTLʼ;xsvN2g)BNa7=褫t\8 U>4B9#OݖW Cp ;!t΂Hi]+ᖇ+鬷C,#[-Djd$$)<s5aHs9a(φɀ Ʌ8!R?!!k!@!!b^i+!W>Dx !i*9.B4$PirƄY2¿)55C(-)Ŏo庶I%+d vlB /KXCI=[j&H@q*Ǧ1]o SWL.yGރ)O̎WksP5@yqji4O@_1 -(y0çbԭʗS1p< QNvI}̫xk(mbv"~CRX[.8dH_JeZ:f=11Z?D,+tD}ԃ#M%+ HUn2%#]<@YmTb I<ñvCw{M%U7TWcœp0e`+ϋX4q!7:fRYlbn< fO*.Y[&/nfqJd,4j5o]E-I~cl%HU0#(sgets<޷8aJ^Ta_']^XQ[m^;X*3Xx<Y$)ҏ*"pH^QXqr UYrYC%1**gńW5bJhA _\NfʳFjV.> ?T>+cw,WiUqW/SoA.Շ񓼜^$@L`ԩL48N2*ZVq,wNvug dW( b?GѪaME ֥j~*O)A?+t/ ԇ!:,$@qI &.O[Ue8%V&n81 @|`̛7/=ړ _r D?sO*Xn"/a,3 ?Wl5/B@Ȗ"-e WueɌ#F|Jɗ$scۧ"gN$ ?~E Q] fذakι%\eoy$@Q" Nf %J%1X&`* wҦM#X>e 6?SHB@84XÅ 3337Ӈ@aI [̷Q*ė/2rH~,u4! j#@:`lCɴ-$@BbdͅbяH `*C~-ʒ@"PSĦ$]F0]vYԂ@b"bgG`СY 0m&24$@Q"L2&-'=5'dvmY;OycI[z\2[U옜H `@W3gN|)onⴜ-%>c^{{@f.GqCSdqsXh۷洴2.:Z0_H4$g{x嗏v\?`dNm PTl߾}mP2'Q$g[#MSd&7GRRR;4H:|Z bI pSYtx_1arF1/^\Ub$w8S $z7Җ-[իWIMϲH PckٳӰL~5R0 !8')8?~ Bi+; 7ʨ&9d jlٲe=X[CEKick@L!8wFQIB#x9SgwcIPL3s!CvLX!`UF0^LQ;:y?>jZ@fy:D#\]vCcSH`$:|R(6P4'vm&:,"H&*i2ŬHΝR6L:P4'eN߿IC M &O?iP2Aɜ 9~t̏l1O[n93| '|YA"*(e։I#B*6jP*^$P2ݑ L1 E0KyT01Xx%^ؾ.F3@ɜ`I`JH P2W![P2+00 T0uT!Jy{ƌ]b D)G(# Y]f=o ~=p?33}IJoKT0qyXx%iPx 2;[  4^z@ " D~$%X\޽{Q*ْ@5,8Q `l3F.h3|B~7F^uT0uIJYISdPۡ`~ e>D>-3ZV$P5\7"J9sf=}&4ٿRRRw\ǃa.M/E3K)**J-..NCSvfTH?;l;xF<ӖcQ <oy#yVKQnԨQ!pLAT0ɬH ӧOoE3]?i^3¶ ɧ6㠮*f(訏q#'Lt 1}v i:z[DG(Sq+ qp#_6)# [Cm*3ةHWF(z#lܻ.{7ĴWݨKaє#@SI`ͥ#:'Q^ctP9{:Q#FxQaA=Z#m"i$BC~͎nFv6;e KNNލ1(B3h:gΜԒFh$Wf+O7D w"c;MtKpS"`piH6 W31y?uq5nܸI:$!v{)Fl[ta#0m p'L@0Gr`f*a9c`FoIl05mů( gj@~~}ݺu< IB}`K #۞={9|Mىpe6a!Cl]uXV7-تCsl ?  _3UP0aߟxN3:ahHN nGСCe&Fy.“a|-2ZkD9:Ư/o +H B`[lΦOS786;!Wc',@':LysPϲ5Sp7YVZիWIA3@9gNv'P֢cBh1}kd+.M' өׯю5Z=MYY)q,r`p6hH `$ k'AVHl|yTg֥v İ\7|AY]loQuI$5q ;؋`jٲg8Sd_~9Ѕ\Wp--b݀Q7T0QGH 0(E~7",.8pK'/7z: /u ;_9 &eё LRd_&ĝxaÆ|=W" Fç.N~j.viY*D% Ǎ{ 7ggc=Hܸ5|6WhǃДÇQUT0U%t$πbWC^w8OƟD7 y<:p'L_(p #/+[Q*c|Bir"ns2%==}Z~k4$Νh#pMe)>%49PKH X2`z?n @ؙAE#)AQkeL=AHCI 4~(n >vNP_7< f,P Ea$2X!$OKPdeɗ1`)t @H\"Į\Bb`$ ׼\rȽ Dfr H \. Q#FoHyxdnHfé`iM!);RD"*H |L92i8EfM!@r*G>{łE6 !{߇7DIc ݘpx4(7(H҇҈::6vz/_:tmU4٣o1t}M_|Rч5Yy+kԩə>s|#`O>p ʥIMPTeݰ]>, 9 PlƻCO-<=^$C,0~. bH V ȿwŲ.Q:NzG1p PTuzޤ{M:Q곺U $tw}{rU gCU [鸢_ue ?w W_lp_YEӨfEQg<'iG99PT~GfYE|E7Q]^X,#O+q1uy WdJZv!c861`xWm=zto!}v˵9Jƴ̪tЅ;ʿ޶ ia 磩_kĊ%Hc/ňIԿ9y/#5={rG BאNES(SۆP6lSVyyV^4FvprM; lذt1Gzi? S8{8ӏpF2:Э?Ueڵfa':ٶD8S}DGՓc4v?IDAT(㸯otrNױǡt:'_ELvk"(iUUYkSmXTFΣR\JB]mt%fEi6 ; e:NSW!خ90 w W{P(8 D}F#WaF~6USF;Su՘*mixu9b/ 2TO q;Ҭ4qڮh|yU^hW䞑{ &wjt>ނH%_i)_a%6VtjK&HMNLW ]4P:F}%#[tWmA?H ?-[մ;^}5!]iѐ S`Vz?,96EaѮ.(d>"kgiy-G!&2 F%xokgKpEn2+eJmfW;4Iɘ%e&FomZIH*/w}[+}%CQqkE֯Kj6o;o_pMSKGk21)VzMSoC篘}NF=}(MWP!dya/+UrPD0Ix*S-ޅn(=^QAua*AV4y*K; /o.x FA[ <1l~nO?eÓ Y!y!^Y=Uvl[5b/umC3ɱ%wl(1ݪ"5󅲘λiIw$(6u˵g]' [&JҗA9ܢH8;#gݺJ/8N2ZHtL -q@Ⱦ`V6 dwgí)MaNŝz%V9<*/\^V9Q/{ &ʔ}|h׮jtM9j%_e\ix/(uܽ_'aoE̴EeT Xnk v䲰 ǦunLB7*mxz_iYx "{NQ?Fn0FaRڎ!WO"?lX0vIo@nEڴѫxVyuH؞<b %etDM8`yQюE8WCB>4o(.(ؽ(v}lrz(ر8+=gRx4>A-Fcf9(` ?yU^L^{F(d,Inc!Vz;tYerVv9txvN:iMqCWo$?sT$8dKp7wKRv(y:{֛& sR_:LD`go~[xKC5Bo(h,.O^ BR_03O9Xx(PH 0j a2wp ̳nә|3X'_l%[E'o]TXO* $. |;cx_~FKsmtP.b*(1Çnc`%x$&hN;]m:hM-&Y`۟|*8D]$M3([ж[Xyg!b٦%bhρ=e*F,TUT0U%t$&qlAt_L5'qafh$PeP$FAHkq,۪U" ʧ IfΜydqqq_7!usl(Eӧϙ)H <ͳݻ3 .q-sa)BxE }I |7 _7,8pநW$ _~ח(eR1"0`Ay@% t7F[u1|JVzX?.M)"(#``? La$tA!Y`ޕN[ٰaÏ0v~4$`5gϞ1u\3g@B-233?5S w*HfϞu@j@?^5i߾ڬ|)L/ׯIG\q ͇z}2`nLmO$ #cI8o'J'B-8`)82bk(.żS @vTtEt":a$6rNL# ~"ѡmBM6Uaԣ]km#68X7m>mP$?`mccQ8aَNޏ(FS2c$ wL-@EXkǛ#FvST084$@yLȓt-bvVP!LD4tqOFis#-v7&^jsIG#!Ǎ"uoǨ(_qg>",2;L쯌/c3ھ)è`puА T:S :fH|ǍX:i~)ï~I\8.ıƱW Ddg\P7S J|[> Iة&6N[ ,QN nűun6T0F H*C@FHӠcOh䑇lTHCX teBMFzE\SlF]PBv qQ5#]^IENDB`configurable-http-proxy-4.5.0/doc/rest-api.yml000066400000000000000000000056131407527146400213470ustar00rootroot00000000000000swagger: "2.0" info: title: "Configurable HTTP Proxy" description: "The REST API for configurable-http-proxy.\n\n Find out more: https://github.com/jupyterhub/configurable-http-proxy/blob/HEAD/README.md" version: "1.1.0" securityDefinitions: token: type: "apiKey" name: "Authorization" in: "header" security: - token: [] basePath: "/api/" produces: - "application/json" tags: - name: "routes" description: "Everything about configured routes" schemes: - "http" - "https" paths: /routes: get: tags: - "routes" summary: "Fetches the routing table" description: "All routes currently in the proxy's routing table, excluding the default route." parameters: - name: "inactive_since" in: "query" description: "Only return routes with last_activity before this time" required: false type: "string" format: "ISO8601 Timestamp" responses: 200: description: "The routing table" schema: $ref: "#/definitions/RoutingTable" 400: description: "Invalid timestamp provided" 404: description: "Routing table missing" /routes/{route_spec}: post: tags: - "routes" summary: "Create a new route" parameters: - name: "route_spec" description: "Route specification to create - a path prefix if the proxy is in path mode (default) or '/' followed by hostname if it is in host mode." in: "path" required: true type: "string" format: "Path" - name: "body" in: "body" schema: $ref: "#/definitions/RouteTarget" required: true responses: 201: description: "Successfully created" delete: tags: - "routes" summary: "Delete the given route" parameters: - name: "route_spec" in: "path" required: true type: "string" format: "Path" description: >- Route specification to create - a path prefix if the proxy is in path mode (default) or '/' followed by hostname if it is in host mode. responses: 204: description: "Successfully deleted" definitions: RoutingTable: type: "object" description: "Maps keys (route path prefixes / hostnames) to their targets" additionalProperties: $ref: "#/definitions/RouteTarget" RouteTarget: type: "object" properties: target: type: "string" format: "URI" description: >- Fully qualified URL that will be the target of proxied requests that match this route last_activity: type: "string" format: "ISO8601 Timestamp" readOnly: true description: "ISO8601 Timestamp of when this route was last used to route a request" configurable-http-proxy-4.5.0/index.js000066400000000000000000000000621407527146400177710ustar00rootroot00000000000000module.exports = require("./lib/configproxy.js"); configurable-http-proxy-4.5.0/lib/000077500000000000000000000000001407527146400170745ustar00rootroot00000000000000configurable-http-proxy-4.5.0/lib/configproxy.js000066400000000000000000000472051407527146400220110ustar00rootroot00000000000000// A Configurable node-http-proxy // // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. // // POST, DELETE to /api/routes[:/path/to/proxy] to update the routing table // GET /api/routes to see the current routing table // "use strict"; var http = require("http"), https = require("https"), fs = require("fs"), path = require("path"), EventEmitter = require("events").EventEmitter, httpProxy = require("http-proxy"), winston = require("winston"), util = require("util"), URL = require("url"), defaultLogger = require("./log").defaultLogger, querystring = require("querystring"), metrics = require("./metrics"); function bound(that, method) { // bind a method, to ensure `this=that` when it is called // because prototype languages are bad return function () { return method.apply(that, arguments); }; } function argumentsArray(args) { // cast arguments object to array, because Javascript. return Array.prototype.slice.call(args, 0); } function fail(req, res, code, msg) { // log a failure, and finish the HTTP request with an error code msg = msg || ""; res._logMsg = msg; if (res.writableEnded) return; // response already done if (res.writeHead) res.writeHead(code); if (res.write) { if (!msg) { msg = http.STATUS_CODES[code]; } res.write(msg); } if (res.end) res.end(); } function jsonHandler(handler) { // wrap json handler, so the handler is called with parsed data, // rather than implementing streaming parsing in the handler itself return function (req, res) { var args = argumentsArray(arguments); var buf = ""; req.on("data", function (chunk) { buf += chunk; }); req.on("end", function () { var data; try { data = JSON.parse(buf) || {}; } catch (e) { fail(req, res, 400, "Body not valid JSON: " + e); return; } args.push(data); handler.apply(handler, args); }); }; } function authorized(method) { // decorator for token-authorized handlers return function (req, res) { if (!this.authToken) { return method.apply(this, arguments); } var match = (req.headers.authorization || "").match(/token\s+(\S+)/); var token; if (match !== null) { token = match[1]; } if (token === this.authToken) { return method.apply(this, arguments); } else { this.log.debug( "Rejecting API request from: %s", req.headers.authorization || "no authorization" ); res.writeHead(403); res.end(); } }; } function parseHost(req) { var host = req.headers.host; if (host) { host = host.split(":")[0]; } return host; } function camelCaseify(options) { // camelCaseify options dict, for backward compatibility let camelOptions = {}; Object.keys(options).forEach((key) => { const camelKey = key.replace(/_(.)/g, function (match, part, offset, string) { return part.toUpperCase(); }); if (camelKey !== key) { this.log.warn("option %s is deprecated, use %s.", key, camelKey); } camelOptions[camelKey] = options[key]; }); return camelOptions; } const loadStorage = (options) => { if (options.storageBackend) { const BackendStorageClass = require(options.storageBackend); return new BackendStorageClass(options); } // loads default storage strategy const store = require("./store.js"); return new store.MemoryStore(options); }; function _logUrl(url) { // format a url for logging, e.g. strip url params if (url) return url.split("?", 1)[0]; } class ConfigurableProxy extends EventEmitter { constructor(options) { super(); var that = this; this.log = (options || {}).log; if (!this.log) { this.log = defaultLogger(); } this.options = camelCaseify.apply(this, [options || {}]); this._routes = loadStorage(options || {}); this.authToken = this.options.authToken; if (options.includePrefix !== undefined) { this.includePrefix = options.includePrefix; } else { this.includePrefix = true; } this.headers = this.options.headers; this.hostRouting = this.options.hostRouting; this.errorTarget = options.errorTarget; if (this.errorTarget && this.errorTarget.slice(-1) !== "/") { this.errorTarget = this.errorTarget + "/"; // ensure trailing / } this.errorPath = options.errorPath || path.join(__dirname, "error"); if (this.options.enableMetrics) { this.metrics = new metrics.Metrics(); } else { this.metrics = new metrics.MockMetrics(); } if (this.options.defaultTarget) { this.addRoute("/", { target: this.options.defaultTarget, }); } options.ws = true; var proxy = (this.proxy = httpProxy.createProxyServer(options)); // tornado-style regex routing, // because cross-language cargo-culting is always a good idea this.apiHandlers = [ [ /^\/api\/routes(\/.*)?$/, { get: bound(this, authorized(this.getRoutes)), post: jsonHandler(bound(this, authorized(this.postRoutes))), delete: bound(this, authorized(this.deleteRoutes)), }, ], ]; var logErrors = (handler) => { return function (req, res) { function logError(e) { that.log.error("Error in handler for %s %s: %s", req.method, _logUrl(req.url), e); } try { let p = handler.apply(that, arguments); if (p) { return p.catch(logError); } } catch (e) { logError(e); } }; }; // handle API requests var apiCallback = logErrors(that.handleApiRequest); if (this.options.apiSsl) { this.apiServer = https.createServer(this.options.apiSsl, apiCallback); } else { this.apiServer = http.createServer(apiCallback); } // handle metrics if (this.options.enableMetrics) { var metricsCallback = logErrors(that.handleMetrics); this.metricsServer = http.createServer(metricsCallback); } // proxy requests separately var proxyCallback = logErrors(this.handleProxyWeb); if (this.options.ssl) { this.proxyServer = https.createServer(this.options.ssl, proxyCallback); } else { this.proxyServer = http.createServer(proxyCallback); } // proxy websockets this.proxyServer.on("upgrade", bound(this, this.handleProxyWs)); this.proxy.on("proxyRes", function (proxyRes, req, res) { that.metrics.requestsProxyCount.labels(proxyRes.statusCode).inc(); }); } logResponse(req, res) { // log function called when any response is finished var code = res.statusCode; var logF; if (code < 400) { logF = this.log.info; } else if (code < 500) { logF = this.log.warn; } else { logF = this.log.error; } var msg = res._logMsg || ""; logF("%s %s %s %s", code, req.method.toUpperCase(), _logUrl(req.url), msg); } addRoute(path, data) { // add a route to the routing table path = this._routes.cleanPath(path); if (this.hostRouting && path !== "/") { data.host = path.split("/")[1]; } this.log.info("Adding route %s -> %s", path, data.target); var that = this; return this._routes.add(path, data).then(() => { that.updateLastActivity(path); that.log.info("Route added %s -> %s", path, data.target); }); } removeRoute(path) { // remove a route from the routing table var routes = this._routes; return routes.get(path).then((result) => { if (result) { this.log.info("Removing route %s", path); return routes.remove(path); } }); } getRoute(req, res, path) { // GET a single route path = this._routes.cleanPath(path); return this._routes.get(path).then(function (route) { if (!route) { res.writeHead(404); res.end(); return; } else { res.writeHead(200, { "Content-Type": "application/json" }); res.write(JSON.stringify(route)); res.end(); } }); } getRoutes(req, res, path) { // GET /api/routes/(path) gets a single route if (path && path.length && path !== "/") { return this.getRoute(req, res, path); } // GET returns routing table as JSON dict var that = this; var parsed = URL.parse(req.url); var inactiveSince = null; if (parsed.query) { var query = querystring.parse(parsed.query); if (query.inactive_since !== undefined) { // camelCaseify query.inactiveSince = query.inactive_since; } if (query.inactiveSince !== undefined) { var timestamp = Date.parse(query.inactiveSince); if (isFinite(timestamp)) { inactiveSince = new Date(timestamp); } else { fail(req, res, 400, "Invalid datestamp '" + query.inactiveSince + "' must be ISO8601."); return; } } } res.writeHead(200, { "Content-Type": "application/json" }); return this._routes.getAll().then((routes) => { var results = {}; if (inactiveSince) { Object.keys(routes).forEach(function (path) { if (routes[path].last_activity < inactiveSince) { results[path] = routes[path]; } }); } else { results = routes; } res.write(JSON.stringify(results)); res.end(); that.metrics.apiRouteGetCount.inc(); }); } postRoutes(req, res, path, data) { // POST adds a new route path = path || "/"; if (typeof data.target !== "string") { this.log.warn("Bad POST data: %s", JSON.stringify(data)); fail(req, res, 400, "Must specify 'target' as string"); return; } var that = this; return this.addRoute(path, data).then(function () { res.writeHead(201); res.end(); that.metrics.apiRouteAddCount.inc(); }); } deleteRoutes(req, res, path) { // DELETE removes an existing route return this._routes.get(path).then((result) => { var p, code; if (result) { p = this.removeRoute(path); code = 204; } else { p = Promise.resolve(); code = 404; } return p.then(() => { res.writeHead(code); res.end(); this.metrics.apiRouteDeleteCount.inc(); }); }); } async targetForReq(req) { var metricsTimerEnd = this.metrics.findTargetForReqSummary.startTimer(); // return proxy target for a given url path var basePath = this.hostRouting ? "/" + parseHost(req) : ""; var path = basePath + decodeURIComponent(URL.parse(req.url).pathname); var route = await this._routes.getTarget(path); metricsTimerEnd(); if (route) { return { prefix: route.prefix, target: route.data.target, }; } } updateLastActivity(prefix) { var metricsTimerEnd = this.metrics.lastActivityUpdatingSummary.startTimer(); var routes = this._routes; return routes .get(prefix) .then(function (result) { if (result) { return routes.update(prefix, { last_activity: new Date() }); } }) .then(metricsTimerEnd); } _handleProxyErrorDefault(code, kind, req, res) { // called when no custom error handler is registered, // or is registered and doesn't work if (res.writableEnded) return; // response already done if (!res.headersSent && res.writeHead) res.writeHead(code); if (res.write) res.write(http.STATUS_CODES[code]); if (res.end) res.end(); } handleProxyError(code, kind, req, res, e) { // called when proxy itself has an error // so far, just 404 for no target and 503 for target not responding // custom error server gets `/CODE?url=/escapedUrl/`, e.g. // /404?url=%2Fuser%2Ffoo var errMsg = ""; this.metrics.requestsProxyCount.labels(code).inc(); if (e) { // avoid stack traces on known not-our-problem errors: // ECONNREFUSED, EHOSTUNREACH (backend isn't there) // ECONNRESET, ETIMEDOUT (backend is there, but didn't respond) switch (e.code) { case "ECONNREFUSED": case "ECONNRESET": case "EHOSTUNREACH": case "ETIMEDOUT": errMsg = e.message; break; default: // logging the error object shows a stack trace. // Anything that gets here is an unknown error, // so log more info. errMsg = e; } } this.log.error("%s %s %s %s", code, req.method, _logUrl(req.url), errMsg); if (!res) { this.log.debug("Socket error, no response to send"); // socket-level error, no response to build return; } if (this.errorTarget) { var urlSpec = URL.parse(this.errorTarget); // error request is $errorTarget/$code?url=$requestUrl urlSpec.search = "?" + querystring.encode({ url: req.url }); urlSpec.pathname = urlSpec.pathname + code.toString(); var secure = /https/gi.test(urlSpec.protocol) ? true : false; var url = URL.format(urlSpec); this.log.debug("Requesting custom error page: %s", urlSpec.format()); // construct request target from urlSpec var target = URL.parse(url); target.method = "GET"; // add client SSL config if error target is using https if (secure && this.options.clientSsl) { target.key = this.options.clientSsl.key; target.cert = this.options.clientSsl.cert; target.ca = this.options.clientSsl.ca; } var errorRequest = (secure ? https : http).request(target, function (upstream) { if (res.writableEnded) return; // response already done ["content-type", "content-encoding"].map(function (key) { if (!upstream.headers[key]) return; if (res.setHeader) res.setHeader(key, upstream.headers[key]); }); if (res.writeHead) res.writeHead(code); upstream.on("data", (data) => { if (res.write && !res.writableEnded) res.write(data); }); upstream.on("end", () => { if (res.end) res.end(); }); }); errorRequest.on("error", (e) => { // custom error failed, fallback on default this.log.error("Failed to get custom error page: %s", e); this._handleProxyErrorDefault(code, kind, req, res); }); errorRequest.end(); } else if (this.errorPath) { var filename = path.join(this.errorPath, code.toString() + ".html"); if (!fs.existsSync(filename)) { this.log.debug("No error file %s", filename); filename = path.join(this.errorPath, "error.html"); if (!fs.existsSync(filename)) { this.log.error("No error file %s", filename); this._handleProxyErrorDefault(code, kind, req, res); return; } } fs.readFile(filename, (err, data) => { if (err) { this.log.error("Error reading %s %s", filename, err); this._handleProxyErrorDefault(code, kind, req, res); return; } if (!res.writable) return; // response already done if (res.writeHead) res.writeHead(code, { "Content-Type": "text/html" }); if (res.write) res.write(data); if (res.end) res.end(); }); } else { this._handleProxyErrorDefault(code, kind, req, res); } } handleProxy(kind, req, res) { // proxy any request var that = this; // handleProxy is invoked by handleProxyWeb and handleProxyWs, which pass // different arguments to handleProxy. // - handleProxyWeb: args = [req, res] // - handleProxyWs: args = [req, socket, head] var args = Array.prototype.slice.call(arguments, 1); // get the proxy target return this.targetForReq(req) .then((match) => { if (!match) { that.handleProxyError(404, kind, req, res); return; } if (kind === "web") { that.emit("proxyRequest", req, res); } else { that.emit("proxyRequestWs", req, res, args[2]); } var prefix = match.prefix; var target = match.target; that.log.debug("PROXY %s %s to %s", kind.toUpperCase(), _logUrl(req.url), target); if (!that.includePrefix) { req.url = req.url.slice(prefix.length); } target = URL.parse(target); if (that.options.clientSsl) { target.key = that.options.clientSsl.key; target.cert = that.options.clientSsl.cert; target.ca = that.options.clientSsl.ca; } // add config argument args.push({ target: target }); // add error handling args.push(function (e) { that.handleProxyError(503, kind, req, res, e); }); // dispatch the actual method, either: // - proxy.web(req, res, options, errorHandler) // - proxy.ws(req, socket, head, options, errorHandler) that.proxy[kind].apply(that.proxy, args); // update timestamp on any request/reply data as well (this includes websocket data) req.on("data", function () { that.updateLastActivity(prefix); }); res.on("data", function () { that.updateLastActivity(prefix); }); if (kind === "web") { // update last activity on completion of the request // only consider 'successful' requests activity // A flood of invalid requests such as 404s or 403s // or 503s because the endpoint is down // shouldn't make it look like the endpoint is 'active' // we no longer register activity at the *start* of the request // because at that point we don't know if the endpoint is even available res.on("finish", function () { // (don't count redirects...but should we?) if (res.statusCode < 300) { that.updateLastActivity(prefix); } else { that.log.debug("Not recording activity for status %s on %s", res.statusCode, prefix); } }); } }) .catch(function (e) { if (res.finished) throw e; that.handleProxyError(500, kind, req, res, e); }); } handleProxyWs(req, socket, head) { // Proxy a websocket request this.metrics.requestsWsCount.inc(); return this.handleProxy("ws", req, socket, head); } handleProxyWeb(req, res) { this.handleHealthCheck(req, res); if (res.finished) return; // Proxy a web request this.metrics.requestsWebCount.inc(); return this.handleProxy("web", req, res); } handleHealthCheck(req, res) { if (req.url === "/_chp_healthz") { res.writeHead(200, { "Content-Type": "application/json" }); res.write(JSON.stringify({ status: "OK" })); res.end(); } } handleMetrics(req, res) { if (req.url === "/metrics") { return this.metrics.render(res); } fail(req, res, 404); } handleApiRequest(req, res) { // Handle a request to the REST API if (res) { res.on("finish", () => { this.metrics.requestsApiCount.labels(res.statusCode).inc(); this.logResponse(req, res); }); } var args = [req, res]; function pushPathArg(arg) { args.push(arg === undefined ? arg : decodeURIComponent(arg)); } var path = URL.parse(req.url).pathname; for (var i = 0; i < this.apiHandlers.length; i++) { var pat = this.apiHandlers[i][0]; var match = pat.exec(path); if (match) { var handlers = this.apiHandlers[i][1]; var handler = handlers[req.method.toLowerCase()]; if (!handler) { // 405 on found resource, but not found method fail(req, res, 405, "Method not supported"); return Promise.resolve(); } match.slice(1).forEach(pushPathArg); return handler.apply(handler, args) || Promise.resolve(); } } fail(req, res, 404); } } exports.ConfigurableProxy = ConfigurableProxy; configurable-http-proxy-4.5.0/lib/error/000077500000000000000000000000001407527146400202255ustar00rootroot00000000000000configurable-http-proxy-4.5.0/lib/error/404.html000066400000000000000000000003771407527146400214310ustar00rootroot00000000000000 404: Not Found

404: Not Found

No service is registered at this URL


configurable-http-proxy

configurable-http-proxy-4.5.0/lib/error/503.html000066400000000000000000000004241407527146400214220ustar00rootroot00000000000000 503: Proxy Target Missing

503: Proxy Target Missing

The upstream service is unavailable


configurable-http-proxy

configurable-http-proxy-4.5.0/lib/error/error.html000066400000000000000000000003111407527146400222370ustar00rootroot00000000000000 Proxy Error

Proxy Error


configurable-http-proxy

configurable-http-proxy-4.5.0/lib/log.js000066400000000000000000000013161407527146400202140ustar00rootroot00000000000000"use strict"; var strftime = require("strftime"), winston = require("winston"); const simpleFormat = winston.format.printf((info) => { // console.log(info); return `${info.timestamp} [${info.label}] ${info.level}: ${info.message}`; }); function defaultLogger(options) { options = options || {}; options.format = winston.format.combine( winston.format.colorize(), winston.format.label({ label: "ConfigProxy" }), winston.format.splat(), winston.format.timestamp({ format: () => strftime("%H:%M:%S.%L", new Date()), }), simpleFormat ); options.transports = [new winston.transports.Console()]; return winston.createLogger(options); } exports.defaultLogger = defaultLogger; configurable-http-proxy-4.5.0/lib/metrics.js000066400000000000000000000052471407527146400211100ustar00rootroot00000000000000"use strict"; var client = require("prom-client"); class Metrics { constructor() { this.register = new client.Registry(); client.collectDefaultMetrics({ register: this.register }); this.apiRouteGetCount = new client.Counter({ name: "api_route_get", help: "Count of API route get requests", registers: [this.register], }); this.apiRouteAddCount = new client.Counter({ name: "api_route_add", help: "Count of API route add requests", registers: [this.register], }); this.apiRouteDeleteCount = new client.Counter({ name: "api_route_delete", help: "Count of API route delete requests", registers: [this.register], }); this.findTargetForReqSummary = new client.Summary({ name: "find_target_for_req", help: "Summary of find target requests", registers: [this.register], }); this.lastActivityUpdatingSummary = new client.Summary({ name: "last_activity_updating", help: "Summary of last activity updating requests", registers: [this.register], }); this.requestsWsCount = new client.Counter({ name: "requests_ws", help: "Count of websocket requests", registers: [this.register], }); this.requestsWebCount = new client.Counter({ name: "requests_web", help: "Count of web requests", registers: [this.register], }); this.requestsProxyCount = new client.Counter({ name: "requests_proxy", help: "Count of proxy requests", labelNames: ["status"], registers: [this.register], }); this.requestsApiCount = new client.Counter({ name: "requests_api", help: "Count of API requests", labelNames: ["status"], registers: [this.register], }); } render(res) { return this.register.metrics().then((s) => { res.writeHead(200, { "Content-Type": this.register.contentType }); res.write(s); res.end(); }); } } class MockMetrics { constructor() { return new Proxy(this, { get(target, name) { const mockCounter = new Proxy( {}, { get(target, name) { if (name == "inc") { return () => {}; } if (name == "startTimer") { return () => { return () => {}; }; } if (name == "labels") { return () => { return mockCounter; }; } }, } ); return mockCounter; }, }); } render(res) { return Promise.resolve(); } } module.exports = { Metrics, MockMetrics, }; configurable-http-proxy-4.5.0/lib/store.js000066400000000000000000000031541407527146400205710ustar00rootroot00000000000000"use strict"; var trie = require("./trie.js"); var NotImplemented = function (name) { return { name: "NotImplementedException", message: "method '" + name + "' not implemented", }; }; class BaseStore { // "abstract" methods getTarget(path) { throw NotImplemented("getTarget"); } getAll() { throw NotImplemented("getAll"); } add(path, data) { throw NotImplemented("add"); } update(path, data) { throw NotImplemented("update"); } remove(path) { throw NotImplemented("remove"); } get(path) { // default get implementation derived from getAll // only needs overriding if a more efficient implementation is available path = this.cleanPath(path); return this.getAll().then((routes) => routes[path]); } cleanPath(path) { return trie.trimPrefix(path); } } class MemoryStore extends BaseStore { constructor() { super(); this.routes = {}; this.urls = new trie.URLTrie(); } get(path) { return Promise.resolve(this.routes[this.cleanPath(path)]); } getTarget(path) { return Promise.resolve(this.urls.get(path)); } getAll() { return Promise.resolve(this.routes); } add(path, data) { path = this.cleanPath(path); this.routes[path] = data; this.urls.add(path, data); return Promise.resolve(null); } update(path, data) { Object.assign(this.routes[this.cleanPath(path)], data); } remove(path) { path = this.cleanPath(path); var route = this.routes[path]; delete this.routes[path]; this.urls.remove(path); return Promise.resolve(route); } } exports.MemoryStore = MemoryStore; configurable-http-proxy-4.5.0/lib/testutil.js000066400000000000000000000102311407527146400213040ustar00rootroot00000000000000"use strict"; var http = require("http"); var URL = require("url"); var extend = require("util")._extend; var WebSocketServer = require("ws").Server; var querystring = require("querystring"); var configproxy = require("./configproxy"); var defaultLogger = require("./log").defaultLogger; var servers = []; var addTarget = (exports.addTarget = function (proxy, path, port, websocket, targetPath) { var target = "http://127.0.0.1:" + port; if (targetPath) { target = target + targetPath; } var server; var data = { target: target, path: path, }; server = http.createServer(function (req, res) { var reply = extend({}, data); reply.url = req.url; reply.headers = req.headers; res.write(JSON.stringify(reply)); res.end(); }); if (websocket) { var wss = new WebSocketServer({ server: server, }); wss.on("connection", function (ws) { ws.on("message", function (message) { var reply = extend({}, data); reply.message = message; ws.send(JSON.stringify(reply)); }); ws.send("connected"); }); } server.listen(port); servers.push(server); return proxy.addRoute(path, { target: target }).then(() => { // routes are created with an activity timestamp artificially shifted into the past // so that activity can more easily be measured return proxy._routes.update(path, { last_activity: proxy._setup_timestamp }); }); }); var addTargetRedirecting = (exports.addTargetRedirecting = function ( proxy, path, port, targetPath, redirectTo ) { // Like the above, but the server returns a redirect response with a Location header. // Cannot use default arguments as they are apparently not supported. var target = "http://127.0.0.1:" + port; if (targetPath) { target = target + targetPath; } return proxy.addRoute(path, { target: target }).then(function (route) { var server = http.createServer(function (req, res) { res.setHeader("Location", redirectTo); res.statusCode = 301; res.write(""); res.end(); }); server.listen(port); servers.push(server); }); }); function addTargets(proxy, paths, port) { if (paths.length === 0) { return Promise.resolve(); } return addTarget(proxy, paths[0], port, true, null).then(function () { return addTargets(proxy, paths.slice(1), port + 1); }); } exports.setupProxy = function (port, options, paths) { options = options || {}; options.authToken = "secret"; options.log = defaultLogger({ level: "error" }); var proxy = new configproxy.ConfigurableProxy(options); proxy._setup_timestamp = new Date(new Date().getTime() - 60000); var ip = "127.0.0.1"; var countdown = 2; var resolvePromise; var p = new Promise((resolve, reject) => { resolvePromise = resolve; }); var onlisten = function () { if (--countdown === 0) { resolvePromise(proxy); } }; if (options.errorTarget) { countdown++; var errorServer = http.createServer(function (req, res) { var parsed = URL.parse(req.url); var query = querystring.parse(parsed.query); res.setHeader("Content-Type", "text/plain"); req.on("data", function () {}); req.on("end", function () { res.write(query.url); res.end(); }); }); errorServer.on("listening", onlisten); errorServer.listen(URL.parse(options.errorTarget).port, ip); servers.push(errorServer); } servers.push(proxy.apiServer); servers.push(proxy.proxyServer); if (options.enableMetrics) { servers.push(proxy.metricsServer); } proxy.apiServer.on("listening", onlisten); proxy.proxyServer.on("listening", onlisten); addTargets(proxy, paths || ["/"], port + 2).then(function () { proxy.proxyServer.listen(port, ip); proxy.apiServer.listen(port + 1, ip); if (options.enableMetrics) { proxy.metricsServer.listen(port + 3, ip); } }); return p; }; exports.teardownServers = function (callback) { var count = 0; var onclose = function () { count = count + 1; if (count === servers.length) { servers = []; callback(); } }; for (var i = servers.length - 1; i >= 0; i--) { servers[i].close(onclose); } }; configurable-http-proxy-4.5.0/lib/trie.js000066400000000000000000000065121407527146400204010ustar00rootroot00000000000000// A simple trie for URL prefix matching // // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. // // Store data at nodes in the trie with Trie.add("/path/", {data}) // // Get data for a prefix with Trie.get("/path/to/something/inside") // "use strict"; function trimPrefix(prefix) { // cleanup prefix form: /foo/bar // ensure prefix starts with / if (prefix.length === 0 || prefix[0] !== "/") { prefix = "/" + prefix; } // ensure prefix *doesn't* end with / (unless it's exactly /) if (prefix.length > 1 && prefix[prefix.length - 1] === "/") { prefix = prefix.substr(0, prefix.length - 1); } return prefix; } exports.trimPrefix = trimPrefix; function URLTrie(prefix) { // create a new URLTrie with data this.prefix = trimPrefix(prefix || "/"); this.branches = {}; this.size = 0; } var _slashesRe = /^[/]+|[/]+$/g; function stringToPath(s) { // turn a /prefix/string/ into ['prefix', 'string'] s = s.replace(_slashesRe, ""); if (s.length === 0) { // special case because ''.split() gives [''], which is wrong. return []; } else { return s.split("/"); } } exports.stringToPath = stringToPath; URLTrie.prototype.add = function (path, data) { // add data to a node in the trie at path if (typeof path === "string") { path = stringToPath(path); } if (path.length === 0) { this.data = data; return; } var part = path.shift(); if (!Object.prototype.hasOwnProperty.call(this.branches, part)) { // join with /, and handle the fact that only root ends with '/' var prefix = this.prefix.length === 1 ? this.prefix : this.prefix + "/"; this.branches[part] = new URLTrie(prefix + part); this.size += 1; } this.branches[part].add(path, data); }; URLTrie.prototype.remove = function (path) { // remove `path` from the trie if (typeof path === "string") { path = stringToPath(path); } if (path.length === 0) { // allow deleting root delete this.data; return; } var part = path.shift(); var child = this.branches[part]; if (child === undefined) { // Requested node doesn't exist, // consider it already removed. return; } child.remove(path); if (child.size === 0 && child.data === undefined) { // child has no branches and is not a leaf delete this.branches[part]; this.size -= 1; } }; URLTrie.prototype.get = function (path) { // get the data stored at a matching prefix // returns: // { // prefix: "/the/matching/prefix", // data: {whatever: "was stored by add"} // } // if I have data, return me, otherwise return undefined var me = this.data === undefined ? undefined : this; if (typeof path === "string") { path = stringToPath(path); } if (path.length === 0) { // exact match, it's definitely me! return me; } var part = path.shift(); var child = this.branches[part]; if (child === undefined) { // prefix matches, and I don't have any more specific children return me; } else { // I match and I have a more specific child that matches. // That *does not* mean that I have a more specific *leaf* that matches. var node = child.get(path); if (node) { // found a more specific leaf return node; } else { // I'm still the most specific match return me; } } }; exports.URLTrie = URLTrie; configurable-http-proxy-4.5.0/package-lock.json000066400000000000000000002351571407527146400215570ustar00rootroot00000000000000{ "name": "configurable-http-proxy", "version": "4.5.0", "lockfileVersion": 1, "requires": true, "dependencies": { "@babel/code-frame": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", "dev": true, "requires": { "@babel/highlight": "^7.12.13" } }, "@babel/core": { "version": "7.12.17", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.17.tgz", "integrity": "sha512-V3CuX1aBywbJvV2yzJScRxeiiw0v2KZZYYE3giywxzFJL13RiyPjaaDwhDnxmgFTTS7FgvM2ijr4QmKNIu0AtQ==", "dev": true, "requires": { "@babel/code-frame": "^7.12.13", "@babel/generator": "^7.12.17", "@babel/helper-module-transforms": "^7.12.17", "@babel/helpers": "^7.12.17", "@babel/parser": "^7.12.17", "@babel/template": "^7.12.13", "@babel/traverse": "^7.12.17", "@babel/types": "^7.12.17", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", "json5": "^2.1.2", "lodash": "^4.17.19", "semver": "^5.4.1", "source-map": "^0.5.0" }, "dependencies": { "debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { "ms": "2.1.2" } }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true } } }, "@babel/generator": { "version": "7.12.17", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.17.tgz", "integrity": "sha512-DSA7ruZrY4WI8VxuS1jWSRezFnghEoYEFrZcw9BizQRmOZiUsiHl59+qEARGPqPikwA/GPTyRCi7isuCK/oyqg==", "dev": true, "requires": { "@babel/types": "^7.12.17", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-function-name": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.12.13", "@babel/template": "^7.12.13", "@babel/types": "^7.12.13" } }, "@babel/helper-get-function-arity": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", "dev": true, "requires": { "@babel/types": "^7.12.13" } }, "@babel/helper-member-expression-to-functions": { "version": "7.12.17", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.17.tgz", "integrity": "sha512-Bzv4p3ODgS/qpBE0DiJ9qf5WxSmrQ8gVTe8ClMfwwsY2x/rhykxxy3bXzG7AGTnPB2ij37zGJ/Q/6FruxHxsxg==", "dev": true, "requires": { "@babel/types": "^7.12.17" } }, "@babel/helper-module-imports": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz", "integrity": "sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g==", "dev": true, "requires": { "@babel/types": "^7.12.13" } }, "@babel/helper-module-transforms": { "version": "7.12.17", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.17.tgz", "integrity": "sha512-sFL+p6zOCQMm9vilo06M4VHuTxUAwa6IxgL56Tq1DVtA0ziAGTH1ThmJq7xwPqdQlgAbKX3fb0oZNbtRIyA5KQ==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.12.13", "@babel/helper-replace-supers": "^7.12.13", "@babel/helper-simple-access": "^7.12.13", "@babel/helper-split-export-declaration": "^7.12.13", "@babel/helper-validator-identifier": "^7.12.11", "@babel/template": "^7.12.13", "@babel/traverse": "^7.12.17", "@babel/types": "^7.12.17", "lodash": "^4.17.19" } }, "@babel/helper-optimise-call-expression": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", "dev": true, "requires": { "@babel/types": "^7.12.13" } }, "@babel/helper-replace-supers": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.13.tgz", "integrity": "sha512-pctAOIAMVStI2TMLhozPKbf5yTEXc0OJa0eENheb4w09SrgOWEs+P4nTOZYJQCqs8JlErGLDPDJTiGIp3ygbLg==", "dev": true, "requires": { "@babel/helper-member-expression-to-functions": "^7.12.13", "@babel/helper-optimise-call-expression": "^7.12.13", "@babel/traverse": "^7.12.13", "@babel/types": "^7.12.13" } }, "@babel/helper-simple-access": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.13.tgz", "integrity": "sha512-0ski5dyYIHEfwpWGx5GPWhH35j342JaflmCeQmsPWcrOQDtCN6C1zKAVRFVbK53lPW2c9TsuLLSUDf0tIGJ5hA==", "dev": true, "requires": { "@babel/types": "^7.12.13" } }, "@babel/helper-split-export-declaration": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", "dev": true, "requires": { "@babel/types": "^7.12.13" } }, "@babel/helper-validator-identifier": { "version": "7.12.11", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", "dev": true }, "@babel/helpers": { "version": "7.12.17", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.17.tgz", "integrity": "sha512-tEpjqSBGt/SFEsFikKds1sLNChKKGGR17flIgQKXH4fG6m9gTgl3gnOC1giHNyaBCSKuTfxaSzHi7UnvqiVKxg==", "dev": true, "requires": { "@babel/template": "^7.12.13", "@babel/traverse": "^7.12.17", "@babel/types": "^7.12.17" } }, "@babel/highlight": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.12.11", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { "version": "7.12.17", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.17.tgz", "integrity": "sha512-r1yKkiUTYMQ8LiEI0UcQx5ETw5dpTLn9wijn9hk6KkTtOK95FndDN10M+8/s6k/Ymlbivw0Av9q4SlgF80PtHg==", "dev": true }, "@babel/template": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", "dev": true, "requires": { "@babel/code-frame": "^7.12.13", "@babel/parser": "^7.12.13", "@babel/types": "^7.12.13" } }, "@babel/traverse": { "version": "7.12.17", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.17.tgz", "integrity": "sha512-LGkTqDqdiwC6Q7fWSwQoas/oyiEYw6Hqjve5KOSykXkmFJFqzvGMb9niaUEag3Rlve492Mkye3gLw9FTv94fdQ==", "dev": true, "requires": { "@babel/code-frame": "^7.12.13", "@babel/generator": "^7.12.17", "@babel/helper-function-name": "^7.12.13", "@babel/helper-split-export-declaration": "^7.12.13", "@babel/parser": "^7.12.17", "@babel/types": "^7.12.17", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.19" }, "dependencies": { "debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { "ms": "2.1.2" } }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true } } }, "@babel/types": { "version": "7.12.17", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.17.tgz", "integrity": "sha512-tNMDjcv/4DIcHxErTgwB9q2ZcYyN0sUfgGKUK/mm1FJK7Wz+KstoEekxrl/tBiNDgLK1HGi+sppj1An/1DR4fQ==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.12.11", "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } }, "@dabh/diagnostics": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz", "integrity": "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==", "requires": { "colorspace": "1.1.x", "enabled": "2.0.x", "kuler": "^2.0.0" } }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, "requires": { "camelcase": "^5.3.1", "find-up": "^4.1.0", "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" } }, "@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "dev": true, "requires": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" } }, "append-transform": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", "dev": true, "requires": { "default-require-extensions": "^3.0.0" } }, "archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { "sprintf-js": "~1.0.2" } }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", "dev": true, "requires": { "safer-buffer": "~2.1.0" } }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", "dev": true }, "aws4": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", "dev": true }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "dev": true, "requires": { "tweetnacl": "^0.14.3" } }, "bintrees": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.1.tgz", "integrity": "sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ=" }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "caching-transform": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", "dev": true, "requires": { "hasha": "^5.0.0", "make-dir": "^3.0.0", "package-hash": "^4.0.0", "write-file-atomic": "^3.0.0" } }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "dev": true }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true }, "cli": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", "integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=", "dev": true, "requires": { "exit": "0.1.2", "glob": "^7.1.1" } }, "cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "color": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", "requires": { "color-convert": "^1.9.1", "color-string": "^1.5.2" } }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "requires": { "color-name": "1.1.3" } }, "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "color-string": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.5.tgz", "integrity": "sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg==", "requires": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" }, "colorspace": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", "requires": { "color": "3.0.x", "text-hex": "1.0.x" } }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, "requires": { "delayed-stream": "~1.0.0" } }, "commander": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.0.0.tgz", "integrity": "sha512-Xvf85aAtu6v22+E5hfVoLHqyul/jyxh91zvqk/ioJTQuJR7Z78n7H558vMPKanPSRgIEeZemT92I2g9Y8LPbSQ==" }, "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, "console-browserify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", "dev": true, "requires": { "date-now": "^0.1.4" } }, "convert-source-map": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", "dev": true, "requires": { "safe-buffer": "~5.1.1" }, "dependencies": { "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true } } }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "dev": true, "requires": { "assert-plus": "^1.0.0" } }, "date-now": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", "dev": true }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, "default-require-extensions": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", "dev": true, "requires": { "strip-bom": "^4.0.0" } }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, "dom-serializer": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", "dev": true, "requires": { "domelementtype": "^2.0.1", "entities": "^2.0.0" }, "dependencies": { "domelementtype": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", "dev": true }, "entities": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", "dev": true } } }, "domelementtype": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", "dev": true }, "domhandler": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", "dev": true, "requires": { "domelementtype": "1" } }, "domutils": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", "dev": true, "requires": { "dom-serializer": "0", "domelementtype": "1" } }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "dev": true, "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" } }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, "enabled": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" }, "entities": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=", "dev": true }, "es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, "eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, "exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", "dev": true }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", "dev": true }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, "fast-safe-stringify": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" }, "fecha": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz", "integrity": "sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg==" }, "find-cache-dir": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", "dev": true, "requires": { "commondir": "^1.0.1", "make-dir": "^3.0.2", "pkg-dir": "^4.1.0" } }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "fn.name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, "follow-redirects": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.2.tgz", "integrity": "sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA==" }, "foreground-child": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", "dev": true, "requires": { "cross-spawn": "^7.0.0", "signal-exit": "^3.0.2" } }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", "dev": true }, "form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", "mime-types": "^2.1.12" } }, "fromentries": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", "dev": true }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, "get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "dev": true, "requires": { "assert-plus": "^1.0.0" } }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, "graceful-fs": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", "dev": true }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", "dev": true }, "har-validator": { "version": "5.1.5", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", "dev": true, "requires": { "ajv": "^6.12.3", "har-schema": "^2.0.0" } }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "hasha": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", "dev": true, "requires": { "is-stream": "^2.0.0", "type-fest": "^0.8.0" } }, "html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, "htmlparser2": { "version": "3.8.3", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", "dev": true, "requires": { "domelementtype": "1", "domhandler": "2.3", "domutils": "1.5", "entities": "1.0", "readable-stream": "1.1" }, "dependencies": { "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true }, "readable-stream": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", "isarray": "0.0.1", "string_decoder": "~0.10.x" } }, "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", "dev": true } } }, "http-proxy": { "version": "1.18.1", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", "requires": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", "requires-port": "^1.0.0" } }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "dev": true, "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", "sshpk": "^1.7.0" } }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, "indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" } }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "is-arrayish": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, "is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true }, "istanbul-lib-coverage": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", "dev": true }, "istanbul-lib-hook": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", "dev": true, "requires": { "append-transform": "^2.0.0" } }, "istanbul-lib-instrument": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", "dev": true, "requires": { "@babel/core": "^7.7.5", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.0.0", "semver": "^6.3.0" } }, "istanbul-lib-processinfo": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", "dev": true, "requires": { "archy": "^1.0.0", "cross-spawn": "^7.0.0", "istanbul-lib-coverage": "^3.0.0-alpha.1", "make-dir": "^3.0.0", "p-map": "^3.0.0", "rimraf": "^3.0.0", "uuid": "^3.3.3" } }, "istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", "dev": true, "requires": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^3.0.0", "supports-color": "^7.1.0" }, "dependencies": { "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" } } } }, "istanbul-lib-source-maps": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", "dev": true, "requires": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", "source-map": "^0.6.1" }, "dependencies": { "debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { "ms": "2.1.2" } }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true } } }, "istanbul-reports": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", "dev": true, "requires": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "jasmine": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.8.0.tgz", "integrity": "sha512-kdQ3SfcNpMbbMdgJPLyFe9IksixdnrgYaCJapP9sS0aLgdWdIZADNXEr+11Zafxm1VDfRSC5ZL4fzXT0bexzXw==", "dev": true, "requires": { "glob": "^7.1.6", "jasmine-core": "~3.8.0" } }, "jasmine-core": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.8.0.tgz", "integrity": "sha512-zl0nZWDrmbCiKns0NcjkFGYkVTGCPUgoHypTaj+G2AzaWus7QGoXARSlYsSle2VRpSdfJmM+hzmFKzQNhF2kHg==", "dev": true }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, "js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" } }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "dev": true }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, "jshint": { "version": "2.13.0", "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.13.0.tgz", "integrity": "sha512-Nd+md9wIeyfDK+RGrbOBzwLONSTdihGMtyGYU/t7zYcN2EgUa4iuY3VK2oxtPYrW5ycTj18iC+UbhNTxe4C66g==", "dev": true, "requires": { "cli": "~1.0.0", "console-browserify": "1.1.x", "exit": "0.1.x", "htmlparser2": "3.8.x", "lodash": "~4.17.21", "minimatch": "~3.0.2", "shelljs": "0.3.x", "strip-json-comments": "1.0.x" } }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", "dev": true }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, "json5": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", "dev": true, "requires": { "minimist": "^1.2.5" } }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", "json-schema": "0.2.3", "verror": "1.10.0" } }, "kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { "p-locate": "^4.1.0" } }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "requires": { "semver": "^6.0.0" } }, "mime-db": { "version": "1.46.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz", "integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==", "dev": true }, "mime-types": { "version": "2.1.29", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz", "integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==", "dev": true, "requires": { "mime-db": "1.46.0" } }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", "dev": true, "requires": { "process-on-spawn": "^1.0.0" } }, "nyc": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", "dev": true, "requires": { "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.2", "caching-transform": "^4.0.0", "convert-source-map": "^1.7.0", "decamelize": "^1.2.0", "find-cache-dir": "^3.2.0", "find-up": "^4.1.0", "foreground-child": "^2.0.0", "get-package-type": "^0.1.0", "glob": "^7.1.6", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-hook": "^3.0.0", "istanbul-lib-instrument": "^4.0.0", "istanbul-lib-processinfo": "^2.0.2", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.0.2", "make-dir": "^3.0.0", "node-preload": "^0.2.1", "p-map": "^3.0.0", "process-on-spawn": "^1.0.0", "resolve-from": "^5.0.0", "rimraf": "^3.0.0", "signal-exit": "^3.0.2", "spawn-wrap": "^2.0.0", "test-exclude": "^6.0.0", "yargs": "^15.0.2" } }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { "wrappy": "1" } }, "one-time": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", "requires": { "fn.name": "1.x.x" } }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { "p-try": "^2.0.0" } }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { "p-limit": "^2.2.0" } }, "p-map": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", "dev": true, "requires": { "aggregate-error": "^3.0.0" } }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, "package-hash": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", "dev": true, "requires": { "graceful-fs": "^4.1.15", "hasha": "^5.0.0", "lodash.flattendeep": "^4.4.0", "release-zalgo": "^1.0.0" } }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, "pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "requires": { "find-up": "^4.0.0" } }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "process-on-spawn": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", "dev": true, "requires": { "fromentries": "^1.2.0" } }, "prom-client": { "version": "13.1.0", "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-13.1.0.tgz", "integrity": "sha512-jT9VccZCWrJWXdyEtQddCDszYsiuWj5T0ekrPszi/WEegj3IZy6Mm09iOOVM86A4IKMWq8hZkT2dD9MaSe+sng==", "requires": { "tdigest": "^0.1.1" } }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", "dev": true }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "dev": true }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", "dev": true, "requires": { "es6-error": "^4.0.1" } }, "request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", "dev": true, "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", "caseless": "~0.12.0", "combined-stream": "~1.0.6", "extend": "~3.0.2", "forever-agent": "~0.6.1", "form-data": "~2.3.2", "har-validator": "~5.1.3", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "oauth-sign": "~0.9.0", "performance-now": "^2.1.0", "qs": "~6.5.2", "safe-buffer": "^5.1.2", "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" } }, "request-promise-core": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", "dev": true, "requires": { "lodash": "^4.17.19" } }, "request-promise-native": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", "dev": true, "requires": { "request-promise-core": "1.1.4", "stealthy-require": "^1.1.1", "tough-cookie": "^2.3.3" } }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, "resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { "glob": "^7.1.3" } }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { "shebang-regex": "^3.0.0" } }, "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, "shelljs": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", "dev": true }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, "simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", "requires": { "is-arrayish": "^0.3.1" } }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, "spawn-wrap": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", "dev": true, "requires": { "foreground-child": "^2.0.0", "is-windows": "^1.0.2", "make-dir": "^3.0.0", "rimraf": "^3.0.0", "signal-exit": "^3.0.2", "which": "^2.0.1" } }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, "sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", "dev": true, "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", "bcrypt-pbkdf": "^1.0.0", "dashdash": "^1.12.0", "ecc-jsbn": "~0.1.1", "getpass": "^0.1.1", "jsbn": "~0.1.0", "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" } }, "stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" }, "stealthy-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", "dev": true }, "strftime": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/strftime/-/strftime-0.10.0.tgz", "integrity": "sha1-s/D6QZKVICpaKJ9ta+n0kJphcZM=" }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.0" } }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "requires": { "safe-buffer": "~5.2.0" } }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { "ansi-regex": "^5.0.0" } }, "strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true }, "strip-json-comments": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", "dev": true }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" } }, "tdigest": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.1.tgz", "integrity": "sha1-Ljyyw56kSeVdHmzZEReszKRYgCE=", "requires": { "bintrees": "1.0.1" } }, "test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, "requires": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" } }, "text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, "tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "dev": true, "requires": { "psl": "^1.1.28", "punycode": "^2.1.1" } }, "triple-beam": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "dev": true, "requires": { "safe-buffer": "^5.0.1" } }, "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "dev": true }, "type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true }, "typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", "dev": true, "requires": { "is-typedarray": "^1.0.0" } }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "requires": { "punycode": "^2.1.0" } }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", "dev": true }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "dev": true, "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { "isexe": "^2.0.0" } }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, "winston": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz", "integrity": "sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==", "requires": { "@dabh/diagnostics": "^2.0.2", "async": "^3.1.0", "is-stream": "^2.0.0", "logform": "^2.2.0", "one-time": "^1.0.0", "readable-stream": "^3.4.0", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", "winston-transport": "^4.4.0" }, "dependencies": { "async": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" }, "logform": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz", "integrity": "sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg==", "requires": { "colors": "^1.2.1", "fast-safe-stringify": "^2.0.4", "fecha": "^4.2.0", "ms": "^2.1.1", "triple-beam": "^1.3.0" } }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" } }, "winston-transport": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz", "integrity": "sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==", "requires": { "readable-stream": "^2.3.7", "triple-beam": "^1.2.0" }, "dependencies": { "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } } } } } }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" }, "dependencies": { "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { "color-convert": "^2.0.1" } }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { "color-name": "~1.1.4" } }, "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true } } }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, "write-file-atomic": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", "dev": true, "requires": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", "signal-exit": "^3.0.2", "typedarray-to-buffer": "^3.1.5" } }, "ws": { "version": "7.5.3", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz", "integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==", "dev": true }, "y18n": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", "dev": true }, "yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "dev": true, "requires": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "yargs-parser": { "version": "18.1.3", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } } } } configurable-http-proxy-4.5.0/package.json000066400000000000000000000022401407527146400206120ustar00rootroot00000000000000{ "version": "4.5.0", "name": "configurable-http-proxy", "description": "A configurable-on-the-fly HTTP Proxy", "author": "Jupyter Developers", "license": "BSD-3-Clause", "repository": { "type": "git", "url": "https://github.com/jupyterhub/configurable-http-proxy.git" }, "dependencies": { "commander": "~8.0.0", "http-proxy": "^1.18.1", "prom-client": "13.1.0", "strftime": "~0.10.0", "winston": "~3.3.0" }, "devDependencies": { "jasmine": "^3.5.0", "jshint": "^2.10.2", "nyc": "^15.0.0", "request": "^2.87.0", "request-promise-native": "^1.0.4", "ws": "^7.0.0" }, "engines": { "node": ">= 12.0" }, "engineStrict": true, "main": "index.js", "files": [ "index.js", "lib/*.js", "lib/error/*.html", "bin/configurable-http-proxy" ], "bin": { "configurable-http-proxy": "bin/configurable-http-proxy" }, "scripts": { "lint": "jshint bin/ lib/ test/", "fmt": "pre-commit run --all-files", "test": "nyc jasmine JASMINE_CONFIG_PATH=test/jasmine.json", "coverage-html": "nyc report --reporter=html", "codecov": "nyc report --reporter=lcov && codecov" } } configurable-http-proxy-4.5.0/test/000077500000000000000000000000001407527146400173055ustar00rootroot00000000000000configurable-http-proxy-4.5.0/test/.eslintrc.js000066400000000000000000000000671407527146400215470ustar00rootroot00000000000000module.exports = { env: { jasmine: true, }, }; configurable-http-proxy-4.5.0/test/api_spec.js000066400000000000000000000160641407527146400214350ustar00rootroot00000000000000// jshint jasmine: true "use strict"; var util = require("../lib/testutil"); var extend = require("util")._extend; var request = require("request-promise-native"); var log = require("winston"); // disable logging during tests log.remove(log.transports.Console); describe("API Tests", function () { var port = 8902; var apiPort = port + 1; var proxy; var apiUrl = "http://127.0.0.1:" + apiPort + "/api/routes"; var r; beforeEach(function (callback) { util .setupProxy(port) .then(function (newProxy) { proxy = newProxy; }) .then(function () { r = request.defaults({ method: "GET", headers: { Authorization: "token " + proxy.authToken }, port: apiPort, url: apiUrl, }); }) .then(function () { callback(); }); }); afterEach(function (callback) { util.teardownServers(callback); }); it("Basic proxy constructor", function () { expect(proxy).toBeDefined(); expect(proxy.defaultTarget).toBe(undefined); return proxy.targetForReq({ url: "/" }).then(function (route) { expect(route).toEqual({ prefix: "/", target: "http://127.0.0.1:" + (port + 2), }); }); }); it("Default target is used for /any/random/url", function (done) { proxy.targetForReq({ url: "/any/random/url" }).then(function (target) { expect(target).toEqual({ prefix: "/", target: "http://127.0.0.1:" + (port + 2), }); done(); }); }); it("Default target is used for /", function (done) { proxy.targetForReq({ url: "/" }).then(function (target) { expect(target).toEqual({ prefix: "/", target: "http://127.0.0.1:" + (port + 2), }); done(); }); }); it("GET /api/routes fetches the routing table", function (done) { r(apiUrl) .then(function (body) { var reply = JSON.parse(body); var keys = Object.keys(reply); expect(keys.length).toEqual(1); expect(keys).toContain("/"); }) .then(done); }); it("GET /api/routes[/path] fetches a single route", function (done) { var path = "/path"; var url = "https://127.0.0.1:54321"; proxy .addRoute(path, { target: url }) .then(function () { return r(apiUrl + path); }) .then(function (body) { var reply = JSON.parse(body); var keys = Object.keys(reply); expect(keys).toContain("target"); expect(reply.target).toEqual(url); }) .then(done); }); it("GET /api/routes[/path] fetches a single route (404 if missing)", function (done) { r(apiUrl + "/path") .then((body) => { done.fail("Expected a 404"); }) .catch((error) => { expect(error.statusCode).toEqual(404); }) .then(done); }); it("POST /api/routes[/path] creates a new route", function (done) { var port = 8998; var target = "http://127.0.0.1:" + port; r.post({ url: apiUrl + "/user/foo", body: JSON.stringify({ target: target }), }) .then((body) => { expect(body).toEqual(""); }) .then(() => proxy._routes.get("/user/foo")) .then((route) => { expect(route.target).toEqual(target); expect(typeof route.last_activity).toEqual("object"); }) .then(done); }); it("POST /api/routes[/foo%20bar] handles URI escapes", function (done) { var port = 8998; var target = "http://127.0.0.1:" + port; r.post({ url: apiUrl + "/user/foo%40bar", body: JSON.stringify({ target: target }), }) .then((body) => { expect(body).toEqual(""); }) .then(() => proxy._routes.get("/user/foo@bar")) .then((route) => { expect(route.target).toEqual(target); expect(typeof route.last_activity).toEqual("object"); }) .then(() => proxy.targetForReq({ url: "/user/foo@bar/path" })) .then((proxyTarget) => { expect(proxyTarget.target).toEqual(target); }) .then(done); }); it("POST /api/routes creates a new root route", function (done) { var port = 8998; var target = "http://127.0.0.1:" + port; r.post({ url: apiUrl, body: JSON.stringify({ target: target }), }) .then((body) => { expect(body).toEqual(""); return proxy._routes.get("/"); }) .then((route) => { expect(route.target).toEqual(target); expect(typeof route.last_activity).toEqual("object"); done(); }); }); it("DELETE /api/routes[/path] deletes a route", function (done) { var port = 8998; var target = "http://127.0.0.1:" + port; var path = "/user/bar"; util .addTarget(proxy, path, port, null, null) .then(() => proxy._routes.get(path)) .then((route) => expect(route.target).toEqual(target)) .then(() => r.del(apiUrl + path)) .then((body) => expect(body).toEqual("")) .then(() => proxy._routes.get(path)) .then((deletedRoute) => expect(deletedRoute).toBe(undefined)) .then(done); }); it("GET /api/routes?inactiveSince= with bad value returns a 400", function (done) { r.get(apiUrl + "?inactiveSince=endoftheuniverse") .then(() => done.fail("Expected 400")) .catch((err) => expect(err.statusCode).toEqual(400)) .then(done); }); it("GET /api/routes?inactiveSince= filters inactive entries", function (done) { var port = 8998; var path = "/yesterday"; var now = new Date(); var yesterday = new Date(now.getTime() - 24 * 3.6e6); var longAgo = new Date(1); var hourAgo = new Date(now.getTime() - 3.6e6); var hourFromNow = new Date(now.getTime() + 3.6e6); var tests = [ { name: "long ago", since: longAgo, expected: {}, }, { name: "an hour ago", since: hourAgo, expected: { "/yesterday": true }, }, { name: "the future", since: hourFromNow, expected: { "/yesterday": true, "/today": true, }, }, ]; var seen = 0; var doReq = function (i) { var t = tests[i]; return r.get(apiUrl + "?inactiveSince=" + t.since.toISOString()).then(function (body) { var routes = JSON.parse(body); var routeKeys = Object.keys(routes); var expectedKeys = Object.keys(t.expected); routeKeys.forEach(function (key) { // check that all routes are expected expect(expectedKeys).toContain(key); }); expectedKeys.forEach(function (key) { // check that all expected routes are found expect(routeKeys).toContain(key); }); seen += 1; if (seen === tests.length) { done(); } else { return doReq(seen); } }); }; proxy .removeRoute("/") .then(() => util.addTarget(proxy, "/yesterday", port, null, null)) .then(() => util.addTarget(proxy, "/today", port + 1, null, null)) .then(() => proxy._routes.update("/yesterday", { last_activity: yesterday })) .then(() => doReq(0)) .then(); }); }); configurable-http-proxy-4.5.0/test/cli_spec.js000066400000000000000000000142211407527146400214240ustar00rootroot00000000000000// jshint jasmine: true "use strict"; const http = require("http"); var spawn = require("child_process").spawn; var request = require("request-promise-native"); // utility functions function executeCLI(execCmd = "bin/configurable-http-proxy", args = []) { var defaultRouteRequested = args.includes("--default-target"); var redirectRequested = args.includes("--redirect-port"); var defaultRouteAdded = false; var redirectAdded = false; var cliProcess = spawn(execCmd, args); // uncomment the below line for debugging of tests //cliProcess.stdout.pipe(process.stdout); const cliReady = new Promise((resolve, reject) => { var promiseResolved = false; cliProcess.stdout.on("data", (data) => { if (data.includes("Route added /")) { defaultRouteAdded = true; } if (data.includes("Added HTTP to HTTPS redirection")) { redirectAdded = true; } if ( !promiseResolved && defaultRouteAdded === defaultRouteRequested && redirectAdded === redirectRequested ) { promiseResolved = true; resolve(cliProcess); } }); }); return cliReady; } var servers = []; function addServer(name, port) { var server = http.createServer(function (req, res) { var reply = {}; reply.url = req.url; reply.headers = req.headers; reply.name = name; res.write(JSON.stringify(reply)); res.end(); }); var serverListening = new Promise((resolve) => { server.listen(port, resolve); servers.push(server); }); return serverListening; } function teardownServers() { var count = 0; var onclose = function () { count = count + 1; if (count === servers.length) { servers = []; return; } }; for (var i = servers.length - 1; i >= 0; i--) { servers[i].close(onclose); } } describe("CLI Tests", function () { var execCmd = "bin/configurable-http-proxy"; var port = 8902; var apiPort = port + 1; var testPort = port + 10; var redirectPort = testPort + 1; var redirectToPort = redirectPort + 1; var childProcess; var proxyUrl = "http://127.0.0.1:" + port; var SSLproxyUrl = "https://127.0.0.1:" + port; var testUrl = "http://127.0.0.1:" + testPort; var redirectUrl = "http://127.0.0.1:" + redirectPort; var redirectToUrl = "https://127.0.0.1:" + redirectToPort; var r = request.defaults({ method: "GET", //url: proxyUrl, followRedirect: false, strictSSL: false, }); beforeEach(function (callback) { addServer("default", testPort).then(callback); }); afterEach(function (callback) { teardownServers(); childProcess.on("exit", () => { callback(); }); childProcess.kill(); }); it("basic HTTP request", function (done) { var args = ["--ip", "127.0.0.1", "--port", port, "--default-target", testUrl]; executeCLI(execCmd, args).then((cliProcess) => { childProcess = cliProcess; r(proxyUrl).then((body) => { body = JSON.parse(body); expect(body).toEqual( jasmine.objectContaining({ name: "default", }) ); done(); }); }); }); it("basic HTTPS request", function (done) { var args = [ "--ip", "127.0.0.1", "--ssl-cert", "test/server.crt", "--ssl-key", "test/server.key", "--port", port, "--default-target", testUrl, ]; executeCLI(execCmd, args).then((cliProcess) => { childProcess = cliProcess; r(SSLproxyUrl).then((body) => { body = JSON.parse(body); expect(body).toEqual( jasmine.objectContaining({ name: "default", }) ); done(); }); }); }); it("redirect-port", function (done) { // Attempts to connect to redirectPort, and gets redirected to port var args = [ "--ip", "127.0.0.1", "--ssl-cert", "test/server.crt", "--ssl-key", "test/server.key", "--port", port, "--default-target", testUrl, "--redirect-port", redirectPort, ]; executeCLI(execCmd, args).then((cliProcess) => { childProcess = cliProcess; r(redirectUrl) .then(() => { fail("A 301 redirect should have been thrown."); }) .catch((requestError) => { expect(requestError.statusCode).toEqual(301); expect(requestError.response.headers.location).toContain(SSLproxyUrl); }); r({ url: redirectUrl, followRedirect: true }).then((body) => { body = JSON.parse(body); expect(body).toEqual( jasmine.objectContaining({ name: "default", }) ); done(); }); }); }); it("redirect-to", function (done) { // Attempts to connect to redirectPort, and gets redirected to redirectToPort var args = [ "--ip", "127.0.0.1", "--ssl-cert", "test/server.crt", "--ssl-key", "test/server.key", "--port", port, "--default-target", testUrl, "--redirect-port", redirectPort, "--redirect-to", redirectToPort, ]; executeCLI(execCmd, args).then((cliProcess) => { childProcess = cliProcess; r(redirectUrl) .then(() => { fail("A 301 redirect should have been thrown."); }) .catch((requestError) => { expect(requestError.statusCode).toEqual(301); expect(requestError.response.headers.location).toContain(redirectToUrl); done(); }); }); }); it("custom-header", function (done) { var args = [ "--ip", "127.0.0.1", "--ssl-cert", "test/server.crt", "--ssl-key", "test/server.key", "--port", port, "--default-target", testUrl, "--custom-header", "k1: v1", "--custom-header", " k2 : v2 v2 ", ]; executeCLI(execCmd, args).then((cliProcess) => { childProcess = cliProcess; r(SSLproxyUrl).then((body) => { body = JSON.parse(body); expect(body.headers).toEqual( jasmine.objectContaining({ k1: "v1", k2: "v2 v2", }) ); done(); }); }); }); }); configurable-http-proxy-4.5.0/test/dummy-store.js000066400000000000000000000003031407527146400221240ustar00rootroot00000000000000"use strict"; class PlugableDummyStore { get(path) {} getTarget(path) {} getAll() {} add(path, data) {} update(path, data) {} remove(path) {} } module.exports = PlugableDummyStore; configurable-http-proxy-4.5.0/test/error/000077500000000000000000000000001407527146400204365ustar00rootroot00000000000000configurable-http-proxy-4.5.0/test/error/404.html000066400000000000000000000000161407527146400216300ustar00rootroot00000000000000404'D! configurable-http-proxy-4.5.0/test/error/error.html000066400000000000000000000000251407527146400224520ustar00rootroot00000000000000UNKNOWN ERROR configurable-http-proxy-4.5.0/test/jasmine.json000066400000000000000000000001401407527146400216210ustar00rootroot00000000000000{ "spec_dir": "test", "stopSpecOnExpectationFailure": false, "spec_files": ["*spec.js"] } configurable-http-proxy-4.5.0/test/proxy_spec.js000066400000000000000000000317161407527146400220460ustar00rootroot00000000000000// jshint jasmine: true "use strict"; var path = require("path"); var util = require("../lib/testutil"); var request = require("request-promise-native"); var WebSocket = require("ws"); var ConfigurableProxy = require("../lib/configproxy").ConfigurableProxy; jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; describe("Proxy Tests", function () { var port = 8902; var testPort = port + 10; var proxy; var proxyUrl = "http://127.0.0.1:" + port; var hostTest = "test.localhost.jovyan.org"; var hostUrl = "http://" + hostTest + ":" + port; var r = request.defaults({ method: "GET", url: proxyUrl, followRedirect: false, }); beforeEach(function (callback) { util.setupProxy(port).then(function (newProxy) { proxy = newProxy; callback(); }); }); afterEach(function (callback) { util.teardownServers(callback); }); it("basic HTTP request", function (done) { r(proxyUrl).then((body) => { body = JSON.parse(body); expect(body).toEqual( jasmine.objectContaining({ path: "/", }) ); // check last_activity was updated return proxy._routes.get("/").then((route) => { expect(route.last_activity).toBeGreaterThan(proxy._setup_timestamp); done(); }); }); }); it("basic WebSocket request", function (done) { var ws = new WebSocket("ws://127.0.0.1:" + port); ws.on("error", function () { // jasmine fail is only in master expect("error").toEqual("ok"); done(); }); var nmsgs = 0; ws.on("message", function (msg) { if (nmsgs === 0) { expect(msg).toEqual("connected"); } else { msg = JSON.parse(msg); expect(msg).toEqual( jasmine.objectContaining({ path: "/", message: "hi", }) ); // check last_activity was updated return proxy._routes.get("/").then((route) => { expect(route.last_activity).toBeGreaterThan(proxy._setup_timestamp); ws.close(); done(); }); } nmsgs++; }); ws.on("open", function () { ws.send("hi"); }); }); it("proxyRequest event can modify headers", function (done) { var called = {}; proxy.on("proxyRequest", function (req, res) { req.headers.testing = "Test Passed"; called.proxyRequest = true; }); r(proxyUrl) .then(function (body) { body = JSON.parse(body); expect(called.proxyRequest).toBe(true); expect(body).toEqual( jasmine.objectContaining({ path: "/", }) ); expect(body.headers).toEqual( jasmine.objectContaining({ testing: "Test Passed", }) ); }) .then(done); }); it("target path is prepended by default", function (done) { util .addTarget(proxy, "/bar", testPort, false, "/foo") .then(() => r(proxyUrl + "/bar/rest/of/it")) .then((body) => { body = JSON.parse(body); expect(body).toEqual( jasmine.objectContaining({ path: "/bar", url: "/foo/bar/rest/of/it", }) ); done(); }); }); it("/prefix?query is proxied correctly", function (done) { util .addTarget(proxy, "/bar", testPort, null, "/foo") .then(() => r(proxyUrl + "/bar?query=foo")) .then((body) => { body = JSON.parse(body); expect(body).toEqual( jasmine.objectContaining({ target: "http://127.0.0.1:" + testPort + "/foo", path: "/bar", url: "/foo/bar?query=foo", }) ); done(); }); }); it("handle URI encoding", function (done) { util .addTarget(proxy, "/b@r/b r", testPort, false, "/foo") .then(() => r(proxyUrl + "/b%40r/b%20r/rest/of/it")) .then((body) => { body = JSON.parse(body); expect(body).toEqual( jasmine.objectContaining({ path: "/b@r/b r", url: "/foo/b%40r/b%20r/rest/of/it", }) ); done(); }); }); it("handle @ in URI same as %40", function (done) { util .addTarget(proxy, "/b@r/b r", testPort, false, "/foo") .then(() => r(proxyUrl + "/b@r/b%20r/rest/of/it")) .then((body) => { body = JSON.parse(body); expect(body).toEqual( jasmine.objectContaining({ path: "/b@r/b r", url: "/foo/b@r/b%20r/rest/of/it", }) ); done(); }); }); it("prependPath: false prevents target path from being prepended", function (done) { proxy.proxy.options.prependPath = false; util .addTarget(proxy, "/bar", testPort, false, "/foo") .then(() => r(proxyUrl + "/bar/rest/of/it")) .then((body) => { body = JSON.parse(body); expect(body).toEqual( jasmine.objectContaining({ path: "/bar", url: "/bar/rest/of/it", }) ); done(); }); }); it("includePrefix: false strips routing prefix from request", function (done) { proxy.includePrefix = false; util .addTarget(proxy, "/bar", testPort, false, "/foo") .then(() => r(proxyUrl + "/bar/rest/of/it")) .then((body) => { body = JSON.parse(body); expect(body).toEqual( jasmine.objectContaining({ path: "/bar", url: "/foo/rest/of/it", }) ); done(); }); }); it("options.defaultTarget", function (done) { var options = { defaultTarget: "http://127.0.0.1:9001", }; var cp = new ConfigurableProxy(options); cp._routes.get("/").then(function (route) { expect(route.target).toEqual("http://127.0.0.1:9001"); done(); }); }); it("options.storageBackend", function (done) { const options = { storageBackend: "mybackend", }; expect(() => { const cp = new ConfigurableProxy(options); }).toThrowMatching(function (e) { return e.message.includes("Cannot find module 'mybackend'"); }); done(); }); it("options.storageBackend with an user-defined backend", function (done) { const store = path.resolve(__dirname, "dummy-store.js"); const options = { storageBackend: store, }; const cp = new ConfigurableProxy(options); expect(cp._routes.constructor.name).toEqual("PlugableDummyStore"); done(); }); it("includePrefix: false + prependPath: false", function (done) { proxy.includePrefix = false; proxy.proxy.options.prependPath = false; util .addTarget(proxy, "/bar", testPort, false, "/foo") .then(() => r(proxyUrl + "/bar/rest/of/it")) .then((body) => { body = JSON.parse(body); expect(body).toEqual( jasmine.objectContaining({ path: "/bar", url: "/rest/of/it", }) ); done(); }); }); it("hostRouting: routes by host", function (done) { proxy.hostRouting = true; util .addTarget(proxy, "/" + hostTest, testPort, false, null) .then(() => r(hostUrl + "/some/path")) .then((body) => { body = JSON.parse(body); expect(body).toEqual( jasmine.objectContaining({ target: "http://127.0.0.1:" + testPort, url: "/some/path", }) ); }) .then(done); }); it("last_activity not updated on errors", function (done) { let now = new Date(); // mock timestamp in the past let firstActivity = new Date(now.getTime() - 60000); function expectNoActivity() { return proxy._routes.get("/missing", (route) => { expect(route.last_activity).toEqual(proxy._setup_timestamp); }); } proxy .removeRoute("/") // add a route to nowhere .then(() => proxy.addRoute("/missing", { target: "https://127.0.0.1:54321" })) .then(() => { // set last_activity into the past proxy._routes.update("/missing", { last_activity: firstActivity }); }) // fail a web request .then(() => r(hostUrl + "/missing/prefix")) .then((body) => done.fail("Expected 503")) .catch((err) => { expect(err.statusCode).toEqual(503); }) // check that activity was not updated .then(expectNoActivity) // fail a websocket request .then(() => { var ws = new WebSocket("ws://127.0.0.1:" + port + "/missing/ws"); ws.on("error", () => { // expect this, since there is no websocket handler // check last_activity was not updated expectNoActivity().then((route) => { ws.close(); done(); }); }); ws.on("open", () => { done.fail("Expected websocket error"); }); }); }); it("custom error target", function (done) { var proxyPort = 55550; util .setupProxy(proxyPort, { errorTarget: "http://127.0.0.1:55565" }, []) .then(() => r("http://127.0.0.1:" + proxyPort + "/foo/bar")) .then((body) => done.fail("Expected 404")) .catch((err) => { expect(err.statusCode).toEqual(404); expect(err.response.headers["content-type"]).toEqual("text/plain"); expect(err.response.body).toEqual("/foo/bar"); }) .then(done); }); it("custom error path", function (done) { proxy.errorPath = path.join(__dirname, "error"); proxy .removeRoute("/") .then(() => proxy.addRoute("/missing", { target: "https://127.0.0.1:54321" })) .then(() => r(hostUrl + "/nope")) .then((body) => done.fail("Expected 404")) .catch((err) => { expect(err.statusCode).toEqual(404); expect(err.response.headers["content-type"]).toEqual("text/html"); expect(err.response.body).toMatch(/404'D/); }) .then(() => r(hostUrl + "/missing/prefix")) .then((body) => done.fail("Expected 503")) .catch((err) => { expect(err.statusCode).toEqual(503); expect(err.response.headers["content-type"]).toEqual("text/html"); expect(err.response.body).toMatch(/UNKNOWN/); }) .then(done); }); it("default error html", function (done) { proxy.removeRoute("/"); proxy .addRoute("/missing", { target: "https://127.0.0.1:54321" }) .then(() => r(hostUrl + "/nope")) .then((body) => done.fail("Expected 404")) .catch((err) => { expect(err.statusCode).toEqual(404); expect(err.response.headers["content-type"]).toEqual("text/html"); expect(err.response.body).toMatch(/404:/); }) .then(() => r(hostUrl + "/missing/prefix")) .then((body) => done.fail("Expected 503")) .catch((err) => { expect(err.statusCode).toEqual(503); expect(err.response.headers["content-type"]).toEqual("text/html"); expect(err.response.body).toMatch(/503:/); }) .then(done); }); it("backend error", function (done) { var proxyPort = 55550; util .setupProxy(proxyPort, { errorTarget: "http://127.0.0.1:55565" }, []) .then(() => r("http://127.0.0.1:" + proxyPort + "/%")) .then((body) => done.fail("Expected 500")) .catch((err) => { expect(err.statusCode).toEqual(500); expect(err.response.headers["content-type"]).toEqual("text/plain"); expect(err.response.body).toEqual("/%"); }) .then(done); }); it("Redirect location untouched without rewrite options", function (done) { var redirectTo = "http://foo.com:12345/whatever"; util .addTargetRedirecting(proxy, "/external/urlpath/", testPort, "/internal/urlpath/", redirectTo) .then(() => r(proxyUrl + "/external/urlpath/rest/of/it")) .then((body) => done.fail("Expected 301")) .catch((err) => { expect(err.statusCode).toEqual(301); expect(err.response.headers.location).toEqual(redirectTo); }) .then(done); }); it("Redirect location with rewriting", function (done) { var proxyPort = 55556; var options = { protocolRewrite: "https", autoRewrite: true, }; // where the backend server redirects us. // Note that http-proxy requires (logically) the redirection to be to the same (internal) host. var redirectTo = "https://127.0.0.1:" + testPort + "/whatever"; var expectedRedirect = "https://127.0.0.1:" + proxyPort + "/whatever"; util .setupProxy(proxyPort, options, []) .then((proxy) => util.addTargetRedirecting( proxy, "/external/urlpath/", testPort, "/internal/urlpath/", redirectTo ) ) .then(() => r("http://127.0.0.1:" + proxyPort + "/external/urlpath/")) .then((body) => done.fail("Expected 301")) .catch((err) => { expect(err.statusCode).toEqual(301); expect(err.response.headers.location).toEqual(expectedRedirect); }) .then(done); }); it("health check request", function (done) { r(proxyUrl + "/_chp_healthz").then((body) => { body = JSON.parse(body); expect(body).toEqual({ status: "OK" }); done(); }); }); }); configurable-http-proxy-4.5.0/test/server.crt000066400000000000000000000025671407527146400213370ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIID3DCCAsSgAwIBAgIJAMj2JtcDwW7iMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCTEyNy4wLjAuMTAeFw0xOTAxMDMxMTI3 MzhaFw0yODEyMzExMTI3MzhaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21l LVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV BAMMCTEyNy4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO5B BHuBUF7rx+f2KidqlIv3p0u7gIH+DHT5/wLTAgE/TZr2prOb0ib/Lx6sesQ/xoZP ogjlvCMNlJnqlTDYiqfTkjTycsjEu5mHGg9x6HaSPRfOmUg+R7iX3PsfuPVdlfm1 CnY1ne09Q96WbpeHiwWh9Zps+C4m6lZ2gicQ8bthUczQ7kTUCelaWAMioH91OliP HiKjs4WKN/+SJVhpZqgYA5U7lSq019n5Fz0Wab72Xd7G8MuJnUHuJaGo7+hBmghb YZtlAuE94eSZE7bTIg994Or4pfPuO9xbI1vAsKyRvnVtnSJEJQvaI1ISms06P5tF 1pSx3UopVI92Y7T6DG0CAwEAAaOBpjCBozBzBgNVHSMEbDBqoV2kWzBZMQswCQYD VQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQg V2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDDAkxMjcuMC4wLjGCCQDI9ibXA8Fu4jAJ BgNVHRMEAjAAMAsGA1UdDwQEAwIE8DAUBgNVHREEDTALggkxMjcuMC4wLjEwDQYJ KoZIhvcNAQELBQADggEBAMMlwlt67CgCsegFuwoxZPAcftp/YeOx7BQ3BdqaxX1h j+lkDE3G6TdnM3ppsGPdCcV3HETId9LsV20kAYNUR6drYkfpWVRPVPhN3MQL2LoB nyW6BvBLvl9QhDdpCAp5bI15Fm0J7MkChG6pvdXT+gYUu6PWNfjcHVZluQrG8s83 zvJBkstNTcuFKHd5pNXTdzf24aWR9rlxTBNF/aJVvZO/x0R2pIVFRUlqtm9X6URC s4NQVlwwIfosM/ODP0A15zYzNuLuuOeItw8eScX1lfIE+CPSN5TNEsd2Z8EwYz8y QAnRbuNnpW+NaAyrQiPb6RR64QGmS7gPtXx8UtWhehI= -----END CERTIFICATE----- configurable-http-proxy-4.5.0/test/server.key000066400000000000000000000032171407527146400213300ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA7kEEe4FQXuvH5/YqJ2qUi/enS7uAgf4MdPn/AtMCAT9Nmvam s5vSJv8vHqx6xD/Ghk+iCOW8Iw2UmeqVMNiKp9OSNPJyyMS7mYcaD3HodpI9F86Z SD5HuJfc+x+49V2V+bUKdjWd7T1D3pZul4eLBaH1mmz4LibqVnaCJxDxu2FRzNDu RNQJ6VpYAyKgf3U6WI8eIqOzhYo3/5IlWGlmqBgDlTuVKrTX2fkXPRZpvvZd3sbw y4mdQe4loajv6EGaCFthm2UC4T3h5JkTttMiD33g6vil8+473FsjW8CwrJG+dW2d IkQlC9ojUhKazTo/m0XWlLHdSilUj3ZjtPoMbQIDAQABAoIBAQDMhV+VraISeZA3 XSc+gs1VgZhI+IIOFMkYuWnhQMVd0LuLpOPhFofGFhSa3jDcumpu7XkI7j0cUhIY NjkvnxMXkhTWArHqCoeH+WOzknKdJlfvUdhfHadvnF2D7O1XI6kUnAaJBum0t3zF XJk+1onOogM1NwmshJdw9XYwlukLkYz9crZxlDXxnXU4XJQoD2PCOuAsIq8dbKIw YbEEVG3IGrADq9iaOkiDaI9VMwRZjMMr9N9Vnf7NlUj/+NpuDGAW+T47F5ekPus6 KdetOK8l66uK42PFrhQaQ/KdlrgWz0IHPi+8cBFKTMguaw1ezMSwW/G7uqdRSd/A OQnJEkQBAoGBAP3a57mDBbbe8HebUZ7MzG8v8GUyuaFGdGydBSw3JxdNwgCaI0PP yjG38JXSnTWhwrRwXexVDVUn/rtkEHyTQWk4toaCHzxZMt5/JmEkd4GL1m8l4ixP d/BiuyQV/91Fc2RJ3f65hMIIslPZdIkT2lJhK3Jisy91F5v1z5UiRMjtAoGBAPBE XeFme/jlk2QmwTt8YivUmOxcpfjyArGBo9Zh6h8Y6pfFMNpHPIIoi2tD7e8is+r9 uOruuIvLwgIAgGwXuslPKPybWAUAPIZTj1IY1zdHCcX1w7Crvla450yF9EZZhAF6 tfc0PshlHhj5UTHVNrz6UicAjDiTgoNlTv32C2GBAoGAZuv45Wrsxy7uWd/8IKEy BaZvFeiSAIQ7Mcobzchyre0Vyf60a4r0lGHaRjRFDviNo3Cnc+kr5am4iqtQT+G4 NHwM7M4W8fXMPQ9aNSp+1WQnxZqbb5GstCajb0cIHONKnI+iVONMowq5mmtg5y7I ZnwFDHRd59DWiKIJzpI9RyUCgYEAp6X8n8dj36NJOgu0ry42TgEVB3AKO2+ao4Mi 7/b4ZuR65JWqZdRpCyUBS0Jl5oOfaOvLONqDmL//SmhRM5tHMSp0HfbC7xJgKRZr HczJdv+xeRjoiAD+WgLBGesqLGBPtLyL9cbVu/yaiLCGkDAG9svvwNkn/l4nJknt WVVyUYECgYBOLlDrai4iLxXfff7jCfSnlk4bEfGp+TohySTpCrjiRPmStx+bZ293 IW2ZXwfZpk5tBhKX2fVtDwUY5EsWNr4GMNnPdRRJrSGQIInQYHsTjhyl67fK+CTu S7m5NhLQ4XEnE4J3SuaqNviidTgsNX7Mi8BKUwXUsUoIxfOotR0KBQ== -----END RSA PRIVATE KEY----- configurable-http-proxy-4.5.0/test/store_spec.js000066400000000000000000000075241407527146400220210ustar00rootroot00000000000000// jshint jasmine: true "use strict"; var MemoryStore = require("../lib/store.js").MemoryStore; describe("MemoryStore", function () { beforeEach(function () { this.subject = new MemoryStore(); }); describe("get", function () { it("returns the data for the specified path", function (done) { this.subject.add("/myRoute", { test: "value" }); this.subject.get("/myRoute").then(function (data) { expect(data).toEqual({ test: "value" }); done(); }); }); it("returns undefined when not found", function (done) { this.subject.get("/wut").then((result) => { expect(result).toBe(undefined); done(); }); }); }); describe("getTarget", function () { it("returns the target object for the path", function (done) { this.subject.add("/myRoute", { target: "http://localhost:8213" }); this.subject.getTarget("/myRoute").then(function (target) { expect(target.prefix).toEqual("/myRoute"); expect(target.data.target).toEqual("http://localhost:8213"); done(); }); }); }); describe("getAll", function () { it("returns all routes", function (done) { this.subject.add("/myRoute", { test: "value1" }); this.subject.add("/myOtherRoute", { test: "value2" }); this.subject.getAll().then(function (routes) { expect(Object.keys(routes).length).toEqual(2); expect(routes["/myRoute"]).toEqual({ test: "value1" }); expect(routes["/myOtherRoute"]).toEqual({ test: "value2" }); done(); }); }); it("returns a blank object when no routes defined", function (done) { this.subject.getAll().then(function (routes) { expect(routes).toEqual({}); done(); }); }); }); describe("add", function () { it("adds data to the store for the specified path", function (done) { this.subject.add("/myRoute", { test: "value" }); this.subject.get("/myRoute").then(function (route) { expect(route).toEqual({ test: "value" }); done(); }); }); it("overwrites any existing values", function (done) { this.subject.add("/myRoute", { test: "value" }); this.subject.add("/myRoute", { test: "updatedValue" }); this.subject.get("/myRoute").then(function (route) { expect(route).toEqual({ test: "updatedValue" }); done(); }); }); }); describe("update", function () { it("merges supplied data with existing data", function (done) { this.subject.add("/myRoute", { version: 1, test: "value" }); this.subject.update("/myRoute", { version: 2 }); this.subject.get("/myRoute").then(function (route) { expect(route.version).toEqual(2); expect(route.test).toEqual("value"); done(); }); }); }); describe("remove", function () { it("removes a route from the table", function (done) { this.subject.add("/myRoute", { test: "value" }); this.subject.remove("/myRoute"); this.subject.get("/myRoute").then(function (route) { expect(route).toBe(undefined); done(); }); }); it("doesn't explode when route is not defined", function (done) { // would blow up if an error was thrown this.subject.remove("/myRoute/foo/bar").then(done); }); }); describe("hasRoute", function () { it("returns false when the path is not found", function (done) { this.subject .add("/myRoute", { test: "value" }) .then(() => this.subject.get("/myRoute")) .then((result) => { expect(result).toEqual({ test: "value" }); }) .then(done); }); it("returns false when the path is not found", function (done) { this.subject .get("/wut") .then(function (result) { expect(result).toBe(undefined); }) .then(done); }); }); }); configurable-http-proxy-4.5.0/test/trie_spec.js000066400000000000000000000134751407527146400216320ustar00rootroot00000000000000// jshint jasmine: true "use strict"; var URLTrie = require("../lib/trie").URLTrie; describe("URLTrie", function () { var fullTrie = function () { // return a simple trie for testing var trie = new URLTrie(); var paths = ["/1", "/2", "/a/b/c/d", "/a/b/d", "/a/b/e", "/b", "/b/c", "/b/c/d"]; for (var i = 0; i < paths.length; i++) { var path = paths[i]; trie.add(path, { path: path }); } return trie; }; it("trie.init", function (done) { var trie = new URLTrie(); expect(trie.prefix).toEqual("/"); expect(trie.size).toEqual(0); expect(trie.data).toBe(undefined); expect(trie.branches).toEqual({}); trie = new URLTrie("/foo"); expect(trie.size).toEqual(0); expect(trie.prefix).toEqual("/foo"); expect(trie.data).toBe(undefined); expect(trie.branches).toEqual({}); done(); }); it("trie.root", function (done) { var trie = new URLTrie(); trie.add("/", -1); var node = trie.get("/1/etc/etc/"); expect(node).toBeTruthy(); expect(node.prefix).toEqual("/"); expect(node.data).toEqual(-1); node = trie.get("/"); expect(node).toBeTruthy(); expect(node.prefix).toEqual("/"); expect(node.data).toEqual(-1); node = trie.get(""); expect(node).toBeTruthy(); expect(node.prefix).toEqual("/"); expect(node.data).toEqual(-1); done(); }); it("trie.add", function (done) { var trie = new URLTrie(); trie.add("foo", 1); expect(trie.size).toEqual(1); expect(trie.data).toBe(undefined); expect(trie.branches.foo.data).toEqual(1); expect(trie.branches.foo.size).toEqual(0); trie.add("bar/leaf", 2); expect(trie.size).toEqual(2); var bar = trie.branches.bar; expect(bar.prefix).toEqual("/bar"); expect(bar.size).toEqual(1); expect(bar.branches.leaf.data).toEqual(2); trie.add("/a/b/c/d", 4); expect(trie.size).toEqual(3); var a = trie.branches.a; expect(a.prefix).toEqual("/a"); expect(a.size).toEqual(1); expect(a.data).toBe(undefined); var b = a.branches.b; expect(b.prefix).toEqual("/a/b"); expect(b.size).toEqual(1); expect(b.data).toBe(undefined); var c = b.branches.c; expect(c.prefix).toEqual("/a/b/c"); expect(c.size).toEqual(1); expect(c.data).toBe(undefined); var d = c.branches.d; expect(d.prefix).toEqual("/a/b/c/d"); expect(d.size).toEqual(0); expect(d.data).toEqual(4); done(); }); it("trie.get", function (done) { var trie = fullTrie(); expect(trie.get("/not/found")).toBe(undefined); var node = trie.get("/1"); expect(node.prefix).toEqual("/1"); expect(node.data.path).toEqual("/1"); node = trie.get("/1/etc/etc/"); expect(node).toBeTruthy(); expect(node.prefix).toEqual("/1"); expect(node.data.path).toEqual("/1"); expect(trie.get("/a")).toBe(undefined); expect(trie.get("/a/b/c")).toBe(undefined); node = trie.get("/a/b/c/d/e/f"); expect(node).toBeTruthy(); expect(node.prefix).toEqual("/a/b/c/d"); expect(node.data.path).toEqual("/a/b/c/d"); node = trie.get("/b/c/d/word"); expect(node).toBeTruthy(); expect(node.prefix).toEqual("/b/c/d"); expect(node.data.path).toEqual("/b/c/d"); node = trie.get("/b/c/dword"); expect(node).toBeTruthy(); expect(node.prefix).toEqual("/b/c"); expect(node.data.path).toEqual("/b/c"); done(); }); it("trie.remove", function (done) { var trie = fullTrie(); var size = trie.size; var node; node = trie.get("/b/just-b"); expect(node.prefix).toEqual("/b"); trie.remove("/b"); // deleting a node doesn't change size if no children expect(trie.size).toEqual(size); expect(trie.get("/b/just-b")).toBe(undefined); node = trie.get("/b/c/sub-still-here"); expect(node.prefix).toEqual("/b/c"); node = trie.get("/a/b/c/d/word"); expect(node.prefix).toEqual("/a/b/c/d"); var b = trie.branches.a.branches.b; expect(b.size).toEqual(3); trie.remove("/a/b/c/d"); expect(b.size).toEqual(2); expect(b.branches.c).toBe(undefined); trie.remove("/"); node = trie.get("/"); expect(node).toBe(undefined); done(); }); it("trie.subPaths", function (done) { var trie = new URLTrie(), node; trie.add("/", { path: "/", }); node = trie.get("/prefix/sub"); expect(node).toBeTruthy(); expect(node.prefix).toEqual("/"); // add /prefix/sub/tree trie.add("/prefix/sub/tree", {}); // which shouldn't change the results for /prefix and /prefix/sub node = trie.get("/prefix"); expect(node).toBeTruthy(); expect(node.prefix).toEqual("/"); node = trie.get("/prefix/sub"); expect(node).toBeTruthy(); expect(node.prefix).toEqual("/"); node = trie.get("/prefix/sub/tree"); expect(node).toBeTruthy(); expect(node.prefix).toEqual("/prefix/sub/tree"); // add /prefix, and run one more time trie.add("/prefix", {}); node = trie.get("/prefix"); expect(node).toBeTruthy(); expect(node.prefix).toEqual("/prefix"); node = trie.get("/prefix/sub"); expect(node).toBeTruthy(); expect(node.prefix).toEqual("/prefix"); node = trie.get("/prefix/sub/tree"); expect(node).toBeTruthy(); expect(node.prefix).toEqual("/prefix/sub/tree"); done(); }); it("remove first leaf doesn't remove root", function (done) { var trie = new URLTrie(), node; trie.add("/", { path: "/", }); node = trie.get("/prefix/sub"); expect(node).toBeTruthy(); expect(node.prefix).toEqual("/"); trie.add("/prefix", { path: "/prefix", }); node = trie.get("/prefix/sub"); expect(node).toBeTruthy(); expect(node.prefix).toEqual("/prefix"); trie.remove("/prefix/"); node = trie.get("/prefix/sub"); expect(node).toBeTruthy(); expect(node.prefix).toEqual("/"); done(); }); });